Skip to main content

sva_cli/
output.rs

1// Concern: builds this CLI's subcommand JSON objects | Non-concern: the envelope they are answered in, and the query data (sva-core) | IO: (Output, fields) -> a JSON string
2
3use sva_core::json::{NONE, escape, list, num, strings};
4
5/// `target: null` is a whole-directory lint — the only mode that runs the entry-point check —
6/// so a caller can tell an empty `diagnostics` there from an empty one under a target, where
7/// that check never runs at all.
8pub fn lint_data(dir: &str, target: Option<&str>, nodes: usize) -> String {
9    let target = target.map_or_else(|| NONE.to_string(), |t| format!("\"{}\"", escape(t)));
10    format!(
11        "{{\n  \"dir\": \"{}\",\n  \"target\": {target},\n  \"nodes\": {nodes}\n  }}",
12        escape(dir)
13    )
14}
15
16/// Every key a trace declares is written, `null` where the node has no such thing — the one
17/// null convention `sva_core::NONE` states, so a caller reads a field rather than probing for it.
18pub fn trace_data(t: &crate::Traceable) -> String {
19    let d = &t.traced;
20    let text = |held: Option<&String>| {
21        held.map_or_else(|| NONE.to_string(), |held| format!("\"{}\"", escape(held)))
22    };
23    format!(
24        "{{\n  \"node\": \"{}\",\n  \"expr\": \"{}\",\n  \"ty\": \"{}\",\n  \
25         \"discrete\": {},\n  \"bpm\": {},\n  \"meter\": {},\n  \"entry\": {},\n  \
26         \"file\": {},\n  \"loop\": {},\n  \"down\": {},\n  \"up\": {}\n}}",
27        escape(&d.node),
28        escape(&d.expr),
29        escape(&d.ty),
30        text(d.discrete.as_ref()),
31        t.bpm.map_or_else(|| NONE.to_string(), num),
32        text(t.meter.as_ref()),
33        strings(&d.entry),
34        text(d.file.as_ref()),
35        d.cycle.as_ref().map_or_else(
36            || NONE.to_string(),
37            |members| format!("{{ \"order\": {} }}", strings(members))
38        ),
39        strings(&d.down),
40        list(&d.up, |u| {
41            format!(
42                "\n    {{ \"node\": \"{}\", \"expr\": \"{}\", \"ty\": \"{}\" }}",
43                escape(&u.node),
44                escape(&u.expr),
45                escape(&u.ty)
46            )
47        })
48    )
49}
50
51fn pair_list(items: &[(&str, &str)], key: &str, value: &str) -> String {
52    list(items, |(a, b)| {
53        format!(
54            "{{ \"{key}\": \"{}\", \"{value}\": \"{}\" }}",
55            escape(a),
56            escape(b)
57        )
58    })
59}
60
61pub fn builtins_data(b: &crate::Builtins) -> String {
62    let callables = list(&b.callables, |c| {
63        format!(
64            "\n    {{ \"name\": \"{}\", \"required\": {}, \"max_positional\": {}, \"named\": {}, \
65             \"required_named\": {}, \"takes_gain\": {} }}",
66            escape(c.name),
67            c.required,
68            c.max_positional,
69            strings(c.named),
70            strings(c.required_named),
71            c.takes_gain
72                .map_or_else(|| NONE.to_string(), |g| g.to_string())
73        )
74    });
75    let casts = list(&b.casts, |c| {
76        format!(
77            "\n    {{ \"name\": \"{}\", \"crossings\": {} }}",
78            escape(c.name),
79            list(&c.rows, |(from, to)| format!(
80                "{{ \"from\": \"{from}\", \"to\": \"{to}\" }}"
81            ))
82        )
83    });
84    format!(
85        "{{\n  \"callables\": {callables},\n  \"casts\": {casts},\n  \
86         \"rule_table\": {{ \"version\": {}, \"families\": {} }},\n  \
87         \"refusals\": {},\n  \"unit_suffixes\": {},\n  \"note_names\": \"{}\",\n  \
88         \"reserved\": {},\n  \"special_forms\": {},\n  \"not_supported\": {}\n}}",
89        b.table_version,
90        pair_list(b.families, "name", "duals"),
91        pair_list(b.refusals, "code", "when"),
92        pair_list(b.unit_suffixes, "suffix", "meaning"),
93        escape(b.note_names),
94        pair_list(b.reserved, "name", "note"),
95        pair_list(b.special_forms, "name", "shape"),
96        strings(b.not_supported)
97    )
98}
99
100pub fn new_data(s: &crate::Scaffolded) -> String {
101    format!(
102        "{{ \"root\": \"{}\", \"files\": {}, \"next\": {} }}",
103        escape(&s.root.display().to_string()),
104        strings(&s.files),
105        pair_list(&crate::NEXT, "command", "why")
106    )
107}
108
109pub fn help_data(page: &str) -> String {
110    format!("{{ \"help\": \"{}\" }}", escape(page))
111}
112
113pub fn version_data(name: &str, version: &str) -> String {
114    format!(
115        "{{ \"name\": \"{}\", \"version\": \"{}\" }}",
116        escape(name),
117        escape(version)
118    )
119}