///|
/// Output formats supported by the command-line report adapter.
pub enum CliFormat {
Text
Markdown
Json
Csv
} derive(Debug, Eq)
///|
/// Parsed command-line options for embedding the library in small tools.
pub struct CliOptions {
mut input : String?
mut output : String?
mut signal_id : String?
mut start : Int64?
mut end : Int64?
mut format : CliFormat
mut strict : Bool
mut include_unknown : Bool
mut benchmark : Bool
mut help : Bool
} derive(Debug, Eq)
///|
/// Return conservative defaults suitable for a terminal invocation.
pub fn default_cli_options() -> CliOptions {
{
input: None,
output: None,
signal_id: None,
start: None,
end: None,
format: Text,
strict: false,
include_unknown: true,
benchmark: false,
help: false,
}
}
///|
/// Parse options without depending on an operating-system argument parser.
/// Unknown switches are intentionally ignored so applications can add their
/// own flags while reusing this package's report options.
pub fn parse_cli_options(args : Array[String]) -> CliOptions {
let result = default_cli_options()
let mut index = 0
while index < args.length() {
let arg = args[index]
if arg == "--help" || arg == "-h" {
result.help = true
} else if arg == "--json" {
result.format = Json
} else if arg == "--markdown" || arg == "-m" {
result.format = Markdown
} else if arg == "--csv" {
result.format = Csv
} else if arg == "--strict" {
result.strict = true
} else if arg == "--known-only" {
result.include_unknown = false
} else if arg == "--benchmark" {
result.benchmark = true
} else if arg == "--signal" && index + 1 < args.length() {
index += 1
result.signal_id = Some(args[index])
} else if arg == "--output" && index + 1 < args.length() {
index += 1
result.output = Some(args[index])
} else if arg == "--start" && index + 1 < args.length() {
index += 1
result.start = parse_cli_int(args[index])
} else if arg == "--end" && index + 1 < args.length() {
index += 1
result.end = parse_cli_int(args[index])
} else if arg.has_prefix("--start=") {
result.start = parse_cli_int(arg.get_view(start=8).unwrap().to_owned())
} else if arg.has_prefix("--end=") {
result.end = parse_cli_int(arg.get_view(start=6).unwrap().to_owned())
} else if arg.has_prefix("-") {
()
} else if result.input is None {
result.input = Some(arg)
}
index += 1
}
result
}
///|
fn parse_cli_int(text : String) -> Int64? {
if text.trim().to_owned() == "" {
return None
}
let mut value : Int64 = 0L
for char in text.trim() {
if !char.is_ascii_digit() {
return None
}
value = value * 10L + (char.to_int().to_int64() - 48L)
}
Some(value)
}
///|
/// Render concise command help for documentation and shell completion.
pub fn cli_usage() -> String {
[
"moonbit-vcd [options] ", " --signal restrict reports to one identifier",
" --start inclusive start time", " --end inclusive end time",
" --json emit deterministic JSON", " --markdown emit Markdown",
" --csv emit change rows", " --known-only omit unknown and high-impedance values",
" --strict enable validation diagnostics", " --benchmark run the built-in synthetic benchmark",
" -h, --help show this help",
].join("\n")
}
///|
/// Select changes according to the parsed time and signal filters.
pub fn apply_cli_window(trace : Trace, options : CliOptions) -> Trace {
let start = match options.start {
Some(value) => value
None => 0L
}
let end = match options.end {
Some(value) => value
None => trace.end_time
}
let changes = trace.changes.filter(fn(change) {
let selected_signal = match options.signal_id {
Some(id) => id == change.id
None => true
}
let selected_value = options.include_unknown || !change.value.is_unknown()
selected_signal &&
selected_value &&
change.time >= start &&
change.time <= end
})
{ header: trace.header, signals: trace.signals, changes, end_time: end }
}
///|
/// Render a trace according to CLI options.
pub fn cli_report(trace : Trace, options : CliOptions) -> String {
if options.help {
return cli_usage()
}
if options.benchmark {
return benchmark_suite_text(benchmark_suite(8, 128, 20))
}
let selected = apply_cli_window(trace, options)
match options.format {
Text =>
if options.strict {
extended_text_report(selected, default_report_options())
} else {
text_report(selected)
}
Markdown => markdown_report(selected, markdown_report_options())
Json => trace_json(selected)
Csv =>
match options.signal_id {
Some(id) => changes_csv(selected, signal_id=id)
None => changes_csv(selected)
}
}
}
///|
/// Return the selected time range in a shell-friendly representation.
pub fn cli_range(options : CliOptions) -> String {
let start = match options.start {
Some(value) => value
None => 0L
}
let end = match options.end {
Some(value) => value.to_string()
None => "*"
}
start.to_string() + ".." + end
}
///|
/// Validate the relationship between CLI bounds before a report is run.
pub fn cli_options_valid(options : CliOptions) -> Bool {
match (options.start, options.end) {
(Some(start), Some(end)) => start <= end
_ => true
}
}
///|
/// Explain an invalid range without throwing from a command-line adapter.
pub fn cli_options_diagnostic(options : CliOptions) -> String {
if cli_options_valid(options) {
"ok"
} else {
"--start must be less than or equal to --end"
}
}