///|
fn describe_values(values : @lib.DatasetValues) -> String {
match values {
Integers(items) =>
"int[" +
items.length().to_string() +
"] " +
(if items.length() > 0 { items[0].to_string() } else { "" })
UnsignedIntegers(items) =>
"uint[" +
items.length().to_string() +
"] " +
(if items.length() > 0 { items[0].to_string() } else { "" })
Floats(items) =>
"float[" +
items.length().to_string() +
"] " +
(if items.length() > 0 { items[0].to_string() } else { "" })
Strings(items) =>
"string[" +
items.length().to_string() +
"] " +
(if items.length() > 0 { items[0] } else { "" })
Enums(items) =>
"enum[" +
items.length().to_string() +
"] " +
(if items.length() > 0 { items[0] } else { "" })
Structured(items) => "structured[" + items.length().to_string() + "]"
}
}
///|
fn kind_name(kind : @lib.Hdf5ObjectKind) -> String {
match kind {
Group => "group"
Dataset => "dataset"
Datatype => "datatype"
Unknown => "object"
}
}
///|
fn dimensions_text(dimensions : Array[UInt64]) -> String {
if dimensions.length() == 0 {
return "scalar"
}
let mut output = "["
for index in 0.. 0 {
output = output + ","
}
output = output + dimensions[index].to_string()
}
output + "]"
}
///|
fn maximum_dimensions_text(dimensions : Array[UInt64]) -> String {
if dimensions.length() == 0 {
return "scalar"
}
let mut output = "["
for index in 0.. 0 {
output = output + ","
}
output = output +
(if dimensions[index] == 0xFFFFFFFFFFFFFFFFUL {
"unlimited"
} else {
dimensions[index].to_string()
})
}
output + "]"
}
///|
fn datatype_name(datatype : @lib.Datatype) -> String {
match datatype.class {
0 => if datatype.signed { "integer" } else { "unsigned integer" }
1 => "floating point"
2 => "time"
3 => "fixed string"
4 => "bitfield"
5 => "opaque"
6 => "compound"
7 => "reference"
8 => "enum"
9 =>
if datatype.vlen_is_string {
"variable string"
} else {
"variable sequence"
}
10 => "array"
11 => "complex"
_ => "unknown"
}
}
///|
fn layout_name(layout : @lib.DatasetLayoutKind) -> String {
match layout {
Compact => "compact"
Contiguous => "contiguous"
Chunked => "chunked"
Virtual => "virtual"
Unknown => "unknown"
}
}
///|
fn print_metadata(metadata : @lib.DatasetMetadata) -> Unit {
println("shape: " + dimensions_text(metadata.dataspace.dimensions))
println(
"maximum shape: " +
maximum_dimensions_text(metadata.dataspace.maximum_dimensions),
)
println(
"datatype: " +
datatype_name(metadata.datatype) +
" (" +
metadata.datatype.size.to_string() +
" bytes)",
)
println("layout: " + layout_name(metadata.layout.kind()))
let chunk_shape = metadata.layout.chunk_shape()
if chunk_shape.length() > 0 {
println(
"chunks: " + dimensions_text(chunk_shape.map(value => value.to_uint64())),
)
}
}
///|
fn print_attributes(file : @lib.Hdf5File, path : String) -> Bool {
let attributes = file.attributes(path) catch {
error => {
eprintln("attributes " + path + ": " + error.message())
return false
}
}
let mut success = true
for attribute in attributes {
let value = match attribute.values {
Some(values) => describe_values(values)
None => {
success = false
eprintln(
"attribute " + path + ": " + attribute.name + ": unsupported datatype",
)
""
}
}
println(" attr " + attribute.name + " = " + value)
}
success
}
///|
fn read_file(path : String) -> @lib.Hdf5File? {
let bytes = @fs.read_file_to_bytes(path) catch {
_ => {
eprintln("cannot read file: " + path)
return None
}
}
Some(@lib.Hdf5File::parse(bytes)) catch {
error => {
eprintln("parse error: " + error.message())
None
}
}
}
///|
fn inspect_file(path : String) -> Int32 {
guard read_file(path) is Some(file) else { return 1 }
println(path + " (superblock v" + file.superblock_version().to_string() + ")")
let mut success = print_attributes(file, "/")
let nodes = file.walk() catch {
error => {
eprintln("parse error: " + error.message())
return 1
}
}
for node in nodes {
println(node.path + " " + kind_name(node.kind))
if node.kind == Dataset {
try {
let dataset = file.read_dataset(node.path)
println(" " + describe_values(dataset.values))
} catch {
error => {
eprintln("dataset " + node.path + ": " + error.message())
success = false
}
}
}
if !print_attributes(file, node.path) {
success = false
}
}
if success {
0
} else {
1
}
}
///|
fn metadata_command(path : String, dataset_path : String) -> Int32 {
guard read_file(path) is Some(file) else { return 1 }
try {
print_metadata(file.read_dataset_metadata(dataset_path))
0
} catch {
error => {
eprintln("dataset " + dataset_path + ": " + error.message())
1
}
}
}
///|
fn parse_uint(raw : String) -> UInt64? {
if raw.length() == 0 {
return None
}
let mut value = 0UL
for index in 0.. 57 {
return None
}
let digit = (byte - 48).to_uint64()
if value > (0xFFFFFFFFFFFFFFFFUL - digit) / 10UL {
return None
}
value = value * 10UL + digit
}
Some(value)
}
///|
fn parse_coordinate(raw : String) -> Array[UInt64]? {
if raw == "" {
return Some([])
}
let coordinate : Array[UInt64] = []
for item in raw.split(",") {
guard parse_uint(item.to_owned()) is Some(value) else { return None }
coordinate.push(value)
}
Some(coordinate)
}
///|
fn parse_points(raw : String) -> Array[Array[UInt64]]? {
if raw == "" {
return Some([])
}
let points : Array[Array[UInt64]] = []
for item in raw.split(";") {
guard parse_coordinate(item.to_owned()) is Some(point) else { return None }
points.push(point)
}
Some(points)
}
///|
fn append_preview(output : String, value : String, index : Int32) -> String {
output + (if index == 0 { "" } else { ", " }) + value
}
///|
fn preview_values(values : @lib.DatasetValues) -> String {
let mut output = "["
match values {
Integers(items) =>
for index in 0..
for index in 0..
for index in 0..
for index in 0.. return "structured[" + items.length().to_string() + "]"
}
output + "]"
}
///|
fn print_selection(result : @lib.DatasetSelectionResult) -> Unit {
println("shape: " + dimensions_text(result.dimensions))
println("values: " + preview_values(result.values))
}
///|
fn read_command(args : Array[String]) -> Int32 {
if args.length() != 7 && args.length() != 6 {
return 2
}
guard read_file(args[2]) is Some(file) else { return 1 }
let selection = if args.length() == 6 && args[4] == "--points" {
guard parse_points(args[5]) is Some(points) else {
eprintln("invalid point coordinates")
return 2
}
@lib.Points(points)
} else if args.length() == 7 {
guard parse_coordinate(args[4]) is Some(start) &&
parse_coordinate(args[5]) is Some(count) &&
parse_coordinate(args[6]) is Some(stride) else {
eprintln("invalid hyperslab coordinates")
return 2
}
@lib.Hyperslab(start, count, stride)
} else {
return 2
}
try {
print_selection(file.read_dataset_selection(args[3], selection))
0
} catch {
error => {
eprintln("dataset " + args[3] + ": " + error.message())
1
}
}
}
///|
fn float_stats(values : Array[Double]) -> String {
if values.length() == 0 {
return "count=0"
}
let mut minimum = values[0]
let mut maximum = values[0]
let mut sum = 0.0
for value in values {
if value < minimum {
minimum = value
}
if value > maximum {
maximum = value
}
sum = sum + value
}
"count=" +
values.length().to_string() +
" min=" +
minimum.to_string() +
" max=" +
maximum.to_string() +
" mean=" +
(sum / values.length().to_double()).to_string()
}
///|
fn integer_stats(values : Array[Int64]) -> String {
if values.length() == 0 {
return "count=0"
}
let mut minimum = values[0]
let mut maximum = values[0]
let mut sum = 0.0
for value in values {
if value < minimum {
minimum = value
}
if value > maximum {
maximum = value
}
sum = sum + value.to_double()
}
"count=" +
values.length().to_string() +
" min=" +
minimum.to_string() +
" max=" +
maximum.to_string() +
" mean=" +
(sum / values.length().to_double()).to_string()
}
///|
fn unsigned_stats(values : Array[UInt64]) -> String {
if values.length() == 0 {
return "count=0"
}
let mut minimum = values[0]
let mut maximum = values[0]
let mut sum = 0.0
for value in values {
if value < minimum {
minimum = value
}
if value > maximum {
maximum = value
}
sum = sum + value.to_double()
}
"count=" +
values.length().to_string() +
" min=" +
minimum.to_string() +
" max=" +
maximum.to_string() +
" mean=" +
(sum / values.length().to_double()).to_string()
}
///|
fn stats_text(values : @lib.DatasetValues) -> String {
match values {
Integers(items) => integer_stats(items)
UnsignedIntegers(items) => unsigned_stats(items)
Floats(items) => float_stats(items)
Strings(items) => {
let mut empty = 0
for item in items {
if item == "" {
empty = empty + 1
}
}
"count=" + items.length().to_string() + " empty=" + empty.to_string()
}
Enums(items) => "count=" + items.length().to_string()
Structured(items) => "count=" + items.length().to_string()
}
}
///|
fn stats_command(path : String, dataset_path : String) -> Int32 {
guard read_file(path) is Some(file) else { return 1 }
try {
let dataset = file.read_dataset(dataset_path)
println(stats_text(dataset.values))
0
} catch {
error => {
eprintln("dataset " + dataset_path + ": " + error.message())
1
}
}
}
///|
fn print_usage() -> Unit {
eprintln("usage:")
eprintln(" moon run src/cmd/main -- ")
eprintln(" moon run src/cmd/main -- inspect ")
eprintln(" moon run src/cmd/main -- metadata ")
eprintln(" moon run src/cmd/main -- stats ")
eprintln(
" moon run src/cmd/main -- read ",
)
eprintln(
" moon run src/cmd/main -- read --points ",
)
}
///|
fn run(args : Array[String]) -> Int32 {
let status = if args.length() == 2 {
inspect_file(args[1])
} else if args.length() == 3 && args[1] == "inspect" {
inspect_file(args[2])
} else if args.length() == 4 && args[1] == "metadata" {
metadata_command(args[2], args[3])
} else if args.length() == 4 && args[1] == "stats" {
stats_command(args[2], args[3])
} else if args.length() >= 2 && args[1] == "read" {
read_command(args)
} else {
2
}
if status == 2 {
print_usage()
}
status
}
///|
fn main {
@sys.exit(run(@env.args()))
}