dyn calling works + vibed dioxus ui components + some mo provision prototyping
This commit is contained in:
167
parts/mize.dev/src/codegen.rs
Normal file
167
parts/mize.dev/src/codegen.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
//! Code generation for mize bindings.
|
||||
//!
|
||||
//! Takes recorded PartMetadata (collected while in generate mode) and produces
|
||||
//! a Rust source file that looks like the hand-written `generated.rs`.
|
||||
|
||||
use mize::prelude::*;
|
||||
use mize::instance::{PartMetadata, MethodMetadata, OptMetadata, NativeMetadata};
|
||||
use std::fmt::Write as _;
|
||||
|
||||
/// Convert a part name like "cli" into a PascalCase struct name like "CliPart".
|
||||
fn part_name_to_struct(part_name: &str) -> String {
|
||||
let mut out = String::new();
|
||||
let mut upper_next = true;
|
||||
for c in part_name.chars() {
|
||||
if c == '.' || c == '-' || c == '_' || c == ' ' {
|
||||
upper_next = true;
|
||||
continue;
|
||||
}
|
||||
if upper_next {
|
||||
out.extend(c.to_uppercase());
|
||||
upper_next = false;
|
||||
} else {
|
||||
out.push(c);
|
||||
}
|
||||
}
|
||||
out.push_str("Part");
|
||||
out
|
||||
}
|
||||
|
||||
/// Generate the full `src/lib.rs` content for the generated bindings package.
|
||||
pub fn generate_lib_rs(all_metadata: &[PartMetadata]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("// AUTO-GENERATED by mize dev gen -- do not edit manually\n");
|
||||
out.push_str("use mize::prelude::*;\n\n\n");
|
||||
|
||||
for meta in all_metadata {
|
||||
generate_part_struct(&mut out, meta);
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn generate_part_struct(out: &mut String, meta: &PartMetadata) {
|
||||
let struct_name = part_name_to_struct(&meta.part_name);
|
||||
let part_id_str = meta.part_name.replace('.', ".");
|
||||
|
||||
// struct definition
|
||||
let _ = writeln!(out, "pub struct {struct_name} {{");
|
||||
let _ = writeln!(out, " id: MizeId");
|
||||
let _ = writeln!(out, "}}");
|
||||
let _ = writeln!(out);
|
||||
|
||||
// MizePart impl
|
||||
let _ = writeln!(out, "impl MizePart for {struct_name} {{");
|
||||
let _ = writeln!(out, " fn id(&self) -> MizeId {{");
|
||||
let _ = writeln!(out, " self.id.clone()");
|
||||
let _ = writeln!(out, " }}");
|
||||
let _ = writeln!(out, "}}");
|
||||
let _ = writeln!(out);
|
||||
|
||||
// inherent impl
|
||||
let _ = writeln!(out, "impl {struct_name} {{");
|
||||
|
||||
// get() constructor
|
||||
let _ = writeln!(out, " pub fn get(name: &str) -> MizeResult<{struct_name}> {{");
|
||||
let _ = writeln!(out, " let mut mize = get_global_mize()?;");
|
||||
let _ = writeln!(out, " let part = mize.get_part(name)?;");
|
||||
let _ = writeln!(out, " Ok({struct_name} {{ id: part.id() }})");
|
||||
let _ = writeln!(out, " }}");
|
||||
|
||||
// one method per recorded method/effect/thread
|
||||
for m in meta.methods.iter().chain(meta.effects.iter()).chain(meta.threads.iter()) {
|
||||
generate_method_fn(out, &struct_name, m);
|
||||
}
|
||||
|
||||
// one getter-ish fn per recorded opt (opts are config values; expose a dyn_call getter)
|
||||
for o in &meta.opts {
|
||||
generate_opt_fn(out, &struct_name, o);
|
||||
}
|
||||
|
||||
let _ = writeln!(out, "}}");
|
||||
let _ = writeln!(out);
|
||||
|
||||
// native type wrappers
|
||||
for n in &meta.natives {
|
||||
generate_native_struct(out, n);
|
||||
}
|
||||
|
||||
let _ = writeln!(out, "// part id string: {part_id_str}");
|
||||
let _ = writeln!(out);
|
||||
}
|
||||
|
||||
fn generate_method_fn(out: &mut String, _struct_name: &str, m: &MethodMetadata) {
|
||||
let fn_name = &m.name;
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" pub fn {fn_name}(&self, args: ItemData) -> MizeResult<ItemData> {{"
|
||||
);
|
||||
let _ = writeln!(out, " dyn_call(self.id.clone(), \"{fn_name}\", args)?;");
|
||||
// note: dyn_call returns ItemData already, so wrap:
|
||||
let _ = writeln!(out, " dyn_call(self.id.clone(), \"{fn_name}\", args)");
|
||||
let _ = writeln!(out, " }}");
|
||||
let _ = writeln!(out);
|
||||
}
|
||||
|
||||
fn generate_opt_fn(out: &mut String, _struct_name: &str, o: &OptMetadata) {
|
||||
let fn_name = &o.name;
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" pub fn opt_{fn_name}(&self) -> MizeResult<ItemData> {{"
|
||||
);
|
||||
let _ = writeln!(out, " get_config(&format!(\"{{}}/{fn_name}\", self.id.dot()))");
|
||||
let _ = writeln!(out, " }}");
|
||||
let _ = writeln!(out);
|
||||
}
|
||||
|
||||
fn generate_native_struct(out: &mut String, n: &NativeMetadata) {
|
||||
let struct_name = &n.name;
|
||||
let _ = writeln!(out, "// native type: {} ({})", n.name, n.type_name);
|
||||
let _ = writeln!(out, "pub struct {struct_name} {{");
|
||||
let _ = writeln!(out, " native_id: u32");
|
||||
let _ = writeln!(out, "}}");
|
||||
let _ = writeln!(out);
|
||||
let _ = writeln!(out, "impl {struct_name} {{");
|
||||
for assoc in &n.associated {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" pub fn {assoc}(args: ItemData) -> MizeResult<ItemData> {{"
|
||||
);
|
||||
let _ = writeln!(out, " let mut mize = get_global_mize()?;");
|
||||
let _ = writeln!(out, " let id = mize.id_new_dots(\"{}.{assoc}\")?;", n.name);
|
||||
let _ = writeln!(out, " dyn_call(id, \"{assoc}\", args)");
|
||||
let _ = writeln!(out, " }}");
|
||||
}
|
||||
for meth in &n.methods {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" pub fn {meth}(&self, args: ItemData) -> MizeResult<ItemData> {{"
|
||||
);
|
||||
let _ = writeln!(out, " let mut mize = get_global_mize()?;");
|
||||
let _ = writeln!(out, " let id = mize.id_new_dots(\"{}.{meth}\")?;", n.name);
|
||||
let _ = writeln!(out, " dyn_call(id, \"{meth}\", args)");
|
||||
let _ = writeln!(out, " }}");
|
||||
}
|
||||
let _ = writeln!(out, "}}");
|
||||
let _ = writeln!(out);
|
||||
}
|
||||
|
||||
/// Generate the `Cargo.toml` content for the generated package.
|
||||
pub fn generate_cargo_toml(package_name: &str) -> String {
|
||||
format!(
|
||||
r#"[package]
|
||||
name = "{package_name}"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "{lib_name}"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mize = {{ path = "../../core" }}
|
||||
"#,
|
||||
package_name = package_name,
|
||||
lib_name = package_name.replace('-', "_"),
|
||||
)
|
||||
}
|
||||
@@ -34,7 +34,11 @@ impl CliPart {
|
||||
pub fn subcommand<T: Fn(ArgMatches) -> MizeResult<()> + Send + Sync + 'static>(&self, subcmd: Command, action: T) -> MizeResult<()> {
|
||||
let mut subcmd_arg = ItemData::new();
|
||||
subcmd_arg.set_path("_native", subcmd.native_id)?;
|
||||
let mut action_arg = ItemData::from_native(action)?;
|
||||
let mut action_arg = ItemData::fun(Box::new(move |args: ItemData| {
|
||||
let matches = args.clone().get_path("0")?.native_take_from::<ArgMatches>()?;
|
||||
action(matches)?;
|
||||
Ok(ItemData::new())
|
||||
}))?;
|
||||
let mut args = ItemData::new();
|
||||
args.set_path("0", subcmd_arg)?;
|
||||
args.set_path("1", action_arg)?;
|
||||
@@ -52,6 +56,12 @@ pub struct ArgMatches {
|
||||
native_id: u32
|
||||
}
|
||||
|
||||
impl FromItemData for ArgMatches {
|
||||
fn from_itemdata(data: ItemData) -> MizeResult<Self> {
|
||||
Ok(ArgMatches { native_id: data.get_path("_native")?.value_u32()? })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Action {
|
||||
native_id: u32
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fs::create_dir_all;
|
||||
use std::fs::{create_dir_all, write};
|
||||
use mize::prelude::*;
|
||||
use cmd_lib::run_cmd as sh;
|
||||
use mize::tracing::info;
|
||||
@@ -6,6 +6,7 @@ use mize::tracing::info;
|
||||
// a static version of what would be generated.
|
||||
// as we need mize.dev to generate anything.
|
||||
mod generated;
|
||||
mod codegen;
|
||||
use generated::*;
|
||||
|
||||
// spacetime gen command: sh!(RUST_LOG=error RUST_BACKTRACE=0 spacetime generate --build-options="--lint-dir=" -p $SRC/mize/marts/spacetime -o $SRC/mize/marts/generated/src/spacetime --lang rust)?;
|
||||
@@ -17,13 +18,16 @@ extern "C" fn part_dev() -> MizeResult<()> {
|
||||
cli.set_command("dev");
|
||||
|
||||
cli.subcommand(Command::new("test")?, |_| {println!("hello from test command"); Ok(())})?;
|
||||
//cli.subcommand(Command::new("publish").alias("p"), publish_mize)?;
|
||||
//cli.subcommand(Command::new("genBindings").alias("b"), gen_bindings)?;
|
||||
cli.subcommand(Command::new("generate")?, gen_bindings)?;
|
||||
cli.subcommand(Command::new("gen")?, gen_bindings)?; // alias for generate
|
||||
cli.subcommand(Command::new("publish")?, publish_mize)?;
|
||||
cli.subcommand(Command::new("p")?, publish_mize)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn publish_mize(_ : &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
pub fn publish_mize(_ : ArgMatches) -> MizeResult<()> {
|
||||
let mut mize = get_global_mize()?;
|
||||
let mize_src = mize.get_config("mize.src")?.value_string()?;
|
||||
let cargo_dir = std::env::home_dir().unwrap().join(".cargo");
|
||||
|
||||
@@ -57,7 +61,77 @@ pub fn publish_mize(_ : &ArgMatches, mut mize: Mize) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_bindings(_ : &ArgMatches, mize: Mize) -> MizeResult<()> {
|
||||
|
||||
/// The `dev generate` command (alias `gen` to be added once bindings support it).
|
||||
///
|
||||
/// Reads a `part` TOML file from the current directory, loads the parts listed
|
||||
/// in `gen = [...]` in generate mode (recording metadata instead of executing),
|
||||
/// then writes a full Cargo package with Rust bindings to `<cwd>/generated/`.
|
||||
pub fn gen_bindings(_ : ArgMatches) -> MizeResult<()> {
|
||||
let mut mize = get_global_mize()?;
|
||||
|
||||
// 1. read the "part" file from the current directory
|
||||
let cwd = std::env::current_dir().map_err(|e| mize_err!("could not get current dir: {e}"))?;
|
||||
let part_file = cwd.join("part");
|
||||
info!("reading part description file: {}", part_file.display());
|
||||
let part_toml = std::fs::read_to_string(&part_file)
|
||||
.map_err(|e| mize_err!("could not read part file {}: {e}", part_file.display()))?;
|
||||
let part_data = ItemData::from_toml(&part_toml)?;
|
||||
|
||||
// 2. parse the part name and the gen = [...] list
|
||||
let part_name: String = part_data
|
||||
.get_path("name")
|
||||
.ok()
|
||||
.and_then(|v| v.value_string().ok())
|
||||
.ok_or_else(|| mize_err!("part file must have a 'name' field"))?;
|
||||
info!("part name: {part_name}");
|
||||
|
||||
let gen_data = part_data
|
||||
.get_path("gen")
|
||||
.map_err(|_| mize_err!("part file must have a 'gen' array, eg: gen = [\"cli\", \"auth\"]"))?;
|
||||
let gen_arr = gen_data
|
||||
.cbor()
|
||||
.as_array()
|
||||
.ok_or_else(|| mize_err!("'gen' is not an array"))?;
|
||||
let mut gen_parts: Vec<String> = Vec::new();
|
||||
for v in gen_arr.iter() {
|
||||
let s = v.as_text().ok_or_else(|| mize_err!("'gen' array must contain only strings"))?;
|
||||
gen_parts.push(s.to_owned());
|
||||
}
|
||||
info!("parts to generate bindings for: {:?}", gen_parts);
|
||||
|
||||
// 3. enable generate mode and clear any old metadata
|
||||
mize.set_generate_mode(true)?;
|
||||
mize.clear_metadata()?;
|
||||
|
||||
// 4. load each part — in generate mode their part_smth funcs only record metadata
|
||||
for p in &gen_parts {
|
||||
info!("loading part in generate mode: {p}");
|
||||
mize.load_part(p)?;
|
||||
}
|
||||
|
||||
// 5. disable generate mode again
|
||||
mize.set_generate_mode(false)?;
|
||||
|
||||
// 6. collect all recorded metadata
|
||||
let all_metadata = mize.get_all_metadata()?;
|
||||
info!("recorded metadata for {} part(s)", all_metadata.len());
|
||||
|
||||
// 7. generate the bindings package at <cwd>/generated
|
||||
let gen_dir = cwd.join("generated");
|
||||
let src_dir = gen_dir.join("src");
|
||||
create_dir_all(&src_dir).map_err(|e| mize_err!("could not create generated/src dir: {e}"))?;
|
||||
|
||||
let package_name = format!("mize-generated-{part_name}");
|
||||
let cargo_toml = codegen::generate_cargo_toml(&package_name);
|
||||
write(gen_dir.join("Cargo.toml"), cargo_toml)
|
||||
.map_err(|e| mize_err!("could not write generated/Cargo.toml: {e}"))?;
|
||||
|
||||
let lib_rs = codegen::generate_lib_rs(&all_metadata);
|
||||
write(src_dir.join("lib.rs"), lib_rs)
|
||||
.map_err(|e| mize_err!("could not write generated/src/lib.rs: {e}"))?;
|
||||
|
||||
info!("bindings written to {}", gen_dir.display());
|
||||
println!("generated bindings package: {}", gen_dir.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user