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(|| info.parent_paths[0].clone(), |f| f.to_string_lossy().to_string());
73        println!(
74            "{} parent      {}",
75            "│".dimmed(),
76            parent_display.yellow(),
77        );
78    }
79
80    if let Some(stats) = &info.block_stats {
81        let mut parts = Vec::new();
82        if stats.data_blocks > 0 {
83            parts.push(format!(
84                "{} data ({} unique)",
85                stats.data_blocks, stats.unique_blocks
86            ));
87        }
88        if stats.parent_ref_blocks > 0 {
89            parts.push(format!("{} parent refs", stats.parent_ref_blocks));
90        }
91        if stats.zero_blocks > 0 {
92            parts.push(format!("{} zero", stats.zero_blocks));
93        }
94        if !parts.is_empty() {
95            println!("{} blocks      {}", "│".dimmed(), parts.join(", "));
96        }
97    }
98
99    if let Some(len) = info.metadata_length {
100        println!("{} metadata    {} bytes", "╰".dimmed(), len);
101    } else {
102        println!("{}", "╰".dimmed());
103    }
104
105    Ok(())
106}