Skip to main content

sim_lib_compute_cli/
render.rs

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