use std::io::{self, Read, Write};
use std::path::Path;
use anyhow::Context;
use clap::Args;
use biodream::{Datafile, ParseResult};
#[derive(Debug, Args)]
pub struct InfoArgs {
#[arg(value_name = "FILE")]
pub path: std::path::PathBuf,
#[arg(long)]
pub json: bool,
}
pub fn run(args: &InfoArgs) -> anyhow::Result<()> {
let stdout = io::stdout();
let mut out = stdout.lock();
run_inner(&args.path, args.json, &mut out)
}
pub fn run_inner(path: &Path, json: bool, writer: &mut impl Write) -> anyhow::Result<()> {
let result = read_acq(path)?;
print_info(&result, json, writer)
}
pub(super) fn read_acq(path: &Path) -> anyhow::Result<ParseResult<Datafile>> {
read_acq_with_stdin(path, &mut io::stdin())
}
pub(super) fn read_acq_with_stdin<R: Read>(
path: &Path,
stdin: &mut R,
) -> anyhow::Result<ParseResult<Datafile>> {
if path == Path::new("-") {
let mut bytes = Vec::new();
stdin
.read_to_end(&mut bytes)
.context("failed to read from stdin")?;
biodream::read_bytes(&bytes).context("failed to parse .acq from stdin")
} else {
biodream::read_file(path).with_context(|| format!("failed to read {}", path.display()))
}
}
pub fn print_info(
result: &ParseResult<Datafile>,
json: bool,
writer: &mut impl Write,
) -> anyhow::Result<()> {
let df = &result.value;
if json {
print_json(df, writer)
} else {
write!(writer, "{}", df.summary()).context("write failed")?;
Ok(())
}
}
fn print_json(df: &Datafile, writer: &mut impl Write) -> anyhow::Result<()> {
let channels: Vec<serde_json::Value> = df
.channels()
.enumerate()
.map(|(i, ch)| {
serde_json::json!({
"index": i,
"name": ch.name,
"units": ch.units,
"samples_per_second": ch.samples_per_second,
"samples": ch.point_count,
})
})
.collect();
let byte_order = match df.metadata.byte_order {
biodream::ByteOrder::LittleEndian => "LittleEndian",
biodream::ByteOrder::BigEndian => "BigEndian",
};
let obj = serde_json::json!({
"revision": df.metadata.file_revision.0,
"version": df.metadata.file_revision.display_version(),
"compressed": df.metadata.compressed,
"byte_order": byte_order,
"samples_per_second": df.metadata.samples_per_second,
"duration_seconds": df.duration(),
"channel_count": df.channel_count(),
"marker_count": df.marker_count(),
"title": df.metadata.title.as_deref(),
"acquisition_datetime": df.metadata.acquisition_datetime.as_ref().map(std::string::ToString::to_string),
"channels": channels,
});
serde_json::to_writer_pretty(&mut *writer, &obj).context("JSON serialisation failed")?;
writeln!(writer).context("write failed")?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use biodream::{
ChannelData, FileRevision,
domain::{ByteOrder, Channel, GraphMetadata, Journal},
error::ParseResult,
};
fn make_datafile() -> ParseResult<Datafile> {
ParseResult::ok(Datafile {
metadata: GraphMetadata {
file_revision: FileRevision::new(73),
samples_per_second: 1000.0,
channel_count: 1,
byte_order: ByteOrder::LittleEndian,
compressed: false,
title: None,
acquisition_datetime: None,
max_samples_per_second: None,
},
channels: vec![Channel {
name: String::from("ECG"),
units: String::from("mV"),
samples_per_second: 1000.0,
frequency_divider: 1,
data: ChannelData::Raw(vec![0i16, 1, 2]),
point_count: 3,
}],
markers: vec![],
journal: None::<Journal>,
})
}
#[test]
fn info_human_contains_acqknowledge() {
let result = make_datafile();
let mut out = Vec::new();
print_info(&result, false, &mut out).ok();
let s = String::from_utf8(out).unwrap_or_default();
assert!(
s.contains("AcqKnowledge"),
"expected 'AcqKnowledge' in: {s}"
);
}
#[test]
fn info_json_is_valid_json() {
let result = make_datafile();
let mut out = Vec::new();
print_info(&result, true, &mut out).ok();
let s = String::from_utf8(out).unwrap_or_default();
let v: serde_json::Value = serde_json::from_str(&s).unwrap_or(serde_json::Value::Null);
assert_eq!(v.get("revision"), Some(&serde_json::json!(73)));
assert_eq!(v.get("channel_count"), Some(&serde_json::json!(1)));
}
#[test]
fn info_json_channels_array() {
let result = make_datafile();
let mut out = Vec::new();
print_info(&result, true, &mut out).ok();
let s = String::from_utf8(out).unwrap_or_default();
let v: serde_json::Value = serde_json::from_str(&s).unwrap_or(serde_json::Value::Null);
let empty = vec![];
let channels = v
.get("channels")
.and_then(|c| c.as_array())
.unwrap_or(&empty);
assert_eq!(channels.len(), 1);
assert_eq!(
channels.first().and_then(|ch| ch.get("name")),
Some(&serde_json::json!("ECG"))
);
}
}