From e92dcf59a8fe2bcdf843327f82fd8cb5739ced01 Mon Sep 17 00:00:00 2001 From: Matt Stavola Date: Wed, 8 Oct 2025 23:06:37 -0400 Subject: [PATCH] Use toml file --- mlf-cli/src/check.rs | 36 ++++++++++++- mlf-cli/src/fetch.rs | 103 +++++++++++++++++++++++++++++++++--- mlf-cli/src/generate/mod.rs | 91 +++++++++++++++++++++++++++++++ mlf-cli/src/main.rs | 27 ++++++---- 4 files changed, 237 insertions(+), 20 deletions(-) diff --git a/mlf-cli/src/check.rs b/mlf-cli/src/check.rs index e9f5653..4a432f9 100644 --- a/mlf-cli/src/check.rs +++ b/mlf-cli/src/check.rs @@ -1,3 +1,4 @@ +use crate::config::{find_project_root, ConfigError, MlfConfig}; use miette::Diagnostic; use mlf_diagnostics::{ParseDiagnostic, ValidationDiagnostic}; use std::path::PathBuf; @@ -55,12 +56,43 @@ pub enum CheckError { RecordValidation { errors: Vec, }, + + #[error("Failed to load config: {0}")] + #[diagnostic(code(mlf::check::config_error))] + ConfigError(#[from] ConfigError), } -pub fn run(input_patterns: Vec) -> Result<(), CheckError> { +pub fn run_check(input_patterns: Vec) -> Result<(), CheckError> { + // If no input patterns provided, use source directory from mlf.toml + let patterns = if input_patterns.is_empty() { + let current_dir = std::env::current_dir() + .map_err(|e| CheckError::ReadFile { + path: ".".to_string(), + source: e, + })?; + + match find_project_root(¤t_dir) { + Ok(project_root) => { + let config_path = project_root.join("mlf.toml"); + let config = MlfConfig::load(&config_path)?; + let source_pattern = format!("{}/**/*.mlf", config.source.directory); + println!("Using source directory from mlf.toml: {}", config.source.directory); + vec![source_pattern] + } + Err(ConfigError::NotFound) => { + return Err(CheckError::ValidationErrors { + help: Some("No input files provided and no mlf.toml found. Please provide input files or create a mlf.toml configuration.".to_string()), + }); + } + Err(e) => return Err(CheckError::ConfigError(e)), + } + } else { + input_patterns + }; + let mut file_paths = Vec::new(); - for pattern in input_patterns { + for pattern in patterns { if pattern.contains('*') || pattern.contains('?') { for entry in glob::glob(&pattern).map_err(|source| CheckError::InvalidGlob { pattern: pattern.clone(), diff --git a/mlf-cli/src/fetch.rs b/mlf-cli/src/fetch.rs index 63aa822..ee0639a 100644 --- a/mlf-cli/src/fetch.rs +++ b/mlf-cli/src/fetch.rs @@ -106,11 +106,34 @@ struct AtProtoRecord { value: serde_json::Value, } -pub fn fetch_lexicon(nsid: &str) -> Result<(), FetchError> { +/// Main entry point for fetch command +pub fn run_fetch(nsid: Option, save: bool) -> Result<(), FetchError> { // Find project root let current_dir = std::env::current_dir()?; - let project_root = match find_project_root(¤t_dir) { - Ok(root) => root, + let project_root = ensure_project_root(¤t_dir)?; + + match nsid { + Some(namespace) => { + // Fetch single namespace + fetch_lexicon(&namespace, &project_root)?; + + // Save to mlf.toml if --save flag is provided + if save { + save_dependency(&project_root, &namespace)?; + } + + Ok(()) + } + None => { + // Fetch all dependencies from mlf.toml + fetch_all_dependencies(&project_root) + } + } +} + +fn ensure_project_root(current_dir: &std::path::Path) -> Result { + match find_project_root(current_dir) { + Ok(root) => Ok(root), Err(ConfigError::NotFound) => { // Ask user if they want to create mlf.toml eprintln!("No mlf.toml found in current or parent directories."); @@ -125,16 +148,80 @@ pub fn fetch_lexicon(nsid: &str) -> Result<(), FetchError> { let config_path = current_dir.join("mlf.toml"); MlfConfig::create_default(&config_path).map_err(FetchError::NoProjectRoot)?; println!("Created mlf.toml in {}", current_dir.display()); - current_dir + Ok(current_dir.to_path_buf()) } else { - return Err(FetchError::NoProjectRoot(ConfigError::NotFound)); + Err(FetchError::NoProjectRoot(ConfigError::NotFound)) } } - Err(e) => return Err(FetchError::NoProjectRoot(e)), - }; + Err(e) => Err(FetchError::NoProjectRoot(e)), + } +} + +fn fetch_all_dependencies(project_root: &std::path::Path) -> Result<(), FetchError> { + // Load mlf.toml + let config_path = project_root.join("mlf.toml"); + let config = MlfConfig::load(&config_path).map_err(FetchError::NoProjectRoot)?; + + if config.dependencies.is_empty() { + println!("No dependencies found in mlf.toml"); + return Ok(()); + } + + println!("Fetching {} dependencies...", config.dependencies.len()); + + let mut errors = Vec::new(); + let mut success_count = 0; + + for dep in &config.dependencies { + println!("\nFetching: {}", dep); + match fetch_lexicon(dep, project_root) { + Ok(()) => { + success_count += 1; + } + Err(e) => { + errors.push((dep.clone(), format!("{}", e))); + } + } + } + + if !errors.is_empty() { + eprintln!( + "\n{} dependency(ies) fetched successfully, {} error(s):", + success_count, + errors.len() + ); + for (dep, error) in &errors { + eprintln!(" {} - {}", dep, error); + } + return Err(FetchError::HttpError(format!( + "Failed to fetch {} dependencies", + errors.len() + ))); + } + + println!("\nāœ“ Successfully fetched all {} dependencies", success_count); + Ok(()) +} + +fn save_dependency(project_root: &std::path::Path, nsid: &str) -> Result<(), FetchError> { + let config_path = project_root.join("mlf.toml"); + let mut config = MlfConfig::load(&config_path).map_err(FetchError::NoProjectRoot)?; + + if config.dependencies.contains(&nsid.to_string()) { + println!("Dependency '{}' already in mlf.toml", nsid); + return Ok(()); + } + + config.dependencies.push(nsid.to_string()); + config.save(&config_path).map_err(FetchError::NoProjectRoot)?; + + println!("Added '{}' to dependencies in mlf.toml", nsid); + Ok(()) +} +pub fn fetch_lexicon(nsid: &str, project_root: &std::path::Path) -> Result<(), FetchError> { // Initialize .mlf directory - init_mlf_cache(&project_root).map_err(FetchError::InitFailed)?; + init_mlf_cache(project_root).map_err(FetchError::InitFailed)?; let mlf_dir = get_mlf_cache_dir(&project_root); let cache_file = mlf_dir.join(".lexicon-cache.toml"); diff --git a/mlf-cli/src/generate/mod.rs b/mlf-cli/src/generate/mod.rs index 08c24dd..02f53cd 100644 --- a/mlf-cli/src/generate/mod.rs +++ b/mlf-cli/src/generate/mod.rs @@ -1,3 +1,94 @@ +use crate::config::{find_project_root, ConfigError, MlfConfig}; +use std::path::PathBuf; + pub mod code; pub mod lexicon; pub mod mlf; + +/// Run all output configurations from mlf.toml +pub fn run_all() -> Result<(), std::io::Error> { + let current_dir = std::env::current_dir()?; + + let project_root = find_project_root(¤t_dir) + .map_err(|e| match e { + ConfigError::NotFound => { + std::io::Error::new( + std::io::ErrorKind::NotFound, + "No mlf.toml found. Please create a configuration file or provide explicit arguments." + ) + } + _ => std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to load config: {}", e)), + })?; + + let config_path = project_root.join("mlf.toml"); + let config = MlfConfig::load(&config_path) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to load config: {}", e)))?; + + if config.output.is_empty() { + println!("No output configurations found in mlf.toml"); + return Ok(()); + } + + println!("Running {} output configuration(s)...", config.output.len()); + + // Build input pattern from source directory + let source_pattern = format!("{}/**/*.mlf", config.source.directory); + let input_patterns = vec![source_pattern]; + + let mut errors = Vec::new(); + let mut success_count = 0; + + for output_config in &config.output { + let output_type = &output_config.r#type; + let output_dir = PathBuf::from(&output_config.directory); + + println!("\nGenerating {} output to {}...", output_type, output_config.directory); + + let result = match output_type.as_str() { + "lexicon" => { + lexicon::run(input_patterns.clone(), output_dir, false) + .map_err(|e| format!("{}", e)) + } + "mlf" => { + // For MLF output, we expect JSON lexicons as input + // This is a bit different - we'd need JSON input patterns + eprintln!(" Warning: MLF generation from TOML config not yet fully implemented"); + continue; + } + generator_type => { + // Assume it's a code generator (typescript, go, rust, etc.) + code::run(generator_type.to_string(), input_patterns.clone(), output_dir, false) + .map_err(|e| format!("{}", e)) + } + }; + + match result { + Ok(()) => { + success_count += 1; + println!(" āœ“ Generated {} output successfully", output_type); + } + Err(e) => { + errors.push((output_type.clone(), e)); + eprintln!(" āœ— Failed to generate {} output", output_type); + } + } + } + + if !errors.is_empty() { + eprintln!( + "\n{} output(s) generated successfully, {} error(s)", + success_count, + errors.len() + ); + for (output_type, error) in &errors { + eprintln!(" {} - {}", output_type, error); + } + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Failed to generate {} output(s)", errors.len()) + )); + } + + println!("\nāœ“ Successfully generated all {} output(s)", success_count); + Ok(()) +} diff --git a/mlf-cli/src/main.rs b/mlf-cli/src/main.rs index 9da9d2c..fd7c67b 100644 --- a/mlf-cli/src/main.rs +++ b/mlf-cli/src/main.rs @@ -31,7 +31,7 @@ struct Cli { #[derive(Subcommand)] enum Commands { Check { - #[arg(help = "MLF lexicon file(s) to validate (glob patterns supported)")] + #[arg(help = "MLF lexicon file(s) to validate (glob patterns supported). If omitted, checks source directory from mlf.toml")] input: Vec, }, @@ -45,12 +45,15 @@ enum Commands { Generate { #[command(subcommand)] - command: GenerateCommands, + command: Option, }, Fetch { - #[arg(help = "Namespace to fetch (e.g., stream.place)")] - nsid: String, + #[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, }, } @@ -93,24 +96,28 @@ fn main() { let result: Result<(), miette::Report> = match cli.command { Commands::Check { input } => { - check::run(input).into_diagnostic() + check::run_check(input).into_diagnostic() } Commands::Validate { lexicon, record } => { check::validate(lexicon, record).into_diagnostic() } Commands::Generate { command } => match command { - GenerateCommands::Lexicon { input, output, flat } => { + Some(GenerateCommands::Lexicon { input, output, flat }) => { generate::lexicon::run(input, output, flat).into_diagnostic() } - GenerateCommands::Code { generator, input, output, flat } => { + Some(GenerateCommands::Code { generator, input, output, flat }) => { generate::code::run(generator, input, output, flat).into_diagnostic() } - GenerateCommands::Mlf { input, output } => { + 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 } => { - fetch::fetch_lexicon(&nsid).into_diagnostic() + Commands::Fetch { nsid, save } => { + fetch::run_fetch(nsid, save).into_diagnostic() } }; -- 2.51.2