zacor 0.1.0

Package manager and dispatcher for zr — install, manage, and run modular CLI packages
Documentation
use crate::{dispatch, paths, receipt};
use clap::{CommandFactory, FromArgMatches, Parser};
use std::path::Path;

#[derive(Parser)]
#[command(
    name = "zr",
    version,
    about = "Module dispatcher",
    disable_help_subcommand = true,
    allow_external_subcommands = true,
)]
struct ZrCli {
    /// Output raw JSONL instead of rendered text
    #[arg(long, conflicts_with = "text")]
    json: bool,

    /// Force rendered text output even when piped
    #[arg(long, conflicts_with = "json")]
    text: bool,

    #[command(subcommand)]
    command: Option<ZrCommand>,
}

#[derive(clap::Subcommand)]
enum ZrCommand {
    #[command(external_subcommand)]
    Module(Vec<String>),
}

/// Build the dynamic "Available packages" section for `zr --help`.
fn build_package_list_help(home: &Path) -> String {
    let mut out = String::new();

    if let Ok(packages) = receipt::list_all(home) {
        let active: Vec<_> = packages.iter().filter(|(_, r)| r.active).collect();
        if active.is_empty() {
            out.push_str("No packages installed. Use `zacor install` to add packages.");
        } else {
            out.push_str("Available packages:");
            for (name, r) in active {
                out.push_str(&format!("\n  {}  v{}", name, r.current));
            }
        }
    } else {
        out.push_str("No packages installed. Use `zacor install` to add packages.");
    }

    out
}

pub fn run() -> i32 {
    let home = match paths::zr_home() {
        Ok(h) => h,
        Err(e) => {
            eprintln!("error: {:#}", e);
            return 1;
        }
    };

    if let Err(e) = paths::ensure_dirs(&home) {
        eprintln!("error: {:#}", e);
        return 1;
    }

    let package_list = build_package_list_help(&home);
    let cli = match ZrCli::command()
        .after_help(package_list)
        .try_get_matches()
    {
        Ok(matches) => match ZrCli::from_arg_matches(&matches) {
            Ok(cli) => cli,
            Err(e) => {
                eprintln!("{}", e);
                return 1;
            }
        },
        Err(e) => {
            if e.use_stderr() {
                eprintln!("{}", e);
                return 1;
            } else {
                print!("{}", e);
                return 0;
            }
        }
    };

    match cli.command {
        Some(ZrCommand::Module(args)) => {
            let module_name = &args[0];
            let module_args: Vec<String> = args[1..].to_vec();
            match dispatch::run(&home, module_name, &module_args, cli.json, cli.text) {
                Ok(code) => code,
                Err(e) => {
                    eprintln!("error: {:#}", e);
                    1
                }
            }
        }
        None => {
            // No command given — show help with package list
            let package_list = build_package_list_help(&home);
            let _ = ZrCli::command()
                .after_help(package_list)
                .print_help();
            println!();
            0
        }
    }
}