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