Skip to main content

hexz_cli/cmd/data/
inspect.rs

1//! Inspect archive metadata and display archive information.
2
3use anyhow::{Context, Result};
4use colored::Colorize;
5use hexz_ops::inspect::inspect_archive;
6use indicatif::HumanBytes;
7use std::path::Path;
8
9/// Execute the `hexz show` command to display archive metadata.
10pub fn run(snap: &Path, json: bool) -> Result<()> {
11    let info = inspect_archive(snap).context("Failed to inspect archive")?;
12
13    let total_uncompressed = info.total_uncompressed();
14    let ratio = info.compression_ratio();
15
16    if json {
17        let out = serde_json::json!({
18            "path": snap.display().to_string(),
19            "version": info.version,
20            "compression": info.compression,
21            "block_size": info.block_size,
22            "encrypted": info.features.encrypted,
23            "has_main": info.features.has_main,
24            "has_auxiliary": info.features.has_auxiliary,
25            "variable_blocks": info.features.variable_blocks,
26            "original_size": total_uncompressed,
27            "compressed_size": info.file_size,
28            "compression_ratio": ratio,
29            "index_offset": info.index_offset,
30            "main_pages": info.main_pages,
31            "auxiliary_pages": info.auxiliary_pages,
32            "parent_paths": info.parent_paths,
33            "metadata_len": info.metadata_length,
34            "block_stats": info.block_stats,
35        });
36        println!("{}", serde_json::to_string_pretty(&out)?);
37        return Ok(());
38    }
39
40    let filename = snap.file_name().map_or_else(
41        || snap.display().to_string(),
42        |f| f.to_string_lossy().to_string(),
43    );
44
45    let comp_name = match info.compression {
46        hexz_core::format::header::CompressionType::Lz4 => "LZ4",
47        hexz_core::format::header::CompressionType::Zstd => "Zstd",
48    };
49
50    println!("{} {}", "╭".dimmed(), filename.cyan());
51
52    let block_kib = info.block_size / 1024;
53    println!(
54        "{} format      v{}, {}, {} KiB blocks",
55        "│".dimmed(),
56        info.version,
57        comp_name,
58        block_kib,
59    );
60
61    println!(
62        "{} size        {} on disk, {} uncompressed ({:.2}x)",
63        "│".dimmed(),
64        HumanBytes(info.file_size).to_string().green(),
65        HumanBytes(total_uncompressed).to_string().green(),
66        ratio,
67    );
68
69    if !info.parent_paths.is_empty() {
70        let parent_display = std::path::Path::new(&info.parent_paths[0])
71            .file_name()
72            .map_or_else(
73                || info.parent_paths[0].clone(),
74                |f| f.to_string_lossy().to_string(),
75            );
76        println!("{} parent      {}", "│".dimmed(), parent_display.yellow());
77    }
78
79    if let Some(stats) = &info.block_stats {
80        let mut parts = Vec::new();
81        if stats.data_blocks > 0 {
82            parts.push(format!(
83                "{} data ({} unique)",
84                stats.data_blocks, stats.unique_blocks
85            ));
86        }
87        if stats.parent_ref_blocks > 0 {
88            parts.push(format!("{} parent refs", stats.parent_ref_blocks));
89        }
90        if stats.zero_blocks > 0 {
91            parts.push(format!("{} zero", stats.zero_blocks));
92        }
93        if !parts.is_empty() {
94            println!("{} blocks      {}", "│".dimmed(), parts.join(", "));
95        }
96    }
97
98    if let Some(len) = info.metadata_length {
99        println!("{} metadata    {} bytes", "╰".dimmed(), len);
100    } else {
101        println!("{}", "╰".dimmed());
102    }
103
104    Ok(())
105}