sim_lib_compute_cli/
render.rs1use crate::{
4 args::ComputeCommand,
5 evidence::{ProfileEvidence, ProviderEvidence, RecipeEvidence},
6};
7
8pub 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}