Skip to main content

sim_lib_compute_cli/
render.rs

1//! Rendering for compute command evidence.
2
3use crate::{
4    acceptance::AcceptanceEvidence,
5    args::ComputeCommand,
6    evidence::{ProfileEvidence, ProviderEvidence, RecipeEvidence},
7};
8
9/// Renders command help.
10pub fn help() -> &'static str {
11    "Usage: sim compute <devices|probe|profile|explain|recipe|acceptance> [OPTIONS]\n"
12}
13
14pub(crate) fn render_providers(command: &ComputeCommand, rows: &[ProviderEvidence]) -> String {
15    if json(command) {
16        let body = rows.iter().map(provider_json).collect::<Vec<_>>().join(",");
17        format!("{{\"providers\":[{body}]}}\n")
18    } else {
19        let mut out = String::new();
20        for row in rows {
21            out.push_str(&format!(
22                "{}\t{}\t{}\t{}\n",
23                row.selector,
24                row.site,
25                if row.installed { "installed" } else { "absent" },
26                row.status
27            ));
28        }
29        out
30    }
31}
32
33pub(crate) fn render_profile(command: &ComputeCommand, evidence: &ProfileEvidence) -> String {
34    if json(command) {
35        let keys = evidence
36            .keys
37            .iter()
38            .map(|key| format!("\"{}\"", escape(key)))
39            .collect::<Vec<_>>()
40            .join(",");
41        let decision = evidence
42            .decision
43            .as_ref()
44            .map(|value| format!("\"{}\"", escape(value)))
45            .unwrap_or_else(|| "null".to_owned());
46        format!(
47            "{{\"status\":\"{}\",\"keys\":[{}],\"decision\":{}}}\n",
48            escape(&evidence.status),
49            keys,
50            decision
51        )
52    } else {
53        let mut out = format!("profile\t{}\n", evidence.status);
54        for key in &evidence.keys {
55            out.push_str(&format!("key\t{key}\n"));
56        }
57        if let Some(decision) = &evidence.decision {
58            out.push_str(&format!("decision\t{decision}\n"));
59        }
60        out
61    }
62}
63
64pub(crate) fn render_recipe(command: &ComputeCommand, evidence: &RecipeEvidence) -> String {
65    if json(command) {
66        let capabilities = evidence
67            .capabilities
68            .iter()
69            .map(|capability| format!("\"{}\"", escape(capability)))
70            .collect::<Vec<_>>()
71            .join(",");
72        format!(
73            "{{\"recipe\":\"{}\",\"route\":\"{}\",\"capabilities\":[{}]}}\n",
74            escape(&evidence.id),
75            escape(&evidence.route),
76            capabilities
77        )
78    } else {
79        format!(
80            "recipe\t{}\nroute\t{}\ncapabilities\t{}\n",
81            evidence.id,
82            evidence.route,
83            evidence.capabilities.join(",")
84        )
85    }
86}
87
88pub(crate) fn render_acceptance(evidence: &AcceptanceEvidence) -> String {
89    format!(
90        "acceptance\t{}\tschema={}\tcases={}\tartifact={}\n",
91        evidence.status, evidence.schema, evidence.cases, evidence.artifact
92    )
93}
94
95fn json(command: &ComputeCommand) -> bool {
96    match command {
97        ComputeCommand::Help => false,
98        ComputeCommand::Devices(selection) | ComputeCommand::Probe(selection) => {
99            matches!(selection.output, crate::args::OutputMode::Json)
100        }
101        ComputeCommand::Profile(request) | ComputeCommand::Explain(request) => {
102            matches!(request.output, crate::args::OutputMode::Json)
103        }
104        ComputeCommand::Recipe(request) => matches!(request.output, crate::args::OutputMode::Json),
105        ComputeCommand::Acceptance(_) => false,
106    }
107}
108
109fn provider_json(row: &ProviderEvidence) -> String {
110    let capability = row
111        .capability
112        .as_ref()
113        .map(|value| format!("\"{}\"", escape(value)))
114        .unwrap_or_else(|| "null".to_owned());
115    format!(
116        "{{\"selector\":\"{}\",\"site\":\"{}\",\"installed\":{},\"capability\":{},\"status\":\"{}\"}}",
117        escape(&row.selector),
118        escape(&row.site),
119        row.installed,
120        capability,
121        escape(&row.status)
122    )
123}
124
125fn escape(value: &str) -> String {
126    value.replace('\\', "\\\\").replace('"', "\\\"")
127}