include!(concat!(env!("OUT_DIR"), "/version_string.rs"));
use clap::Parser;
use std::fs;
use std::io::{self, IsTerminal, Read, Write};
use zeck::zeck_file_format::compress::BestCompressionResult;
use zeck::zeck_file_format::{
compress::compress_zeck_be, compress::compress_zeck_best, compress::compress_zeck_le,
};
#[derive(Debug, Clone, Copy)]
enum EndianUsed {
Big,
Little,
}
impl EndianUsed {
fn display_name(&self) -> &'static str {
match self {
EndianUsed::Big => "big endian",
EndianUsed::Little => "little endian",
}
}
}
#[derive(Parser, Debug)]
#[command(
name = "zeck-compress",
version = VERSION_STRING,
about = "Compress data using the Zeckendorf representation algorithm",
long_about = None
)]
struct Args {
#[arg(value_name = "INPUT")]
maybe_input: Option<String>,
#[arg(short = 'o', long = "output", value_name = "FILE")]
maybe_output: Option<String>,
#[arg(
short = 'e',
long = "endian",
value_name = "ENDIAN",
default_value = "best"
)]
endian: String,
#[arg(short, long, default_value_t = true)]
verbose: bool,
}
fn main() {
let args = Args::parse();
let input_data = if let Some(input_path) = &args.maybe_input {
match fs::read(input_path) {
Ok(data) => data,
Err(err) => {
eprintln!("Error: Failed to read input file '{}': {}", input_path, err);
std::process::exit(1);
}
}
} else {
if io::stdin().is_terminal() {
eprintln!(
"Warning: Reading from stdin, but no data was piped in. Waiting for input..."
);
eprintln!("Hint: Pipe data using: cat file.bin | zeck-compress");
}
let mut data = Vec::new();
match io::stdin().read_to_end(&mut data) {
Ok(_) => data,
Err(err) => {
eprintln!("Error: Failed to read from stdin: {}", err);
std::process::exit(1);
}
}
};
if input_data.is_empty() {
eprintln!("Error: Input data is empty");
std::process::exit(1);
}
let original_size = input_data.len();
let (zeck_file, maybe_be_size, maybe_le_size) = match args.endian.to_lowercase().as_str() {
"big" => {
let zeck_file = match compress_zeck_be(&input_data) {
Ok(file) => file,
Err(e) => {
eprintln!("Error: Compression failed: {}", e);
std::process::exit(1);
}
};
let be_size = zeck_file.compressed_data.len();
(zeck_file, Some(be_size), None)
}
"little" => {
let zeck_file = match compress_zeck_le(&input_data) {
Ok(file) => file,
Err(e) => {
eprintln!("Error: Compression failed: {}", e);
std::process::exit(1);
}
};
let le_size = zeck_file.compressed_data.len();
(zeck_file, None, Some(le_size))
}
"best" => {
let best_compression_result = match compress_zeck_best(&input_data) {
Ok(result) => result,
Err(e) => {
eprintln!("Error: Best compression failed: {}", e);
std::process::exit(1);
}
};
match best_compression_result {
BestCompressionResult::BigEndianBest { zeck_file, le_size } => {
let be_size = zeck_file.compressed_data.len();
(zeck_file, Some(be_size), Some(le_size))
}
BestCompressionResult::LittleEndianBest { zeck_file, be_size } => {
let le_size = zeck_file.compressed_data.len();
(zeck_file, Some(be_size), Some(le_size))
}
BestCompressionResult::Neither { be_size, le_size } => {
eprintln!(
"Error: Neither compression method produced a smaller output than the original. Big endian size: {} bytes, Little endian size: {} bytes",
be_size, le_size
);
std::process::exit(1);
}
}
}
_ => {
eprintln!(
"Error: Invalid endianness '{}'. Must be 'big', 'little', or 'best'.",
args.endian
);
std::process::exit(1);
}
};
let endian_used = if zeck_file.is_big_endian() {
EndianUsed::Big
} else {
EndianUsed::Little
};
let zeck_file_as_data = zeck_file.to_bytes();
let compressed_data_size = zeck_file.compressed_data.len();
let total_size = zeck_file.total_size();
let file_extension = ".zeck";
let final_output_path = if let Some(output_path) = &args.maybe_output {
if output_path.ends_with(".zeck") {
output_path.clone()
} else {
format!("{output_path}{file_extension}")
}
} else if let Some(input_path) = &args.maybe_input {
format!("{input_path}{file_extension}")
} else {
String::new()
};
if final_output_path.is_empty() {
if let Err(err) = io::stdout().write_all(&zeck_file_as_data) {
eprintln!("Error: Failed to write to stdout: {}", err);
std::process::exit(1);
}
} else {
if let Err(err) = fs::write(&final_output_path, &zeck_file_as_data) {
eprintln!(
"Error: Failed to write output file '{}': {}",
final_output_path, err
);
std::process::exit(1);
}
println!("Compressed to: {}", final_output_path);
}
if args.verbose {
let compression_ratio = compressed_data_size as f64 / original_size as f64;
let compression_percentage = if compression_ratio < 1.0 {
(1.0 - compression_ratio) * 100.0
} else {
(compression_ratio - 1.0) * 100.0
};
eprintln!("Endianness used: {}", endian_used.display_name());
if let (Some(be_size), Some(le_size)) = (maybe_be_size, maybe_le_size) {
eprintln!("Big endian size: {} bytes", be_size);
eprintln!("Little endian size: {} bytes", le_size);
}
eprintln!("Original size: {} bytes", original_size);
eprintln!("Compressed data size: {} bytes", compressed_data_size);
eprintln!("Total file size (with header): {} bytes", total_size);
if compression_ratio < 1.0 {
eprintln!(
"File content was compressed by {compression_percentage:.2}% (original content size: {original_size} bytes -> compressed content size: {compressed_data_size} bytes)\nTotal file size with header: {total_size} bytes",
);
} else {
eprintln!(
"File content was expanded by {compression_percentage:.2}% (original content size: {original_size} bytes -> expanded content size: {compressed_data_size} bytes)\nTotal file size with header: {total_size} bytes",
);
}
}
}