xacli 0.2.1

A modern, developer-friendly CLI framework for Rust
Documentation
//! Basic example demonstrating xacli-core capabilities
//!
//! This example shows:
//! - App creation with metadata
//! - Commands and subcommands
//! - Flags, options, and positional arguments
//! - Command execution hooks (pre_run, run, post_run)
//! - Argument access in handlers
//!
//! Run with:
//! ```bash
//! cargo run --example basic -- --help
//! cargo run --example basic -- greet --name World
//! cargo run --example basic -- greet --name World --count 3
//! cargo run --example basic -- echo Hello World
//! cargo run --example basic -- calc add 1 2
//! ```

use std::io::Write;

use xacli::{App, Arg, ArgType, Command, Context, InputValue};

fn create_app() -> App {
    App::new("basic", "1.0.0")
        // Greet command - demonstrates flags and options
        .command(
            Command::new("greet")
                .title("Greet someone")
                .description("Print a greeting message to the specified name")
                .arg(
                    Arg::option("name", ArgType::String)
                        .short('n')
                        .title("Name to greet")
                        .description("The name of the person to greet")
                        .default("World"),
                )
                .arg(
                    Arg::option("count", ArgType::Int)
                        .short('c')
                        .title("Repeat count")
                        .description("Number of times to repeat the greeting")
                        .default("1"),
                )
                .arg(
                    Arg::flag("loud")
                        .short('l')
                        .title("Loud mode")
                        .description("Print in uppercase"),
                )
                .pre_run(Box::new(|ctx: &mut dyn Context| {
                    let mut out = ctx.stdout();
                    writeln!(out, "[pre_run] Preparing to greet...")?;
                    Ok(())
                }))
                .run(Box::new(|ctx: &mut dyn Context| {
                    let mut out = ctx.stdout();
                    let args = &ctx.info().args;

                    // Get argument values with defaults
                    let name = args
                        .get("name")
                        .and_then(|v| match v {
                            InputValue::String(s) => Some(s.clone()),
                            _ => None,
                        })
                        .unwrap_or_else(|| "World".to_string());

                    let count = args
                        .get("count")
                        .and_then(|v| match v {
                            InputValue::Int(n) => Some(*n),
                            _ => None,
                        })
                        .unwrap_or(1);

                    let loud = args
                        .get("loud")
                        .and_then(|v| match v {
                            InputValue::Bool(b) => Some(*b),
                            _ => None,
                        })
                        .unwrap_or(false);

                    for _ in 0..count {
                        let msg = format!("Hello, {}!", name);
                        if loud {
                            writeln!(out, "{}", msg.to_uppercase())?;
                        } else {
                            writeln!(out, "{}", msg)?;
                        }
                    }

                    Ok(())
                }))
                .post_run(Box::new(|ctx: &mut dyn Context| {
                    let mut out = ctx.stdout();
                    writeln!(out, "[post_run] Greeting complete!")?;
                    Ok(())
                })),
        )
        // Echo command - demonstrates positional arguments
        .command(
            Command::new("echo")
                .alias("e")
                .title("Echo messages")
                .description("Echo back the provided messages")
                .arg(
                    Arg::positional("messages", ArgType::String)
                        .title("Messages")
                        .description("Messages to echo")
                        .multiple(),
                )
                .run(Box::new(|ctx: &mut dyn Context| {
                    let mut out = ctx.stdout();
                    let args = &ctx.info().args;

                    if let Some(InputValue::Array(messages)) = args.get("messages") {
                        let strs: Vec<String> = messages
                            .iter()
                            .filter_map(|v| match v.as_ref() {
                                InputValue::String(s) => Some(s.clone()),
                                _ => None,
                            })
                            .collect();
                        writeln!(out, "{}", strs.join(" "))?;
                    } else {
                        writeln!(out, "(no messages)")?;
                    }

                    Ok(())
                })),
        )
        // Calc command - demonstrates subcommands
        .command(
            Command::new("calc")
                .title("Calculator")
                .description("Perform arithmetic operations")
                .subcommand(
                    Command::new("add")
                        .title("Add numbers")
                        .description("Add two numbers together")
                        .arg(
                            Arg::positional("a", ArgType::Int)
                                .title("First number")
                                .required(),
                        )
                        .arg(
                            Arg::positional("b", ArgType::Int)
                                .title("Second number")
                                .required(),
                        )
                        .run(Box::new(|ctx: &mut dyn Context| {
                            let mut out = ctx.stdout();
                            let args = &ctx.info().args;

                            let a = args
                                .get("a")
                                .and_then(|v| match v {
                                    InputValue::Int(n) => Some(*n),
                                    _ => None,
                                })
                                .unwrap_or(0);

                            let b = args
                                .get("b")
                                .and_then(|v| match v {
                                    InputValue::Int(n) => Some(*n),
                                    _ => None,
                                })
                                .unwrap_or(0);

                            writeln!(out, "{} + {} = {}", a, b, a + b)?;
                            Ok(())
                        })),
                )
                .subcommand(
                    Command::new("mul")
                        .title("Multiply numbers")
                        .description("Multiply two numbers together")
                        .arg(
                            Arg::positional("a", ArgType::Int)
                                .title("First number")
                                .required(),
                        )
                        .arg(
                            Arg::positional("b", ArgType::Int)
                                .title("Second number")
                                .required(),
                        )
                        .run(Box::new(|ctx: &mut dyn Context| {
                            let mut out = ctx.stdout();
                            let args = &ctx.info().args;

                            let a = args
                                .get("a")
                                .and_then(|v| match v {
                                    InputValue::Int(n) => Some(*n),
                                    _ => None,
                                })
                                .unwrap_or(0);

                            let b = args
                                .get("b")
                                .and_then(|v| match v {
                                    InputValue::Int(n) => Some(*n),
                                    _ => None,
                                })
                                .unwrap_or(0);

                            writeln!(out, "{} × {} = {}", a, b, a * b)?;
                            Ok(())
                        })),
                ),
        )
}

fn main() {
    let app = create_app();
    match app.execute() {
        Ok(_) => {}
        Err(e) => {
            eprintln!("Error: {}", e);
            std::process::exit(1);
        }
    }
}

#[cfg(test)]
mod tests {
    use xacli::testing::{assert, TestCase, TestCaseStatus, TestingApp};

    use super::*;

    #[test]
    fn test_app() {
        let app = TestingApp::new(create_app());
        let test_case = TestCase::new("help")
            .args(vec!["--help".to_string()])
            .assertions(vec![
                assert::success(),
                assert::stdout()
                    .contains("greet")
                    .contains("echo")
                    .contains("calc"),
            ]);

        let result = app.execute(test_case);
        println!("stdout: {}", result.stdout);
        assert!(
            matches!(result.status, TestCaseStatus::Passed),
            "Test failed: {:?}",
            result.status
        );
    }
}