tar_light 0.1.9

Simple tar archive reader and writer library
Documentation
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);
            }
            
            // Check for -y option
            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() {
        // Create test files
        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();
        
        // Execute pack function
        let files = vec![test_file1, test_file2];
        pack(test_tar, &files);
        
        // Verify tar file was created
        assert!(Path::new(test_tar).exists());
        
        // Cleanup
        fs::remove_file(test_file1).unwrap();
        fs::remove_file(test_file2).unwrap();
        fs::remove_file(test_tar).unwrap();
    }

    #[test]
    fn test_unpack_command() {
        // Create test file and tar archive
        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();
        
        // Create tar archive
        let files = vec![test_file];
        pack(test_tar, &files);
        
        // Execute unpack function
        unpack_with_options(test_tar, output_dir, true, false);
        
        // Verify file was extracted
        let extracted_file = Path::new(output_dir).join(test_file);
        assert!(extracted_file.exists());
        
        // Verify file content
        let content = fs::read_to_string(&extracted_file).unwrap();
        assert_eq!(content, test_content);
        
        // Cleanup
        fs::remove_file(test_file).unwrap();
        fs::remove_file(test_tar).unwrap();
        fs::remove_dir_all(output_dir).unwrap();
    }

    #[test]
    fn test_list_command() {
        // Create test files and tar archive
        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();
        
        // Create tar archive
        let files = vec![test_file1, test_file2];
        pack(test_tar, &files);
        
        // Execute list function
        let headers = list(test_tar).unwrap();
        
        // Verify results
        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);
        
        // Cleanup
        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);
        // cleanup
        fs::remove_file("a.tar.gz").unwrap();
    }

}