znippy-cli 0.9.0

CLI for Znippy, a parallel chunked compression system.
// znippy-cli/src/main.rs

use anyhow::Result;
use clap::{Parser, Subcommand};
use std::path::PathBuf;

use znippy_common::{VerifyReport, list_archive_contents, verify_archive_integrity};
use znippy_common::plugin::PluginRegistry;
use znippy_common::plugins::wasm_loader::WasmPlugin;
use znippy_compress::compress_dir;
use znippy_decompress::decompress_archive;

pub mod handlers;

#[derive(Parser)]
#[command(name = "znippy")]
#[command(about = "Znippy: fast archive format with per-file compression", long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Compress a directory into a .znippy archive
    Compress {
        #[arg(short, long)]
        input: PathBuf,

        #[arg(short, long)]
        output: PathBuf,

        #[arg(long)]
        no_skip: bool,

        /// Package handler to use: a name or alias (`rust`/`cargo`, `python`, `maven`).
        /// One archive carries one package type.
        #[arg(long, default_value = "rust")]
        format: String,

        /// Path to a .wasm plugin for metadata extraction (overrides --format).
        #[arg(long)]
        plugin: Option<PathBuf>,

        /// DenseUnion type_id for the WASM plugin given via --plugin.
        #[arg(long, default_value_t = 1)]
        plugin_type_id: i8,
    },

    /// Decompress a .znippy archive
    Decompress {
        #[arg(short, long)]
        input: PathBuf,

        #[arg(short, long)]
        output: PathBuf,
    },

    /// List contents of a .znippy archive
    List {
        #[arg(short, long)]
        input: PathBuf,
    },

    /// Verify archive integrity (checksum)
    Verify {
        #[arg(short, long)]
        input: PathBuf,
    },

    /// List the available package handlers (the compiled-in register).
    Handlers,

    /// Run a handler-specific subcommand, e.g. `znippy run rust coords foo.crate`.
    Run {
        /// Handler name/alias to dispatch to.
        format: String,
        /// Subcommand advertised by the handler's meta().
        cmd: String,
        /// Arguments passed to the subcommand.
        args: Vec<String>,
    },
}

pub fn run() -> Result<()> {
    env_logger::init();
    let cli = Cli::parse();

    match cli.command {
        Commands::Compress {
            input,
            output,
            no_skip,
            format,
            plugin,
            plugin_type_id,
        } => {
            let registry = match plugin {
                Some(wasm_path) => {
                    let wp = WasmPlugin::load(&wasm_path.to_string_lossy(), "wasm-plugin", plugin_type_id)?;
                    PluginRegistry::with_plugin(Box::new(wp))
                }
                None => {
                    let handler = handlers::find_handler(&format)?;
                    println!("πŸ”Œ Handler: {} (type_id {})", handler.meta().name, handler.type_id());
                    PluginRegistry::with_plugin(handler)
                }
            };
            let report = compress_dir(&input, &output, no_skip, Some(&registry), None)?;
            println!("\nβœ… Komprimering klar:");
            println!("πŸ“ Totalt antal filer:         {}", report.total_files);
            println!("πŸ“ Totalt antal chunks:         {}", report.chunks);

            println!("πŸ“‚ Totalt antal kataloger:     {}", report.total_dirs);
            println!("πŸ“¦ Filer komprimerade:         {}", report.compressed_files);
            println!(
                "πŸ“„ Filer ej komprimerade:      {}",
                report.uncompressed_files
            );
            println!("πŸ“₯ Totalt inlΓ€sta bytes:       {}", report.total_bytes_in);
            println!("πŸ“€ Totalt skrivna bytes:       {}", report.total_bytes_out);
            println!("πŸ“‰ Bytes som komprimerades:    {}", report.compressed_bytes);
            println!(
                "πŸ“ƒ Bytes ej komprimerade:      {}",
                report.uncompressed_bytes
            );
            println!(
                "πŸ“Š Komprimeringsgrad:          {:.2}%",
                report.compression_ratio
            );
        }

        Commands::Decompress { input, output } => {
            let report: VerifyReport = decompress_archive(&input, &output)?;
            println!("\nβœ… Dekomprimering och verifiering klar:");
            println!("πŸ“ Totala filer:       {}", report.total_files);
            println!("πŸ” Verifierade filer:  {}", report.verified_files);
            println!("πŸ“₯  chunks:    {}", report.chunks);
            println!("❌ Korrupta filer:     {}", report.corrupt_files);
            println!("πŸ“₯ Totala bytes:       {}", report.total_bytes);
            println!("πŸ“€ Verifierade bytes:  {}", report.verified_bytes);
            println!("⚠️  Korrupta bytes:    {}", report.corrupt_bytes);
        }

        Commands::List { input } => {
            list_archive_contents(&input)?;
        }

        Commands::Verify { input } => {
            let report: VerifyReport = verify_archive_integrity(&input)?;
            println!("\nπŸ” Verifiering klar:");
            println!("πŸ“ Totala filer:       {}", report.total_files);
            println!("πŸ” Verifierade filer:  {}", report.verified_files);
            println!("❌ Korrupta filer:     {}", report.corrupt_files);
            println!("πŸ“₯ Totala bytes:       {}", report.total_bytes);
            println!("πŸ“€ Verifierade bytes:  {}", report.verified_bytes);
            println!("⚠️  Korrupta bytes:    {}", report.corrupt_bytes);
        }

        Commands::Handlers => {
            handlers::print_catalog();
        }

        Commands::Run { format, cmd, args } => {
            let handler = handlers::find_handler(&format)?;
            handler.run_command(&cmd, &args)?;
        }
    }

    Ok(())
}