use clap::Parser; use std::collections::HashMap; use std::fs; use std::io::{self, Read}; use std::path::PathBuf; use uson::{parse, apply_builtin_types, Value}; use base64::Engine; #[derive(Parser)] #[command(name = "uson")] #[command(author, version)] #[command(about = "μson (uson) is a shorthand for JSON", long_about = None)] struct Cli { /// μson expression to parse (can be multiple words without quotes) #[arg(value_name = "EXPRESSION", num_args = 0..)] expression: Vec, /// "object" mode - combines all expressions into one object #[arg(short, long)] object: bool, /// "json" mode (default) #[arg(short, long)] json: bool, /// Load data from file #[arg(short, long, value_name = "FILE")] input: Option, /// Write output to file #[arg(long, value_name = "FILE")] output: Option, /// Pretty print output #[arg(short, long)] pretty: bool, /// Output format: json (default), uson, form, yaml #[arg(short = 'F', long, value_name = "FORMAT", default_value = "json")] format: String, /// Return output in form query-string #[arg(short, long)] #[cfg(feature = "form")] form: bool, /// Return output in YAML #[arg(short = 'y', long)] #[cfg(feature = "yaml")] yaml: bool, /// Use custom usonrc file instead of ~/.usonrc.toml #[arg(short, long, value_name = "FILE")] usonrc: Option, /// Output in hex encoding #[arg(long)] hex: bool, /// Output in base64 encoding #[arg(long)] base64: bool, } #[derive(Debug, serde::Deserialize)] struct UsonConfig { #[serde(default)] types: HashMap, } fn main() { let cli = Cli::parse(); if let Err(e) = run(cli) { eprintln!("Error: {}", e); std::process::exit(1); } } fn run(cli: Cli) -> Result<(), Box> { // Extract all values we need from cli before any moves let expression = if !cli.expression.is_empty() { Some(cli.expression.join(" ")) } else { None }; let input_file = cli.input; let object_mode = cli.object; let output_file = cli.output; let hex_encode = cli.hex; let base64_encode = cli.base64; let pretty = cli.pretty; let usonrc_path = cli.usonrc; let format = cli.format.to_lowercase(); #[cfg(feature = "yaml")] let yaml_output = cli.yaml; #[cfg(feature = "form")] let form_output = cli.form; // Load configuration let config = load_config(usonrc_path)?; // Get input let input = get_input(expression, input_file)?; // Parse let mut values = parse(&input)?; // Apply built-in type handlers first values = values.into_iter().map(apply_builtin_types).collect(); // Apply custom type handlers from config if !config.types.is_empty() { values = apply_type_handlers(values, &config)?; } // Format output based on specified format let output = match format.as_str() { "uson" | "microson" => { // Output in μson format format_uson(&values, object_mode, pretty)? } "json" => { // Convert to output format based on mode let result = if object_mode { merge_to_object(values)? } else { serde_json::to_value(&values)? }; format_output( &result, pretty, #[cfg(feature = "yaml")] yaml_output, #[cfg(feature = "form")] form_output, )? } _ => { return Err(format!("Unknown format: {}", format).into()); } }; // Encode if needed let final_output = if hex_encode { hex::encode(&output) } else if base64_encode { base64::engine::general_purpose::STANDARD.encode(&output) } else { output }; // Write output write_output(&final_output, output_file)?; Ok(()) } fn format_uson(values: &[Value], object_mode: bool, pretty: bool) -> Result> { if object_mode { // Merge all into one object first let mut merged = HashMap::new(); for value in values { match value { Value::Object(obj) => { for (key, val) in obj { merged.insert(key.clone(), val.clone()); } } _ => { return Err("Object mode requires all values to be objects".into()); } } } let merged_value = Value::Object(merged); Ok(if pretty { merged_value.to_uson_string_pretty() } else { merged_value.to_uson_string() }) } else { // Output each value if values.len() == 1 { Ok(if pretty { values[0].to_uson_string_pretty() } else { values[0].to_uson_string() }) } else { let strings: Vec = values.iter().map(|v| { if pretty { v.to_uson_string_pretty() } else { v.to_uson_string() } }).collect(); Ok(strings.join(if pretty { "\n\n" } else { " " })) } } } fn load_config(custom_path: Option) -> Result> { let config_path = if let Some(path) = custom_path { path } else { let home = dirs::home_dir().ok_or("Could not find home directory")?; home.join(".usonrc.toml") }; if config_path.exists() { let content = fs::read_to_string(config_path)?; let config: UsonConfig = toml::from_str(&content)?; Ok(config) } else { Ok(UsonConfig { types: HashMap::new(), }) } } fn get_input( expression: Option, input_file: Option, ) -> Result> { if let Some(expr) = expression { Ok(expr) } else if let Some(file) = input_file { Ok(fs::read_to_string(file)?) } else { // Read from stdin let mut buffer = String::new(); io::stdin().read_to_string(&mut buffer)?; Ok(buffer) } } fn apply_type_handlers( values: Vec, _config: &UsonConfig, ) -> Result, Box> { // Note: Custom type handlers would require dynamic evaluation // For now, we'll keep typed values as-is // In a real implementation, you might want to use a scripting engine like rhai or lua Ok(values) } fn merge_to_object(values: Vec) -> Result> { let mut result = serde_json::Map::new(); for value in values { match value { Value::Object(obj) => { for (key, val) in obj { result.insert(key, serde_json::to_value(val)?); } } _ => { return Err("Object mode requires all values to be objects".into()); } } } Ok(serde_json::Value::Object(result)) } fn format_output( value: &serde_json::Value, pretty: bool, #[cfg(feature = "yaml")] yaml: bool, #[cfg(feature = "form")] form: bool, ) -> Result> { #[cfg(feature = "yaml")] if yaml { return Ok(serde_yaml::to_string(value)?); } #[cfg(feature = "form")] if form { return Ok(serde_qs::to_string(value)?); } // Default: JSON if pretty { Ok(serde_json::to_string_pretty(value)?) } else { Ok(serde_json::to_string(value)?) } } fn write_output( output: &str, output_file: Option, ) -> Result<(), Box> { if let Some(file) = output_file { fs::write(file, output)?; } else { println!("{}", output); } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_merge_to_object() { let values = vec![ Value::Object({ let mut map = HashMap::new(); map.insert("user".to_string(), Value::String("john".to_string())); map }), Value::Object({ let mut map = HashMap::new(); map.insert("age".to_string(), Value::Number(uson::Number::Integer(42))); map }), ]; let result = merge_to_object(values).unwrap(); assert!(result.is_object()); let obj = result.as_object().unwrap(); assert_eq!(obj.get("user").unwrap().as_str().unwrap(), "john"); assert_eq!(obj.get("age").unwrap().as_i64().unwrap(), 42); } }