feat(tools): h5rs, pure-Rust HDF5 tools (ls, dump, stat, diff, check)

New workspace crate clawhdf5-tools with one binary, h5rs, built only on the
clawhdf5 facade and clawhdf5-format (no libhdf5, no C):

- ls [-r] [-v] FILE[/path]: h5ls's listing (same text in its first two
  columns) plus the datatype; -v adds address, link count, layout and chunk
  index, chunk size, storage, filters, datatype and attributes.
- dump [--json] [-A] [-p] [-d PATH] FILE: h5dump DDL (byte-identical to
  h5dump 1.14.6 on the test files) or hdf5-json.
- stat FILE: h5stat's object/link/rank/layout/filter/attribute counts, raw
  data and total size.
- diff [-r] [-q] [-d D] [-p R] A B [OBJ1 [OBJ2]]: structural and value
  differences, exit 0/1/2 like h5diff.
- check [--data] FILE: walks every object, parses every message, verifies
  the checksums of every v2+ structure (including the fractal heap blocks
  the library never checks), checks chunk indexes against their datasets
  and raw data for out-of-file or overlapping extents; every problem with
  its address.

Values over --max-bytes are reported, not read; dense-storage heaps are
verified before objects are read from them; panics are caught (exit 3).
Tests compare with h5ls, h5stat, h5dump and h5diff and with h5py's values,
and flip the checksum of every checksummed structure in a v1.14-format file.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-26 00:29:28 -05:00
co-authored by Claude Opus 5.5
parent bb78d70b99
commit 310448bfcb
18 changed files with 6550 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
//! Argument handling and output plumbing shared by the subcommands.
use std::io::Write;
/// Where a subcommand writes: `o` for results, `e` for diagnostics.
pub struct Out<'a> {
pub o: &'a mut dyn Write,
pub e: &'a mut dyn Write,
}
/// The arguments after the subcommand name.
pub struct Args {
cmd: &'static str,
rest: std::vec::IntoIter<String>,
}
impl Args {
pub fn new(cmd: &'static str, rest: Vec<String>) -> Self {
Self {
cmd,
rest: rest.into_iter(),
}
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Option<String> {
self.rest.next()
}
/// The value after an option such as `--max-bytes`.
pub fn value(&mut self) -> Option<String> {
self.rest.next()
}
/// A numeric option value.
pub fn number<T: std::str::FromStr>(&mut self) -> Option<T> {
self.rest.next().and_then(|s| s.parse().ok())
}
/// Report a usage problem; exit status 2.
pub fn usage_error(&self, out: &mut Out, msg: &str, usage: &str) -> std::io::Result<i32> {
writeln!(out.e, "h5rs {}: {msg}\n\n{usage}", self.cmd)?;
Ok(2)
}
}