tovuk 0.1.86

Deploy Rust workers, static frontends, and full-stack services to Tovuk.
use super::{flags, model::CliOptions};
use crate::cli::errors::{Result, agent_error};
use std::env;

const END_OF_OPTIONS: &str = "\x2d\x2d";
const OUTPUT_ENV: &str = "TOVUK_OUTPUT";

pub(crate) fn parse_args(argv: &[String]) -> Result<CliOptions> {
    let mut cli = CliOptions::default();
    apply_output_env(&mut cli)?;
    let mut positional = Vec::new();
    let mut index = 0usize;

    while index < argv.len() {
        let arg = argv[index].clone();
        if arg == END_OF_OPTIONS {
            positional.extend(argv.iter().skip(index + 1).cloned());
            break;
        }
        if let Some((name, inline)) = flags::parse_flag(&arg) {
            let consumed = flags::apply_flag(&mut cli, name, inline, argv, index)?;
            index += consumed;
        } else if arg.starts_with('-') {
            return Err(agent_error(
                "unknown_argument",
                format!("Unknown Tovuk option: {arg}."),
                "Run `tovuk --help`, remove or correct the unsupported option, then retry.",
                cli.output.json,
            ));
        } else {
            positional.push(arg);
            index += 1;
        }
    }

    if let Some(command) = positional.first() {
        cli.command.clone_from(command);
        cli.args = positional.into_iter().skip(1).collect();
    }
    cli.api_url
        .truncate(cli.api_url.trim_end_matches('/').len());
    Ok(cli)
}

fn apply_output_env(cli: &mut CliOptions) -> Result<()> {
    let Ok(value) = env::var(OUTPUT_ENV) else {
        return Ok(());
    };
    let value = value.trim();
    if value.is_empty() {
        return Ok(());
    }
    flags::set_output_format(cli, value, OUTPUT_ENV, false)
}

#[cfg(test)]
mod tests {
    use super::parse_args;

    fn args(values: &[&str]) -> Vec<String> {
        values
            .iter()
            .map(std::string::ToString::to_string)
            .collect()
    }

    #[test]
    fn output_json_enables_json_output() {
        let parsed = parse_args(&args(&["check", "--output", "json"]));

        assert!(
            parsed
                .as_ref()
                .is_ok_and(|cli| cli.output.json && cli.command == "check")
        );
    }

    #[test]
    fn output_text_overrides_json_flag() {
        let parsed = parse_args(&args(&["check", "--json", "--output=text"]));

        assert!(parsed.as_ref().is_ok_and(|cli| !cli.output.json));
    }

    #[test]
    fn invalid_output_value_is_rejected() {
        let parsed = parse_args(&args(&["check", "--output", "yaml"]));

        assert!(
            parsed
                .as_ref()
                .is_err_and(|error| error.payload().code == "invalid_argument")
        );
    }
}