xzip 0.4.0

ZIP CLI with explicit filename encoding for pack and unpack workflows.
Documentation
use std::path::PathBuf;

use clap::{Parser, Subcommand};

#[derive(Parser, Debug)]
#[command(
    name = "xzip",
    version,
    about = "ZIP tool with explicit filename encoding control",
    long_about = "Pack and unpack ZIP archives with configurable filename encoding.\n\
                  Defaults to utf-8 when --encoding is omitted; use -e gbk (or cp936) \
                  for archives created on zh_CN Windows systems."
)]
pub struct Cli {
    /// Increase output verbosity (-v, -vv).
    #[arg(short, long, action = clap::ArgAction::Count, global = true)]
    pub verbose: u8,

    #[command(subcommand)]
    pub command: Command,
}

#[derive(Subcommand, Debug)]
pub enum Command {
    /// Pack a directory or file into a ZIP archive.
    Pack {
        /// Input file or directory to archive.
        #[arg(short = 'i', long)]
        input: PathBuf,
        /// Output ZIP file path.
        #[arg(short = 'o', long)]
        output: PathBuf,
        /// Filename encoding for ZIP entry names (default: utf-8).
        #[arg(short = 'e', long, value_name = "ENCODING", default_value = "utf-8")]
        encoding: String,
        /// Recursively include nested files and directories.
        #[arg(short = 'r', long)]
        recursive: bool,
        /// Only include paths matching this glob (repeatable).
        #[arg(long = "include", value_name = "GLOB")]
        include: Vec<String>,
        /// Exclude paths matching this glob (repeatable).
        #[arg(long = "exclude", value_name = "GLOB")]
        exclude: Vec<String>,
    },
    /// Unpack a ZIP archive using a specific filename encoding.
    Unpack {
        /// Input ZIP archive path.
        #[arg(short = 'i', long)]
        input: PathBuf,
        /// Output directory for extracted files.
        #[arg(short = 'o', long)]
        output: PathBuf,
        /// Filename encoding used for ZIP entry names (default: utf-8).
        #[arg(short = 'e', long, value_name = "ENCODING", default_value = "utf-8")]
        encoding: String,
        /// Only extract paths matching this glob (repeatable).
        #[arg(long = "include", value_name = "GLOB")]
        include: Vec<String>,
        /// Skip paths matching this glob (repeatable).
        #[arg(long = "exclude", value_name = "GLOB")]
        exclude: Vec<String>,
        /// Parallel extraction jobs (`0` = auto, `1` = sequential, `N` = thread count).
        #[arg(short = 'j', long = "jobs", default_value = "0")]
        jobs: usize,
        /// Print per-phase unpack timings to stderr.
        #[arg(long)]
        profile: bool,
    },
    /// List archive contents without extracting.
    #[command(visible_alias = "ls")]
    List {
        /// Input ZIP archive path.
        #[arg(short = 'i', long)]
        input: PathBuf,
        /// Filename encoding used for ZIP entry names (default: utf-8).
        #[arg(short = 'e', long, value_name = "ENCODING", default_value = "utf-8")]
        encoding: String,
        /// Only list paths matching this glob (repeatable).
        #[arg(long = "include", value_name = "GLOB")]
        include: Vec<String>,
        /// Skip paths matching this glob (repeatable).
        #[arg(long = "exclude", value_name = "GLOB")]
        exclude: Vec<String>,
    },
}