use std::collections::BTreeMap;
use std::panic::{catch_unwind, AssertUnwindSafe};
use rust_hdf5::format::messages::datatype::{
ByteOrder, CompoundMember, DatatypeMessage, EnumMember,
};
use rust_hdf5::format::messages::filter::{
Filter, FilterPipeline, FILTER_BLOSC, FILTER_BSHUF, FILTER_BZIP2, FILTER_DEFLATE,
FILTER_FLETCHER32, FILTER_LZ4, FILTER_LZF, FILTER_NBIT, FILTER_SCALEOFFSET, FILTER_SHUFFLE,
FILTER_SZIP, FILTER_ZSTD, FLAG_MANDATORY, FLAG_OPTIONAL,
};
use rust_hdf5::format::messages::shared::MessageStorage;
use rust_hdf5::format::messages::{
MSG_ATTRIBUTE, MSG_ATTR_INFO, MSG_BTREE_K, MSG_DATASPACE, MSG_DATATYPE, MSG_DATA_LAYOUT,
MSG_DRIVER_INFO, MSG_EXTERNAL_FILE_LIST, MSG_FILE_SPACE_INFO, MSG_FILL_VALUE,
MSG_FILL_VALUE_OLD, MSG_FILTER_PIPELINE, MSG_GROUP_INFO, MSG_LINK, MSG_LINK_INFO, MSG_MOD_TIME,
MSG_MOD_TIME_OLD, MSG_NULL, MSG_OBJ_HEADER_CONTINUATION, MSG_OBJ_REF_COUNT,
MSG_SHARED_MESSAGE_TABLE, MSG_SYMBOL_TABLE,
};
use rust_hdf5::format::sohm::SharedLocation;
use rust_hdf5::types::VarLenUnicode;
use rust_hdf5::{
AllocTime, AttributeStorage, ChunkIndex, CreationOrder, DatasetAccess, ExternalFileSegment,
FillTime, FillValue, H5Attribute, H5Dataset, H5File, H5FileOptions, H5Group, H5NamedDatatype,
Hdf5Error, Hyperslab, HyperslabBlock, LibverBound, LinkClass, LinkStorage, Reference,
Selection, StorageLayout, VirtualMapping, VirtualView,
};
use rust_hdf5::{FileSpaceInfoMessage, FileSpaceStrategy};
const CANON_VERSION: &str = "10";
const RAW_LIMIT: usize = 1024;
const MAX_DEPTH: usize = 32;
fn esc(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for ch in s.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
c if (' '..='~').contains(&c) => out.push(c),
c => {
let o = c as u32;
if o <= 0xff {
out.push_str(&format!("\\x{o:02x}"));
} else if o <= 0xffff {
out.push_str(&format!("\\u{o:04x}"));
} else {
out.push_str(&format!("\\U{o:08x}"));
}
}
}
}
out.push('"');
out
}
fn hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push_str(&format!("{b:02x}"));
}
s
}
fn dims_str(dims: &[usize]) -> String {
let parts: Vec<String> = dims.iter().map(|d| d.to_string()).collect();
format!("[{}]", parts.join(","))
}
fn maxdims_str(dims: &[Option<usize>]) -> String {
let parts: Vec<String> = dims
.iter()
.map(|d| match d {
Some(n) => n.to_string(),
None => "U".to_string(),
})
.collect();
format!("[{}]", parts.join(","))
}
fn filter_name(id: u16) -> String {
match id {
FILTER_DEFLATE => "deflate".into(),
FILTER_SHUFFLE => "shuffle".into(),
FILTER_FLETCHER32 => "fletcher32".into(),
FILTER_SZIP => "szip".into(),
FILTER_NBIT => "nbit".into(),
FILTER_SCALEOFFSET => "scaleoffset".into(),
FILTER_BZIP2 => "bzip2".into(),
FILTER_LZF => "lzf".into(),
FILTER_BLOSC => "blosc".into(),
FILTER_LZ4 => "lz4".into(),
FILTER_BSHUF => "bshuf".into(),
FILTER_ZSTD => "zstd".into(),
other => other.to_string(),
}
}
fn filters_str(filters: &[Filter]) -> String {
let parts: Vec<String> = filters
.iter()
.map(|f| {
let cd: Vec<String> = f.cd_values.iter().map(|c| c.to_string()).collect();
format!("{}({})@{}", filter_name(f.id), cd.join("|"), f.flags)
})
.collect();
format!("[{}]", parts.join(","))
}
fn external_str(segments: &[ExternalFileSegment]) -> String {
if segments.is_empty() {
return "-".into();
}
let parts: Vec<String> = segments
.iter()
.map(|s| format!("{}@{}+{}", esc(&s.name), s.offset, s.size))
.collect();
format!("[{}]", parts.join(","))
}
fn dims_u64_str(dims: &[u64]) -> String {
let parts: Vec<String> = dims.iter().map(|d| d.to_string()).collect();
format!("[{}]", parts.join(","))
}
fn selection_bounds_str(sel: &Selection, all_extent: Option<&[usize]>) -> String {
let bounds = match (sel, all_extent) {
(Selection::All, Some(shape)) => Some((
vec![0u64; shape.len()],
shape.iter().map(|&d| d.saturating_sub(1) as u64).collect(),
)),
_ => sel.bounds(),
};
match bounds {
Some((lo, hi)) => format!("{}-{}", dims_u64_str(&lo), dims_u64_str(&hi)),
None => "?".to_string(),
}
}
fn virtual_str(mappings: &[VirtualMapping], vds_shape: &[usize]) -> String {
if mappings.is_empty() {
return "-".into();
}
let parts: Vec<String> = mappings
.iter()
.map(|m| {
format!(
"{}::{} {}->{}",
esc(&m.source_file_name),
esc(&m.source_dset_name),
selection_bounds_str(&m.source_selection, None),
selection_bounds_str(&m.virtual_selection, Some(vds_shape)),
)
})
.collect();
format!("[{}]", parts.join(","))
}
fn encode_raw(bytes: &[u8]) -> String {
if bytes.len() <= RAW_LIMIT {
format!("raw:{}", hex(bytes))
} else {
format!("sha256:{}", hex(&sha256(bytes)))
}
}
fn encode_vals(vals: &[String]) -> String {
let body = format!("vals:[{}]", vals.join(","));
if body.len() <= RAW_LIMIT {
body
} else {
format!("valsha256:{}", hex(&sha256(body.as_bytes())))
}
}
fn order_str(o: &ByteOrder) -> &'static str {
match o {
ByteOrder::LittleEndian => "le",
ByteOrder::BigEndian => "be",
}
}
fn ieee_params(size: u32) -> Option<(u8, u8, u8, u8, u8, u32)> {
match size {
2 => Some((15, 10, 5, 0, 10, 15)),
4 => Some((31, 23, 8, 0, 23, 127)),
8 => Some((63, 52, 11, 0, 52, 1023)),
_ => None,
}
}
fn charset_str(c: u8) -> String {
match c {
0 => "ascii".into(),
1 => "utf8".into(),
n => format!("cset{n}"),
}
}
fn strpad_str(p: u8) -> String {
match p {
0 => "null".into(),
1 => "nullpad".into(),
2 => "spacepad".into(),
n => format!("pad{n}"),
}
}
fn canon_dtype(dt: &DatatypeMessage) -> String {
match dt {
DatatypeMessage::FixedPoint {
size,
byte_order,
signed,
bit_offset,
bit_precision,
} => {
let mut s = format!(
"{}{}{}",
if *signed { "i" } else { "u" },
size * 8,
order_str(byte_order)
);
if *bit_offset != 0 || u32::from(*bit_precision) != size * 8 {
s.push_str(&format!("+off{bit_offset}p{bit_precision}"));
}
s
}
DatatypeMessage::FloatingPoint {
size,
byte_order,
sign_location,
bit_offset,
bit_precision,
exponent_location,
exponent_size,
mantissa_location,
mantissa_size,
exponent_bias,
} => {
let mut s = format!("f{}{}", size * 8, order_str(byte_order));
let actual = (
*sign_location,
*exponent_location,
*exponent_size,
*mantissa_location,
*mantissa_size,
*exponent_bias,
);
let standard = ieee_params(*size);
if standard != Some(actual) || *bit_offset != 0 || u32::from(*bit_precision) != size * 8
{
s.push_str(&format!(
"+s{sign_location}e{exponent_location},{exponent_size}\
m{mantissa_location},{mantissa_size}b{exponent_bias}\
off{bit_offset}p{bit_precision}"
));
}
s
}
DatatypeMessage::BitField {
size,
byte_order,
bit_offset,
bit_precision,
} => {
let mut s = format!("bits[{size}]{}", order_str(byte_order));
if *bit_offset != 0 || u32::from(*bit_precision) != size * 8 {
s.push_str(&format!("+off{bit_offset}p{bit_precision}"));
}
s
}
DatatypeMessage::Opaque { size, tag } => format!("opaque[{size}],tag={}", esc(tag)),
DatatypeMessage::FixedString {
size,
padding,
charset,
} => format!(
"str[{size}],pad={},cset={}",
strpad_str(*padding),
charset_str(*charset)
),
DatatypeMessage::VarLenString { charset, .. } => {
format!("vstr,cset={}", charset_str(*charset))
}
DatatypeMessage::Compound { size, members } => {
let parts: Vec<String> = members
.iter()
.map(|m| format!("{}@{}:{}", m.name, m.offset, canon_dtype(&m.datatype)))
.collect();
format!("compound[{size}]{{{}}}", parts.join(";"))
}
DatatypeMessage::Enum { base, members } => {
let parts: Vec<String> = members
.iter()
.map(|m| format!("{}={}", m.name, enum_value(base, &m.value)))
.collect();
format!("enum({}){{{}}}", canon_dtype(base), parts.join(";"))
}
DatatypeMessage::Reference { size, kind } => if kind.is_revised() {
"stdref"
} else if *size == 8 {
"objref"
} else {
"regref"
}
.to_string(),
DatatypeMessage::VarLenSequence { base } => format!("vlen({})", canon_dtype(base)),
DatatypeMessage::Array { dims, base } => {
let parts: Vec<String> = dims.iter().map(|d| d.to_string()).collect();
format!("array[{}]({})", parts.join(","), canon_dtype(base))
}
}
}
fn enum_value(base: &DatatypeMessage, raw: &[u8]) -> String {
if let DatatypeMessage::FixedPoint {
size,
byte_order,
signed,
..
} = base
{
let n = (*size as usize).min(raw.len()).min(16);
let mut le = [0u8; 16];
match byte_order {
ByteOrder::LittleEndian => le[..n].copy_from_slice(&raw[..n]),
ByteOrder::BigEndian => {
for (i, slot) in le[..n].iter_mut().enumerate() {
*slot = raw[n - 1 - i];
}
}
}
if *signed {
if n == 0 || n >= 16 {
return i128::from_le_bytes(le).to_string();
}
if le[n - 1] & 0x80 != 0 {
for slot in le[n..].iter_mut() {
*slot = 0xff;
}
}
i128::from_le_bytes(le).to_string()
} else {
u128::from_le_bytes(le).to_string()
}
} else {
format!("0x{}", hex(raw))
}
}
fn render_elem(base: &DatatypeMessage, bytes: &[u8]) -> String {
match base {
DatatypeMessage::FixedPoint { .. } => enum_value(base, bytes),
DatatypeMessage::FloatingPoint {
size, byte_order, ..
} => {
let mut be: Vec<u8> = bytes[..(*size as usize).min(bytes.len())].to_vec();
if matches!(byte_order, ByteOrder::LittleEndian) {
be.reverse();
}
format!("0x{}", hex(&be))
}
_ => format!("0x{}", hex(bytes)),
}
}
fn renders_as_values(dt: &DatatypeMessage) -> bool {
match dt {
DatatypeMessage::VarLenString { .. }
| DatatypeMessage::VarLenSequence { .. }
| DatatypeMessage::Reference { .. } => true,
DatatypeMessage::Array { base, .. } => renders_as_values(base),
DatatypeMessage::Compound { members, .. } => {
members.iter().any(|m| renders_as_values(&m.datatype))
}
_ => false,
}
}
fn render_ref(r: &Reference) -> String {
fn target(file: &Option<String>, path: &Option<String>, address: u64) -> String {
let named = path.clone().unwrap_or_else(|| format!("<{address:#x}>"));
match file {
None => named,
Some(file) => format!("{file}{named}"),
}
}
fn coords(dims: &[u64]) -> String {
let parts: Vec<String> = dims.iter().map(|d| d.to_string()).collect();
format!("[{}]", parts.join(","))
}
match r {
Reference::Null => "objref:null".to_string(),
Reference::Object {
address,
file,
path,
} => format!("objref:{}", target(file, path, *address)),
Reference::Region {
address,
file,
path,
selection,
} => match selection.bounds() {
Some((lo, hi)) => format!(
"regref:{}:{}-{}",
target(file, path, *address),
coords(&lo),
coords(&hi)
),
None => format!("regref:{}:unbounded", target(file, path, *address)),
},
Reference::Attr {
address,
file,
path,
name,
} => format!("attrref:{}:{name}", target(file, path, *address)),
}
}
fn vlen_strpads(dt: &DatatypeMessage, whence: &str, out: &mut Vec<String>) {
match dt {
DatatypeMessage::VarLenString { padding, .. } => {
let at = if whence.is_empty() { "." } else { whence };
out.push(format!("{at}={}", strpad_str(*padding)));
}
DatatypeMessage::Array { base, .. } => vlen_strpads(base, &format!("{whence}[]"), out),
DatatypeMessage::VarLenSequence { base } => vlen_strpads(base, &format!("{whence}()"), out),
DatatypeMessage::Compound { members, .. } => {
for m in members {
vlen_strpads(&m.datatype, &format!("{whence}.{}", m.name), out);
}
}
_ => {}
}
}
fn strpad_field(dtype: Option<&DatatypeMessage>) -> std::result::Result<String, String> {
match dtype {
Some(dt) => {
let mut pads = Vec::new();
vlen_strpads(dt, "", &mut pads);
Ok(if pads.is_empty() {
"-".into()
} else {
pads.join(";")
})
}
None => Err("datatype unavailable, so the string pad cannot be classified".into()),
}
}
const K: [u32; 64] = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
];
fn sha256(data: &[u8]) -> [u8; 32] {
let mut h: [u32; 8] = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
0x5be0cd19,
];
let bitlen = (data.len() as u64).wrapping_mul(8);
let mut msg = data.to_vec();
msg.push(0x80);
while msg.len() % 64 != 56 {
msg.push(0);
}
msg.extend_from_slice(&bitlen.to_be_bytes());
let mut w = [0u32; 64];
for block in msg.as_chunks::<64>().0 {
for (i, slot) in w.iter_mut().take(16).enumerate() {
*slot = u32::from_be_bytes([
block[i * 4],
block[i * 4 + 1],
block[i * 4 + 2],
block[i * 4 + 3],
]);
}
for i in 16..64 {
let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16]
.wrapping_add(s0)
.wrapping_add(w[i - 7])
.wrapping_add(s1);
}
let (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh) =
(h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]);
for i in 0..64 {
let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
let ch = (e & f) ^ ((!e) & g);
let t1 = hh
.wrapping_add(s1)
.wrapping_add(ch)
.wrapping_add(K[i])
.wrapping_add(w[i]);
let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
let maj = (a & b) ^ (a & c) ^ (b & c);
let t2 = s0.wrapping_add(maj);
hh = g;
g = f;
f = e;
e = d.wrapping_add(t1);
d = c;
c = b;
b = a;
a = t1.wrapping_add(t2);
}
for (slot, v) in h.iter_mut().zip([a, b, c, d, e, f, g, hh]) {
*slot = slot.wrapping_add(v);
}
}
let mut out = [0u8; 32];
for (i, v) in h.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&v.to_be_bytes());
}
out
}
fn unsupported(field: &str, why: &str) -> String {
format!("UNSUPPORTED({field}): {why}")
}
fn oneline(e: impl std::fmt::Display) -> String {
e.to_string()
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
}
fn guarded<T>(f: impl FnOnce() -> T) -> std::result::Result<T, String> {
catch_unwind(AssertUnwindSafe(f)).map_err(|payload| {
let msg = payload
.downcast_ref::<&str>()
.map(|s| (*s).to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "non-string panic payload".to_string());
oneline(msg)
})
}
struct Dump {
lines: Vec<String>,
access: DatasetAccess,
}
impl Dump {
fn new(access: DatasetAccess) -> Self {
Self {
lines: Vec::new(),
access,
}
}
fn emit(&mut self, key: &str, value: impl AsRef<str>) {
self.lines
.push(format!("{key}\t{}", value.as_ref().replace('\t', " ")));
}
fn field(
&mut self,
path: &str,
field: &str,
f: impl FnOnce() -> std::result::Result<String, String>,
) {
let value = match guarded(f) {
Ok(Ok(v)) => v,
Ok(Err(e)) => unsupported(field, &e),
Err(p) => unsupported(field, &format!("panic: {p}")),
};
self.emit(&format!("{path}#{field}"), value);
}
}
enum Child {
Group,
Dataset,
NamedDatatype,
Soft(String),
External(String, String),
Unclassified(String),
}
fn child_path(parent: &str, name: &str) -> String {
if parent == "/" {
format!("/{name}")
} else {
format!("{parent}/{name}")
}
}
fn dump_file(path: &str, access: DatasetAccess) -> std::result::Result<String, String> {
let mut d = Dump::new(access);
d.emit("!canon", CANON_VERSION);
let file = match guarded(|| H5File::open(path)) {
Ok(Ok(f)) => f,
Ok(Err(e)) => return Err(format!("H5File::open failed: {}", oneline(e))),
Err(p) => return Err(format!("H5File::open panicked: {p}")),
};
d.field("", "superblock", || {
file.superblock_version()
.map(|v| v.to_string())
.map_err(oneline)
});
d.field("", "userblock", || Ok(file.userblock_size().to_string()));
d.field("", "fspace", || {
Ok(fspace_str(
file.superblock_extension().file_space_info.as_ref(),
))
});
d.field("", "freespace", || {
file.tracked_free_space()
.map(|bytes| if bytes > 0 { "tracked" } else { "none" }.to_string())
.map_err(oneline)
});
let root = file.root_group();
dump_group(&mut d, &file, "/", &root, 0);
Ok(d.lines.join("\n") + "\n")
}
fn fspace_str(info: Option<&FileSpaceInfoMessage>) -> String {
let Some(info) = info else {
return "fsmaggr/false/1/4096".to_string();
};
let strategy = match info.strategy {
FileSpaceStrategy::FsmAggr => "fsmaggr".to_string(),
FileSpaceStrategy::Page => "page".to_string(),
FileSpaceStrategy::Aggr => "aggr".to_string(),
FileSpaceStrategy::None => "none".to_string(),
FileSpaceStrategy::Unknown(v) => format!("unknown({v})"),
};
format!(
"{strategy}/{}/{}/{}",
info.persist, info.threshold, info.page_size
)
}
fn crt_order_str(order: CreationOrder) -> &'static str {
match order {
CreationOrder::Untracked => "-",
CreationOrder::Tracked => "tracked",
CreationOrder::Indexed => "tracked+indexed",
}
}
fn message_class_name(msg_type: u8) -> String {
match msg_type {
MSG_NULL => "null".into(),
MSG_DATASPACE => "dataspace".into(),
MSG_LINK_INFO => "linfo".into(),
MSG_DATATYPE => "datatype".into(),
MSG_FILL_VALUE_OLD => "fill".into(),
MSG_FILL_VALUE => "fill_new".into(),
MSG_LINK => "link".into(),
MSG_EXTERNAL_FILE_LIST => "external_file_list".into(),
MSG_DATA_LAYOUT => "layout".into(),
MSG_GROUP_INFO => "ginfo".into(),
MSG_FILTER_PIPELINE => "filter_pipeline".into(),
MSG_ATTRIBUTE => "attribute".into(),
MSG_MOD_TIME_OLD => "mtime".into(),
MSG_SHARED_MESSAGE_TABLE => "shared_message_table".into(),
MSG_OBJ_HEADER_CONTINUATION => "hdr_continuation".into(),
MSG_SYMBOL_TABLE => "stab".into(),
MSG_MOD_TIME => "mtime_new".into(),
MSG_BTREE_K => "btreek".into(),
MSG_DRIVER_INFO => "driver_info".into(),
MSG_ATTR_INFO => "ainfo".into(),
MSG_OBJ_REF_COUNT => "refcount".into(),
MSG_FILE_SPACE_INFO => "fsinfo".into(),
other => format!("msg0x{other:02x}"),
}
}
fn msgflags_str(file: &H5File, path: &str) -> std::result::Result<String, String> {
let mut parts: Vec<String> = file
.object_message_flags(path)
.map_err(oneline)?
.into_iter()
.map(|(msg_type, flags)| {
format!(
"{}:{}",
message_class_name(msg_type),
message_flags_name(flags)
)
})
.collect();
parts.sort();
Ok(format!("[{}]", parts.join(",")))
}
fn message_flags_name(flags: u8) -> String {
const TOKENS: [(u8, &str); 8] = [
(0x01, "C"),
(0x02, "S"),
(0x04, "DS"),
(0x08, "FIUW"),
(0x10, "MIU"),
(0x20, "WU"),
(0x40, "SA"),
(0x80, "FIUA"),
];
let set: Vec<&str> = TOKENS
.iter()
.filter(|(bit, _)| flags & bit != 0)
.map(|(_, name)| *name)
.collect();
if set.is_empty() {
"none".to_string()
} else {
set.join("+")
}
}
fn dtypever_str(file: &H5File, path: &str) -> std::result::Result<String, String> {
let nodes = file.object_datatype_versions(path).map_err(oneline)?;
let mut parts = Vec::new();
let mut under_vlen: Option<usize> = None;
for node in nodes {
match under_vlen {
Some(depth) if node.depth > depth => continue,
_ => under_vlen = None,
}
parts.push(format!("{}:{}", node.class, node.version));
if node.class == "vlen" {
under_vlen = Some(node.depth);
}
}
Ok(format!("[{}]", parts.join(",")))
}
fn hdrtimes_str(file: &H5File, path: &str) -> std::result::Result<String, String> {
file.object_records_times(path)
.map(|yes| if yes { "yes" } else { "no" }.to_string())
.map_err(oneline)
}
fn shared_str(file: &H5File, path: &str) -> std::result::Result<String, String> {
let mut parts: Vec<String> = file
.object_message_storage(path)
.map_err(oneline)?
.into_iter()
.map(|(msg_type, storage)| {
let where_ = match storage {
MessageStorage::Private => "private",
MessageStorage::Shareable => "shareable",
MessageStorage::Shared(SharedLocation::Sohm) => "sohm",
MessageStorage::Shared(SharedLocation::Committed) => "committed",
MessageStorage::Shared(SharedLocation::Here) => "here",
MessageStorage::Shared(SharedLocation::Unshared) => "unshared",
};
format!("{}:{where_}", message_class_name(msg_type))
})
.collect();
parts.sort();
Ok(format!("[{}]", parts.join(",")))
}
fn attrstore_str(storage: AttributeStorage) -> &'static str {
if storage.is_dense() {
"dense"
} else {
"compact"
}
}
fn linkstore_str(storage: LinkStorage) -> &'static str {
match storage {
LinkStorage::SymbolTable => "symtab",
LinkStorage::Compact => "compact",
LinkStorage::Dense => "dense",
}
}
fn dump_group(d: &mut Dump, file: &H5File, path: &str, group: &H5Group, depth: usize) {
d.emit(&format!("{path}#kind"), "group");
d.field(path, "linkorder", || {
group
.link_creation_order()
.map(crt_order_str)
.map(str::to_string)
.map_err(oneline)
});
d.field(path, "attrorder", || {
group
.attr_creation_order()
.map(crt_order_str)
.map(str::to_string)
.map_err(oneline)
});
d.field(path, "linkstore", || {
group
.link_storage()
.map(linkstore_str)
.map(str::to_string)
.map_err(oneline)
});
d.field(path, "shared", || shared_str(file, path));
d.field(path, "msgflags", || msgflags_str(file, path));
d.field(path, "hdrtimes", || hdrtimes_str(file, path));
dump_group_attrs(d, path, group);
if depth >= MAX_DEPTH {
d.emit(&format!("{path}#truncated"), "depth");
return;
}
let mut children: BTreeMap<String, Child> = BTreeMap::new();
match guarded(|| group.group_names()) {
Ok(Ok(names)) => {
for n in names {
children.insert(n, Child::Group);
}
}
Ok(Err(e)) => d.emit(
&format!("{path}#group_names"),
unsupported("group_names", &oneline(e)),
),
Err(p) => d.emit(
&format!("{path}#group_names"),
unsupported("group_names", &format!("panic: {p}")),
),
}
match guarded(|| group.dataset_names()) {
Ok(Ok(names)) => {
for n in names {
children.insert(n, Child::Dataset);
}
}
Ok(Err(e)) => d.emit(
&format!("{path}#dataset_names"),
unsupported("dataset_names", &oneline(e)),
),
Err(p) => d.emit(
&format!("{path}#dataset_names"),
unsupported("dataset_names", &format!("panic: {p}")),
),
}
match guarded(|| group.named_datatype_names()) {
Ok(Ok(names)) => {
for n in names {
children.insert(n, Child::NamedDatatype);
}
}
Ok(Err(e)) => d.emit(
&format!("{path}#named_datatype_names"),
unsupported("named_datatype_names", &oneline(e)),
),
Err(p) => d.emit(
&format!("{path}#named_datatype_names"),
unsupported("named_datatype_names", &format!("panic: {p}")),
),
}
match guarded(|| group.link_names()) {
Ok(Ok(names)) => {
for n in names {
let class = match guarded(|| group.link_class(&n)) {
Ok(Ok(c)) => c,
Ok(Err(e)) => {
children.insert(n, Child::Unclassified(oneline(e)));
continue;
}
Err(p) => {
children.insert(n, Child::Unclassified(format!("panic: {p}")));
continue;
}
};
match class {
LinkClass::Hard => {
children.entry(n).or_insert_with(|| {
Child::Unclassified(
"the link is hard but the object it reaches is in neither \
group_names() nor dataset_names()"
.into(),
)
});
}
LinkClass::Soft { path } => {
children.insert(n, Child::Soft(path));
}
LinkClass::External { file, path } => {
children.insert(n, Child::External(file, path));
}
LinkClass::UserDefined { link_type } => {
children.insert(
n,
Child::Unclassified(format!(
"user-defined link of type {link_type}, which this crate \
does not interpret"
)),
);
}
}
}
}
Ok(Err(e)) => d.emit(
&format!("{path}#link_names"),
unsupported("link_names", &oneline(e)),
),
Err(p) => d.emit(
&format!("{path}#link_names"),
unsupported("link_names", &format!("panic: {p}")),
),
}
for (name, child) in children {
let cpath = child_path(path, &name);
match child {
Child::Group => match guarded(|| group.group(&name)) {
Ok(Ok(sub)) => dump_group(d, file, &cpath, &sub, depth + 1),
Ok(Err(e)) => d.emit(&format!("{cpath}#kind"), unsupported("kind", &oneline(e))),
Err(p) => d.emit(
&format!("{cpath}#kind"),
unsupported("kind", &format!("panic: {p}")),
),
},
Child::Dataset => {
let lookup = cpath.trim_start_matches('/').to_string();
let access = d.access.clone();
match guarded(|| file.dataset_with(&lookup, access)) {
Ok(Ok(ds)) => dump_dataset(d, file, &cpath, &ds),
Ok(Err(e)) => {
d.emit(&format!("{cpath}#kind"), unsupported("kind", &oneline(e)))
}
Err(p) => d.emit(
&format!("{cpath}#kind"),
unsupported("kind", &format!("panic: {p}")),
),
}
}
Child::NamedDatatype => match guarded(|| group.named_datatype(&name)) {
Ok(Ok(t)) => dump_named_datatype(d, file, &cpath, &t),
Ok(Err(e)) => d.emit(&format!("{cpath}#kind"), unsupported("kind", &oneline(e))),
Err(p) => d.emit(
&format!("{cpath}#kind"),
unsupported("kind", &format!("panic: {p}")),
),
},
Child::Soft(target) => {
d.emit(&format!("{cpath}#kind"), "softlink");
d.emit(&format!("{cpath}#target"), target);
}
Child::External(efile, epath) => {
d.emit(&format!("{cpath}#kind"), "extlink");
d.emit(&format!("{cpath}#target"), format!("{efile}::{epath}"));
d.field(&cpath, "resolved", || resolve_extlink(file, &cpath));
}
Child::Unclassified(why) => d.emit(&format!("{cpath}#kind"), unsupported("kind", &why)),
}
}
}
fn resolve_extlink(file: &H5File, cpath: &str) -> std::result::Result<String, String> {
let lookup = cpath.trim_start_matches('/').to_string();
if let Ok(Ok(_)) = guarded(|| file.named_datatype(&lookup)) {
return Ok("committed-datatype".into());
}
match guarded(|| file.dataset(&lookup)) {
Ok(Ok(ds)) => {
let dims = guarded(|| ds.shape()).map_err(|p| format!("panic: {p}"))?;
let dtype = guarded(|| ds.datatype()).ok().and_then(|r| r.ok());
let payload = dataset_payload(&ds, dtype.as_ref())?;
Ok(format!("dataset {} {}", dims_str(&dims), payload))
}
Ok(Err(Hdf5Error::DanglingLink { .. }))
| Ok(Err(Hdf5Error::ExternalFileNotFound { .. })) => Ok("dangling".into()),
Ok(Err(e)) => Err(oneline(e)),
Err(p) => Err(format!("panic: {p}")),
}
}
fn dump_dataset(d: &mut Dump, file: &H5File, path: &str, ds: &H5Dataset) {
d.emit(&format!("{path}#kind"), "dataset");
let dtype = guarded(|| ds.datatype()).ok().and_then(|r| r.ok());
d.field(path, "dtype", || match &dtype {
Some(dt) => Ok(canon_dtype(dt)),
None => Err("H5Dataset::datatype() failed or is unavailable".into()),
});
let is_null = guarded(|| ds.is_null()).unwrap_or(false);
d.field(path, "strpad", || strpad_field(dtype.as_ref()));
d.field(path, "dtypever", || dtypever_str(file, path));
d.field(path, "shape", || {
if is_null {
return Ok("null".into());
}
Ok(dims_str(
&guarded(|| ds.shape()).map_err(|p| format!("panic: {p}"))?,
))
});
d.field(path, "maxshape", || {
if is_null {
return Ok("null".into());
}
guarded(|| ds.max_shape())
.map_err(|p| format!("panic: {p}"))?
.map(|dims| maxdims_str(&dims))
.map_err(oneline)
});
let chunked = guarded(|| ds.is_chunked()).unwrap_or(false);
d.field(path, "layout", || {
guarded(|| ds.storage_layout())
.map_err(|p| format!("panic: {p}"))?
.map(|layout| {
match layout {
StorageLayout::Compact => "compact",
StorageLayout::Contiguous => "contiguous",
StorageLayout::Chunked => "chunked",
StorageLayout::Virtual => "virtual",
}
.to_string()
})
.map_err(oneline)
});
d.field(path, "chunk", || {
if !chunked {
return Ok("-".into());
}
match guarded(|| ds.chunk_dims()).map_err(|p| format!("panic: {p}"))? {
Some(dims) => Ok(dims_str(&dims)),
None => Err("is_chunked() is true but chunk_dims() returned None".into()),
}
});
d.field(path, "chunkindex", || {
if !chunked {
return Ok("-".into());
}
match guarded(|| ds.chunk_index()).map_err(|p| format!("panic: {p}"))? {
Ok(Some(kind)) => Ok(match kind {
ChunkIndex::BtreeV1 => "btree1",
ChunkIndex::BtreeV2 => "btree2",
ChunkIndex::SingleChunk => "single",
ChunkIndex::Implicit => "implicit",
ChunkIndex::FixedArray => "farray",
ChunkIndex::ExtensibleArray => "earray",
}
.to_string()),
Ok(None) => Err("is_chunked() is true but chunk_index() returned None".into()),
Err(e) => Err(oneline(e)),
}
});
d.field(path, "external", || {
guarded(|| ds.external_files())
.map_err(|p| format!("panic: {p}"))?
.map(|segments| external_str(&segments))
.map_err(oneline)
});
d.field(path, "virtual", || {
let vds_shape = guarded(|| ds.shape()).map_err(|p| format!("panic: {p}"))?;
guarded(|| ds.virtual_mappings())
.map_err(|p| format!("panic: {p}"))?
.map(|mappings| virtual_str(&mappings, &vds_shape))
.map_err(oneline)
});
d.field(path, "filters", || {
guarded(|| ds.filters())
.map_err(|p| format!("panic: {p}"))?
.map(|filters| filters_str(&filters))
.map_err(oneline)
});
d.field(path, "fillvalue", || {
guarded(|| ds.fill_value())
.map_err(|p| format!("panic: {p}"))?
.map(|fv| match fv {
FillValue::Default => "default".to_string(),
FillValue::Undefined => "undefined".to_string(),
FillValue::UserDefined(bytes) => format!("0x{}", hex(&bytes)),
})
.map_err(oneline)
});
d.field(path, "filltime", || {
guarded(|| ds.fill_time())
.map_err(|p| format!("panic: {p}"))?
.map(|ft| {
match ft {
FillTime::Alloc => "alloc",
FillTime::Never => "never",
FillTime::IfSet => "ifset",
}
.to_string()
})
.map_err(oneline)
});
d.field(path, "alloctime", || {
guarded(|| ds.alloc_time())
.map_err(|p| format!("panic: {p}"))?
.map(|at| {
match at {
AllocTime::Early => "early",
AllocTime::Late => "late",
AllocTime::Incr => "incr",
}
.to_string()
})
.map_err(oneline)
});
d.field(path, "shared", || shared_str(file, path));
d.field(path, "msgflags", || msgflags_str(file, path));
d.field(path, "hdrtimes", || hdrtimes_str(file, path));
dump_object_attrs(d, path, ds);
d.field(path, "data", || dataset_payload(ds, dtype.as_ref()));
}
fn dataset_payload(
ds: &H5Dataset,
dtype: Option<&DatatypeMessage>,
) -> std::result::Result<String, String> {
if guarded(|| ds.is_null()).unwrap_or(false) {
return Ok("empty".into());
}
let dt = dtype.ok_or("datatype unavailable, so the payload cannot be classified")?;
if !renders_as_values(dt) {
let bytes = guarded(|| ds.read_raw_bytes())
.map_err(|p| format!("panic: {p}"))?
.map_err(oneline)?;
return Ok(encode_raw(&bytes));
}
match dt {
DatatypeMessage::VarLenString { .. } => {
let strings = guarded(|| ds.read_vlen_strings())
.map_err(|p| format!("panic: {p}"))?
.map_err(oneline)?;
let vals: Vec<String> = strings.iter().map(|s| esc(s)).collect();
Ok(encode_vals(&vals))
}
DatatypeMessage::VarLenSequence { base } => {
let width = base.element_size() as usize;
if width == 0 {
return Err("variable-length sequence with a zero-width base".into());
}
let items = guarded(|| ds.read_vlen_bytes())
.map_err(|p| format!("panic: {p}"))?
.map_err(oneline)?;
let vals: Vec<String> = items
.iter()
.map(|item| {
let elems: Vec<String> =
item.chunks(width).map(|c| render_elem(base, c)).collect();
format!("[{}]", elems.join(","))
})
.collect();
Ok(encode_vals(&vals))
}
DatatypeMessage::Reference { .. } => {
let refs = guarded(|| ds.read_references())
.map_err(|p| format!("panic: {p}"))?
.map_err(oneline)?;
let vals: Vec<String> = refs.iter().map(render_ref).collect();
Ok(encode_vals(&vals))
}
other => Err(format!(
"no public reader for a {} payload",
canon_dtype(other)
)),
}
}
trait AttrSource {
fn attr_names(&self) -> rust_hdf5::Result<Vec<String>>;
fn attr(&self, name: &str) -> rust_hdf5::Result<H5Attribute>;
fn header_attr_count(&self) -> std::result::Result<u64, String>;
fn attr_storage(&self) -> std::result::Result<AttributeStorage, String>;
}
impl AttrSource for H5Dataset {
fn attr_names(&self) -> rust_hdf5::Result<Vec<String>> {
H5Dataset::attr_names(self)
}
fn attr(&self, name: &str) -> rust_hdf5::Result<H5Attribute> {
H5Dataset::attr(self, name)
}
fn header_attr_count(&self) -> std::result::Result<u64, String> {
H5Dataset::header_attr_count(self).map_err(oneline)
}
fn attr_storage(&self) -> std::result::Result<AttributeStorage, String> {
H5Dataset::attr_storage(self).map_err(oneline)
}
}
impl AttrSource for H5NamedDatatype {
fn attr_names(&self) -> rust_hdf5::Result<Vec<String>> {
H5NamedDatatype::attr_names(self)
}
fn attr(&self, name: &str) -> rust_hdf5::Result<H5Attribute> {
H5NamedDatatype::attr(self, name)
}
fn header_attr_count(&self) -> std::result::Result<u64, String> {
H5NamedDatatype::header_attr_count(self).map_err(oneline)
}
fn attr_storage(&self) -> std::result::Result<AttributeStorage, String> {
Err("H5NamedDatatype exposes no compact/dense attribute storage accessor".into())
}
}
fn dump_named_datatype(d: &mut Dump, file: &H5File, path: &str, t: &H5NamedDatatype) {
d.emit(&format!("{path}#kind"), "committed-datatype");
let dtype = guarded(|| t.datatype()).ok().and_then(|r| r.ok());
d.field(path, "dtype", || match &dtype {
Some(dt) => Ok(canon_dtype(dt)),
None => Err("H5NamedDatatype::datatype() failed or is unavailable".into()),
});
d.field(path, "strpad", || strpad_field(dtype.as_ref()));
d.field(path, "dtypever", || dtypever_str(file, path));
d.field(path, "msgflags", || msgflags_str(file, path));
d.field(path, "hdrtimes", || hdrtimes_str(file, path));
dump_object_attrs(d, path, t);
}
fn dump_object_attrs<T: AttrSource>(d: &mut Dump, path: &str, ds: &T) {
let names = match guarded(|| ds.attr_names()) {
Ok(Ok(mut n)) => {
n.sort();
n
}
Ok(Err(e)) => {
d.emit(
&format!("{path}#nattrs"),
unsupported("nattrs", &oneline(e)),
);
return;
}
Err(p) => {
d.emit(
&format!("{path}#nattrs"),
unsupported("nattrs", &format!("panic: {p}")),
);
return;
}
};
d.emit(&format!("{path}#nattrs"), names.len().to_string());
d.field(path, "nattrs_hdr", || {
ds.header_attr_count().map(|n| n.to_string())
});
d.field(path, "attrstore", || {
ds.attr_storage().map(attrstore_str).map(str::to_string)
});
for name in names {
let key = format!("{path}@{name}");
let attr = match guarded(|| ds.attr(&name)) {
Ok(Ok(a)) => Some(a),
_ => None,
};
let dtype = attr
.as_ref()
.and_then(|a| guarded(|| a.datatype()).ok())
.and_then(|r| r.ok());
d.field(&key, "dtype", || match &dtype {
Some(dt) => Ok(canon_dtype(dt)),
None => Err("H5Attribute::datatype() failed or is unavailable".into()),
});
d.field(&key, "strpad", || strpad_field(dtype.as_ref()));
d.field(&key, "shape", || {
Err("H5Attribute exposes no shape() accessor".into())
});
d.field(&key, "value", || {
let a = attr.as_ref().ok_or("attr() did not return a handle")?;
let dt = dtype
.as_ref()
.ok_or("datatype unavailable, so the value cannot be classified")?;
if !renders_as_values(dt) {
let bytes = guarded(|| a.read_raw())
.map_err(|p| format!("panic: {p}"))?
.map_err(oneline)?;
return Ok(encode_raw(&bytes));
}
match dt {
DatatypeMessage::VarLenString { .. } => {
let s = guarded(|| a.read_string())
.map_err(|p| format!("panic: {p}"))?
.map_err(oneline)?;
Ok(encode_vals(&[esc(&s)]))
}
DatatypeMessage::Reference { .. } => {
let refs = guarded(|| a.read_references())
.map_err(|p| format!("panic: {p}"))?
.map_err(oneline)?;
let vals: Vec<String> = refs.iter().map(render_ref).collect();
Ok(encode_vals(&vals))
}
other => Err(format!(
"no public reader for a {} attribute",
canon_dtype(other)
)),
}
});
}
}
fn dump_group_attrs(d: &mut Dump, path: &str, group: &H5Group) {
let names = match guarded(|| group.attr_names()) {
Ok(Ok(mut n)) => {
n.sort();
n
}
Ok(Err(e)) => {
d.emit(
&format!("{path}#nattrs"),
unsupported("nattrs", &oneline(e)),
);
return;
}
Err(p) => {
d.emit(
&format!("{path}#nattrs"),
unsupported("nattrs", &format!("panic: {p}")),
);
return;
}
};
d.emit(&format!("{path}#nattrs"), names.len().to_string());
d.field(path, "nattrs_hdr", || {
group
.header_attr_count()
.map(|n| n.to_string())
.map_err(oneline)
});
d.field(path, "attrstore", || {
group
.attr_storage()
.map(attrstore_str)
.map(str::to_string)
.map_err(oneline)
});
for name in names {
let key = format!("{path}@{name}");
d.emit(
&format!("{key}#dtype"),
unsupported("dtype", "H5Group has no attr() handle in read mode"),
);
d.emit(
&format!("{key}#strpad"),
unsupported("strpad", "H5Group has no attr() handle in read mode"),
);
d.emit(
&format!("{key}#shape"),
unsupported("shape", "H5Group has no attr() handle in read mode"),
);
let detail = match guarded(|| group.attr_string(&name)) {
Ok(Ok(s)) => format!(
"H5Group has no attr() handle; attr_string() gave {}",
esc(&s)
),
Ok(Err(e)) => format!(
"H5Group has no attr() handle; attr_string() failed: {}",
oneline(e)
),
Err(p) => format!("H5Group has no attr() handle; attr_string() panicked: {p}"),
};
d.emit(&format!("{key}#value"), unsupported("value", &detail));
}
}
struct Unsupported(String);
type WriteResult = std::result::Result<(), Unsupported>;
fn unsup(why: &str) -> WriteResult {
Err(Unsupported(why.to_string()))
}
fn be_bytes_ramp(width: usize, n: u64) -> Vec<u8> {
let mut out = Vec::with_capacity(width * n as usize);
for i in 0..n {
let full = i.to_be_bytes();
out.extend_from_slice(&full[8 - width..]);
}
out
}
fn fixed_string_bytes(strings: &[&str], width: usize, pad: u8) -> Vec<u8> {
let mut out = Vec::with_capacity(strings.len() * width);
for s in strings {
let mut cell = vec![pad; width];
let b = s.as_bytes();
cell[..b.len()].copy_from_slice(b);
out.extend_from_slice(&cell);
}
out
}
const STRINGS: [&str; 4] = ["alpha", "b", "", "delta12"];
const UNISTR: [&str; 4] = ["été", "日本", "", "café"];
const F16_RAMP: [u16; 8] = [
0x0000, 0x3C00, 0x4000, 0x4200, 0x4400, 0x4500, 0x4600, 0x4700,
];
const SPECIAL_BITS: [u64; 8] = [
0x7FF8_0000_0000_0001,
0x7FF0_0000_0000_0000,
0xFFF0_0000_0000_0000,
0x8000_0000_0000_0000,
0x0000_0000_0000_0001,
0x3FF0_0000_0000_0000,
0xBFF0_0000_0000_0000,
0x0000_0000_0000_0000,
];
fn f16_dtype() -> DatatypeMessage {
DatatypeMessage::FloatingPoint {
size: 2,
byte_order: ByteOrder::LittleEndian,
sign_location: 15,
bit_offset: 0,
bit_precision: 16,
exponent_location: 10,
exponent_size: 5,
mantissa_location: 0,
mantissa_size: 10,
exponent_bias: 15,
}
}
fn be_of(dt: DatatypeMessage) -> DatatypeMessage {
match dt {
DatatypeMessage::FixedPoint {
size,
signed,
bit_offset,
bit_precision,
..
} => DatatypeMessage::FixedPoint {
size,
byte_order: ByteOrder::BigEndian,
signed,
bit_offset,
bit_precision,
},
DatatypeMessage::FloatingPoint {
size,
sign_location,
bit_offset,
bit_precision,
exponent_location,
exponent_size,
mantissa_location,
mantissa_size,
exponent_bias,
..
} => DatatypeMessage::FloatingPoint {
size,
byte_order: ByteOrder::BigEndian,
sign_location,
bit_offset,
bit_precision,
exponent_location,
exponent_size,
mantissa_location,
mantissa_size,
exponent_bias,
},
other => other,
}
}
fn raw_typed(
file: &H5File,
name: &str,
dt: DatatypeMessage,
shape: &[usize],
bytes: &[u8],
) -> rust_hdf5::Result<()> {
let ds = file
.new_dataset::<u8>()
.datatype(dt)
.shape(shape)
.create(name)?;
ds.write_raw_bytes(bytes)
}
fn write_case(case: &str, path: &str) -> rust_hdf5::Result<WriteResult> {
match case {
"int_i8" => simple_ramp::<i8>(path, ramp_n::<i8>(8)),
"int_u8" => simple_ramp::<u8>(path, ramp_n::<u8>(8)),
"int_i16le" => simple_ramp::<i16>(path, ramp_n::<i16>(8)),
"int_u16le" => simple_ramp::<u16>(path, ramp_n::<u16>(8)),
"int_i32le" => simple_ramp::<i32>(path, ramp_n::<i32>(8)),
"int_u32le" => simple_ramp::<u32>(path, ramp_n::<u32>(8)),
"int_i64le" => simple_ramp::<i64>(path, ramp_n::<i64>(8)),
"int_u64le" => simple_ramp::<u64>(path, ramp_n::<u64>(8)),
"int_i16be" => be_ramp(path, DatatypeMessage::i16_type(), 2),
"int_i32be" => {
let file = earliest_file(path)?;
let ds = file
.new_dataset::<i32>()
.datatype(be_of(DatatypeMessage::i32_type()))
.shape([8usize])
.create("data")?;
ds.write_raw(&(0..8i32).collect::<Vec<_>>())?;
file.close()?;
Ok(Ok(()))
}
"int_u64be" => be_ramp(path, DatatypeMessage::u64_type(), 8),
"float_f16le" => {
let bytes: Vec<u8> = F16_RAMP.iter().flat_map(|b| b.to_le_bytes()).collect();
let file = earliest_file(path)?;
raw_typed(&file, "data", f16_dtype(), &[8], &bytes)?;
file.close()?;
Ok(Ok(()))
}
"float_f32le" => simple_ramp::<f32>(path, (0..8).map(|i| i as f32).collect()),
"float_f64le" => simple_ramp::<f64>(path, (0..8).map(|i| i as f64).collect()),
"float_f64be" => {
let bytes: Vec<u8> = (0..8u64).flat_map(|i| (i as f64).to_be_bytes()).collect();
let file = earliest_file(path)?;
raw_typed(
&file,
"data",
be_of(DatatypeMessage::f64_type()),
&[8],
&bytes,
)?;
file.close()?;
Ok(Ok(()))
}
"float_specials" => {
let vals: Vec<f64> = SPECIAL_BITS.iter().map(|b| f64::from_bits(*b)).collect();
simple_ramp::<f64>(path, vals)
}
"str_fixed_ascii" => {
let file = earliest_file(path)?;
raw_typed(
&file,
"data",
DatatypeMessage::FixedString {
size: 8,
padding: 1,
charset: 0,
},
&[4],
&fixed_string_bytes(&STRINGS, 8, 0),
)?;
file.close()?;
Ok(Ok(()))
}
"str_fixed_nullpad" | "str_fixed_spacepad" => {
let (padding, pad_byte) = if case == "str_fixed_spacepad" {
(2u8, b' ')
} else {
(1u8, 0u8)
};
let file = earliest_file(path)?;
lowlevel_creation(&file)?;
raw_typed(
&file,
"data",
DatatypeMessage::FixedString {
size: 8,
padding,
charset: 0,
},
&[4],
&fixed_string_bytes(&STRINGS, 8, pad_byte),
)?;
file.close()?;
Ok(Ok(()))
}
"str_fixed_utf8" => {
let file = earliest_file(path)?;
lowlevel_creation(&file)?;
raw_typed(
&file,
"data",
DatatypeMessage::FixedString {
size: 16,
padding: 1,
charset: 1,
},
&[4],
&fixed_string_bytes(&UNISTR, 16, 0),
)?;
file.close()?;
Ok(Ok(()))
}
"str_vlen_ascii" => {
let file = earliest_file(path)?;
file.write_vlen_strings_ascii("data", &STRINGS)?;
file.close()?;
Ok(Ok(()))
}
"str_vlen_utf8" => {
let file = earliest_file(path)?;
file.write_vlen_strings("data", &UNISTR)?;
file.close()?;
Ok(Ok(()))
}
"compound_simple" => {
let dt = DatatypeMessage::Compound {
size: 8,
members: vec![
member("x", 0, DatatypeMessage::f32_type()),
member("y", 4, DatatypeMessage::f32_type()),
],
};
let mut bytes = Vec::new();
for i in 0..4u32 {
bytes.extend_from_slice(&(i as f32).to_le_bytes());
bytes.extend_from_slice(&((100 + i) as f32).to_le_bytes());
}
let file = earliest_file(path)?;
raw_typed(&file, "data", dt, &[4], &bytes)?;
file.close()?;
Ok(Ok(()))
}
"compound_nested" => {
let inner = DatatypeMessage::Compound {
size: 4,
members: vec![
member("u", 0, DatatypeMessage::i16_type()),
member("v", 2, DatatypeMessage::i16_type()),
],
};
let dt = DatatypeMessage::Compound {
size: 8,
members: vec![
member("a", 0, DatatypeMessage::i32_type()),
member("inner", 4, inner),
],
};
let mut bytes = Vec::new();
for i in 0..4i32 {
bytes.extend_from_slice(&i.to_le_bytes());
bytes.extend_from_slice(&((10 + i) as i16).to_le_bytes());
bytes.extend_from_slice(&((20 + i) as i16).to_le_bytes());
}
let file = earliest_file(path)?;
raw_typed(&file, "data", dt, &[4], &bytes)?;
file.close()?;
Ok(Ok(()))
}
"compound_with_string" => {
let dt = DatatypeMessage::Compound {
size: 12,
members: vec![
member("id", 0, DatatypeMessage::i32_type()),
member(
"name",
4,
DatatypeMessage::FixedString {
size: 8,
padding: 1,
charset: 0,
},
),
],
};
let names = ["aa", "bbb", "cccc"];
let mut bytes = Vec::new();
for (i, n) in names.iter().enumerate() {
bytes.extend_from_slice(&(i as i32).to_le_bytes());
bytes.extend_from_slice(&fixed_string_bytes(&[n], 8, 0));
}
let file = earliest_file(path)?;
raw_typed(&file, "data", dt, &[3], &bytes)?;
file.close()?;
Ok(Ok(()))
}
"compound_padded" => {
let dt = DatatypeMessage::Compound {
size: 12,
members: vec![
member("a", 0, DatatypeMessage::i16_type()),
member("b", 4, DatatypeMessage::i32_type()),
],
};
let mut bytes = Vec::new();
for i in 0..4i32 {
bytes.extend_from_slice(&(i as i16).to_le_bytes());
bytes.extend_from_slice(&[0, 0]);
bytes.extend_from_slice(&(1000 + i).to_le_bytes());
bytes.extend_from_slice(&[0, 0, 0, 0]);
}
let file = earliest_file(path)?;
raw_typed(&file, "data", dt, &[4], &bytes)?;
file.close()?;
Ok(Ok(()))
}
"compound_dtype_v4" => {
let dt = DatatypeMessage::Compound {
size: 8,
members: vec![
member("x", 0, DatatypeMessage::f32_type()),
member("y", 4, DatatypeMessage::f32_type()),
],
};
let mut bytes = Vec::new();
for i in 0..4u32 {
bytes.extend_from_slice(&(i as f32).to_le_bytes());
bytes.extend_from_slice(&((100 + i) as f32).to_le_bytes());
}
let file = H5File::create(path)?;
file.set_libver_bound(LibverBound::V112)?;
let ds = file
.new_dataset::<u8>()
.datatype(dt)
.shape([4usize])
.chunk(&[4])
.create("data")?;
ds.write_raw_bytes(&bytes)?;
file.close()?;
Ok(Ok(()))
}
"array_dtype" => {
let dt = DatatypeMessage::Array {
dims: vec![2, 3],
base: Box::new(DatatypeMessage::f64_type()),
};
let bytes: Vec<u8> = (0..12u64).flat_map(|i| (i as f64).to_le_bytes()).collect();
let file = earliest_file(path)?;
lowlevel_creation(&file)?;
raw_typed(&file, "data", dt, &[2], &bytes)?;
file.close()?;
Ok(Ok(()))
}
"enum_i8" => {
let dt = DatatypeMessage::Enum {
base: Box::new(DatatypeMessage::i8_type()),
members: vec![
EnumMember {
name: "BLUE".into(),
value: vec![2],
},
EnumMember {
name: "GREEN".into(),
value: vec![1],
},
EnumMember {
name: "RED".into(),
value: vec![0],
},
],
};
let file = earliest_file(path)?;
raw_typed(&file, "data", dt, &[4], &[0u8, 1, 2, 1])?;
file.close()?;
Ok(Ok(()))
}
"enum_i32" => {
let dt = DatatypeMessage::Enum {
base: Box::new(DatatypeMessage::i32_type()),
members: vec![
EnumMember {
name: "HIGH".into(),
value: 1000i32.to_le_bytes().to_vec(),
},
EnumMember {
name: "LOW".into(),
value: (-1i32).to_le_bytes().to_vec(),
},
EnumMember {
name: "MID".into(),
value: 0i32.to_le_bytes().to_vec(),
},
],
};
let mut bytes = Vec::new();
for v in [-1i32, 0, 1000, 0] {
bytes.extend_from_slice(&v.to_le_bytes());
}
let file = earliest_file(path)?;
raw_typed(&file, "data", dt, &[4], &bytes)?;
file.close()?;
Ok(Ok(()))
}
"vlen_bytes" => {
let file = earliest_file(path)?;
let a: &[u8] = &[0, 1, 2];
let b: &[u8] = &[];
let c: &[u8] = &[255];
file.write_vlen_bytes("data", &[a, b, c])?;
file.close()?;
Ok(Ok(()))
}
"vlen_numeric" => {
let file = earliest_file(path)?;
let a: &[i32] = &[1, 2, 3];
let b: &[i32] = &[];
let c: &[i32] = &[-7];
file.write_vlen_numeric("data", &[a, b, c])?;
file.close()?;
Ok(Ok(()))
}
"named_datatype" => {
let file = earliest_file(path)?;
file.commit_datatype("t", DatatypeMessage::i32_type())?;
file.new_dataset::<i32>()
.shape([8usize])
.create("data")?
.write_raw(&ramp_n::<i32>(8))?;
lowlevel_creation(&file)?;
file.new_dataset::<i32>()
.committed_type("t")
.shape([8usize])
.create("shared")?
.write_raw(&ramp_n::<i32>(8))?;
file.close()?;
Ok(Ok(()))
}
"opaque" => {
let file = earliest_file(path)?;
lowlevel_creation(&file)?;
let bytes: Vec<u8> = (0u8..12).collect();
raw_typed(
&file,
"data",
DatatypeMessage::Opaque {
size: 4,
tag: "raw4".into(),
},
&[3],
&bytes,
)?;
file.close()?;
Ok(Ok(()))
}
"bitfield" => {
let file = earliest_file(path)?;
lowlevel_creation(&file)?;
raw_typed(
&file,
"data",
DatatypeMessage::BitField {
size: 1,
byte_order: ByteOrder::LittleEndian,
bit_offset: 0,
bit_precision: 8,
},
&[4],
&[0x01, 0x80, 0xFF, 0x00],
)?;
file.close()?;
Ok(Ok(()))
}
"ref_object" => {
let file = earliest_file(path)?;
let target = file.new_dataset::<i32>().shape([8]).create("target")?;
target.write_raw(&ramp_n::<i32>(8))?;
file.create_group("grp")?;
let refs = file
.new_dataset::<u64>()
.object_references()
.shape([2])
.create("refs")?;
refs.write_object_references(&["/target", "/grp"])?;
file.close()?;
Ok(Ok(()))
}
"ref_region" => {
let file = earliest_file(path)?;
let target = file.new_dataset::<i32>().shape([8]).create("target")?;
target.write_raw(&ramp_n::<i32>(8))?;
let refs = file
.new_dataset::<u64>()
.region_references()
.shape([2])
.create("refs")?;
let slice = |start: u64, end: u64| Selection::Hyperslab {
rank: 1,
form: Hyperslab::Blocks(vec![HyperslabBlock {
start: vec![start],
end: vec![end],
}]),
};
refs.write_region_references(&[("/target", slice(0, 2)), ("/target", slice(4, 7))])?;
file.close()?;
Ok(Ok(()))
}
"layout_contiguous" => simple_ramp::<i32>(path, ramp_n::<i32>(16)),
"layout_compact" => {
let file = earliest_file(path)?;
lowlevel_creation(&file)?;
let ds = file
.new_dataset::<i32>()
.shape([16usize])
.compact()
.create("data")?;
ds.write_raw(&ramp_n::<i32>(16))?;
file.close()?;
Ok(Ok(()))
}
"external_storage" => {
let raw = format!(
"{}_ext.raw",
std::path::Path::new(path)
.file_stem()
.unwrap_or_default()
.to_string_lossy()
);
let file = earliest_file(path)?;
let ds = file
.new_dataset::<i32>()
.shape([16usize])
.external(&[(raw.as_str(), 0, 64)])
.create("data")?;
ds.write_raw(&ramp_n::<i32>(16))?;
file.close()?;
Ok(Ok(()))
}
"vds" => {
let src_name = format!(
"{}_src.h5",
std::path::Path::new(path)
.file_stem()
.unwrap_or_default()
.to_string_lossy()
);
let src_path = std::path::Path::new(path).with_file_name(&src_name);
let src = earliest_file(src_path.to_string_lossy().as_ref())?;
src.new_dataset::<i32>()
.shape([16usize])
.create("src")?
.write_raw(&ramp_n::<i32>(16))?;
src.close()?;
let file = earliest_file(path)?;
lowlevel_creation(&file)?;
file.new_dataset::<i32>()
.shape([16usize])
.virtual_mapping(Selection::All, &src_name, "src", Selection::All)
.create("vds")?;
file.close()?;
Ok(Ok(()))
}
"external_unlimited" => {
use rust_hdf5::format::messages::external_file_list::UNLIMITED;
let raw = format!(
"{}_ext.raw",
std::path::Path::new(path)
.file_stem()
.unwrap_or_default()
.to_string_lossy()
);
let file = earliest_file(path)?;
let ds = file
.new_dataset::<i32>()
.shape([16usize])
.max_shape(&[None])
.external(&[(raw.as_str(), 0, UNLIMITED)])
.create("data")?;
ds.write_raw(&ramp_n::<i32>(16))?;
file.close()?;
Ok(Ok(()))
}
"vds_unlim" => {
let src_name = format!(
"{}_src.h5",
std::path::Path::new(path)
.file_stem()
.unwrap_or_default()
.to_string_lossy()
);
let src_path = std::path::Path::new(path).with_file_name(&src_name);
let src = earliest_file(src_path.to_string_lossy().as_ref())?;
let sds = src
.new_dataset::<i32>()
.shape([10usize, 2])
.max_shape(&[None, Some(2)])
.chunk(&[5, 2])
.create("src")?;
sds.write_raw(&ramp_n::<i32>(20))?;
src.close()?;
let unlim = || Selection::Hyperslab {
rank: 2,
form: Hyperslab::Regular(rust_hdf5::RegularHyperslab {
start: vec![0, 0],
stride: vec![1, 1],
count: vec![rust_hdf5::format::selection::UNLIMITED, 1],
block: vec![1, 2],
}),
};
let file = earliest_file(path)?;
lowlevel_creation(&file)?;
file.new_dataset::<i32>()
.shape([1usize, 2])
.max_shape(&[None, Some(2)])
.virtual_mapping(unlim(), &src_name, "src", unlim())
.create("vds")?;
file.close()?;
Ok(Ok(()))
}
"vds_printf_unlim" => {
let stem = std::path::Path::new(path)
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
for b in 0..3u64 {
let block = std::path::Path::new(path).with_file_name(format!("{stem}_b{b}.h5"));
let src = earliest_file(block.to_string_lossy().as_ref())?;
src.new_dataset::<i32>()
.shape([4usize])
.create("data")?
.write_raw(&(0..4i32).map(|i| i + 10 * b as i32).collect::<Vec<_>>())?;
src.close()?;
}
let unlim_rows = Selection::Hyperslab {
rank: 2,
form: Hyperslab::Regular(rust_hdf5::RegularHyperslab {
start: vec![0, 0],
stride: vec![1, 1],
count: vec![rust_hdf5::format::selection::UNLIMITED, 1],
block: vec![1, 4],
}),
};
let file = earliest_file(path)?;
lowlevel_creation(&file)?;
file.new_dataset::<i32>()
.shape([1usize, 4])
.max_shape(&[None, Some(4)])
.virtual_mapping(
unlim_rows,
&format!("{stem}_b%b.h5"),
"data",
Selection::All,
)
.create("vds")?;
file.close()?;
Ok(Ok(()))
}
"vds_printf_gap" => {
let stem = std::path::Path::new(path)
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
for b in [0u64, 1, 3] {
let block = std::path::Path::new(path).with_file_name(format!("{stem}_b{b}.h5"));
let src = earliest_file(block.to_string_lossy().as_ref())?;
src.new_dataset::<i32>()
.shape([4usize])
.create("data")?
.write_raw(&(0..4i32).map(|i| i + 10 * b as i32).collect::<Vec<_>>())?;
src.close()?;
}
let unlim_rows = Selection::Hyperslab {
rank: 2,
form: Hyperslab::Regular(rust_hdf5::RegularHyperslab {
start: vec![0, 0],
stride: vec![1, 1],
count: vec![rust_hdf5::format::selection::UNLIMITED, 1],
block: vec![1, 4],
}),
};
let file = earliest_file(path)?;
lowlevel_creation(&file)?;
file.new_dataset::<i32>()
.shape([1usize, 4])
.max_shape(&[None, Some(4)])
.fill_value(-7i32)
.virtual_mapping(
unlim_rows,
&format!("{stem}_b%b.h5"),
"data",
Selection::All,
)
.create("vds")?;
file.close()?;
Ok(Ok(()))
}
"vds_split" => {
let file = earliest_file(path)?;
file.new_dataset::<i32>()
.shape([2usize, 4])
.create("src")?
.write_raw(&ramp_n::<i32>(8))?;
lowlevel_creation(&file)?;
file.new_dataset::<i32>()
.shape([4usize, 4])
.fill_value(-9i32)
.virtual_mapping(
Selection::Hyperslab {
rank: 2,
form: Hyperslab::Regular(rust_hdf5::RegularHyperslab {
start: vec![0, 0],
stride: vec![2, 1],
count: vec![2, 1],
block: vec![1, 4],
}),
},
".",
"/src",
Selection::All,
)
.create("vds")?;
file.close()?;
Ok(Ok(()))
}
"vds_view_trail" => {
let strided = || Selection::Hyperslab {
rank: 2,
form: Hyperslab::Regular(rust_hdf5::RegularHyperslab {
start: vec![0, 0],
stride: vec![3, 1],
count: vec![rust_hdf5::format::selection::UNLIMITED, 1],
block: vec![2, 2],
}),
};
let file = earliest_file(path)?;
file.new_dataset::<i32>()
.shape([3usize, 2])
.max_shape(&[None, Some(2)])
.chunk(&[1, 2])
.create("src")?
.write_raw(&ramp_n::<i32>(6))?;
lowlevel_creation(&file)?;
file.new_dataset::<i32>()
.shape([1usize, 2])
.max_shape(&[None, Some(2)])
.fill_value(-9i32)
.virtual_mapping(strided(), ".", "/src", strided())
.create("vds")?;
file.close()?;
Ok(Ok(()))
}
"layout_contiguous_v108" => layout_at_libver(path, LibverBound::V18, None),
"layout_contiguous_v110" => layout_at_libver(path, LibverBound::V110, None),
"layout_chunked_v108" => layout_at_libver(path, LibverBound::V18, Some(&[16])),
"layout_chunked_v110" => layout_at_libver(path, LibverBound::V110, Some(&[16])),
"chunkidx_btree1" => {
let file = earliest_file(path)?;
file.set_libver_latest(false)?;
let ds = file
.new_dataset::<i32>()
.shape([8usize])
.chunk(&[4])
.max_shape(&[None])
.create("data")?;
ds.write_raw(&ramp_n::<i32>(8))?;
file.close()?;
Ok(Ok(()))
}
"chunkidx_single" => chunked_ramp(path, 8, &[8], &[Some(8)]),
"chunkidx_implicit" => {
let file = H5File::create(path)?;
lowlevel_creation(&file)?;
let ds = file
.new_dataset::<i32>()
.shape([16usize])
.chunk(&[4])
.early_allocation()
.create("data")?;
ds.write_raw(&ramp_n::<i32>(16))?;
file.close()?;
Ok(Ok(()))
}
"chunkidx_farray" => chunked_ramp(path, 16, &[4], &[Some(16)]),
"chunkidx_earray" => chunked_ramp(path, 16, &[4], &[None]),
"chunkidx_earray_unlim_inner" => {
let file = H5File::create(path)?;
let ds = file
.new_dataset::<i32>()
.shape([4usize, 4])
.chunk(&[2, 2])
.max_shape(&[Some(4), None])
.create("data")?;
ds.write_raw(&ramp_n::<i32>(16))?;
file.close()?;
Ok(Ok(()))
}
"chunkidx_earray_dim1" => {
let file = H5File::create(path)?;
let ds = file
.new_dataset::<i32>()
.shape([4usize, 4])
.chunk(&[2, 4])
.max_shape(&[Some(4), None])
.create("data")?;
ds.write_raw(&ramp_n::<i32>(16))?;
file.close()?;
Ok(Ok(()))
}
"chunkidx_btree2" => {
let file = H5File::create(path)?;
let ds = file
.new_dataset::<i32>()
.shape([4usize, 4])
.chunk(&[2, 2])
.max_shape(&[None, None])
.create("data")?;
ds.write_raw(&ramp_n::<i32>(16))?;
file.close()?;
Ok(Ok(()))
}
"filter_deflate" => filtered(path, |b| b.deflate(6)),
"filter_shuffle" => filtered(path, |b| b.shuffle()),
"filter_deflate_shuffle" => filtered(path, |b| b.shuffle_deflate(6)),
"filter_fletcher32" => filtered(path, |b| {
b.filter_pipeline(FilterPipeline {
filters: vec![Filter {
id: FILTER_FLETCHER32,
flags: FLAG_MANDATORY,
cd_values: vec![],
}],
})
}),
"filter_szip_ec" => filtered(path, |b| b.filter_pipeline(szip(141, 8))),
"filter_szip_nn" => filtered(path, |b| b.filter_pipeline(szip(169, 16))),
"filter_scaleoffset" => filtered(path, |b| {
b.filter_pipeline(
FilterPipeline::scaleoffset(&DatatypeMessage::i32_type(), 16, 0)
.expect("i32 is scale-offset filterable"),
)
}),
"fill_default" => {
let file = H5File::create(path)?;
file.new_dataset::<i32>()
.shape([16usize])
.chunk(&[4])
.create("data")?;
file.close()?;
Ok(Ok(()))
}
"fill_set_int" => {
let file = H5File::create(path)?;
let ds = file
.new_dataset::<i32>()
.shape([16usize])
.chunk(&[4])
.fill_value(-1i32)
.create("data")?;
ds.write_slice::<i32>(&[0], &[4], &ramp_n::<i32>(4))?;
file.close()?;
Ok(Ok(()))
}
"fill_set_float_nan" => {
let file = H5File::create(path)?;
file.new_dataset::<f64>()
.shape([16usize])
.chunk(&[4])
.fill_value(f64::NAN)
.create("data")?;
file.close()?;
Ok(Ok(()))
}
"space_scalar" => {
let file = earliest_file(path)?;
let ds = file.new_dataset::<i32>().scalar().create("data")?;
ds.write_raw(&[42i32])?;
file.close()?;
Ok(Ok(()))
}
"space_null" => {
let file = earliest_file(path)?;
file.new_dataset::<i32>().null().create("data")?;
file.close()?;
Ok(Ok(()))
}
"space_zerosized" => {
let file = earliest_file(path)?;
file.new_dataset::<i32>().shape([0usize]).create("data")?;
file.close()?;
Ok(Ok(()))
}
"space_unlimited_resized" => {
let file = H5File::create(path)?;
let ds = file
.new_dataset::<i32>()
.shape([4usize])
.chunk(&[4])
.max_shape(&[None])
.create("data")?;
ds.write_raw(&ramp_n::<i32>(4))?;
ds.extend(&[12])?;
let tail: Vec<i32> = (0..8).map(|i| i + 100).collect();
ds.write_slice::<i32>(&[4], &[8], &tail)?;
file.close()?;
Ok(Ok(()))
}
"groups_nested" => {
let file = earliest_file(path)?;
let a = file.root_group().create_group("a")?;
let b = a.create_group("b")?;
b.create_group("c")?;
b.new_dataset::<i32>()
.shape([8usize])
.create("leaf")?
.write_raw(&ramp_n::<i32>(8))?;
file.new_dataset::<i32>()
.shape([8usize])
.create("top")?
.write_raw(&ramp_n::<i32>(8))?;
file.close()?;
Ok(Ok(()))
}
"link_hard" => {
let file = earliest_file(path)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("orig")?
.write_raw(&ramp_n::<i32>(8))?;
file.root_group().link("alias", "/orig")?;
file.close()?;
Ok(Ok(()))
}
"link_soft" => {
let file = earliest_file(path)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("orig")?
.write_raw(&ramp_n::<i32>(8))?;
file.create_soft_link("alias", "/orig")?;
file.close()?;
Ok(Ok(()))
}
"link_external" => {
let target = std::path::Path::new(path).with_file_name(format!(
"{}_ext.h5",
std::path::Path::new(path)
.file_stem()
.unwrap_or_default()
.to_string_lossy()
));
let ext = earliest_file(&target)?;
ext.new_dataset::<i32>()
.shape([8usize])
.create("payload")?
.write_raw(&ramp_n::<i32>(8))?;
ext.close()?;
let file = earliest_file(path)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("orig")?
.write_raw(&ramp_n::<i32>(8))?;
file.create_external_link(
"ext",
&target.file_name().unwrap_or_default().to_string_lossy(),
"/payload",
)?;
file.close()?;
Ok(Ok(()))
}
"link_external_read" => {
let target = std::path::Path::new(path).with_file_name(format!(
"{}_data.h5",
std::path::Path::new(path)
.file_stem()
.unwrap_or_default()
.to_string_lossy()
));
let data = earliest_file(&target)?;
data.new_dataset::<f64>()
.shape([8usize])
.create("top")?
.write_raw(&(0..8).map(|i| i as f64).collect::<Vec<_>>())?;
data.root_group()
.create_group("deep")?
.new_dataset::<i16>()
.shape([8usize])
.create("inner")?
.write_raw(&ramp_n::<i16>(8))?;
data.close()?;
let name = target.file_name().unwrap_or_default().to_string_lossy();
let file = earliest_file(path)?;
file.create_external_link("direct", &name, "/top")?;
file.create_external_link("nested", &name, "/deep/inner")?;
file.create_external_link("gone_object", &name, "/absent")?;
file.create_external_link("gone_file", "no_such_file.h5", "/top")?;
file.close()?;
Ok(Ok(()))
}
"link_nonascii" => {
let file = earliest_file(path)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("데이터")?
.write_raw(&ramp_n::<i32>(8))?;
let root = file.root_group();
root.create_group("plain")?;
for parent in ["그룹", "ascii_only"] {
root.create_group(parent)?
.new_dataset::<i32>()
.shape([4usize])
.create("inner")?
.write_raw(&ramp_n::<i32>(4))?;
}
file.close()?;
Ok(Ok(()))
}
"links_dense" => {
let file = H5File::create(path)?;
file.set_track_order(true)?;
let g = file.root_group().create_group("g")?;
for i in 0..12i32 {
g.new_dataset::<i32>()
.shape([1usize])
.create(&format!("d{i:02}"))?
.write_raw(&[i])?;
}
file.close()?;
Ok(Ok(()))
}
"track_order" => {
let file = H5FileOptions::new()
.libver(LibverBound::Earliest)
.track_order(true)
.create(path)?;
file.set_track_order(false)?;
let root = file.root_group();
for name in ["zebra", "apple", "mango"] {
root.create_group(name)?;
}
for (i, key) in ["zeta", "alpha", "mu"].iter().enumerate() {
file.set_attr_numeric(key, &(i as i32))?;
}
file.set_track_order(true)?;
let g = root.create_group("g")?;
g.new_dataset::<i32>()
.shape([8usize])
.create("data")?
.write_raw(&ramp_n::<i32>(8))?;
g.set_attr_numeric("second", &2i32)?;
g.set_attr_numeric("first", &1i32)?;
file.close()?;
Ok(Ok(()))
}
"group_storage_modern_root" => {
let file = H5FileOptions::new()
.libver(LibverBound::Earliest)
.track_order(true)
.create(path)?;
file.set_track_order(false)?;
let legacy = file.root_group().create_group("legacy")?;
legacy
.new_dataset::<i32>()
.shape([8usize])
.create("a")?
.write_raw(&ramp_n::<i32>(8))?;
let inner = legacy.create_group("inner")?;
inner
.new_dataset::<i16>()
.shape([8usize])
.create("c")?
.write_raw(&ramp_n::<i16>(8))?;
file.close()?;
Ok(Ok(()))
}
"group_storage_legacy_root" => {
let file = earliest_file(path)?;
let legacy = file.root_group().create_group("legacy")?;
legacy
.new_dataset::<i32>()
.shape([8usize])
.create("a")?
.write_raw(&ramp_n::<i32>(8))?;
file.set_track_order(true)?;
let modern = file.root_group().create_group("modern")?;
file.set_track_order(false)?;
modern
.new_dataset::<f64>()
.shape([8usize])
.create("b")?
.write_raw(&(0..8).map(|i| i as f64).collect::<Vec<_>>())?;
let inner = modern.create_group("inner")?;
inner
.new_dataset::<i16>()
.shape([8usize])
.create("c")?
.write_raw(&ramp_n::<i16>(8))?;
file.close()?;
Ok(Ok(()))
}
"attr_scalar_num" => {
let file = earliest_file(path)?;
let ds = file.new_dataset::<i32>().shape([8usize]).create("data")?;
ds.write_raw(&ramp_n::<i32>(8))?;
ds.new_attr::<f64>()
.shape(())
.create("gain")?
.write_numeric(&2.5f64)?;
ds.new_attr::<i32>()
.shape(())
.create("count")?
.write_numeric(&7i32)?;
file.close()?;
Ok(Ok(()))
}
"attr_array_num" => {
let file = earliest_file(path)?;
let ds = file.new_dataset::<i32>().shape([8usize]).create("data")?;
ds.write_raw(&ramp_n::<i32>(8))?;
ds.new_attr::<i32>()
.shape([4usize])
.create("offsets")?
.write_array(&ramp_n::<i32>(4))?;
let matrix: Vec<f64> = (0..6).map(|i| i as f64).collect();
ds.new_attr::<f64>()
.shape([2usize, 3])
.create("matrix")?
.write_array(&matrix)?;
file.close()?;
Ok(Ok(()))
}
"attr_ref_object" => {
let file = earliest_file(path)?;
let ds = file.new_dataset::<i32>().shape([8usize]).create("data")?;
ds.write_raw(&ramp_n::<i32>(8))?;
let grp = file.create_group("grp")?;
ds.new_attr::<u64>()
.shape([2usize])
.create("neighbours")?
.write_object_references(&["/data", "/grp"])?;
grp.set_attr_object_reference("owner", "/data")?;
file.set_attr_object_references("entry", &["/grp", "/data"])?;
file.close()?;
Ok(Ok(()))
}
"attr_large" => {
let file = H5File::create(path)?;
let ds = file.new_dataset::<i32>().shape([8usize]).create("data")?;
ds.write_raw(&ramp_n::<i32>(8))?;
let big: Vec<i32> = (0..25600i32).collect();
ds.new_attr::<i32>()
.shape([25600usize])
.create("big")?
.write_array(&big)?;
file.close()?;
Ok(Ok(()))
}
"attr_string" => {
let file = earliest_file(path)?;
let ds = file.new_dataset::<i32>().shape([8usize]).create("data")?;
ds.write_raw(&ramp_n::<i32>(8))?;
ds.new_attr::<VarLenUnicode>()
.shape(())
.create("units")?
.write_string("volt")?;
file.root_group()
.create_group("g")?
.set_attr_string("NX_class", "NXdetector")?;
file.close()?;
Ok(Ok(()))
}
"attrs_dense" => {
let file = H5File::create(path)?;
let ds = file.new_dataset::<i32>().shape([8usize]).create("data")?;
ds.write_raw(&ramp_n::<i32>(8))?;
for i in 0..12i32 {
ds.new_attr::<i32>()
.shape(())
.create(&format!("a{i:02}"))?
.write_numeric(&i)?;
}
file.close()?;
Ok(Ok(()))
}
"attrs_dense_group" => {
let file = H5File::create(path)?;
let g = file.root_group().create_group("g")?;
for i in 0..12i32 {
g.set_attr_numeric(&format!("g{i:02}"), &i)?;
}
for i in 0..12i32 {
file.set_attr_numeric(&format!("r{i:02}"), &i)?;
}
file.new_dataset::<i32>()
.shape([8usize])
.create("data")?
.write_raw(&ramp_n::<i32>(8))?;
file.close()?;
Ok(Ok(()))
}
"attr_on_root" => {
let file = earliest_file(path)?;
file.set_attr_string("title", "root")?;
file.set_attr_numeric("version", &3i64)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("data")?
.write_raw(&ramp_n::<i32>(8))?;
file.close()?;
Ok(Ok(()))
}
"libver_earliest" => libver_case(path, LibverBound::Earliest),
"libver_v108" => libver_case(path, LibverBound::V18),
"libver_v110" => libver_case(path, LibverBound::V110),
"libver_latest" => libver_case(path, LibverBound::V200),
"fsm_persist" => {
let file = H5File::options()
.libver(LibverBound::Earliest)
.file_space(FileSpaceStrategy::FsmAggr, true, 1)
.create(path)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("data")?
.write_raw(&ramp_n::<i32>(8))?;
file.new_dataset::<i32>()
.shape([256usize])
.create("bulk")?
.write_raw(&(0..256).collect::<Vec<i32>>())?;
file.root_group().create_group("g")?;
file.close()?;
let file = H5File::open_rw(path)?;
file.delete_dataset("bulk")?;
file.new_dataset::<i32>()
.shape([8usize])
.create("appended")?
.write_raw(&ramp_n::<i32>(8))?;
file.close()?;
Ok(Ok(()))
}
"fsm_persist_page" => {
let file = H5File::options()
.libver(LibverBound::Earliest)
.file_space(FileSpaceStrategy::Page, true, 1)
.create(path)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("data")?
.write_raw(&ramp_n::<i32>(8))?;
file.new_dataset::<i32>()
.shape([256usize])
.create("bulk")?
.write_raw(&(0..256).collect::<Vec<i32>>())?;
file.root_group().create_group("g")?;
file.close()?;
let file = H5File::open_rw(path)?;
file.delete_dataset("bulk")?;
file.new_dataset::<i32>()
.shape([8usize])
.create("appended")?
.write_raw(&ramp_n::<i32>(8))?;
file.close()?;
Ok(Ok(()))
}
"fsm_page_size" => {
let file = H5File::options()
.libver(LibverBound::Earliest)
.file_space(FileSpaceStrategy::Page, true, 1)
.file_space_page_size(512)
.create(path)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("data")?
.write_raw(&ramp_n::<i32>(8))?;
file.new_dataset::<i32>()
.shape([256usize])
.create("bulk")?
.write_raw(&(0..256).collect::<Vec<i32>>())?;
file.root_group().create_group("g")?;
file.close()?;
let file = H5File::open_rw(path)?;
file.delete_dataset("bulk")?;
file.new_dataset::<i32>()
.shape([8usize])
.create("appended")?
.write_raw(&ramp_n::<i32>(8))?;
file.close()?;
Ok(Ok(()))
}
"reopen_append_earliest" => reopen_append_case(path, LibverBound::Earliest),
"reopen_append_v108" => reopen_append_case(path, LibverBound::V18),
"reopen_append_latest" => reopen_append_case(path, LibverBound::V200),
"userblock" => {
let file = H5File::options()
.libver(LibverBound::Earliest)
.userblock(512)
.create(path)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("data")?
.write_raw(&ramp_n::<i32>(8))?;
file.root_group().create_group("g")?;
file.close()?;
let prefix = b"#!/bin/sh\n# userblock\n";
let mut block = prefix.to_vec();
block.resize(511, b'#');
block.push(b'\n');
let mut fh = std::fs::OpenOptions::new().write(true).open(path)?;
std::io::Write::write_all(&mut fh, &block)?;
Ok(Ok(()))
}
"swmr_created" => {
use rust_hdf5::swmr::SwmrFileWriter;
let mut w = SwmrFileWriter::create(path)?;
let idx = w.create_streaming_dataset::<f32>("stream", &[4u64])?;
w.start_swmr()?;
for i in 0..8u32 {
let frame: Vec<u8> = (0..4u32)
.flat_map(|j| ((i * 4 + j) as f32).to_le_bytes())
.collect();
w.append_frame(idx, &frame)?;
}
w.close()?;
Ok(Ok(()))
}
"large_multi_mb" => {
let file = H5File::create(path)?;
let data: Vec<f64> = (0..512u32 * 512).map(|i| i as f64).collect();
let ds = file
.new_dataset::<f64>()
.shape([512usize, 512])
.chunk(&[64, 512])
.create("big")?;
ds.write_raw(&data)?;
file.close()?;
Ok(Ok(()))
}
"sohm_list" => sohm_file(path, 50, 40),
"sohm_btree" => sohm_file(path, 0, 0),
"sohm_list_append" => sohm_append_case(path, 50, 40),
"sohm_btree_append" => sohm_append_case(path, 0, 0),
"ochk_root" => ochk_root_file(path),
_ => Ok(unsup(&format!("no rust writer arm for case '{case}'"))),
}
}
fn sohm_file(path: &str, max_list: u16, min_btree: u16) -> rust_hdf5::Result<WriteResult> {
use rust_hdf5::format::messages::{MSG_ATTRIBUTE, MSG_DATASPACE, MSG_DATATYPE};
use rust_hdf5::format::sohm::type_flag;
let types = type_flag(MSG_DATATYPE).unwrap_or(0)
| type_flag(MSG_DATASPACE).unwrap_or(0)
| type_flag(MSG_ATTRIBUTE).unwrap_or(0);
let file = H5FileOptions::new()
.libver(LibverBound::Earliest)
.track_times(true)
.shared_messages(&[(types, 0)], max_list, min_btree)
.create(path)?;
for i in 0..4i32 {
let ds = file
.new_dataset::<i32>()
.shape([8usize])
.create(&format!("shared{i}"))?;
ds.write_raw(&(0..8i32).map(|j| i * 10 + j).collect::<Vec<_>>())?;
ds.new_attr::<f64>()
.shape([3usize])
.create("cal")?
.write_array(&[0.5f64, 1.5, 2.5])?;
}
file.commit_datatype("named_i32", DatatypeMessage::i32_type())?;
file.new_dataset::<i32>()
.committed_type("named_i32")
.shape([8usize])
.create("uses_named")?
.write_raw(&(100..108i32).collect::<Vec<_>>())?;
file.close()?;
Ok(Ok(()))
}
fn sohm_append_case(path: &str, max_list: u16, min_btree: u16) -> rust_hdf5::Result<WriteResult> {
if let Err(unsupported) = sohm_file(path, max_list, min_btree)? {
return Ok(Err(unsupported));
}
let file = H5File::open_rw(path)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("appended")?
.write_raw(&ramp_n::<i32>(8))?;
file.close()?;
Ok(Ok(()))
}
fn ochk_root_file(path: &str) -> rust_hdf5::Result<WriteResult> {
const TEXT: usize = 256;
let file = H5FileOptions::new()
.libver(LibverBound::V18)
.track_times(true)
.create(path)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("data")?
.write_raw(&ramp_n::<i32>(8))?;
for i in 0..6u8 {
let mut text = vec![b'x'; TEXT];
text[0] = b'0' + i;
text[TEXT - 1] = 0;
file.set_attr_typed(
&format!("note{i}"),
DatatypeMessage::FixedString {
size: TEXT as u32,
padding: 0,
charset: 0,
},
text,
)?;
}
file.close()?;
Ok(Ok(()))
}
fn member(name: &str, offset: u32, datatype: DatatypeMessage) -> CompoundMember {
CompoundMember {
name: name.to_string(),
offset,
datatype,
}
}
fn ramp_n<T: TryFrom<u8>>(n: u8) -> Vec<T>
where
<T as TryFrom<u8>>::Error: std::fmt::Debug,
{
(0..n)
.map(|i| T::try_from(i).expect("ramp index fits the element type"))
.collect()
}
fn earliest_file(path: impl AsRef<std::path::Path>) -> rust_hdf5::Result<H5File> {
H5File::options().libver(LibverBound::Earliest).create(path)
}
fn lowlevel_creation(file: &H5File) -> rust_hdf5::Result<()> {
file.set_track_times(true)
}
fn simple_ramp<T: rust_hdf5::H5Type>(path: &str, data: Vec<T>) -> rust_hdf5::Result<WriteResult> {
let file = earliest_file(path)?;
let ds = file.new_dataset::<T>().shape([data.len()]).create("data")?;
ds.write_raw(&data)?;
file.close()?;
Ok(Ok(()))
}
fn be_ramp(path: &str, le: DatatypeMessage, width: usize) -> rust_hdf5::Result<WriteResult> {
let file = earliest_file(path)?;
raw_typed(&file, "data", be_of(le), &[8], &be_bytes_ramp(width, 8))?;
file.close()?;
Ok(Ok(()))
}
fn chunked_ramp(
path: &str,
n: usize,
chunk: &[usize],
max: &[Option<usize>],
) -> rust_hdf5::Result<WriteResult> {
let file = H5File::create(path)?;
let ds = file
.new_dataset::<i32>()
.shape([n])
.chunk(chunk)
.max_shape(max)
.create("data")?;
ds.write_raw(&ramp_n::<i32>(n as u8))?;
file.close()?;
Ok(Ok(()))
}
fn szip(options_mask: u32, pixels_per_block: u32) -> FilterPipeline {
FilterPipeline {
filters: vec![Filter {
id: FILTER_SZIP,
flags: FLAG_OPTIONAL,
cd_values: vec![options_mask, pixels_per_block, 32, 16],
}],
}
}
fn filtered(
path: &str,
configure: impl FnOnce(
rust_hdf5::dataset::DatasetBuilder<i32>,
) -> rust_hdf5::dataset::DatasetBuilder<i32>,
) -> rust_hdf5::Result<WriteResult> {
let file = H5File::create(path)?;
let builder = file.new_dataset::<i32>().shape([64usize]).chunk(&[16]);
let ds = configure(builder).create("data")?;
let data: Vec<i32> = (0..64).collect();
ds.write_raw(&data)?;
file.close()?;
Ok(Ok(()))
}
fn libver_case(path: &str, libver: LibverBound) -> rust_hdf5::Result<WriteResult> {
let file = H5File::options().libver(libver).create(path)?;
file.new_dataset::<i32>()
.shape([8usize])
.create("data")?
.write_raw(&ramp_n::<i32>(8))?;
file.root_group().create_group("g")?;
file.close()?;
Ok(Ok(()))
}
fn reopen_append_case(path: &str, libver: LibverBound) -> rust_hdf5::Result<WriteResult> {
if let Err(unsupported) = libver_case(path, libver)? {
return Ok(Err(unsupported));
}
let file = H5File::open_rw(path)?;
file.new_dataset::<i32>()
.shape([4usize, 4])
.chunk(&[2, 4])
.create("appended")?
.write_raw(&ramp_n::<i32>(16))?;
file.close()?;
Ok(Ok(()))
}
fn layout_at_libver(
path: &str,
libver: LibverBound,
chunk: Option<&[usize]>,
) -> rust_hdf5::Result<WriteResult> {
let file = H5File::options().libver(libver).create(path)?;
let mut builder = file.new_dataset::<i32>().shape([16usize]);
if let Some(chunk) = chunk {
builder = builder.chunk(chunk);
}
builder.create("data")?.write_raw(&ramp_n::<i32>(16))?;
file.close()?;
Ok(Ok(()))
}
fn usage() -> i32 {
eprintln!(
"usage: oracle_probe dump [--virtual-view first_missing|last_available] \
[--printf-gap N] <file.h5>"
);
eprintln!(" oracle_probe write <case> <file.h5>");
64
}
fn parse_dump_args(args: &[String]) -> Option<(String, DatasetAccess)> {
let mut path = None;
let mut access = DatasetAccess::new();
let mut it = args.iter();
while let Some(arg) = it.next() {
match arg.as_str() {
"--virtual-view" => {
access = access.virtual_view(match it.next()?.as_str() {
"first_missing" => VirtualView::FirstMissing,
"last_available" => VirtualView::LastAvailable,
_ => return None,
})
}
"--printf-gap" => access = access.virtual_printf_gap(it.next()?.parse().ok()?),
_ if path.is_none() => path = Some(arg.clone()),
_ => return None,
}
}
Some((path?, access))
}
fn main() {
std::panic::set_hook(Box::new(|_| {}));
let args: Vec<String> = std::env::args().collect();
let code = match args.get(1).map(String::as_str) {
Some("dump") => match parse_dump_args(&args[2..]) {
Some((path, access)) => match dump_file(&path, access) {
Ok(text) => {
print!("{text}");
0
}
Err(e) => {
println!("!open-error\t{e}");
1
}
},
None => usage(),
},
Some("write") if args.len() == 4 => {
let outcome = guarded(|| write_case(&args[2], &args[3]));
match outcome {
Ok(Ok(Ok(()))) => 0,
Ok(Ok(Err(Unsupported(why)))) => {
println!("UNSUPPORTED-API: {why}");
2
}
Ok(Err(e)) => {
println!("WRITE-ERROR: {}", oneline(e));
1
}
Err(p) => {
println!("WRITE-PANIC: {p}");
1
}
}
}
_ => usage(),
};
std::process::exit(code);
}