use std::fs::File;
use std::process::ExitCode;
use antares_format::{AntReader, AntRecord, Counts, Manifest, SUPPORTED_FORMAT_VERSION};
const EX_OK: u8 = 0;
const EX_USAGE: u8 = 64;
const EX_DATAERR: u8 = 65;
const EX_NOINPUT: u8 = 66;
const USAGE: &str =
"usage:\n openantares validate <file.ant> [file2.ant ...]\n openantares info <file.ant>";
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().collect();
let code = match args.get(1).map(String::as_str) {
Some("validate") if args.len() >= 3 => validate_cmd(&args[2..]),
Some("info") if args.len() == 3 => info_cmd(&args[2]),
_ => {
eprintln!("{USAGE}");
EX_USAGE
}
};
ExitCode::from(code)
}
fn read_all(path: &str) -> Result<(Manifest, Counts, usize, bool), (u8, String)> {
let f = File::open(path).map_err(|e| (EX_NOINPUT, format!("cannot open: {e}")))?;
let mut reader = AntReader::new(f).map_err(|e| (EX_DATAERR, e.to_string()))?;
let mut records = 0usize;
let mut counts = Counts::default();
loop {
match reader.next_record() {
Ok(Some(rec)) => {
records += 1;
match &rec {
AntRecord::SchemaType { .. } => counts.schema_types += 1,
AntRecord::Vertex { .. } => counts.vertices += 1,
AntRecord::Edge { .. } => counts.edges += 1,
AntRecord::Observation { .. } => counts.observations += 1,
AntRecord::Evidence { .. } => counts.evidence += 1,
AntRecord::Belief { .. } => counts.beliefs += 1,
AntRecord::Vector { .. } => counts.vectors += 1,
AntRecord::VertexTombstone { .. } => counts.vertex_tombstones += 1,
AntRecord::EdgeTombstone { .. } => counts.edge_tombstones += 1,
AntRecord::Manifest(_) | AntRecord::Trailer { .. } => {}
}
}
Ok(None) => break,
Err(e) => return Err((EX_DATAERR, e.to_string())),
}
}
assert!(reader.verified, "reader finished without verification");
Ok((reader.manifest, counts, records, reader.minor_ahead))
}
fn validate_cmd(files: &[String]) -> u8 {
let mut rc = EX_OK;
for path in files {
match read_all(path) {
Ok((manifest, _counts, records, minor_ahead)) => {
let ahead = if minor_ahead {
" (file minor version is ahead of this reader; unknown record kinds were skipped)"
} else {
""
};
println!(
"{path}: OK version={} records={records}{ahead}",
manifest.version
);
}
Err((code, msg)) => {
eprintln!("{path}: FAIL {msg}");
rc = rc.max(code);
}
}
}
rc
}
fn info_cmd(path: &str) -> u8 {
match read_all(path) {
Ok((manifest, counts, records, minor_ahead)) => {
println!("file: {path}");
println!("format: {}", manifest.format);
println!(
"format version: {} (this build reads/writes {SUPPORTED_FORMAT_VERSION}.x{})",
manifest.version,
if minor_ahead {
"; file is minor-ahead"
} else {
""
}
);
println!("tenant id: {}", manifest.tenant_id);
println!("project id: {}", manifest.project_id);
if let Some(p) = &manifest.producer {
println!("producer: {p}");
}
if let Some(t) = &manifest.created_at {
println!("created at: {t}");
}
if let Some(s) = &manifest.selection {
println!("selection: {s}");
}
println!("records: {records}");
print_counts(&counts);
EX_OK
}
Err((code, msg)) => {
eprintln!("{path}: FAIL {msg}");
code
}
}
}
fn print_counts(c: &Counts) {
println!("counts:");
println!(" schema types: {}", c.schema_types);
println!(" vertices: {}", c.vertices);
println!(" edges: {}", c.edges);
println!(" observations: {}", c.observations);
println!(" evidence: {}", c.evidence);
println!(" beliefs: {}", c.beliefs);
println!(" vectors: {}", c.vectors);
println!(" vertex tombstones: {}", c.vertex_tombstones);
println!(" edge tombstones: {}", c.edge_tombstones);
}