ruchy 4.1.1

A systems scripting language that transpiles to idiomatic Rust with extreme quality engineering
Documentation
// 20_cli_apps.ruchy - Building command-line applications

import std::env
import std::process

fn main() {
    println("=== CLI Application Examples ===\n")

    // Command-line argument parsing
    let args = env::args()

    println("=== Basic Argument Parsing ===")
    println(f"Program: {args[0]}")
    println(f"Arguments: {args.slice(1)}")

    // Simple CLI parser
    fn parse_args(args) {
        let mut options = {
            verbose: false,
            output: None,
            input: None,
            help: false
        }
        let mut positional = []
        let mut i = 1  // Skip program name

        while i < args.len() {
            let arg = args[i]

            if arg.starts_with("--") {
                // Long option
                let option = arg.slice(2)
                if option == "verbose" {
                    options.verbose = true
                } else if option == "help" {
                    options.help = true
                } else if option.starts_with("output=") {
                    options.output = Some(option.split("=")[1])
                }
            } else if arg.starts_with("-") {
                // Short option
                let flags = arg.slice(1)
                for flag in flags.chars() {
                    match flag {
                        'v' => options.verbose = true,
                        'h' => options.help = true,
                        'o' => {
                            if i + 1 < args.len() {
                                i += 1
                                options.output = Some(args[i])
                            }
                        },
                        _ => println(f"Unknown flag: -{flag}")
                    }
                }
            } else {
                // Positional argument
                positional.append(arg)
            }
            i += 1
        }

        { options: options, args: positional }
    }

    // Help text generation
    fn show_help(program_name) {
        let help_text = f"
{program_name} - A sample CLI application

USAGE:
    {program_name} [OPTIONS] [ARGS]

OPTIONS:
    -h, --help          Show this help message
    -v, --verbose       Enable verbose output
    -o, --output FILE   Specify output file

ARGUMENTS:
    <input>             Input file or data

EXAMPLES:
    {program_name} input.txt
    {program_name} -v --output=result.txt data.csv
    {program_name} -vo output.log input.json
"
        println(help_text)
    }

    // Interactive prompt
    fn prompt(message, default = None) {
        print(f"{message}")
        if default != None {
            print(f" [{default}]")
        }
        print(": ")

        let input = readline()
        if input.trim() == "" && default != None {
            default
        } else {
            input.trim()
        }
    }

    // Confirmation prompt
    fn confirm(message) {
        let response = prompt(f"{message} (y/n)", "n")
        response.lower() == "y" || response.lower() == "yes"
    }

    // Progress bar
    fn show_progress(current, total, width = 50) {
        let percent = current / total
        let filled = (width * percent).to_int()
        let empty = width - filled

        let bar = "█".repeat(filled) + "░".repeat(empty)
        print(f"\r[{bar}] {(percent * 100).to_int()}%")

        if current >= total {
            println("")  // New line when complete
        }
    }

    // Spinner for long operations
    fn spinner(message) {
        let frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
        let mut i = 0

        {
            update: || {
                print(f"\r{frames[i]} {message}")
                i = (i + 1) % frames.len()
            },
            stop: || {
                print(f"\r✓ {message}\n")
            }
        }
    }

    // Table output
    fn print_table(headers, rows) {
        // Calculate column widths
        let mut widths = headers.map(h => h.len())

        for row in rows {
            for (i, cell) in row.enumerate() {
                widths[i] = max(widths[i], cell.to_string().len())
            }
        }

        // Print header
        let header_line = headers.enumerate().map((i, h) =>
            h.pad_right(widths[i])
        ).join(" | ")

        println(header_line)
        println("-".repeat(header_line.len()))

        // Print rows
        for row in rows {
            let row_line = row.enumerate().map((i, cell) =>
                cell.to_string().pad_right(widths[i])
            ).join(" | ")
            println(row_line)
        }
    }

    // Color output
    module colors {
        pub fn red(text) { f"\x1b[31m{text}\x1b[0m" }
        pub fn green(text) { f"\x1b[32m{text}\x1b[0m" }
        pub fn yellow(text) { f"\x1b[33m{text}\x1b[0m" }
        pub fn blue(text) { f"\x1b[34m{text}\x1b[0m" }
        pub fn bold(text) { f"\x1b[1m{text}\x1b[0m" }
        pub fn underline(text) { f"\x1b[4m{text}\x1b[0m" }
    }

    // Example CLI application
    fn todo_app() {
        println("\n=== TODO List CLI ===")

        let mut todos = []

        loop {
            println("\n1. Add task")
            println("2. List tasks")
            println("3. Mark complete")
            println("4. Remove task")
            println("5. Exit")

            let choice = prompt("Choose an option")

            match choice {
                "1" => {
                    let task = prompt("Enter task")
                    todos.append({ task: task, done: false })
                    println(colors::green("✓ Task added"))
                },
                "2" => {
                    if todos.len() == 0 {
                        println(colors::yellow("No tasks"))
                    } else {
                        for (i, todo) in todos.enumerate() {
                            let status = if todo.done { "✓" } else { "○" }
                            let text = if todo.done {
                                colors::green(todo.task)
                            } else {
                                todo.task
                            }
                            println(f"{i + 1}. {status} {text}")
                        }
                    }
                },
                "3" => {
                    let index = prompt("Task number").to_int() - 1
                    if index >= 0 && index < todos.len() {
                        todos[index].done = true
                        println(colors::green("✓ Marked complete"))
                    } else {
                        println(colors::red("Invalid task number"))
                    }
                },
                "4" => {
                    let index = prompt("Task number").to_int() - 1
                    if index >= 0 && index < todos.len() {
                        todos.remove(index)
                        println(colors::green("✓ Task removed"))
                    } else {
                        println(colors::red("Invalid task number"))
                    }
                },
                "5" => {
                    println("Goodbye!")
                    break
                },
                _ => println(colors::red("Invalid option"))
            }
        }
    }

    // File processor CLI
    fn file_processor_cli() {
        let parsed = parse_args(env::args())

        if parsed.options.help {
            show_help("file_processor")
            return
        }

        if parsed.args.len() == 0 {
            println(colors::red("Error: No input file specified"))
            show_help("file_processor")
            return
        }

        let input_file = parsed.args[0]
        let output_file = parsed.options.output || f"{input_file}.processed"

        println(f"Processing: {input_file}")
        if parsed.options.verbose {
            println(f"Output will be saved to: {output_file}")
        }

        // Simulate processing with progress bar
        let total_steps = 100
        for i in 0..=total_steps {
            show_progress(i, total_steps)
            sleep_ms(10)  // Simulate work
        }

        println(colors::green(f"✓ Processing complete: {output_file}"))
    }

    // Configuration management
    fn load_config() {
        let config_paths = [
            env::home() + "/.config/myapp/config.json",
            "./config.json",
            "/etc/myapp/config.json"
        ]

        for path in config_paths {
            if fs::exists(path) {
                let config = parse_json(fs::read_to_string(path))
                println(f"Loaded config from: {path}")
                return config
            }
        }

        // Default config
        {
            verbose: false,
            output_dir: "./output",
            max_threads: 4
        }
    }

    // Environment variables
    fn check_environment() {
        println("\n=== Environment Variables ===")

        let home = env::get("HOME") || "Not set"
        let path = env::get("PATH") || "Not set"
        let custom = env::get("MY_APP_CONFIG") || "Not set"

        println(f"HOME: {home}")
        println(f"PATH: {path.split(":").take(3).join(":")}")
        println(f"MY_APP_CONFIG: {custom}")

        // Set environment variable
        env::set("MY_APP_VAR", "Hello from Ruchy!")
        println(f"Set MY_APP_VAR: {env::get('MY_APP_VAR')}")
    }

    // Run examples
    let data = [
        ["Name", "Age", "City"],
        ["Alice", "30", "NYC"],
        ["Bob", "25", "SF"],
        ["Charlie", "35", "LA"]
    ]

    print_table(data[0], data.slice(1))

    // Example usage of progress bar
    println("\n=== Progress Bar Demo ===")
    for i in 0..=50 {
        show_progress(i, 50)
        sleep_ms(20)
    }

    check_environment()

    // Uncomment to run interactive todo app
    // todo_app()
}