zipatch-rs 1.0.2

Parser for FFXIV ZiPatch patch files
Documentation
//! Print a chunk-type summary for a .patch file.
//!
//! Iterates the entire patch stream, counts how many times each `Chunk`
//! variant appears, and prints a sorted table at the end. Demonstrates
//! [`ZiPatchReader::from_path`] plus matching on the [`Chunk`] enum.
//!
//! Usage:
//!     cargo run --example parse_summary -- path/to/file.patch

#![allow(clippy::doc_markdown)]

use std::collections::BTreeMap;
use std::path::PathBuf;
use std::process::ExitCode;

use zipatch_rs::chunk::SqpkCommand;
use zipatch_rs::{Chunk, ZiPatchReader};

fn main() -> ExitCode {
    let Some(path) = std::env::args_os().nth(1).map(PathBuf::from) else {
        eprintln!("usage: parse_summary <path/to/file.patch>");
        return ExitCode::from(2);
    };

    let reader = match ZiPatchReader::from_path(&path) {
        Ok(r) => r,
        Err(e) => {
            eprintln!("failed to open {}: {e}", path.display());
            return ExitCode::FAILURE;
        }
    };

    let mut counts: BTreeMap<&'static str, u64> = BTreeMap::new();
    let mut total: u64 = 0;

    for result in reader {
        let chunk = match result {
            Ok(c) => c,
            Err(e) => {
                eprintln!("parse error after {total} chunks: {e}");
                return ExitCode::FAILURE;
            }
        };
        total += 1;
        *counts.entry(chunk_label(&chunk)).or_insert(0) += 1;
    }

    println!("file: {}", path.display());
    println!("total chunks: {total}");
    println!();
    println!("{:<32} {:>10}", "variant", "count");
    println!("{:-<32} {:->10}", "", "");
    for (label, count) in &counts {
        println!("{label:<32} {count:>10}");
    }

    ExitCode::SUCCESS
}

// Map a Chunk (and the inner SqpkCommand variant for SQPK chunks) to a
// stable label that's useful in the summary table.
fn chunk_label(chunk: &Chunk) -> &'static str {
    match chunk {
        Chunk::FileHeader(_) => "FileHeader",
        Chunk::ApplyOption(_) => "ApplyOption",
        Chunk::ApplyFreeSpace(_) => "ApplyFreeSpace",
        Chunk::AddDirectory(_) => "AddDirectory",
        Chunk::DeleteDirectory(_) => "DeleteDirectory",
        Chunk::Sqpk(cmd) => match cmd {
            SqpkCommand::AddData(_) => "Sqpk::AddData",
            SqpkCommand::DeleteData(_) => "Sqpk::DeleteData",
            SqpkCommand::ExpandData(_) => "Sqpk::ExpandData",
            SqpkCommand::Header(_) => "Sqpk::Header",
            SqpkCommand::TargetInfo(_) => "Sqpk::TargetInfo",
            SqpkCommand::File(_) => "Sqpk::File",
            SqpkCommand::Index(_) => "Sqpk::Index",
            SqpkCommand::PatchInfo(_) => "Sqpk::PatchInfo",
            _ => "Sqpk::<unknown>",
        },
        Chunk::EndOfFile => "EndOfFile",
        _ => "<unknown>",
    }
}