wash_cli/ctl/
output.rs

1use std::collections::HashMap;
2
3use serde_json::json;
4use term_table::{
5    row::Row,
6    table_cell::{Alignment, TableCell},
7    Table,
8};
9use wash_lib::{cli::CommandOutput, plugin::subcommand::Metadata};
10use wasmcloud_control_interface::{Host, HostInventory, Link};
11
12use crate::util::format_optional;
13
14pub fn get_hosts_output(hosts: Vec<Host>) -> CommandOutput {
15    let mut map = HashMap::new();
16    map.insert("hosts".to_string(), json!(hosts));
17    CommandOutput::new(hosts_table(hosts), map)
18}
19
20pub fn get_host_inventories_output(invs: Vec<HostInventory>) -> CommandOutput {
21    let mut map = HashMap::new();
22    map.insert("inventories".to_string(), json!(invs));
23    CommandOutput::new(host_inventories_table(invs), map)
24}
25
26pub fn get_claims_output(claims: Vec<HashMap<String, String>>) -> CommandOutput {
27    let mut map = HashMap::new();
28    map.insert("claims".to_string(), json!(claims));
29    CommandOutput::new(claims_table(claims), map)
30}
31
32pub fn links_table(mut list: Vec<Link>) -> String {
33    // Sort the list based on the `source_id` field in ascending order
34    list.sort_by(|a, b| a.source_id().cmp(b.source_id()));
35
36    let mut table = Table::new();
37    crate::util::configure_table_style(&mut table, 4);
38
39    table.add_row(Row::new(vec![
40        TableCell::new_with_alignment("Source ID", 1, Alignment::Left),
41        TableCell::new_with_alignment("Target", 1, Alignment::Left),
42        TableCell::new_with_alignment("Interface(s)", 1, Alignment::Left),
43        TableCell::new_with_alignment("Name", 1, Alignment::Left),
44    ]));
45
46    list.iter().for_each(|l| {
47        table.add_row(Row::new(vec![
48            TableCell::new_with_alignment(l.source_id().to_string(), 1, Alignment::Left),
49            TableCell::new_with_alignment(l.target().to_string(), 1, Alignment::Left),
50            TableCell::new_with_alignment(
51                format!(
52                    "{}:{}/{}",
53                    l.wit_namespace(),
54                    l.wit_package(),
55                    l.interfaces().join(",")
56                ),
57                1,
58                Alignment::Left,
59            ),
60            TableCell::new_with_alignment(l.name().to_string(), 1, Alignment::Left),
61        ]))
62    });
63
64    table.render()
65}
66
67/// Helper function to transform a Host list into a table string for printing
68pub fn hosts_table(mut hosts: Vec<Host>) -> String {
69    // Sort hosts by uptime_seconds in descending order
70    // hosts.sort_by(|a, b| b.uptime_seconds().cmp(&a.uptime_seconds()));
71    hosts.sort_by_key(|a| std::cmp::Reverse(a.uptime_seconds()));
72
73    let mut table = Table::new();
74    crate::util::configure_table_style(&mut table, 4);
75
76    table.add_row(Row::new(vec![
77        TableCell::new_with_alignment("Host ID", 2, Alignment::Left),
78        TableCell::new_with_alignment("Friendly name", 1, Alignment::Left),
79        TableCell::new_with_alignment("Uptime (seconds)", 1, Alignment::Left),
80    ]));
81
82    hosts.iter().for_each(|h| {
83        table.add_row(Row::new(vec![
84            TableCell::new_with_alignment(h.id().to_string(), 2, Alignment::Left),
85            TableCell::new_with_alignment(h.friendly_name().to_string(), 1, Alignment::Left),
86            TableCell::new_with_alignment(format!("{}", h.uptime_seconds()), 1, Alignment::Left),
87        ]))
88    });
89
90    table.render()
91}
92
93/// Helper function to transform a HostInventory into a table string for printing
94pub fn host_inventories_table(mut invs: Vec<HostInventory>) -> String {
95    let mut table = Table::new();
96    crate::util::configure_table_style(&mut table, 3);
97
98    // Sort the host inventories alphabetically by host_id
99    invs.sort_by(|a, b| a.host_id().cmp(b.host_id()));
100
101    invs.into_iter().for_each(|inv| {
102        table.add_row(Row::new(vec![
103            TableCell::new_with_alignment("Host ID", 2, Alignment::Left),
104            TableCell::new_with_alignment("Friendly name", 1, Alignment::Left),
105        ]));
106        table.add_row(Row::new(vec![
107            TableCell::new_with_alignment(inv.host_id().to_string(), 2, Alignment::Left),
108            TableCell::new_with_alignment(inv.friendly_name().to_string(), 1, Alignment::Left),
109        ]));
110
111        // Sort the labels alphabetically by key
112        let mut sorted_labels: Vec<_> = inv.labels().iter().collect();
113        sorted_labels.sort_by(|a, b| a.0.cmp(b.0));
114
115        if !sorted_labels.is_empty() {
116            table.add_row(Row::new(vec![TableCell::new_with_alignment(
117                "",
118                3,
119                Alignment::Left,
120            )]));
121            table.add_row(Row::new(vec![TableCell::new_with_alignment(
122                "Host labels",
123                1,
124                Alignment::Left,
125            )]));
126            sorted_labels.into_iter().for_each(|(k, v)| {
127                table.add_row(Row::new(vec![
128                    TableCell::new_with_alignment(k, 1, Alignment::Left),
129                    TableCell::new_with_alignment(v, 1, Alignment::Left),
130                ]))
131            });
132        } else {
133            table.add_row(Row::new(vec![TableCell::new_with_alignment(
134                "No labels present",
135                4,
136                Alignment::Center,
137            )]));
138        }
139
140        table.add_row(Row::new(vec![TableCell::new_with_alignment(
141            "",
142            4,
143            Alignment::Center,
144        )]));
145
146        // Sort the components alphabetically by name
147        let mut components = inv.components().clone();
148        components.sort_by(|a, b| a.name().cmp(&b.name()));
149
150        if !components.is_empty() {
151            table.add_row(Row::new(vec![
152                TableCell::new_with_alignment("Component ID", 1, Alignment::Left),
153                TableCell::new_with_alignment("Name", 1, Alignment::Left),
154                TableCell::new_with_alignment("Max count", 1, Alignment::Left),
155            ]));
156            components.iter().for_each(|a| {
157                let a = a.clone();
158                table.add_row(Row::new(vec![
159                    TableCell::new_with_alignment(a.id(), 1, Alignment::Left),
160                    TableCell::new_with_alignment(
161                        format_optional(a.name().map(String::from)),
162                        1,
163                        Alignment::Left,
164                    ),
165                    TableCell::new_with_alignment(a.max_instances(), 1, Alignment::Left),
166                ]))
167            });
168        } else {
169            table.add_row(Row::new(vec![TableCell::new_with_alignment(
170                "No components found",
171                4,
172                Alignment::Left,
173            )]));
174        }
175
176        table.add_row(Row::new(vec![TableCell::new_with_alignment(
177            "",
178            4,
179            Alignment::Left,
180        )]));
181
182        // Sort the providers alphabetically by name
183        let mut providers = inv.providers().clone();
184        providers.sort_by(|a, b| a.name().cmp(&b.name()));
185
186        if !providers.is_empty() {
187            table.add_row(Row::new(vec![
188                TableCell::new_with_alignment("Provider ID", 1, Alignment::Left),
189                TableCell::new_with_alignment("Name", 1, Alignment::Left),
190            ]));
191            inv.providers().iter().for_each(|p| {
192                let p = p.clone();
193                table.add_row(Row::new(vec![
194                    TableCell::new_with_alignment(p.id(), 1, Alignment::Left),
195                    TableCell::new_with_alignment(
196                        format_optional(p.name().map(String::from)),
197                        1,
198                        Alignment::Left,
199                    ),
200                ]))
201            });
202        } else {
203            table.add_row(Row::new(vec![TableCell::new_with_alignment(
204                "No providers found",
205                4,
206                Alignment::Left,
207            )]));
208        }
209        table.add_row(Row::new(vec![TableCell::new_with_alignment(
210            "",
211            4,
212            Alignment::Left,
213        )]));
214    });
215
216    table.render()
217}
218
219/// Helper function to transform a ClaimsList into a table string for printing
220pub fn claims_table(list: Vec<HashMap<String, String>>) -> String {
221    let mut table = Table::new();
222    crate::util::configure_table_style(&mut table, 2);
223
224    table.add_row(Row::new(vec![TableCell::new_with_alignment(
225        "Claims",
226        2,
227        Alignment::Center,
228    )]));
229
230    list.iter().for_each(|c| {
231        table.add_row(Row::new(vec![
232            TableCell::new_with_alignment("Issuer", 1, Alignment::Left),
233            TableCell::new_with_alignment(
234                c.get("issuer")
235                    .or_else(|| c.get("iss"))
236                    .unwrap_or(&"".to_string()),
237                1,
238                Alignment::Left,
239            ),
240        ]));
241        table.add_row(Row::new(vec![
242            TableCell::new_with_alignment("Subject", 1, Alignment::Left),
243            TableCell::new_with_alignment(
244                c.get("subject")
245                    .or_else(|| c.get("sub"))
246                    .unwrap_or(&"".to_string()),
247                1,
248                Alignment::Left,
249            ),
250        ]));
251        table.add_row(Row::new(vec![
252            TableCell::new_with_alignment("Capabilities", 1, Alignment::Left),
253            TableCell::new_with_alignment(
254                c.get("capabilities")
255                    .or_else(|| c.get("caps"))
256                    .unwrap_or(&"".to_string()),
257                1,
258                Alignment::Left,
259            ),
260        ]));
261        table.add_row(Row::new(vec![
262            TableCell::new_with_alignment("Version", 1, Alignment::Left),
263            TableCell::new_with_alignment(
264                c.get("version").unwrap_or(&"".to_string()),
265                1,
266                Alignment::Left,
267            ),
268        ]));
269        table.add_row(Row::new(vec![
270            TableCell::new_with_alignment("Revision", 1, Alignment::Left),
271            TableCell::new_with_alignment(
272                c.get("revision")
273                    .or_else(|| c.get("rev"))
274                    .unwrap_or(&"".to_string()),
275                1,
276                Alignment::Left,
277            ),
278        ]));
279        table.add_row(Row::new(vec![TableCell::new_with_alignment(
280            String::new(),
281            2,
282            Alignment::Center,
283        )]));
284    });
285
286    table.render()
287}
288
289/// Helper function to transform a list of plugin metadata into a table string for printing
290pub fn plugins_table(list: Vec<&Metadata>) -> String {
291    let mut table = Table::new();
292    crate::util::configure_table_style(&mut table, 4);
293
294    table.add_row(Row::new(vec![
295        TableCell::new_with_alignment("Name", 1, Alignment::Left),
296        TableCell::new_with_alignment("ID", 1, Alignment::Left),
297        TableCell::new_with_alignment("Version", 1, Alignment::Left),
298        TableCell::new_with_alignment("Author", 1, Alignment::Left),
299    ]));
300    list.into_iter().for_each(|metadata| {
301        table.add_row(Row::new(vec![
302            TableCell::new_with_alignment(&metadata.name, 1, Alignment::Left),
303            TableCell::new_with_alignment(&metadata.id, 1, Alignment::Left),
304            TableCell::new_with_alignment(&metadata.version, 1, Alignment::Left),
305            TableCell::new_with_alignment(&metadata.author, 1, Alignment::Left),
306        ]));
307        if !metadata.description.is_empty() {
308            table.add_row(Row::new(vec![TableCell::new_with_alignment(
309                format!("  └ {}", metadata.description),
310                4,
311                Alignment::Left,
312            )]));
313        }
314    });
315
316    table.render()
317}