A human-friendly DSL for ATProto Lexicons
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Use toml file

+237 -20
+34 -2
mlf-cli/src/check.rs
··· 1 + use crate::config::{find_project_root, ConfigError, MlfConfig}; 1 2 use miette::Diagnostic; 2 3 use mlf_diagnostics::{ParseDiagnostic, ValidationDiagnostic}; 3 4 use std::path::PathBuf; ··· 55 56 RecordValidation { 56 57 errors: Vec<mlf_validation::ValidationError>, 57 58 }, 59 + 60 + #[error("Failed to load config: {0}")] 61 + #[diagnostic(code(mlf::check::config_error))] 62 + ConfigError(#[from] ConfigError), 58 63 } 59 64 60 - pub fn run(input_patterns: Vec<String>) -> Result<(), CheckError> { 65 + pub fn run_check(input_patterns: Vec<String>) -> Result<(), CheckError> { 66 + // If no input patterns provided, use source directory from mlf.toml 67 + let patterns = if input_patterns.is_empty() { 68 + let current_dir = std::env::current_dir() 69 + .map_err(|e| CheckError::ReadFile { 70 + path: ".".to_string(), 71 + source: e, 72 + })?; 73 + 74 + match find_project_root(&current_dir) { 75 + Ok(project_root) => { 76 + let config_path = project_root.join("mlf.toml"); 77 + let config = MlfConfig::load(&config_path)?; 78 + let source_pattern = format!("{}/**/*.mlf", config.source.directory); 79 + println!("Using source directory from mlf.toml: {}", config.source.directory); 80 + vec![source_pattern] 81 + } 82 + Err(ConfigError::NotFound) => { 83 + return Err(CheckError::ValidationErrors { 84 + help: Some("No input files provided and no mlf.toml found. Please provide input files or create a mlf.toml configuration.".to_string()), 85 + }); 86 + } 87 + Err(e) => return Err(CheckError::ConfigError(e)), 88 + } 89 + } else { 90 + input_patterns 91 + }; 92 + 61 93 let mut file_paths = Vec::new(); 62 94 63 - for pattern in input_patterns { 95 + for pattern in patterns { 64 96 if pattern.contains('*') || pattern.contains('?') { 65 97 for entry in glob::glob(&pattern).map_err(|source| CheckError::InvalidGlob { 66 98 pattern: pattern.clone(),
+95 -8
mlf-cli/src/fetch.rs
··· 106 106 value: serde_json::Value, 107 107 } 108 108 109 - pub fn fetch_lexicon(nsid: &str) -> Result<(), FetchError> { 109 + /// Main entry point for fetch command 110 + pub fn run_fetch(nsid: Option<String>, save: bool) -> Result<(), FetchError> { 110 111 // Find project root 111 112 let current_dir = std::env::current_dir()?; 112 - let project_root = match find_project_root(&current_dir) { 113 - Ok(root) => root, 113 + let project_root = ensure_project_root(&current_dir)?; 114 + 115 + match nsid { 116 + Some(namespace) => { 117 + // Fetch single namespace 118 + fetch_lexicon(&namespace, &project_root)?; 119 + 120 + // Save to mlf.toml if --save flag is provided 121 + if save { 122 + save_dependency(&project_root, &namespace)?; 123 + } 124 + 125 + Ok(()) 126 + } 127 + None => { 128 + // Fetch all dependencies from mlf.toml 129 + fetch_all_dependencies(&project_root) 130 + } 131 + } 132 + } 133 + 134 + fn ensure_project_root(current_dir: &std::path::Path) -> Result<std::path::PathBuf, FetchError> { 135 + match find_project_root(current_dir) { 136 + Ok(root) => Ok(root), 114 137 Err(ConfigError::NotFound) => { 115 138 // Ask user if they want to create mlf.toml 116 139 eprintln!("No mlf.toml found in current or parent directories."); ··· 125 148 let config_path = current_dir.join("mlf.toml"); 126 149 MlfConfig::create_default(&config_path).map_err(FetchError::NoProjectRoot)?; 127 150 println!("Created mlf.toml in {}", current_dir.display()); 128 - current_dir 151 + Ok(current_dir.to_path_buf()) 129 152 } else { 130 - return Err(FetchError::NoProjectRoot(ConfigError::NotFound)); 153 + Err(FetchError::NoProjectRoot(ConfigError::NotFound)) 154 + } 155 + } 156 + Err(e) => Err(FetchError::NoProjectRoot(e)), 157 + } 158 + } 159 + 160 + fn fetch_all_dependencies(project_root: &std::path::Path) -> Result<(), FetchError> { 161 + // Load mlf.toml 162 + let config_path = project_root.join("mlf.toml"); 163 + let config = MlfConfig::load(&config_path).map_err(FetchError::NoProjectRoot)?; 164 + 165 + if config.dependencies.is_empty() { 166 + println!("No dependencies found in mlf.toml"); 167 + return Ok(()); 168 + } 169 + 170 + println!("Fetching {} dependencies...", config.dependencies.len()); 171 + 172 + let mut errors = Vec::new(); 173 + let mut success_count = 0; 174 + 175 + for dep in &config.dependencies { 176 + println!("\nFetching: {}", dep); 177 + match fetch_lexicon(dep, project_root) { 178 + Ok(()) => { 179 + success_count += 1; 180 + } 181 + Err(e) => { 182 + errors.push((dep.clone(), format!("{}", e))); 131 183 } 132 184 } 133 - Err(e) => return Err(FetchError::NoProjectRoot(e)), 134 - }; 185 + } 186 + 187 + if !errors.is_empty() { 188 + eprintln!( 189 + "\n{} dependency(ies) fetched successfully, {} error(s):", 190 + success_count, 191 + errors.len() 192 + ); 193 + for (dep, error) in &errors { 194 + eprintln!(" {} - {}", dep, error); 195 + } 196 + return Err(FetchError::HttpError(format!( 197 + "Failed to fetch {} dependencies", 198 + errors.len() 199 + ))); 200 + } 201 + 202 + println!("\n✓ Successfully fetched all {} dependencies", success_count); 203 + Ok(()) 204 + } 205 + 206 + fn save_dependency(project_root: &std::path::Path, nsid: &str) -> Result<(), FetchError> { 207 + let config_path = project_root.join("mlf.toml"); 208 + let mut config = MlfConfig::load(&config_path).map_err(FetchError::NoProjectRoot)?; 209 + 210 + if config.dependencies.contains(&nsid.to_string()) { 211 + println!("Dependency '{}' already in mlf.toml", nsid); 212 + return Ok(()); 213 + } 135 214 215 + config.dependencies.push(nsid.to_string()); 216 + config.save(&config_path).map_err(FetchError::NoProjectRoot)?; 217 + 218 + println!("Added '{}' to dependencies in mlf.toml", nsid); 219 + Ok(()) 220 + } 221 + 222 + pub fn fetch_lexicon(nsid: &str, project_root: &std::path::Path) -> Result<(), FetchError> { 136 223 // Initialize .mlf directory 137 - init_mlf_cache(&project_root).map_err(FetchError::InitFailed)?; 224 + init_mlf_cache(project_root).map_err(FetchError::InitFailed)?; 138 225 139 226 let mlf_dir = get_mlf_cache_dir(&project_root); 140 227 let cache_file = mlf_dir.join(".lexicon-cache.toml");
+91
mlf-cli/src/generate/mod.rs
··· 1 + use crate::config::{find_project_root, ConfigError, MlfConfig}; 2 + use std::path::PathBuf; 3 + 1 4 pub mod code; 2 5 pub mod lexicon; 3 6 pub mod mlf; 7 + 8 + /// Run all output configurations from mlf.toml 9 + pub fn run_all() -> Result<(), std::io::Error> { 10 + let current_dir = std::env::current_dir()?; 11 + 12 + let project_root = find_project_root(&current_dir) 13 + .map_err(|e| match e { 14 + ConfigError::NotFound => { 15 + std::io::Error::new( 16 + std::io::ErrorKind::NotFound, 17 + "No mlf.toml found. Please create a configuration file or provide explicit arguments." 18 + ) 19 + } 20 + _ => std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to load config: {}", e)), 21 + })?; 22 + 23 + let config_path = project_root.join("mlf.toml"); 24 + let config = MlfConfig::load(&config_path) 25 + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to load config: {}", e)))?; 26 + 27 + if config.output.is_empty() { 28 + println!("No output configurations found in mlf.toml"); 29 + return Ok(()); 30 + } 31 + 32 + println!("Running {} output configuration(s)...", config.output.len()); 33 + 34 + // Build input pattern from source directory 35 + let source_pattern = format!("{}/**/*.mlf", config.source.directory); 36 + let input_patterns = vec![source_pattern]; 37 + 38 + let mut errors = Vec::new(); 39 + let mut success_count = 0; 40 + 41 + for output_config in &config.output { 42 + let output_type = &output_config.r#type; 43 + let output_dir = PathBuf::from(&output_config.directory); 44 + 45 + println!("\nGenerating {} output to {}...", output_type, output_config.directory); 46 + 47 + let result = match output_type.as_str() { 48 + "lexicon" => { 49 + lexicon::run(input_patterns.clone(), output_dir, false) 50 + .map_err(|e| format!("{}", e)) 51 + } 52 + "mlf" => { 53 + // For MLF output, we expect JSON lexicons as input 54 + // This is a bit different - we'd need JSON input patterns 55 + eprintln!(" Warning: MLF generation from TOML config not yet fully implemented"); 56 + continue; 57 + } 58 + generator_type => { 59 + // Assume it's a code generator (typescript, go, rust, etc.) 60 + code::run(generator_type.to_string(), input_patterns.clone(), output_dir, false) 61 + .map_err(|e| format!("{}", e)) 62 + } 63 + }; 64 + 65 + match result { 66 + Ok(()) => { 67 + success_count += 1; 68 + println!(" ✓ Generated {} output successfully", output_type); 69 + } 70 + Err(e) => { 71 + errors.push((output_type.clone(), e)); 72 + eprintln!(" ✗ Failed to generate {} output", output_type); 73 + } 74 + } 75 + } 76 + 77 + if !errors.is_empty() { 78 + eprintln!( 79 + "\n{} output(s) generated successfully, {} error(s)", 80 + success_count, 81 + errors.len() 82 + ); 83 + for (output_type, error) in &errors { 84 + eprintln!(" {} - {}", output_type, error); 85 + } 86 + return Err(std::io::Error::new( 87 + std::io::ErrorKind::Other, 88 + format!("Failed to generate {} output(s)", errors.len()) 89 + )); 90 + } 91 + 92 + println!("\n✓ Successfully generated all {} output(s)", success_count); 93 + Ok(()) 94 + }
+17 -10
mlf-cli/src/main.rs
··· 31 31 #[derive(Subcommand)] 32 32 enum Commands { 33 33 Check { 34 - #[arg(help = "MLF lexicon file(s) to validate (glob patterns supported)")] 34 + #[arg(help = "MLF lexicon file(s) to validate (glob patterns supported). If omitted, checks source directory from mlf.toml")] 35 35 input: Vec<String>, 36 36 }, 37 37 ··· 45 45 46 46 Generate { 47 47 #[command(subcommand)] 48 - command: GenerateCommands, 48 + command: Option<GenerateCommands>, 49 49 }, 50 50 51 51 Fetch { 52 - #[arg(help = "Namespace to fetch (e.g., stream.place)")] 53 - nsid: String, 52 + #[arg(help = "Namespace to fetch (e.g., stream.place). If omitted, fetches all dependencies from mlf.toml")] 53 + nsid: Option<String>, 54 + 55 + #[arg(long, help = "Add namespace to dependencies in mlf.toml")] 56 + save: bool, 54 57 }, 55 58 } 56 59 ··· 93 96 94 97 let result: Result<(), miette::Report> = match cli.command { 95 98 Commands::Check { input } => { 96 - check::run(input).into_diagnostic() 99 + check::run_check(input).into_diagnostic() 97 100 } 98 101 Commands::Validate { lexicon, record } => { 99 102 check::validate(lexicon, record).into_diagnostic() 100 103 } 101 104 Commands::Generate { command } => match command { 102 - GenerateCommands::Lexicon { input, output, flat } => { 105 + Some(GenerateCommands::Lexicon { input, output, flat }) => { 103 106 generate::lexicon::run(input, output, flat).into_diagnostic() 104 107 } 105 - GenerateCommands::Code { generator, input, output, flat } => { 108 + Some(GenerateCommands::Code { generator, input, output, flat }) => { 106 109 generate::code::run(generator, input, output, flat).into_diagnostic() 107 110 } 108 - GenerateCommands::Mlf { input, output } => { 111 + Some(GenerateCommands::Mlf { input, output }) => { 109 112 generate::mlf::run(input, output).into_diagnostic() 110 113 } 114 + None => { 115 + // Run all outputs from mlf.toml 116 + generate::run_all().into_diagnostic() 117 + } 111 118 }, 112 - Commands::Fetch { nsid } => { 113 - fetch::fetch_lexicon(&nsid).into_diagnostic() 119 + Commands::Fetch { nsid, save } => { 120 + fetch::run_fetch(nsid, save).into_diagnostic() 114 121 } 115 122 }; 116 123