use clap::{Parser, Subcommand};
use std::path::{Path, PathBuf};
#[derive(Parser)]
#[command(
name = "zzz",
version,
about = "zzz: compression multitool",
long_about = "Create and extract archives in multiple formats (zst, tgz, txz, zip, 7z) with smart file filtering and magic number detection"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
#[arg(short, long, global = true)]
pub verbose: bool,
#[arg(short = 'j', long, global = true, default_value = "0")]
pub threads: u32,
}
#[derive(Subcommand)]
pub enum Commands {
#[command(alias = "c")]
Compress {
#[arg(short, long, default_value = "19", value_parser = clap::value_parser!(i32).range(1..=22))]
level: i32,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(short = 'P', long)]
progress: bool,
#[arg(short = 'e', long)]
exclude: Vec<String>,
#[arg(short = 'E', long)]
no_default_excludes: bool,
#[arg(short = 'f', long, value_parser = parse_format)]
format: Option<crate::formats::Format>,
input: PathBuf,
#[arg(short = 'p', long)]
password: Option<String>,
},
#[command(alias = "x")]
Extract {
archive: PathBuf,
destination: Option<PathBuf>,
#[arg(short = 'C', long)]
directory: Option<PathBuf>,
#[arg(short = 'w', long)]
overwrite: bool,
#[arg(short = 'p', long)]
password: Option<String>,
},
#[command(alias = "l")]
List {
archive: PathBuf,
},
#[command(alias = "t")]
Test {
archive: PathBuf,
},
}
fn parse_format(s: &str) -> Result<crate::formats::Format, String> {
match s.to_lowercase().as_str() {
"zst" | "zstd" => Ok(crate::formats::Format::Zstd),
"tgz" | "gz" | "gzip" => Ok(crate::formats::Format::Gzip),
"txz" | "xz" => Ok(crate::formats::Format::Xz),
"zip" => Ok(crate::formats::Format::Zip),
"7z" | "sevenz" => Ok(crate::formats::Format::SevenZ),
_ => Err(format!(
"unsupported format '{s}'. Supported formats: zst, tgz, txz, zip, 7z"
)),
}
}
impl Cli {
pub fn get_output_path(
input: &Path,
output: Option<PathBuf>,
format: Option<crate::formats::Format>,
) -> PathBuf {
output.unwrap_or_else(|| {
let mut path = input.to_path_buf();
let extension = match format {
Some(f) => f.extension(),
None => "zst",
};
if let Some(filename) = path.file_name() {
let mut new_filename = filename.to_os_string();
new_filename.push(".");
new_filename.push(extension);
path.set_file_name(new_filename);
} else {
path.set_extension(extension);
}
path
})
}
pub fn get_extract_dir(destination: Option<PathBuf>, directory: Option<PathBuf>) -> PathBuf {
directory
.or(destination)
.unwrap_or_else(|| PathBuf::from("."))
}
}