Skip to main content

ahk_cli/
report.rs

1//! The report of a run, written as the JSON the notes of the runner promise.
2//!
3//! The fields are the ones of the report, in the order a reader expects
4//! them, and the values of the script are written the way their kinds spell
5//! them: a whole number as a number, a text as a text, a list as an array,
6//! and an object as the class it is and the properties it holds.
7
8use std::collections::BTreeMap;
9
10use ahk::runtime::{Property, Report, Step, Value};
11use ahk::warnings::Warning;
12
13use crate::json::Object;
14
15/// The report of a run that finished, or of one that failed.
16pub fn the_report(report: &Report, notes: &[String]) -> String {
17    let mut object = Object::new(1024);
18    object.boolean("ok", report.ok);
19    object.field("steps", |out| the_steps(out, &report.steps));
20    object.texts("output", &report.output);
21    object.field("warnings", |out| the_warnings(out, &report.warnings));
22    object.texts("notes", notes);
23    match &report.error {
24        Some(error) => object.text("error", error),
25        None => object.nothing("error"),
26    };
27    object.optional_number("error_line", report.error_line.map(i64::from));
28    object.optional_number("exit_code", report.exit_code.map(i64::from));
29    object.number("elapsed_ms", report.elapsed_ms as i64);
30    object.field("variables", |out| the_variables(out, &report.variables));
31    object.finish()
32}
33
34/// The report of a script that did not load.
35///
36/// The payload is reduced on purpose: a script that does not parse has no
37/// run, so it has no `elapsed_ms`, no `exit_code` and no variable.
38pub fn the_failure_of_the_load(error: &str, line: u32, column: u32) -> String {
39    let mut object = Object::new(256);
40    object.boolean("ok", false);
41    object.text("error", error);
42    object.number("error_line", i64::from(line));
43    object.number("column", i64::from(column));
44    // The three lists are empty, and they are written out: a report always
45    // carries them, whatever the run did.
46    object.field("steps", |out| out.push_str("[]"));
47    object.field("output", |out| out.push_str("[]"));
48    object.field("notes", |out| out.push_str("[]"));
49    object.finish()
50}
51
52/// The steps of the run.
53fn the_steps(out: &mut String, steps: &[Step]) {
54    out.push('[');
55    for (index, step) in steps.iter().enumerate() {
56        if index > 0 {
57            out.push(',');
58        }
59        let mut object = Object::new(128);
60        object.number("line", i64::from(step.line));
61        object.text("op", &step.op);
62        object.text("detail", &step.detail);
63        object.boolean("ok", step.ok);
64        object.number("elapsed_ms", step.elapsed_ms as i64);
65        out.push_str(&object.finish());
66    }
67    out.push(']');
68}
69
70/// The warnings of the script.
71fn the_warnings(out: &mut String, warnings: &[Warning]) {
72    out.push('[');
73    for (index, warning) in warnings.iter().enumerate() {
74        if index > 0 {
75            out.push(',');
76        }
77        let mut object = Object::new(128);
78        object.number("line", i64::from(warning.line));
79        object.text("message", &warning.message);
80        out.push_str(&object.finish());
81    }
82    out.push(']');
83}
84
85/// The variables the script left, in the order of their names.
86fn the_variables(out: &mut String, variables: &BTreeMap<String, Value>) {
87    out.push('{');
88    for (index, (name, value)) in variables.iter().enumerate() {
89        if index > 0 {
90            out.push(',');
91        }
92        crate::json::write_text(out, name);
93        out.push(':');
94        the_value(out, value);
95    }
96    out.push('}');
97}
98
99/// One value of the script.
100fn the_value(out: &mut String, value: &Value) {
101    match value {
102        Value::Int(number) => out.push_str(&number.to_string()),
103        Value::Float(number) => {
104            if number.is_finite() {
105                out.push_str(&number.to_string());
106            } else {
107                // The format has no spelling for an infinity or a NaN.
108                out.push_str("null");
109            }
110        }
111        Value::Text(text) => crate::json::write_text(out, text),
112        // The absent value of the script has no text of its own, and the
113        // report writes it as the empty text: a variable that has no value is
114        // not a variable of the report at all, and a value that arrives here
115        // is the absence that a call gave a parameter.
116        Value::Unset | Value::Skipped => crate::json::write_text(out, ""),
117        Value::List(values) => {
118            out.push('[');
119            for (index, value) in values.iter().enumerate() {
120                if index > 0 {
121                    out.push(',');
122                }
123                the_value(out, value);
124            }
125            out.push(']');
126        }
127        Value::Object(object) => {
128            let object = object.borrow();
129            let mut written = Object::new(256);
130            written.text("class", &object.class);
131            written.field("properties", |out| the_properties(out, &object.properties));
132            out.push_str(&written.finish());
133        }
134        Value::Reference(reference) => {
135            let mut written = Object::new(128);
136            written.text("name", &reference.name);
137            written.text("scope", &format!("{:?}", reference.scope));
138            written.boolean("alias", reference.alias);
139            out.push_str(&written.finish());
140        }
141    }
142}
143
144/// The properties of an object.
145fn the_properties(out: &mut String, properties: &BTreeMap<String, Property>) {
146    out.push('{');
147    for (index, (name, property)) in properties.iter().enumerate() {
148        if index > 0 {
149            out.push(',');
150        }
151        crate::json::write_text(out, name);
152        out.push(':');
153        let mut written = Object::new(128);
154        written.text("name", &property.name);
155        written.field("value", |out| the_value(out, &property.value));
156        out.push_str(&written.finish());
157    }
158    out.push('}');
159}