use clap::{Parser, Subcommand}; use miette::IntoDiagnostic; use std::path::PathBuf; use std::process; mod check; mod config; mod fetch; mod generate; mod init; mod workspace_ext; // Import optional code generator plugins // These are automatically registered via inventory when the feature is enabled #[cfg(feature = "typescript")] use mlf_codegen_typescript as _; #[cfg(feature = "go")] use mlf_codegen_go as _; #[cfg(feature = "rust")] use mlf_codegen_rust as _; #[derive(Parser)] #[command(name = "mlf")] #[command(about = "MLF (Matt's Lexicon Format) CLI tool", long_about = None)] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { Init { #[arg(long, help = "Skip prompts and use defaults")] yes: bool, }, Check { #[arg(help = "MLF lexicon file(s) or directory to validate. If omitted, checks source directory from mlf.toml")] input: Vec, #[arg(long, help = "Root directory for namespace calculation (defaults to mlf.toml source directory or current directory)")] root: Option, }, Validate { #[arg(help = "MLF lexicon file")] lexicon: PathBuf, #[arg(help = "JSON record file to validate against the lexicon")] record: PathBuf, }, Generate { #[command(subcommand)] command: Option, }, Fetch { #[arg(help = "Namespace to fetch (e.g., stream.place). If omitted, fetches all dependencies from mlf.toml")] nsid: Option, #[arg(long, help = "Add namespace to dependencies in mlf.toml")] save: bool, #[arg(long, help = "Update dependencies to latest versions (ignores lockfile)")] update: bool, #[arg(long, help = "Require lockfile and fail if dependencies need updating")] locked: bool, }, } #[derive(Subcommand)] enum GenerateCommands { Lexicon { #[arg(short, long, help = "Input MLF file(s) or directory. If omitted, uses source directory from mlf.toml")] input: Vec, #[arg(short, long, help = "Output directory. If omitted, uses first lexicon output from mlf.toml")] output: Option, #[arg(long, help = "Root directory for namespace calculation (defaults to mlf.toml source directory or current directory)")] root: Option, #[arg(long, help = "Use flat file structure (e.g., app.bsky.post.json)")] flat: bool, }, Code { #[arg(short, long, help = "Generator to use (typescript, go, rust, etc.). If omitted, uses first code output from mlf.toml")] generator: Option, #[arg(short, long, help = "Input MLF file(s) or directory. If omitted, uses source directory from mlf.toml")] input: Vec, #[arg(short, long, help = "Output directory. If omitted, uses matching output from mlf.toml")] output: Option, #[arg(long, help = "Root directory for namespace calculation (defaults to mlf.toml source directory or current directory)")] root: Option, #[arg(long, help = "Use flat file structure (e.g., app.bsky.post.ts)")] flat: bool, }, Mlf { #[arg(short, long, help = "Input JSON lexicon files (glob patterns supported)")] input: Vec, #[arg(short, long, help = "Output directory. If omitted, uses first mlf output from mlf.toml")] output: Option, }, } #[tokio::main] async fn main() { let cli = Cli::parse(); let result: Result<(), miette::Report> = match cli.command { Commands::Init { yes } => { init::run_init(yes).into_diagnostic() } Commands::Check { input, root } => { check::run_check(input, root).into_diagnostic() } Commands::Validate { lexicon, record } => { check::validate(lexicon, record).into_diagnostic() } Commands::Generate { command } => match command { Some(GenerateCommands::Lexicon { input, output, root, flat }) => { generate::lexicon::run(input, output, root, flat).into_diagnostic() } Some(GenerateCommands::Code { generator, input, output, root, flat }) => { generate::code::run(generator, input, output, root, flat).into_diagnostic() } Some(GenerateCommands::Mlf { input, output }) => { generate::mlf::run(input, output).into_diagnostic() } None => { // Run all outputs from mlf.toml generate::run_all().into_diagnostic() } }, Commands::Fetch { nsid, save, update, locked } => { fetch::run_fetch(nsid, save, update, locked).await.into_diagnostic() } }; if let Err(e) = result { eprintln!("{:?}", e); process::exit(1); } }