hexz_cli/cmd/data/
inspect.rs1use anyhow::{Context, Result};
4use colored::Colorize;
5use hexz_ops::inspect::inspect_archive;
6use indicatif::HumanBytes;
7use std::path::Path;
8
9pub 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}