use clap::Parser;
use glob::Pattern;
use wavetools::{
names_only, open_wave_files, write_attrs, write_names, write_signals_wave_multi, NameOptions,
SignalOutputOptions, WaveFormat, WaveHierarchy,
};
use std::path::PathBuf;
use std::process;
const VERSION: &str = concat!(
env!("CARGO_PKG_VERSION"),
" (rev ",
env!("WAVETOOLS_GIT_REV"),
")",
);
#[derive(Parser, Debug)]
#[command(name = "wavecat")]
#[command(version = VERSION)]
#[command(about = "Read and display waveform files (FST or VCD)", long_about = "\
Read and display waveform files (FST or VCD format).
Multiple files are overlayed (their signals are unioned).
Examples:
wavecat sim.fst
wavecat --names --sort sim.vcd
wavecat --names --sort clk.vcd counters.vcd
wavecat --start 100 --end 500 sim.fst
wavecat --filter '*.clk' --time-pound sim.fst
wavecat --format vcd dump.dat")]
struct Args {
#[arg(required = true)]
file: Vec<PathBuf>,
#[arg(short, long)]
start: Option<u64>,
#[arg(short, long)]
end: Option<u64>,
#[arg(short, long, conflicts_with = "attrs")]
names: bool,
#[arg(short, long, conflicts_with = "names")]
attrs: bool,
#[arg(long)]
sort: bool,
#[arg(long)]
time_pound: bool,
#[arg(long)]
no_range_space: bool,
#[arg(long, value_parser = parse_format)]
format: Option<WaveFormat>,
#[arg(short, long, action = clap::ArgAction::Append)]
filter: Vec<String>,
}
fn parse_format(s: &str) -> Result<WaveFormat, String> {
match s.to_ascii_lowercase().as_str() {
"fst" => Ok(WaveFormat::Fst),
"vcd" => Ok(WaveFormat::Vcd),
_ => Err(format!("unknown format '{}', expected 'fst' or 'vcd'", s)),
}
}
fn parse_filter_patterns(filter: &[String]) -> Result<Vec<Pattern>, String> {
filter
.iter()
.flat_map(|s| s.split_whitespace())
.map(|p| Pattern::new(p).map_err(|e| format!("Invalid glob pattern '{}': {}", p, e)))
.collect()
}
fn apply_filters(hier: &mut WaveHierarchy, patterns: &[Pattern]) {
if patterns.is_empty() {
return;
}
let tree = &hier.names;
hier.signal_map.retain(|_, info| {
info.vars.retain(|v| {
let name = tree.format_path(v.name);
patterns.iter().any(|p| p.matches(&name))
});
!info.vars.is_empty()
});
}
fn main() {
let args = Args::parse();
if let Err(e) = process_wave_file(&args) {
eprintln!("Error: {}", e);
process::exit(1);
}
}
fn process_wave_file(args: &Args) -> Result<(), String> {
let name_options = NameOptions {
no_range_space: args.no_range_space,
};
let patterns = parse_filter_patterns(&args.filter)?;
let paths: Vec<&std::path::Path> = args.file.iter().map(|p| p.as_path()).collect();
let (readers, mut hierarchy, offsets) = open_wave_files(&paths, &name_options, args.format)?;
apply_filters(&mut hierarchy, &patterns);
if args.attrs {
let mut stdout = std::io::stdout();
write_attrs(&mut stdout, &hierarchy.signal_map, &hierarchy.names, args.sort)
.map_err(|e| format!("Failed to write attrs: {}", e))?;
} else if args.names {
let names = names_only(&hierarchy.signal_map, &hierarchy.names);
let mut stdout = std::io::stdout();
write_names(&mut stdout, &names, args.sort)
.map_err(|e| format!("Failed to write names: {}", e))?;
} else {
let names = names_only(&hierarchy.signal_map, &hierarchy.names);
let output_options = SignalOutputOptions {
time_pound: args.time_pound,
sort: args.sort,
};
let mut stdout = std::io::stdout();
write_signals_wave_multi(
&mut stdout,
readers,
&offsets,
&names,
args.start.unwrap_or(0),
args.end,
&output_options,
)
.or_else(|e| {
if e.kind() == std::io::ErrorKind::BrokenPipe {
Ok(())
} else {
Err(format!("Failed to write signals: {}", e))
}
})?;
}
Ok(())
}