pub mod args;
pub mod extract;
pub mod inspect;
pub mod pack;
pub mod ui;
use std::{
fs::File,
io::{self, BufReader},
path::Path,
};
use zarust::ArchiveReader;
use crate::cli::{
args::{Command, Invocation, UsageError},
ui::Reporter,
};
const READ_BUFFER: usize = 256 * 1024;
#[derive(Debug)]
pub struct Failure(pub String);
impl Failure {
pub fn new(message: impl Into<String>) -> Self {
Self(message.into())
}
}
impl From<io::Error> for Failure {
fn from(error: io::Error) -> Self {
Self(error.to_string())
}
}
impl From<zarust::Error> for Failure {
fn from(error: zarust::Error) -> Self {
Self(error.to_string())
}
}
pub type Outcome = Result<(), Failure>;
pub mod exit {
pub const SUCCESS: u8 = 0;
pub const FAILURE: u8 = 1;
pub const USAGE: u8 = 2;
}
pub fn open_archive(path: &Path) -> Result<ArchiveReader<BufReader<File>>, Failure> {
let file = File::open(path)
.map_err(|error| Failure::new(format!("cannot open `{}`: {error}", path.display())))?;
ArchiveReader::new(BufReader::with_capacity(READ_BUFFER, file))
.map_err(|error| Failure::new(format!("cannot read `{}`: {error}", path.display())))
}
pub fn run(invocation: Invocation) -> u8 {
let Invocation { command, options } = invocation;
let mut reporter = Reporter::new(options.color, options.verbosity, options.progress);
let outcome = match command {
Command::Pack { input, output } => pack::run(&mut reporter, &input, output, &options),
Command::Extract { archive, output } => {
extract::run(&mut reporter, &archive, output, &options)
}
Command::List { archive, path } => inspect::list(&mut reporter, &archive, path, &options),
Command::Info { archive } => inspect::info(&mut reporter, &archive),
Command::Verify { archive } => inspect::verify(&mut reporter, &archive),
Command::Help => help(&mut reporter),
Command::Version => version(&mut reporter),
};
let flushed = reporter.flush();
match outcome.and_then(|()| flushed.map_err(Failure::from)) {
Ok(()) => exit::SUCCESS,
Err(failure) => {
report_error(&failure.0);
exit::FAILURE
}
}
}
pub fn report_usage(error: &UsageError) -> u8 {
report_error(&error.0);
eprintln!("Try `zarust --help` for the list of commands.");
exit::USAGE
}
fn report_error(message: &str) {
let palette = ui::stderr_palette(ui::ColorChoice::Auto);
eprintln!(
"{}{}error{}: {message}",
palette.red, palette.bold, palette.reset
);
}
fn version(reporter: &mut Reporter) -> Outcome {
reporter.line(format_args!(
"{} {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
))
}
const HELP_COLUMN: usize = 26;
fn help(reporter: &mut Reporter) -> Outcome {
let p = reporter.palette();
reporter.line(format_args!(
"{bold}{cyan}zarust{reset} {dim}{}{reset} — read and write ZArchive {dim}(.zar / .wua){reset} files",
env!("CARGO_PKG_VERSION"),
bold = p.bold,
cyan = p.cyan,
dim = p.dim,
reset = p.reset,
))?;
reporter.line(format_args!("\n{}{}USAGE{}", p.yellow, p.bold, p.reset))?;
reporter.line(format_args!(
" {}zarust{} {}<command>{} {}[options]{}",
p.magenta, p.reset, p.bold, p.reset, p.dim, p.reset
))?;
reporter.line(format_args!("\n{}{}COMMANDS{}", p.yellow, p.bold, p.reset))?;
for (name, arguments, description) in [
(
"pack",
"<dir> [archive]",
"Create an archive from a directory",
),
(
"extract",
"<archive> [dir]",
"Extract an archive into a directory",
),
("list", "<archive> [path]", "List the entries in an archive"),
("info", "<archive>", "Show sizes and the compression ratio"),
("verify", "<archive>", "Check the stored integrity hash"),
] {
let pad = HELP_COLUMN.saturating_sub(name.len() + 1 + arguments.len());
reporter.line(format_args!(
" {m}{b}{name}{r} {d}{arguments}{r}{:pad$}{description}",
"",
m = p.magenta,
b = p.bold,
d = p.dim,
r = p.reset,
))?;
}
reporter.line(format_args!("\n{}{}OPTIONS{}", p.yellow, p.bold, p.reset))?;
for (flag, description) in [
("-o, --output <path>", "Output path, instead of positional"),
("-f, --force", "Overwrite an existing output path"),
("-v, --verbose", "Print every entry as it is processed"),
("-q, --quiet", "Print errors only"),
(" --tree", "Render `list` as a tree"),
(" --check", "Verify integrity before extracting"),
(" --no-progress", "Never draw the progress bar"),
(
" --color <when>",
"auto, always, or never (default auto)",
),
("-h, --help", "Show this help"),
("-V, --version", "Show the version"),
] {
reporter.line(format_args!(
" {c}{flag:<HELP_COLUMN$}{r}{description}",
c = p.cyan,
r = p.reset,
))?;
}
reporter.line(format_args!(
"\n{dim}Given a bare path, zarust packs a directory or extracts an archive, matching{reset}",
dim = p.dim,
reset = p.reset,
))?;
reporter.line(format_args!(
"{dim}the original ZArchive tool: `zarust game/` makes game.zar, and{reset}",
dim = p.dim,
reset = p.reset,
))?;
reporter.line(format_args!(
"{dim}`zarust game.zar` unpacks it into game_extracted/.{reset}",
dim = p.dim,
reset = p.reset,
))
}