diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ab5d0e2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,203 @@ +use std::process; + +use config_forge::{ + cli::{parse_args, Command, OutputFormat, SchemaSource}, + convert::{convert, detect_format, parse_content, read_file, write_file, ConfigFormat, infer_schema}, + highlight::{highlight_env, highlight_ini, highlight_value, highlight_yaml}, + validate::validate_config, + typescript::{generate_ts_interface}, + ConfigForgeError, +}; + +fn main() { + let args = parse_args(); + + if args.no_color { + colored::control::set_override(false); + } + + if let Err(e) = run(args) { + eprintln!("Error: {}", e); + process::exit(1); + } +} + +fn run(args: config_forge::cli::Args) -> std::result::Result<(), ConfigForgeError> { + match args.command { + Command::Convert(convert_args) => { + let content = read_file(&convert_args.input)?; + let format = convert_args.from.unwrap_or_else(|| detect_format(&convert_args.input.to_string_lossy())); + let value = parse_content(&content, format)?; + + let converted = convert(value, convert_args.to.into())?; + + if let Some(output) = convert_args.output { + write_file(&output, &converted)?; + if args.verbose { + println!("Converted {} to {}", convert_args.input.display(), output.display()); + } + } else { + if !convert_args.no_highlight { + let highlighted = match convert_args.to { + OutputFormat::Json => { + let json_val: serde_json::Value = serde_json::from_str(&converted) + .unwrap_or_else(|_| serde_json::Value::String(converted.clone())); + highlight_value(&json_val, 0) + } + OutputFormat::Yaml => highlight_yaml(&converted), + OutputFormat::Ini => highlight_ini(&converted), + OutputFormat::Env => highlight_env(&converted), + OutputFormat::Toml => highlight_yaml(&converted), + }; + println!("{}", highlighted); + } else { + println!("{}", converted); + } + } + } + Command::Validate(validate_args) => { + let config_content = read_file(&validate_args.config)?; + let format = detect_format(&validate_args.config.to_string_lossy()); + + let (schema_path, schema_format) = if let Some(schema_file) = validate_args.schema_file { + (schema_file.to_string_lossy().to_string(), "file") + } else if let Some(schema) = validate_args.schema { + (schema, "inline") + } else { + return Err(ConfigForgeError::ValidationError { + path: String::new(), + reason: "No schema provided. Use --schema or --schema-file".to_string(), + }); + }; + + let result = validate_config(&config_content, &format.to_string(), &schema_path, schema_format)?; + + if result.valid { + println!("{}", "Validation passed".green().bold()); + } else { + println!("{}", "Validation failed".red().bold()); + for error in result.errors { + eprintln!(" {}: {}", error.path, error.message); + } + process::exit(1); + } + } + Command::GenerateTs(generate_args) => { + let content = read_file(&generate_args.input)?; + let format = generate_args.from.unwrap_or_else(|| detect_format(&generate_args.input.to_string_lossy())); + let value = parse_content(&content, format)?; + + let interface_name = generate_args.interface_name.unwrap_or_else(|| { + generate_args.input.file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("Config") + .to_string() + }); + + let ts = generate_ts_interface(&value, &interface_name)?; + + if let Some(output) = generate_args.output { + write_file(&output, &ts)?; + if args.verbose { + println!("Generated TypeScript interface {} to {}", interface_name, output.display()); + } + } else { + println!("{}", ts); + } + } + Command::Batch(batch_args) => { + use rayon::prelude::*; + + let pattern = batch_args.pattern; + let output_dir = batch_args.output_dir.unwrap_or_else(|| std::path::PathBuf::from(".")); + + let files: Vec<_> = glob::glob(&pattern) + .unwrap_or_else(|_| vec![]) + .filter_map(|e| e.ok()) + .filter(|p| p.is_file()) + .collect(); + + if files.is_empty() { + println!("No files found matching pattern: {}", pattern); + return Ok(()); + } + + if args.verbose { + println!("Found {} files to convert", files.len()); + } + + let convert_file = |input_path: std::path::PathBuf| -> std::result::Result<(), ConfigForgeError> { + let content = read_file(&input_path)?; + let format = detect_format(&input_path.to_string_lossy()); + let value = parse_content(&content, format)?; + + let converted = convert(value, batch_args.to.into())?; + + let file_name = input_path.file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("output"); + let output_path = output_dir.join(format!("{}.{}", file_name, batch_args.to)); + + write_file(&output_path, &converted)?; + Ok(()) + }; + + if batch_args.parallel { + files.par_iter() + .try_for_each(|entry| convert_file(entry.clone()))?; + } else { + for entry in &files { + convert_file(entry.clone())?; + } + } + + if args.verbose { + println!("Converted {} files to {}", files.len(), output_dir.display()); + } + } + Command::Infer(infer_args) => { + let content = read_file(&infer_args.input)?; + let format = infer_args.from.unwrap_or_else(|| detect_format(&infer_args.input.to_string_lossy())); + let value = parse_content(&content, format)?; + + let schema = infer_schema(&value); + let schema_str = serde_json::to_string_pretty(&schema)?; + + if let Some(output) = infer_args.output { + write_file(&output, &schema_str)?; + if args.verbose { + println!("Inferred schema from {} to {}", infer_args.input.display(), output.display()); + } + } else { + println!("{}", schema_str); + } + } + Command::Init(init_args) => { + let output = init_args.output.unwrap_or_else(|| std::path::PathBuf::from("configforge.toml")); + let default_config = r#"# ConfigForge default configuration +output_dir = "./" +default_format = "json" +color_output = true +parallel_processing = false +"#; + write_file(&output, default_config)?; + if args.verbose { + println!("Created default configuration at {}", output.display()); + } + } + } + + Ok(()) +} + +impl From for ConfigFormat { + fn from(format: OutputFormat) -> Self { + match format { + OutputFormat::Json => ConfigFormat::Json, + OutputFormat::Yaml => ConfigFormat::Yaml, + OutputFormat::Toml => ConfigFormat::Toml, + OutputFormat::Env => ConfigFormat::Env, + OutputFormat::Ini => ConfigFormat::Ini, + } + } +}