mod commands;
mod style;
mod utils;
mod windows;
use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
use clap_complete::{Shell, generate};
use commands::{
CompressionLevel, OutputFormat, SortBy, cmd_add, cmd_convert, cmd_create, cmd_detect,
cmd_extract, cmd_info, cmd_list, cmd_man, cmd_test,
};
use std::io;
use std::path::PathBuf;
use style::{ColorChoice, Styler};
#[derive(Parser)]
#[command(name = "oxiarc")]
#[command(
author,
version,
about = "The Oxidized Archiver - Pure Rust archive utility"
)]
#[command(long_about = "
OxiArc is a Pure Rust implementation of common archive formats.
Supported formats: ZIP, GZIP, TAR, LZH, XZ, 7z, LZ4, Zstd, Bzip2, Brotli, Snappy
Examples:
oxiarc list archive.zip
oxiarc list archive.7z
oxiarc extract archive.zip
oxiarc extract archive.7z
oxiarc extract data.xz
oxiarc extract data.lz4
oxiarc extract data.zst
oxiarc extract data.bz2
oxiarc extract data.br
oxiarc extract data.sz
oxiarc create archive.zip file1.txt file2.txt
oxiarc create data.xz file.txt
oxiarc create data.lz4 file.txt
oxiarc create data.bz2 file.txt
oxiarc create data.br file.txt
oxiarc create data.sz file.txt
oxiarc convert archive.lzh output.zip
oxiarc convert archive.7z output.zip
oxiarc test archive.lzh
oxiarc info archive.7z
")]
pub struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(long, value_enum, default_value_t = ColorChoice::Auto, global = true)]
color: ColorChoice,
}
#[derive(Subcommand)]
enum Commands {
#[command(alias = "l")]
List {
archive: PathBuf,
#[arg(short, long)]
verbose: bool,
#[arg(short, long)]
json: bool,
#[arg(short = 'T', long)]
tree: bool,
#[arg(short, long, value_enum, default_value = "name")]
sort: SortBy,
#[arg(short = 'r', long)]
reverse: bool,
#[arg(short = 'I', long)]
include: Vec<String>,
#[arg(short = 'X', long)]
exclude: Vec<String>,
#[arg(long)]
lenient: bool,
#[arg(long, value_parser = crate::utils::parse_byte_size)]
memory_limit: Option<u64>,
},
#[command(alias = "x")]
#[command(group = clap::ArgGroup::new("overwrite_mode").multiple(false))]
Extract {
archive: String,
#[arg(short, long, default_value = ".")]
output: String,
files: Vec<String>,
#[arg(short = 'I', long)]
include: Vec<String>,
#[arg(short = 'X', long)]
exclude: Vec<String>,
#[arg(short, long)]
verbose: bool,
#[arg(short = 'P', long, default_value = "true")]
progress: bool,
#[arg(short, long, value_enum)]
format: Option<OutputFormatArg>,
#[arg(long, group = "overwrite_mode")]
overwrite: bool,
#[arg(long, group = "overwrite_mode")]
skip_existing: bool,
#[arg(long, group = "overwrite_mode")]
prompt: bool,
#[arg(short = 't', long)]
preserve_timestamps: bool,
#[arg(long)]
preserve_permissions: bool,
#[arg(short = 'p', long)]
preserve: bool,
#[arg(short = 'n', long)]
dry_run: bool,
#[arg(long)]
password: Option<String>,
#[arg(long)]
strict_names: bool,
#[arg(long)]
lenient: bool,
#[arg(long, value_parser = crate::utils::parse_byte_size)]
memory_limit: Option<u64>,
},
#[command(alias = "t")]
Test {
archive: PathBuf,
#[arg(short, long)]
verbose: bool,
},
#[command(alias = "c")]
Create {
archive: String,
files: Vec<PathBuf>,
#[arg(short, long, value_enum)]
format: Option<OutputFormatArg>,
#[arg(short = 'l', long, value_enum, default_value = "normal")]
compression: CompressionLevelArg,
#[arg(long, default_value_t = 0)]
compress_threshold: u64,
#[arg(short, long)]
verbose: bool,
#[arg(short = 'n', long)]
dry_run: bool,
},
Add {
archive: PathBuf,
files: Vec<PathBuf>,
#[arg(short = 'l', long, value_enum, default_value = "normal")]
compression: CompressionLevelArg,
#[arg(short, long)]
verbose: bool,
#[arg(short = 'n', long)]
dry_run: bool,
},
#[command(alias = "i")]
Info {
archive: PathBuf,
},
Detect {
file: PathBuf,
},
Convert {
input: PathBuf,
output: PathBuf,
#[arg(short, long, value_enum)]
format: Option<OutputFormatArg>,
#[arg(short = 'l', long, value_enum, default_value = "normal")]
compression: CompressionLevelArg,
#[arg(short, long)]
verbose: bool,
},
#[command(hide = true)]
Completion {
#[arg(value_enum)]
shell: Shell,
},
Man {
out_dir: Option<PathBuf>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
enum OutputFormatArg {
Zip,
Tar,
Gzip,
Lzh,
Xz,
Lz4,
Bz2,
Zst,
Br,
Snappy,
}
impl From<OutputFormatArg> for OutputFormat {
fn from(arg: OutputFormatArg) -> Self {
match arg {
OutputFormatArg::Zip => OutputFormat::Zip,
OutputFormatArg::Tar => OutputFormat::Tar,
OutputFormatArg::Gzip => OutputFormat::Gzip,
OutputFormatArg::Lzh => OutputFormat::Lzh,
OutputFormatArg::Xz => OutputFormat::Xz,
OutputFormatArg::Lz4 => OutputFormat::Lz4,
OutputFormatArg::Bz2 => OutputFormat::Bz2,
OutputFormatArg::Zst => OutputFormat::Zst,
OutputFormatArg::Br => OutputFormat::Br,
OutputFormatArg::Snappy => OutputFormat::Snappy,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
enum CompressionLevelArg {
Store,
Fast,
#[default]
Normal,
Best,
}
impl From<CompressionLevelArg> for CompressionLevel {
fn from(arg: CompressionLevelArg) -> Self {
match arg {
CompressionLevelArg::Store => CompressionLevel::Store,
CompressionLevelArg::Fast => CompressionLevel::Fast,
CompressionLevelArg::Normal => CompressionLevel::Normal,
CompressionLevelArg::Best => CompressionLevel::Best,
}
}
}
fn main() {
let cli = Cli::parse();
let styler = Styler::new(cli.color);
let result = match cli.command {
Commands::List {
archive,
verbose,
json,
tree,
sort,
reverse,
include,
exclude,
lenient,
memory_limit,
} => {
let options = commands::list::ListOptions {
verbose,
json,
tree,
sort_by: sort,
reverse,
include: &include,
exclude: &exclude,
lenient,
memory_limit,
};
cmd_list(&archive, &options, &styler)
}
Commands::Extract {
archive,
output,
files,
include,
exclude,
verbose,
progress,
format,
overwrite,
skip_existing,
prompt,
preserve_timestamps,
preserve_permissions,
preserve,
dry_run,
password,
strict_names,
lenient,
memory_limit,
} => cmd_extract(
commands::extract::ExtractArgs {
archive: &archive,
output: &output,
files: &files,
include: &include,
exclude: &exclude,
verbose,
progress,
format_hint: format.map(Into::into),
overwrite,
skip_existing,
prompt,
preserve_timestamps,
preserve_permissions,
preserve,
dry_run,
password,
strict_names,
lenient,
memory_limit,
},
&styler,
),
Commands::Test { archive, verbose } => cmd_test(&archive, verbose),
Commands::Create {
archive,
files,
format,
compression,
compress_threshold,
verbose,
dry_run,
} => cmd_create(
&archive,
&files,
format.map(Into::into),
compression.into(),
compress_threshold,
verbose,
dry_run,
),
Commands::Add {
archive,
files,
compression,
verbose,
dry_run,
} => cmd_add(&archive, &files, compression.into(), verbose, dry_run),
Commands::Info { archive } => cmd_info(&archive, &styler),
Commands::Detect { file } => cmd_detect(&file, &styler),
Commands::Convert {
input,
output,
format,
compression,
verbose,
} => cmd_convert(
&input,
&output,
format.map(Into::into),
compression.into(),
verbose,
),
Commands::Completion { shell } => {
let mut cmd = Cli::command();
generate(shell, &mut cmd, "oxiarc", &mut io::stdout());
return;
}
Commands::Man { out_dir } => {
let cmd = Cli::command();
if let Err(e) = cmd_man(cmd, out_dir) {
eprintln!("{}: {e}", styler.error("Error"));
std::process::exit(1);
}
return;
}
};
if let Err(e) = result {
eprintln!("{}: {}", styler.error("Error"), e);
std::process::exit(1);
}
}