///|
async fn write_stderr(message : String) -> Unit {
@stdio.stderr.write(message + "\n")
}
///|
async fn write_stdout(message : String) -> Unit {
@stdio.stdout.write(message + "\n")
}
///|
/// The usage error of a `replay` request, as a message and a hint, or `None`
/// when the request may proceed. Checked in this order: parse errors, a
/// missing artifact (with or without `--dry-run`), stdin as input, and a
/// missing `--yes` outside `--dry-run`.
fn replay_usage_error(request : CliRequest) -> (String, String)? {
match request.error {
Some(message) => return Some((message, "Try 'mare-mark replay --help'."))
None => ()
}
match request.input {
None =>
Some(
("replay requires an input artifact", "Try 'mare-mark replay --help'."),
)
Some("-") =>
Some(
(
"replay stdin is not available on this command path yet", "Use a file artifact with 'mare-mark replay --help'.",
),
)
Some(_) =>
if request.yes == false && request.dry_run == false {
Some(
(
"replay requires --yes to execute an external command", "Use --dry-run to inspect it first.",
),
)
} else {
None
}
}
}
///|
/// Formats a duration measured in microseconds as milliseconds with three
/// decimals, the resolution of the microsecond clock. Non-finite or negative
/// durations, which a monotonic clock does not produce, are printed as they are.
fn elapsed_text(elapsed_us : Double) -> String {
if elapsed_us.is_nan() || elapsed_us.is_inf() || elapsed_us < 0.0 {
return (elapsed_us / 1000.0).to_string() + "ms"
}
let micros = elapsed_us.round().to_int64()
let fraction = (micros % 1000L).to_string()
(micros / 1000L).to_string() +
"." +
"0".repeat(3 - fraction.length()) +
fraction +
"ms"
}
///|
/// The comparison table `mare-mark report` prints and whether it goes to
/// stderr: it does when the HTML is written to stdout (`-`). `None` under
/// `--quiet` or when the document has no comparisons.
fn report_comparison_output(
document : @ir.PlotDocument,
output_path : String,
quiet : Bool,
) -> (String, Bool)? {
let text = @report.comparisons_text(document)
if quiet || text.is_empty() {
None
} else {
Some((text, output_path == "-"))
}
}
///|
async fn main {
let request = parse_args(@env.args())
if request.show_help {
write_stdout(command_help(request.command))
return
}
match request.command {
Help => write_stdout(usage())
Version => write_stdout("mare-mark 0.3.0")
Unknown(name) => {
write_stderr(
"mare-mark: error: unknown command '" +
name +
"'\nTry 'mare-mark --help' for usage.",
)
@sys.exit(2)
}
Replay => {
match replay_usage_error(request) {
Some((message, hint)) => {
write_stderr("mare-mark: error: " + message + "\n" + hint)
@sys.exit(2)
}
None => ()
}
match request.input {
None => ()
Some(path) =>
match load_replay_spec(path) {
Err(message) => {
write_stderr(
"mare-mark: error: " +
message +
"\nCheck the artifact path and JSONL format.",
)
@sys.exit(1)
}
Ok(spec) => {
let display = "command: " +
spec.command +
"\narguments: [" +
spec.arguments.join(", ") +
"]\ntimeout: " +
spec.timeout_ms.to_string() +
"ms"
if request.dry_run {
write_stdout(display)
} else {
match execute_replay(spec) {
Ok(output) => write_stdout(output)
Err(message) => {
write_stderr(
"mare-mark: error: " +
message +
"\nCheck the recorded command and timeout.",
)
@sys.exit(1)
}
}
}
}
}
}
}
Report => {
if request.error is Some(message) {
write_stderr(
"mare-mark: error: " + message + "\nTry 'mare-mark report --help'.",
)
@sys.exit(2)
}
match (request.input, request.output) {
(Some(input_path), Some(output_path)) => {
let start = @bench.monotonic_clock_start()
let source = if input_path == "-" {
@stdio.stdin.read_all().text()
} else {
try @fs.read_file_to_string(input_path) catch {
_ => {
write_stderr(
"mare-mark: error: cannot read report input: " +
input_path +
"\nCheck that the file exists and is readable.",
)
@sys.exit(1)
""
}
} noraise {
source => source
}
}
let events = source
.split("\n")
.filter(line => line.trim() != "")
.count()
match
@report.document_from_jsonl(
source,
target="native",
baseline?=request.baseline,
) {
Err(message) => {
write_stderr(
"mare-mark: error: " +
message +
(if message.has_prefix("unknown baseline") {
"\nCheck the --baseline value."
} else {
"\nCheck that every input line is valid JSONL."
}),
)
@sys.exit(1)
}
Ok(document) => {
let html = @report.html(document)
let table = report_comparison_output(
document,
output_path,
request.quiet,
)
if output_path == "-" {
@stdio.stdout.write(html)
if table is Some((text, _)) {
write_stderr(text)
}
} else {
let absolute = @path.Path::resolve(output_path).to_string()
try @fs.write_string_to_file(absolute, html) catch {
_ => {
write_stderr(
"mare-mark: error: cannot write report output: " +
absolute +
"\nCheck directory permissions.",
)
@sys.exit(1)
}
} noraise {
_ => ()
}
if request.open {
ignore(@process.run("open", [absolute]))
}
if request.quiet == false {
let elapsed = @bench.monotonic_clock_end(start)
write_stdout(
"report written: " +
absolute +
"\nevents: " +
events.to_string() +
"\nelapsed: " +
elapsed_text(elapsed),
)
}
if table is Some((text, _)) {
write_stdout(text)
}
}
}
}
}
_ => {
write_stderr(
"mare-mark: error: report requires input and output paths\nTry 'mare-mark report --help'.",
)
@sys.exit(2)
}
}
}
}
}