nils-api-websocket 0.4.1

CLI crate for nils-api-websocket in the nils-cli workspace.
mod cli;
mod commands;

use std::io::IsTerminal;
use std::path::PathBuf;

use clap::Parser;
use clap::error::ErrorKind;

use crate::cli::{Cli, Command};
use crate::commands::{cmd_call, cmd_history, cmd_report, cmd_report_from_cmd};

fn argv_with_default_command(raw_args: &[String]) -> Vec<String> {
    let mut argv = vec!["api-websocket".to_string()];
    if raw_args.is_empty() {
        return argv;
    }

    let first = raw_args[0].as_str();
    let is_root_help = first == "-h" || first == "--help";
    let is_root_version = first == "-V" || first == "--version";

    let is_explicit_command = matches!(first, "call" | "history" | "report" | "report-from-cmd");
    if !is_explicit_command && !is_root_help && !is_root_version {
        argv.push("call".to_string());
    }

    argv.extend_from_slice(raw_args);
    argv
}

fn print_root_help() {
    println!("Usage: api-websocket <command> [args]");
    println!();
    println!("Commands:");
    println!(
        "  call      Execute a request file and print the last received message to stdout (default)"
    );
    println!("  history   Print the last (or last N) history entries");
    println!("  report    Generate a Markdown API test report");
    println!("  report-from-cmd  Generate a report from a saved `call` snippet");
    println!();
    println!("Common options (see subcommand help for full details):");
    println!("  --config-dir <dir>   Seed setup/websocket discovery (call/history/report)");
    println!("  --format <text|json> Structured output for call/history");
    println!("  -h, --help           Print help");
    println!();
    println!("Examples:");
    println!("  api-websocket --help");
    println!("  api-websocket call --help");
    println!("  api-websocket report --help");
    println!("  api-websocket report-from-cmd --help");
}

fn main() {
    std::process::exit(run());
}

fn run() -> i32 {
    let raw_args: Vec<String> = std::env::args().skip(1).collect();
    let is_root_help = raw_args.len() == 1 && (raw_args[0] == "-h" || raw_args[0] == "--help");
    if raw_args.is_empty() || is_root_help {
        print_root_help();
        return 0;
    }

    let argv = argv_with_default_command(&raw_args);

    let cli = match Cli::try_parse_from(argv) {
        Ok(v) => v,
        Err(err) => {
            let code = err.exit_code();
            if matches!(
                err.kind(),
                ErrorKind::DisplayHelp | ErrorKind::DisplayVersion
            ) {
                let _ = err.print();
                return 0;
            }
            let _ = err.print();
            return code;
        }
    };

    let invocation_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    let invocation_dir = std::fs::canonicalize(&invocation_dir).unwrap_or(invocation_dir);

    let mut stdout = std::io::stdout().lock();
    let mut stderr = std::io::stderr().lock();
    let stdout_is_tty = std::io::stdout().is_terminal();

    match cli.command {
        None => {
            print_root_help();
            0
        }
        Some(Command::Call(args)) => cmd_call(
            &args,
            &invocation_dir,
            stdout_is_tty,
            &mut stdout,
            &mut stderr,
        ),
        Some(Command::History(args)) => {
            cmd_history(&args, &invocation_dir, &mut stdout, &mut stderr)
        }
        Some(Command::Report(args)) => cmd_report(&args, &invocation_dir, &mut stdout, &mut stderr),
        Some(Command::ReportFromCmd(args)) => {
            cmd_report_from_cmd(&args, &invocation_dir, &mut stdout, &mut stderr)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;

    #[test]
    fn argv_with_default_command_inserts_call() {
        let argv = argv_with_default_command(&[]);
        assert_eq!(argv, vec!["api-websocket".to_string()]);

        let argv = argv_with_default_command(&["--help".to_string()]);
        assert_eq!(
            argv,
            vec!["api-websocket".to_string(), "--help".to_string()]
        );

        let argv = argv_with_default_command(&["history".to_string()]);
        assert_eq!(
            argv,
            vec!["api-websocket".to_string(), "history".to_string()]
        );

        let argv = argv_with_default_command(&["requests/health.ws.json".to_string()]);
        assert_eq!(
            argv,
            vec![
                "api-websocket".to_string(),
                "call".to_string(),
                "requests/health.ws.json".to_string()
            ]
        );
    }
}