mod commands;
mod exit_codes;
mod file_selector;
mod output;
mod password;
mod progress;
use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
use clap_complete::{Shell, generate};
use std::path::PathBuf;
use exit_codes::ExitCode;
#[derive(Parser)]
#[command(name = "zesven")]
#[command(author, version, about = "Pure Rust 7z archive tool", long_about = None)]
#[command(propagate_version = true)]
pub struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(long, short = 'f', value_enum, default_value = "human", global = true)]
format: OutputFormat,
#[arg(long, short = 'q', global = true)]
quiet: bool,
#[arg(long, short = 't', default_value = "0", global = true)]
threads: usize,
#[arg(long, short = 'M', value_parser = parse_size, global = true)]
memory_limit: Option<u64>,
}
fn parse_size(text: &str) -> Result<u64, String> {
let text = text.trim();
let (digits, scale) = match text.as_bytes().last() {
Some(b'k' | b'K') => (&text[..text.len() - 1], 1024),
Some(b'm' | b'M') => (&text[..text.len() - 1], 1024 * 1024),
Some(b'g' | b'G') => (&text[..text.len() - 1], 1024 * 1024 * 1024),
_ => (text, 1),
};
digits
.trim()
.parse::<u64>()
.map_err(|_| format!("'{text}' is not a size; write a number, or one like 512M or 2G"))?
.checked_mul(scale)
.ok_or_else(|| format!("'{text}' is larger than this machine can address"))
}
#[derive(Subcommand)]
enum Commands {
#[command(alias = "x")]
Extract {
archive: PathBuf,
#[arg(short = 'o', long, default_value = ".")]
output: PathBuf,
#[arg(short = 'i', long)]
include: Vec<String>,
#[arg(short = 'e', long)]
exclude: Vec<String>,
#[arg(long, value_enum, default_value = "prompt")]
overwrite: OverwriteMode,
#[arg(short = 'p', long)]
password: Option<String>,
#[arg(long)]
preserve_metadata: bool,
},
#[command(alias = "a")]
Create {
archive: PathBuf,
files: Vec<PathBuf>,
#[arg(short = 'm', long, value_enum, default_value = "lzma2")]
method: CompressionMethod,
#[arg(short = 'l', long, default_value = "5")]
level: u8,
#[arg(long)]
solid: bool,
#[arg(short = 'p', long)]
password: Option<String>,
#[arg(long)]
encrypt_headers: bool,
#[arg(long)]
deterministic: bool,
#[arg(short = 'x', long)]
exclude: Vec<String>,
#[arg(short = 'r', long)]
recursive: bool,
#[arg(long, conflicts_with = "recursive")]
no_recursive: bool,
},
#[command(alias = "l")]
List {
archive: PathBuf,
#[arg(long)]
technical: bool,
#[arg(short = 'p', long)]
password: Option<String>,
},
#[command(alias = "t")]
Test {
archive: PathBuf,
#[arg(short = 'p', long)]
password: Option<String>,
#[arg(short = 'i', long)]
include: Vec<String>,
},
#[command(alias = "i")]
Info {
archive: PathBuf,
#[arg(short = 'p', long)]
password: Option<String>,
},
Completions {
#[arg(value_enum)]
shell: Shell,
},
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
pub enum OutputFormat {
Human,
Json,
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
pub enum OverwriteMode {
Always,
Never,
Prompt,
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
pub enum CompressionMethod {
Copy,
Lzma,
Lzma2,
Deflate,
Bzip2,
}
impl From<CompressionMethod> for zesven::codec::CodecMethod {
fn from(method: CompressionMethod) -> Self {
match method {
CompressionMethod::Copy => zesven::codec::CodecMethod::Copy,
CompressionMethod::Lzma => zesven::codec::CodecMethod::Lzma,
CompressionMethod::Lzma2 => zesven::codec::CodecMethod::Lzma2,
CompressionMethod::Deflate => zesven::codec::CodecMethod::Deflate,
CompressionMethod::Bzip2 => zesven::codec::CodecMethod::BZip2,
}
}
}
fn main() {
ctrlc::set_handler(move || {
eprintln!("\nInterrupted");
std::process::exit(exit_codes::USER_INTERRUPT);
})
.ok();
let cli = Cli::parse();
let exit_code = match cli.command {
Commands::Extract {
archive,
output,
include,
exclude,
overwrite,
password,
preserve_metadata,
} => commands::extract(&commands::ExtractConfig {
archive_path: &archive,
output_dir: &output,
include: &include,
exclude: &exclude,
overwrite,
password,
preserve_metadata,
format: cli.format,
quiet: cli.quiet,
thread_count: cli.threads,
}),
Commands::Create {
archive,
files,
method,
level,
solid,
password,
encrypt_headers,
deterministic,
exclude,
recursive: _,
no_recursive,
} => commands::create(&commands::CreateConfig {
archive_path: &archive,
files: &files,
method,
level,
solid,
password,
encrypt_headers,
deterministic,
exclude: &exclude,
recursive: !no_recursive,
format: cli.format,
quiet: cli.quiet,
thread_count: cli.threads,
memory_limit: cli.memory_limit,
}),
Commands::List {
archive,
technical,
password,
} => commands::list(&archive, technical, password, cli.format, cli.quiet),
Commands::Test {
archive,
password,
include,
} => commands::test(
&archive,
password,
&include,
cli.format,
cli.quiet,
cli.threads,
),
Commands::Info { archive, password } => {
commands::info(&archive, password, cli.format, cli.quiet)
}
Commands::Completions { shell } => {
let mut cmd = Cli::command();
let name = cmd.get_name().to_string();
generate(shell, &mut cmd, name, &mut std::io::stdout());
ExitCode::Success
}
};
std::process::exit(exit_code.code());
}