mod gzip;
mod tar;
mod utils;
mod xz;
use clap::{Args, Parser, Subcommand};
use is_terminal::IsTerminal;
use std::io;
use std::path::{Path, PathBuf};
use utils::*;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct CmprssArgs {
#[command(subcommand)]
format: Option<Format>,
}
#[derive(Subcommand, Debug)]
enum Format {
Tar(TarArgs),
#[clap(visible_alias = "gz")]
Gzip(GzipArgs),
Xz(XzArgs),
}
#[derive(Args, Debug)]
struct TarArgs {
#[clap(flatten)]
common_args: CommonArgs,
}
#[derive(Args, Debug)]
struct CommonArgs {
#[arg(short, long)]
input: Option<String>,
#[arg(short, long)]
output: Option<String>,
#[arg(short, long)]
compress: bool,
#[arg(short, long)]
extract: bool,
#[arg()]
io_list: Vec<String>,
#[arg(long)]
ignore_pipes: bool,
#[arg(long)]
ignore_stdin: bool,
#[arg(long)]
ignore_stdout: bool,
}
#[derive(Args, Debug)]
struct GzipArgs {
#[clap(flatten)]
common_args: CommonArgs,
#[arg(long, default_value_t = 6)]
compression: u32,
}
#[derive(Args, Debug)]
struct XzArgs {
#[clap(flatten)]
common_args: CommonArgs,
#[arg(long, default_value_t = 6)]
level: u32,
}
fn get_input_filename(input: &CmprssInput) -> Result<&Path, io::Error> {
match input {
CmprssInput::Path(paths) => {
if paths.is_empty() {
return Err(io::Error::new(
io::ErrorKind::Other,
"error: no input specified",
));
}
Ok(paths.first().unwrap())
}
CmprssInput::Pipe(_) => Ok(Path::new("archive")),
}
}
#[derive(Debug)]
enum Action {
Compress,
Extract,
}
#[derive(Debug)]
struct Job {
input: CmprssInput,
output: CmprssOutput,
action: Action,
}
fn get_job<T: Compressor>(compressor: &T, common_args: &CommonArgs) -> Result<Job, io::Error> {
let action = {
if common_args.compress {
Action::Compress
} else if common_args.extract {
Action::Extract
} else {
Action::Compress
}
};
let mut inputs = match &common_args.input {
Some(input) => {
let path = PathBuf::from(input);
if !path.try_exists()? {
return Err(io::Error::new(
io::ErrorKind::Other,
"Specified input path does not exist",
));
}
vec![path]
}
None => Vec::new(),
};
let mut output = match &common_args.output {
Some(output) => {
let path = Path::new(output);
if path.try_exists()? && !path.is_dir() {
return Err(io::Error::new(
io::ErrorKind::Other,
"Specified output path already exists",
));
}
Some(path)
}
None => None,
};
let mut io_list = common_args.io_list.clone();
if output.is_none() {
if let Some(possible_output) = common_args.io_list.last() {
let path = Path::new(possible_output);
if !path.try_exists()? {
output = Some(path);
io_list.pop();
} else if path.is_dir() {
match action {
Action::Compress => {
}
Action::Extract => {
output = Some(path);
io_list.pop();
}
};
} else {
}
}
}
for input in &io_list {
let path = PathBuf::from(input);
if !path.try_exists()? {
return Err(io::Error::new(
io::ErrorKind::Other,
"Specified input path does not exist",
));
}
inputs.push(path);
}
let cmprss_input = match inputs.is_empty() {
true => {
if !std::io::stdin().is_terminal()
&& !&common_args.ignore_pipes
&& !&common_args.ignore_stdin
{
CmprssInput::Pipe(std::io::stdin())
} else {
return Err(io::Error::new(io::ErrorKind::Other, "No specified input"));
}
}
false => CmprssInput::Path(inputs),
};
let cmprss_output = match output {
Some(path) => CmprssOutput::Path(path.to_path_buf()),
None => {
if !std::io::stdout().is_terminal()
&& !&common_args.ignore_pipes
&& !&common_args.ignore_stdout
{
CmprssOutput::Pipe(std::io::stdout())
} else {
match action {
Action::Compress => {
CmprssOutput::Path(PathBuf::from(
compressor
.default_compressed_filename(get_input_filename(&cmprss_input)?),
))
}
Action::Extract => CmprssOutput::Path(PathBuf::from(
compressor.default_extracted_filename(get_input_filename(&cmprss_input)?),
)),
}
}
}
};
Ok(Job {
input: cmprss_input,
output: cmprss_output,
action,
})
}
fn command<T: Compressor>(compressor: T, args: &CommonArgs) -> Result<(), io::Error> {
let job = get_job(&compressor, args)?;
match job.action {
Action::Compress => compressor.compress(job.input, job.output)?,
Action::Extract => compressor.extract(job.input, job.output)?,
};
Ok(())
}
fn parse_gzip(args: &GzipArgs) -> gzip::Gzip {
gzip::Gzip {
compression_level: args.compression,
}
}
fn parse_xz(args: &XzArgs) -> xz::Xz {
xz::Xz { level: args.level }
}
fn parse_tar(_args: &TarArgs) -> tar::Tar {
tar::Tar {}
}
fn main() -> Result<(), io::Error> {
let args = CmprssArgs::parse();
match args.format {
Some(Format::Tar(a)) => command(parse_tar(&a), &a.common_args),
Some(Format::Gzip(a)) => command(parse_gzip(&a), &a.common_args),
Some(Format::Xz(a)) => command(parse_xz(&a), &a.common_args),
_ => Err(io::Error::new(io::ErrorKind::Other, "unknown input")),
}
}