wcl 0.6.1-alpha

WCL (Wil's Configuration Language) — a typed, block-structured configuration language
use std::path::Path;

use crate::cli::LibraryArgs;

pub fn run(
    file: &Path,
    to: Option<&str>,
    from: Option<&str>,
    lib_args: &LibraryArgs,
) -> Result<(), String> {
    let source = std::fs::read_to_string(file)
        .map_err(|e| format!("cannot read {}: {}", file.display(), e))?;

    match to {
        Some("json") => {
            let mut options = crate::ParseOptions {
                root_dir: file.parent().unwrap_or(Path::new(".")).to_path_buf(),
                ..Default::default()
            };
            lib_args.apply(&mut options);
            let doc = crate::parse(&source, options);
            if doc.has_errors() {
                for diag in doc.errors() {
                    eprintln!("{}", super::format_diagnostic(diag, &doc.source_map, file));
                }
                return Err("document has errors".to_string());
            }
            let mut json_map = serde_json::Map::new();
            for (key, val) in &doc.values {
                json_map.insert(key.clone(), value_to_json(val));
            }
            let json = serde_json::Value::Object(json_map);
            println!("{}", serde_json::to_string_pretty(&json).unwrap());
            Ok(())
        }
        Some("yaml") | Some("yml") => {
            let mut options = crate::ParseOptions {
                root_dir: file.parent().unwrap_or(Path::new(".")).to_path_buf(),
                ..Default::default()
            };
            lib_args.apply(&mut options);
            let doc = crate::parse(&source, options);
            if doc.has_errors() {
                for diag in doc.errors() {
                    eprintln!("{}", super::format_diagnostic(diag, &doc.source_map, file));
                }
                return Err("document has errors".to_string());
            }
            let mut json_map = serde_json::Map::new();
            for (key, val) in &doc.values {
                json_map.insert(key.clone(), value_to_json(val));
            }
            let json = serde_json::Value::Object(json_map);
            let yaml = serde_yaml_ng::to_string(&json).map_err(|e| format!("YAML error: {}", e))?;
            print!("{}", yaml);
            Ok(())
        }
        Some("toml") => {
            let mut options = crate::ParseOptions {
                root_dir: file.parent().unwrap_or(Path::new(".")).to_path_buf(),
                ..Default::default()
            };
            lib_args.apply(&mut options);
            let doc = crate::parse(&source, options);
            if doc.has_errors() {
                for diag in doc.errors() {
                    eprintln!("{}", super::format_diagnostic(diag, &doc.source_map, file));
                }
                return Err("document has errors".to_string());
            }
            let mut json_map = serde_json::Map::new();
            for (key, val) in &doc.values {
                json_map.insert(key.clone(), value_to_json(val));
            }
            let json = serde_json::Value::Object(json_map);
            let toml_val: toml::Value = serde_json::from_value(json.clone())
                .map_err(|e| format!("TOML conversion error: {}", e))?;
            let toml_str =
                toml::to_string_pretty(&toml_val).map_err(|e| format!("TOML error: {}", e))?;
            print!("{}", toml_str);
            Ok(())
        }
        Some(fmt) => Err(format!("output format '{}' not yet supported", fmt)),
        None => match from {
            Some("json") => {
                let json: serde_json::Value =
                    serde_json::from_str(&source).map_err(|e| format!("invalid JSON: {}", e))?;
                print_json_as_wcl(&json, 0);
                Ok(())
            }
            Some("yaml") | Some("yml") => {
                let yaml: serde_json::Value =
                    serde_yaml_ng::from_str(&source).map_err(|e| format!("invalid YAML: {}", e))?;
                print_json_as_wcl(&yaml, 0);
                Ok(())
            }
            Some("toml") => {
                let toml_val: toml::Value = source
                    .parse::<toml::Value>()
                    .map_err(|e| format!("invalid TOML: {}", e))?;
                let json: serde_json::Value = serde_json::to_value(&toml_val)
                    .map_err(|e| format!("conversion error: {}", e))?;
                print_json_as_wcl(&json, 0);
                Ok(())
            }
            Some(fmt) => Err(format!("input format '{}' not yet supported", fmt)),
            None => Err("specify --to or --from".to_string()),
        },
    }
}

fn value_to_json(val: &crate::Value) -> serde_json::Value {
    match val {
        crate::Value::String(s) => serde_json::Value::String(s.clone()),
        crate::Value::Int(i) => serde_json::json!(i),
        crate::Value::Float(f) => serde_json::json!(f),
        crate::Value::Bool(b) => serde_json::Value::Bool(*b),
        crate::Value::Null => serde_json::Value::Null,
        crate::Value::Identifier(s) => serde_json::Value::String(s.clone()),
        crate::Value::Symbol(s) => serde_json::Value::String(s.clone()),
        crate::Value::List(items) => {
            serde_json::Value::Array(items.iter().map(value_to_json).collect())
        }
        crate::Value::Map(m) => {
            let obj: serde_json::Map<String, serde_json::Value> = m
                .iter()
                .map(|(k, v)| (k.clone(), value_to_json(v)))
                .collect();
            serde_json::Value::Object(obj)
        }
        _ => serde_json::Value::String(format!("{}", val)),
    }
}

fn print_json_as_wcl(json: &serde_json::Value, indent: usize) {
    let indent_str = "    ".repeat(indent);
    match json {
        serde_json::Value::Object(map) => {
            for (key, val) in map {
                match val {
                    serde_json::Value::Object(_) => {
                        println!("{}{} {{", indent_str, key);
                        print_json_as_wcl(val, indent + 1);
                        println!("{}}}", indent_str);
                    }
                    _ => {
                        print!("{}{} = ", indent_str, key);
                        print_json_value(val);
                        println!();
                    }
                }
            }
        }
        _ => {
            print_json_value(json);
            println!();
        }
    }
}

fn print_json_value(val: &serde_json::Value) {
    match val {
        serde_json::Value::String(s) => print!("\"{}\"", s),
        serde_json::Value::Number(n) => print!("{}", n),
        serde_json::Value::Bool(b) => print!("{}", b),
        serde_json::Value::Null => print!("null"),
        serde_json::Value::Array(items) => {
            print!("[");
            for (i, item) in items.iter().enumerate() {
                if i > 0 {
                    print!(", ");
                }
                print_json_value(item);
            }
            print!("]");
        }
        serde_json::Value::Object(_) => print!("{{...}}"),
    }
}