use tar_light::{pack, unpack_with_options, list};
use std::env;
use chrono::{Utc, TimeZone};
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
print_usage();
std::process::exit(1);
}
let command = &args[1];
match command.as_str() {
"pack" => {
if args.len() < 4 {
eprintln!("Error: pack requires at least tarfile and one input file");
print_usage();
std::process::exit(1);
}
let tarfile = &args[2];
let files: Vec<&str> = args[3..].iter().map(|s| s.as_str()).collect();
pack(tarfile, &files);
}
"unpack" => {
if args.len() < 4 {
eprintln!("Error: unpack requires tarfile and output directory");
print_usage();
std::process::exit(1);
}
let mut overwrite = false;
let mut arg_idx = 2;
if args.len() >= 3 && args[2] == "-y" {
overwrite = true;
arg_idx = 3;
}
if args.len() < arg_idx + 2 {
eprintln!("Error: unpack requires tarfile and output directory");
print_usage();
std::process::exit(1);
}
let tarfile = &args[arg_idx];
let output_dir = &args[arg_idx + 1];
unpack_with_options(tarfile, output_dir, overwrite, true);
}
"list" => {
if args.len() < 3 {
eprintln!("Error: list requires tarfile");
print_usage();
std::process::exit(1);
}
let tarfile = &args[2];
match list(tarfile) {
Ok(headers) => {
println!("Files in {}:", tarfile);
println!("{:>10} {}", "Size", "Name");
println!("{}", "-".repeat(50));
for header in &headers {
println!("{:>10} {}", header.size, header.name);
}
println!("\nTotal: {} file(s)", headers.len());
}
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
}
"list_detail" | "detail" => {
if args.len() < 3 {
eprintln!("Error: list_detail requires tarfile");
print_usage();
std::process::exit(1);
}
let tarfile = &args[2];
list_detail(tarfile);
}
_ => {
eprintln!("Error: Unknown command '{}'", command);
print_usage();
std::process::exit(1);
}
}
}
fn print_usage() {
eprintln!("Usage:");
eprintln!(" pack <tarfile> <file1> <file2> … - Create tar archive");
eprintln!(" unpack [-y] <tarfile> <dir> - Extract tar archive");
eprintln!(" list <tarfile> - List files in tar archive");
eprintln!(" detail <tarfile> - List files with detailed information");
}
fn list_detail(tarfile: &str) {
match list(tarfile) {
Ok(headers) => {
println!("Files in {}:", tarfile);
println!("{}", "=".repeat(80));
for (i, header) in headers.iter().enumerate() {
println!("File #{}", i + 1);
println!(" Name: {}", header.name);
println!(" Size: {} bytes", header.size);
println!(" Mode: {:o} (octal)", header.mode);
println!(" UID: {}", header.uid);
println!(" GID: {}", header.gid);
println!(" User: {}", if header.uname.is_empty() { "(none)" } else { &header.uname });
println!(" Group: {}", if header.gname.is_empty() { "(none)" } else { &header.gname });
let datetime = Utc.timestamp_opt(header.mtime as i64, 0).unwrap();
println!(" Timestamp: {}", datetime.format("%Y-%m-%d %H:%M:%S"));
println!(" Checksum: {}", header.checksum);
println!(" Type: {}", match header.typeflag {
b'0' | 0 => "Regular file",
b'1' => "Hard link",
b'2' => "Symbolic link",
b'3' => "Character device",
b'4' => "Block device",
b'5' => "Directory",
b'6' => "FIFO",
_ => "Unknown",
});
if !header.linkname.is_empty() {
println!(" Link name: {}", header.linkname);
}
println!("{}", "-".repeat(80));
}
println!("\nTotal: {} file(s)", headers.len());
}
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::Path;
#[test]
fn test_pack_command() {
let test_file1 = "test_main_file1.txt";
let test_file2 = "test_main_file2.txt";
let test_tar = "test_main_pack.tar";
fs::write(test_file1, "Test content 1").unwrap();
fs::write(test_file2, "Test content 2").unwrap();
let files = vec![test_file1, test_file2];
pack(test_tar, &files);
assert!(Path::new(test_tar).exists());
fs::remove_file(test_file1).unwrap();
fs::remove_file(test_file2).unwrap();
fs::remove_file(test_tar).unwrap();
}
#[test]
fn test_unpack_command() {
let test_file = "test_main_unpack_file.txt";
let test_content = "Main unpack test";
let test_tar = "test_main_unpack.tar";
let output_dir = "test_main_unpack_output";
fs::write(test_file, test_content).unwrap();
let files = vec![test_file];
pack(test_tar, &files);
unpack_with_options(test_tar, output_dir, true, false);
let extracted_file = Path::new(output_dir).join(test_file);
assert!(extracted_file.exists());
let content = fs::read_to_string(&extracted_file).unwrap();
assert_eq!(content, test_content);
fs::remove_file(test_file).unwrap();
fs::remove_file(test_tar).unwrap();
fs::remove_dir_all(output_dir).unwrap();
}
#[test]
fn test_list_command() {
let test_file1 = "test_main_list_file1.txt";
let test_file2 = "test_main_list_file2.txt";
let test_tar = "test_main_list.tar";
fs::write(test_file1, "List test 1").unwrap();
fs::write(test_file2, "List test 2").unwrap();
let files = vec![test_file1, test_file2];
pack(test_tar, &files);
let headers = list(test_tar).unwrap();
assert_eq!(headers.len(), 2);
assert_eq!(headers[0].name, test_file1);
assert_eq!(headers[0].size, 11);
assert_eq!(headers[1].name, test_file2);
assert_eq!(headers[1].size, 11);
fs::remove_file(test_file1).unwrap();
fs::remove_file(test_file2).unwrap();
fs::remove_file(test_tar).unwrap();
}
#[test]
fn test_simple() {
let files = vec![
"src/main.rs",
"src/lib.rs",
"src/tar.rs",
"Cargo.toml",
];
pack("a.tar.gz", &files);
let headers = list("a.tar.gz").unwrap();
assert!(headers.len() == 4);
fs::remove_file("a.tar.gz").unwrap();
}
}