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(|| 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}