use clish::prelude::*;
use log::{error, finished, opening, packed, packing, unpacking, verified, writing};
use srcdmp::cli::{log, viewer};
use srcdmp::{Codec, Dump};
#[command(
help = "Wrap a directory into a .srcdmp snapshot",
param(directory, help = "Directory to wrap"),
param(output, help = "Output file path"),
param(codec, help = "Codec to use", default = "c4", choices = ["7", "8", "c4"]),
param(ignore, help = "Glob patterns to ignore"),
param(gitignore, help = "Respect .gitignore"),
param(from_cargo, name = "from-cargo", help = "Auto-name from Cargo.toml"),
)]
fn wrap(
directory: Pos<String>,
output: Pos<Option<String>>,
codec: Named<String>,
ignore: Named<Vec<String>>,
gitignore: bool,
from_cargo: bool,
) {
let codec_str = codec;
let codec = match codec_str.as_str() {
"7" => Codec::Generic7,
"8" => Codec::Generic8,
"c4" => Codec::Compact4,
_ => unreachable!(),
};
let mut builder = Dump::from_dir(&directory).codec(codec);
if gitignore {
builder = builder.gitignore();
}
for pattern in &ignore {
builder = builder.ignore(pattern);
}
packing(&format!(
"{directory} to srcdmp (SDMP-{})",
codec_str.to_ascii_uppercase()
));
let dump = builder.build().unwrap_or_else(|e| {
error(&e.to_string());
std::process::exit(1);
});
packed(&format!(
"{} files, {} bytes",
dump.file_count(),
dump.original_size()
));
if from_cargo {
dump.from_cargo().write_to(".").unwrap_or_else(|e| {
error(&e.to_string());
std::process::exit(1);
});
finished("snapshot written");
} else if let Some(path) = output.as_deref() {
dump.write(path).unwrap_or_else(|e| {
error(&e.to_string());
std::process::exit(1);
});
writing(path);
finished("snapshot written");
} else {
error("provide an output path or use --from-cargo");
std::process::exit(1);
}
}
#[command(
help = "Decode a .srcdmp into a human readable log",
param(input, help = "Input .srcdmp file"),
param(output, help = "Output .log file")
)]
fn unwrap(input: Pos<String>, output: Pos<String>) {
opening(&input);
let dump = Dump::open(&input).unwrap_or_else(|e| {
error(&e.to_string());
std::process::exit(1);
});
dump.to_log(&output).unwrap_or_else(|e| {
error(&e.to_string());
std::process::exit(1);
});
finished(&format!("{} files → {}", dump.file_count(), output));
}
#[command(
help = "Reconstruct a directory from a .srcdmp or .log",
param(input, help = "Input .srcdmp file"),
param(out_dir, help = "Output directory", name = "output", default = ".")
)]
fn unpack(input: Pos<String>, out_dir: Named<String>) {
opening(&input);
let dump = Dump::open(&input).unwrap_or_else(|e| {
error(&e.to_string());
std::process::exit(1);
});
unpacking(&format!("{} files -> {}", dump.file_count(), out_dir));
dump.unpack_to(&out_dir).unwrap_or_else(|e| {
error(&e.to_string());
std::process::exit(1);
});
finished(&out_dir);
}
#[command(
help = "Print header metadata without decoding",
param(input, help = "Input .srcdmp file")
)]
fn info(input: Pos<String>) {
let dump = Dump::open(&input).unwrap_or_else(|e| {
error(&e.to_string());
std::process::exit(1);
});
log::info(&format!("codec: {}", dump.codec_name()));
log::info(&format!("files: {}", dump.file_count()));
log::info(&format!("size: {} bytes", dump.original_size()));
}
#[command(
help = "Verify file integrity",
param(input, help = "Input .srcdmp file")
)]
fn check(input: Pos<String>) {
opening(&input);
let dump = Dump::open(&input).unwrap_or_else(|e| {
error(&e.to_string());
std::process::exit(1);
});
let mut ok = true;
for path in dump.files() {
match dump.unpack() {
Ok(_) => verified(path),
Err(e) => {
error(&format!("{path} - {e}"));
ok = false;
}
}
}
if ok {
finished(&format!("all {} files ok", dump.file_count()));
} else {
error("file is corrupt");
std::process::exit(1);
}
}
#[command(
help = "Browse a .srcdmp file interactively",
param(input, help = "Input .srcdmp file")
)]
fn view(input: Pos<String>) {
let dump = Dump::open(&input).unwrap_or_else(|e| {
log::error(&e.to_string());
std::process::exit(1);
});
let files = dump.unpack().unwrap_or_else(|e| {
log::error(&e.to_string());
std::process::exit(1);
});
let mut file_list: Vec<(String, String)> = files.into_iter().collect();
file_list.sort_by(|a, b| a.0.cmp(&b.0));
let filename = std::path::Path::new(&input)
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
viewer::Viewer::new(file_list, filename, dump.codec_name().to_string())
.run()
.unwrap_or_else(|e| {
log::error(&e.to_string());
std::process::exit(1);
});
}
fn main() {
app!().run();
}