rimu-cli 0.2.0

A data structure template system.
use std::{
    cell::RefCell,
    error::Error,
    io::{Read, Write},
    process::ExitCode,
    rc::Rc,
    str::FromStr,
};

use clap::Parser;
use clio::*;
use rimu::{evaluate, parse, Environment, ErrorReport, SerdeValue, SourceId, Value};

#[derive(Debug, Clone, Copy)]
enum Format {
    Yaml,
    Json,
    Toml,
}

impl FromStr for Format {
    type Err = String;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s {
            "yaml" | "yml" => Ok(Format::Yaml),
            "json" => Ok(Format::Json),
            "toml" => Ok(Format::Toml),
            _ => Err(format!("Unknown format: {}", s)),
        }
    }
}

#[derive(Debug, Parser)]
#[clap(version)]
struct Args {
    #[clap(long, short, value_parser)]
    input: Input,

    #[arg(long, short, value_parser)]
    env: Option<Input>,

    #[clap(long, short, value_parser, default_value = "-")]
    output: Output,

    #[clap(long, short, default_value = "yaml")]
    format: Format,
}

fn main() -> std::result::Result<ExitCode, Box<dyn Error>> {
    let mut args = Args::parse();

    let mut input = String::new();
    args.input.read_to_string(&mut input)?;
    let input_source = SourceId::from_path(args.input.path().path());

    let env = if let Some(mut env_arg) = args.env {
        let mut env_string = String::new();
        env_arg.read_to_string(&mut env_string)?;
        let env_value: SerdeValue = env_string.into();
        Environment::from_value(&env_value, None)?
    } else {
        Environment::new()
    };

    let (block, errors) = parse(input.as_str(), input_source.clone());

    if !errors.is_empty() {
        for error in errors {
            let report: ErrorReport = error.into();
            report.display(input.as_str(), input_source.clone());
        }
        return Ok(ExitCode::FAILURE);
    }

    let Some(block) = block else {
        println!("No block.");
        return Ok(ExitCode::FAILURE);
    };

    // println!("Block: {}", block);

    let env = Rc::new(RefCell::new(env));
    let value = match evaluate(&block, env) {
        Ok(value) => value,
        Err(error) => {
            let report: ErrorReport = error.into();
            report.display(input.as_str(), input_source.clone());
            return Ok(ExitCode::FAILURE);
        }
    };
    let value: Value = value.into_inner();
    let value: SerdeValue = value.into();

    let output: String = match args.format {
        Format::Yaml => serde_yaml::to_string(&value)?,
        Format::Json => serde_json::to_string(&value)?,
        Format::Toml => toml::to_string(&value)?,
    };

    args.output.write_all(output.as_bytes())?;

    Ok(ExitCode::SUCCESS)
}