zipatch-rs 1.0.2

Parser for FFXIV ZiPatch patch files
Documentation
use std::path::PathBuf;
use tempfile::TempDir;
use zipatch_rs::{Apply, ApplyContext, ZiPatchReader, chunk::Chunk};

fn patch_dir() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/patches")
}

fn patch_files() -> Vec<PathBuf> {
    let dir = patch_dir();
    if !dir.exists() {
        return vec![];
    }
    let mut files: Vec<PathBuf> = std::fs::read_dir(&dir)
        .unwrap()
        .filter_map(|e| e.ok())
        .map(|e| e.path())
        .filter(|p| p.extension().and_then(|s| s.to_str()) == Some("patch"))
        .collect();
    files.sort();
    files
}

#[test]
#[ignore = "requires real .patch files in tests/patches/ (large, gitignored)"]
fn parses_all_chunks_without_error() {
    let files = patch_files();
    assert!(!files.is_empty(), "no .patch files found in tests/patches/");

    for path in &files {
        let name = path.file_name().unwrap().to_string_lossy();
        let f = std::fs::File::open(path).unwrap();
        let reader = ZiPatchReader::new(std::io::BufReader::new(f))
            .unwrap_or_else(|e| panic!("{name}: failed to open ZiPatchReader: {e}"));

        let mut chunks = 0usize;
        for result in reader {
            let chunk = result.unwrap_or_else(|e| panic!("{name}: chunk {chunks} failed: {e}"));
            chunks += 1;
            if matches!(chunk, Chunk::EndOfFile) {
                break;
            }
        }
        assert!(chunks > 0, "{name}: no chunks parsed");
        eprintln!("{name}: {chunks} chunks ok");
    }
}

#[test]
#[ignore = "requires real .patch files in tests/patches/ (large, gitignored); writes to a tempdir"]
fn applies_to_tempdir_without_error() {
    let files = patch_files();
    assert!(!files.is_empty(), "no .patch files found in tests/patches/");

    for path in &files {
        let name = path.file_name().unwrap().to_string_lossy();
        let tmp = TempDir::new().unwrap();
        let mut ctx = ApplyContext::new(tmp.path()).with_ignore_missing(true);

        let f = std::fs::File::open(path).unwrap();
        let reader = ZiPatchReader::new(std::io::BufReader::new(f))
            .unwrap_or_else(|e| panic!("{name}: failed to open ZiPatchReader: {e}"));

        for result in reader {
            let chunk = result.unwrap_or_else(|e| panic!("{name}: parse error: {e}"));
            if matches!(chunk, Chunk::EndOfFile) {
                break;
            }
            chunk
                .apply(&mut ctx)
                .unwrap_or_else(|e| panic!("{name}: apply error: {e}"));
        }
        eprintln!("{name}: applied ok → {}", tmp.path().display());
    }
}