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        Value::List(values) => {
113            out.push('[');
114            for (index, value) in values.iter().enumerate() {
115                if index > 0 {
116                    out.push(',');
117                }
118                the_value(out, value);
119            }
120            out.push(']');
121        }
122        Value::Object(object) => {
123            let object = object.borrow();
124            let mut written = Object::new(256);
125            written.text("class", &object.class);
126            written.field("properties", |out| {
127                the_properties(out, &object.properties)
128            });
129            out.push_str(&written.finish());
130        }
131        Value::Reference(reference) => {
132            let mut written = Object::new(128);
133            written.text("name", &reference.name);
134            written.text("scope", &format!("{:?}", reference.scope));
135            written.boolean("alias", reference.alias);
136            out.push_str(&written.finish());
137        }
138    }
139}
140
141/// The properties of an object.
142fn the_properties(out: &mut String, properties: &BTreeMap<String, Property>) {
143    out.push('{');
144    for (index, (name, property)) in properties.iter().enumerate() {
145        if index > 0 {
146            out.push(',');
147        }
148        crate::json::write_text(out, name);
149        out.push(':');
150        let mut written = Object::new(128);
151        written.text("name", &property.name);
152        written.field("value", |out| the_value(out, &property.value));
153        out.push_str(&written.finish());
154    }
155    out.push('}');
156}