///|
pub enum Command {
Replay
Report
Help
Version
Unknown(String)
}
///|
pub struct CliRequest {
command : Command
input : String?
output : String?
dry_run : Bool
yes : Bool
quiet : Bool
open : Bool
/// `--baseline `: the implementation every other one is compared with.
baseline : String?
show_help : Bool
error : String?
}
///|
fn help_requested(arguments : Array[String]) -> Bool {
arguments.any(value => value == "--help" || value == "-h")
}
///|
/// Whether `value` can be the separate value of an option. Anything starting
/// with `-` is taken as the next option or as `-` (stdin/stdout), never as a
/// value; `--baseline=` passes an id that starts with `-`.
fn is_option_value(value : String) -> Bool {
!value.has_prefix("-")
}
///|
pub fn parse_args(arguments : Array[String]) -> CliRequest {
let command = if arguments.length() < 2 {
Help
} else {
match arguments[1] {
"replay" => Replay
"report" => Report
"--version" | "-V" => Version
"--help" | "-h" => Help
other => Unknown(other)
}
}
if help_requested(arguments) {
return {
command,
input: None,
output: None,
dry_run: false,
yes: false,
quiet: false,
open: false,
baseline: None,
show_help: true,
error: None,
}
}
let values : Array[String] = []
let mut dry_run = false
let mut yes = false
let mut quiet = false
let mut open = false
let mut baseline : String? = None
let mut error : String? = None
let mut i = 2
while i < arguments.length() {
let value = arguments[i]
i = i + 1
if value == "--baseline" {
if i < arguments.length() && is_option_value(arguments[i]) {
baseline = Some(arguments[i])
i = i + 1
} else if error is None {
error = Some("option '--baseline' requires an implementation id")
}
} else if value.has_prefix("--baseline=") {
let id = value.unsafe_substring(start=11, end=value.length())
if id.is_empty() {
if error is None {
error = Some("option '--baseline' requires an implementation id")
}
} else {
baseline = Some(id)
}
} else if value == "--dry-run" {
dry_run = true
} else if value == "--yes" {
yes = true
} else if value == "--quiet" {
quiet = true
} else if value == "--open" {
open = true
} else if value != "-" && value.has_prefix("-") {
if error is None {
error = Some("unknown option '" + value + "'")
}
} else {
values.push(value)
}
}
let input = if values.length() > 0 { Some(values[0]) } else { None }
let output = if values.length() > 1 { Some(values[1]) } else { None }
if values.length() > 2 && error is None {
error = Some("too many positional arguments")
}
{
command,
input,
output,
dry_run,
yes,
quiet,
open,
baseline,
show_help: false,
error,
}
}
///|
pub fn usage() -> String {
"mare-mark [options] [input] [output]\n\nCommands:\n report Convert JSONL events to HTML and print the paired comparisons\n replay Replay the command recorded in a validation_failure artifact\n\nOptions:\n -h, --help Show command help\n -V, --version Show version\n\nExamples:\n mare-mark report events.jsonl report.html\n mare-mark report --baseline scalar events.jsonl report.html\n mare-mark report - -\n mare-mark replay failure.jsonl --dry-run\n\nInput may be '-' for stdin; report output may be '-' for stdout. Replay requires the native target."
}
///|
pub fn command_help(command : Command) -> String {
match command {
Report =>
"mare-mark report [--quiet] [--open] [--baseline ] \nReads JSONL events and writes a self-contained HTML report, then prints the\npaired comparison table (to stderr when the HTML goes to stdout).\n--baseline compares every implementation with (default: the first\nimplementation of each case).\nUse --quiet to suppress progress and the table; --open opens the generated report on native."
Replay =>
"mare-mark replay [--dry-run] [--yes] \n--dry-run prints the recorded command and timeout without executing it.\nExecution requires both --yes and the native target."
_ => usage()
}
}
///|
pub fn report_html(document : @ir.PlotDocument) -> String {
@report.html(document)
}
///|
pub fn render_jsonl_report(
input_path : String,
output_path : String,
target~ : String = "unknown",
baseline? : String,
) -> Result[String, String] {
let source = try @fs.read_file_to_string(input_path) catch {
_ => return Err("cannot read report input: " + input_path)
} noraise {
source => source
}
let document = match @report.document_from_jsonl(source, target~, baseline?) {
Ok(document) => document
Err(message) => return Err(message)
}
try @fs.write_string_to_file(output_path, @report.html(document)) catch {
_ => Err("cannot write report output: " + output_path)
} noraise {
_ => Ok(output_path)
}
}
///|
pub fn load_replay_spec(path : String) -> Result[@model.ReplaySpec, String] {
let source = try @fs.read_file_to_string(path) catch {
_ => return Err("cannot read replay artifact: " + path)
} noraise {
source => source
}
replay_spec_from_jsonl(source)
}
///|
pub fn replay_spec_from_jsonl(
source : String,
) -> Result[@model.ReplaySpec, String] {
let mut line_number = 0
for line_view in source.split("\n") {
line_number += 1
let line = line_view.to_owned().trim()
if line == "" {
continue
}
let value = try @json.parse(line) catch {
_ => return Err("invalid JSONL at line " + line_number.to_string())
} noraise {
value => value
}
guard value is Object(object) else {
return Err(
"JSONL event at line " + line_number.to_string() + " is not an object",
)
}
match object.get("artifact_version") {
Some(String(version)) if version !=
@model.ArtifactVersion::V1.identifier() =>
return Err(
"unsupported artifact version '" +
version +
"' at line " +
line_number.to_string(),
)
Some(String(_)) | None => ()
Some(_) =>
return Err(
"invalid artifact version at line " + line_number.to_string(),
)
}
guard object.get("type") is Some(String("validation_failure")) else {
continue
}
guard object.get("replay_command") is Some(String(command)) else {
return Err(
"validation_failure at line " +
line_number.to_string() +
" has no replay command",
)
}
let arguments = match object.get("replay_arguments") {
Some(Array(values)) =>
values.filter_map(value => {
match value {
String(argument) => Some(argument)
_ => None
}
})
_ => []
}
let timeout_ms = match object.get("replay_timeout_ms") {
Some(Number(value, ..)) => value.to_int()
_ => 5000
}
return Ok(@model.ReplaySpec::new(command, arguments, timeout_ms~))
}
Err("no validation_failure event found")
}