From d3dc8d70fe8655ed066155c46cb4a4e28a466b5b Mon Sep 17 00:00:00 2001 From: Dominik Nakamura Date: Tue, 9 Jan 2024 13:45:17 +0900 Subject: [PATCH] feat(go): implement the CLI for the Go code generator Although the Go code generator existed as library, it still needed the CLI binary to drive the actual codegen step and write files to disk. --- Cargo.lock | 3 + crates/mabo-build/src/lib.rs | 2 +- crates/mabo-compiler/src/resolve/mod.rs | 2 +- crates/mabo-go/Cargo.toml | 3 + crates/mabo-go/src/cli.rs | 10 ++- crates/mabo-go/src/decode.rs | 4 +- crates/mabo-go/src/definition.rs | 11 +-- crates/mabo-go/src/main.rs | 106 ++++++++++++++++++++++++ 8 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 crates/mabo-go/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index b9492dd..7fc398e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,12 +730,15 @@ dependencies = [ name = "mabo-go" version = "0.1.0" dependencies = [ + "anyhow", "clap", "heck", "insta", "mabo-compiler", "mabo-parser", + "mabo-project", "miette", + "mimalloc", ] [[package]] diff --git a/crates/mabo-build/src/lib.rs b/crates/mabo-build/src/lib.rs index 3953ee5..6eb3d27 100644 --- a/crates/mabo-build/src/lib.rs +++ b/crates/mabo-build/src/lib.rs @@ -205,7 +205,7 @@ impl Compiler { } })?); - let out_file = out_dir.join(format!("{stem}.rs",)); + let out_file = out_dir.join(format!("{stem}.rs")); fs::write(&out_file, code).map_err(|source| Error::Write { source, diff --git a/crates/mabo-compiler/src/resolve/mod.rs b/crates/mabo-compiler/src/resolve/mod.rs index 01d7092..7725e29 100644 --- a/crates/mabo-compiler/src/resolve/mod.rs +++ b/crates/mabo-compiler/src/resolve/mod.rs @@ -89,7 +89,7 @@ pub(crate) struct Module<'a> { } impl Module<'_> { - fn path_to_string(&self)->String{ + fn path_to_string(&self) -> String { self.path.join("::") } } diff --git a/crates/mabo-go/Cargo.toml b/crates/mabo-go/Cargo.toml index bfa09df..e32caa5 100644 --- a/crates/mabo-go/Cargo.toml +++ b/crates/mabo-go/Cargo.toml @@ -15,6 +15,9 @@ heck = "0.4.1" miette = { workspace = true, features = ["fancy-no-backtrace"] } mabo-compiler = { path = "../mabo-compiler" } mabo-parser = { path = "../mabo-parser" } +mabo-project = { path = "../mabo-project" } +mimalloc.workspace = true +anyhow.workspace = true [dev-dependencies] insta.workspace = true diff --git a/crates/mabo-go/src/cli.rs b/crates/mabo-go/src/cli.rs index 7ec608f..a0ea8fa 100644 --- a/crates/mabo-go/src/cli.rs +++ b/crates/mabo-go/src/cli.rs @@ -1,10 +1,16 @@ +use std::path::PathBuf; + use clap::Parser; #[derive(Debug, Parser)] #[command(about, author, version, propagate_version = true)] pub struct Cli { - #[arg(num_args(1..))] - files: Vec, + #[arg(long)] + pub project_dir: Option, + #[arg(long, short)] + pub out_dir: Option, + #[arg(long)] + pub no_fmt: bool, } impl Cli { diff --git a/crates/mabo-go/src/decode.rs b/crates/mabo-go/src/decode.rs index cbe6e73..64c9fbe 100644 --- a/crates/mabo-go/src/decode.rs +++ b/crates/mabo-go/src/decode.rs @@ -113,10 +113,10 @@ impl Display for RenderFoundChecks<'_> { for field in &*self.0.fields { writeln!(f, "\tif !found{} {{", heck::AsUpperCamelCase(&field.name))?; writeln!(f, "\t\treturn nil, buf.MissingFieldError{{")?; - writeln!(f, "\t\t\tID: {}", field.id)?; + writeln!(f, "\t\t\tID: {},", field.id)?; writeln!( f, - "\t\t\tField: \"{}\"", + "\t\t\tField: \"{}\",", if self.0.kind == FieldKind::Named { &field.name } else { diff --git a/crates/mabo-go/src/definition.rs b/crates/mabo-go/src/definition.rs index e349826..6849a18 100644 --- a/crates/mabo-go/src/definition.rs +++ b/crates/mabo-go/src/definition.rs @@ -16,8 +16,8 @@ pub fn render_schema<'a>( let mut content = format!( "{}{}{}", RenderHeader, + RenderPackage(opts.package, None), RenderImports, - RenderPackage(opts.package, None) ); let modules = definitions @@ -36,9 +36,10 @@ fn render_definition<'a>(buf: &mut String, definition: &'a Definition<'_>) -> Op match definition { Definition::Module(m) => { let mut content = format!( - "{}{}", + "{}{}{}", RenderHeader, - RenderPackage(m.name, Some(&m.comment)) + RenderPackage(m.name, Some(&m.comment)), + RenderImports, ); let modules = m @@ -114,8 +115,8 @@ struct RenderImports; impl Display for RenderImports { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "import (")?; - writeln!(f, "\t\"github.com/dnaka91/mabo-go\"")?; - writeln!(f, "\t\"github.com/dnaka91/mabo-go/buf\"")?; + writeln!(f, "\tmabo \"github.com/dnaka91/mabo-go\"")?; + writeln!(f, "\tbuf \"github.com/dnaka91/mabo-go/buf\"")?; writeln!(f, ")\n") } } diff --git a/crates/mabo-go/src/main.rs b/crates/mabo-go/src/main.rs new file mode 100644 index 0000000..3db7339 --- /dev/null +++ b/crates/mabo-go/src/main.rs @@ -0,0 +1,106 @@ +//! TODO + +use std::{env, fs, path::Path, process::Command}; + +use anyhow::{bail, ensure, Context, Result}; +use mabo_go::{Opts, Output}; +use mabo_parser::Schema; + +use self::cli::Cli; + +mod cli; + +#[global_allocator] +static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; + +fn main() -> Result<()> { + let cli = Cli::parse(); + let project_dir = match cli.project_dir { + Some(dir) => dir, + None => env::current_dir().context("failed locating project directory")?, + }; + let out_dir = match cli.out_dir { + Some(dir) => dir, + None => env::current_dir().context("failed locating output directory")?, + }; + + ensure!( + project_dir.join("Mabo.toml").exists(), + "directory at {project_dir:?} doesn't appear to be a Mabo project" + ); + + let project = mabo_project::load(project_dir).context("failed loading project")?; + + fs::create_dir_all(&out_dir).context("failed creating output directory")?; + + let inputs = project + .files + .into_iter() + .map(|path| { + let input = fs::read_to_string(&path) + .with_context(|| format!("failed reading schema file at {path:?}"))?; + Ok((path, input)) + }) + .collect::>>()?; + + let validated = inputs + .iter() + .map(|(path, input)| { + let stem = path + .file_stem() + .context("missing file name")? + .to_str() + .context("invalid utf-8 encoding")?; + + let schema = Schema::parse(input, Some(path))?; + mabo_compiler::validate_schema(&schema)?; + + Ok((stem, schema)) + }) + .collect::>>()?; + + let validated = validated + .iter() + .map(|(name, schema)| (*name, schema)) + .collect::>(); + + mabo_compiler::resolve_schemas(&validated)?; + + let opts = Opts { + package: &project.project_file.package.name, + }; + + for (_, schema) in validated { + let schema = mabo_compiler::simplify_schema(schema); + let code = mabo_go::render_schema(&opts, &schema); + + write_output(code, &out_dir)?; + } + + if !cli.no_fmt { + let output = Command::new("gofmt") + .args(["-s", "-w"]) + .arg(out_dir) + .output()?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("failed to format output:\n{stderr}"); + } + } + + Ok(()) +} + +fn write_output(output: Output<'_>, parent: &Path) -> Result<()> { + let path = parent.join(output.name); + + fs::create_dir_all(&path)?; + fs::write(path.join(format!("{}.go", output.name)), output.content)?; + + for module in output.modules { + write_output(module, &path)?; + } + + Ok(()) +}