browser_control/cli/
output.rs1use serde::Serialize;
4use std::io::Write;
5
6pub fn print_table<W: Write>(
12 out: &mut W,
13 headers: &[&str],
14 rows: &[Vec<String>],
15) -> std::io::Result<()> {
16 let ncols = headers.len();
17 let mut widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
18 for row in rows {
19 for (i, cell) in row.iter().enumerate().take(ncols) {
20 if cell.len() > widths[i] {
21 widths[i] = cell.len();
22 }
23 }
24 }
25
26 write_row(out, headers.iter().copied(), &widths)?;
27 let sep: Vec<String> = widths.iter().map(|w| "-".repeat(*w)).collect();
28 write_row(out, sep.iter().map(|s| s.as_str()), &widths)?;
29 for row in rows {
30 write_row(
31 out,
32 (0..ncols).map(|i| row.get(i).map(|s| s.as_str()).unwrap_or("")),
33 &widths,
34 )?;
35 }
36 Ok(())
37}
38
39fn write_row<'a, W: Write, I: Iterator<Item = &'a str>>(
40 out: &mut W,
41 cells: I,
42 widths: &[usize],
43) -> std::io::Result<()> {
44 let cells: Vec<&str> = cells.collect();
45 let last = cells.len().saturating_sub(1);
46 for (i, cell) in cells.iter().enumerate() {
47 if i == last {
48 write!(out, "{}", cell)?;
50 } else {
51 write!(out, "{:<width$} ", cell, width = widths[i])?;
52 }
53 }
54 writeln!(out)
55}
56
57pub fn print_json<W: Write, T: Serialize>(out: &mut W, value: &T) -> anyhow::Result<()> {
59 let s = serde_json::to_string_pretty(value)?;
60 out.write_all(s.as_bytes())?;
61 out.write_all(b"\n")?;
62 Ok(())
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn empty_rows_produces_headers_and_separator() {
71 let mut buf: Vec<u8> = Vec::new();
72 print_table(&mut buf, &["A", "BB"], &[]).unwrap();
73 let s = String::from_utf8(buf).unwrap();
74 let lines: Vec<&str> = s.lines().collect();
75 assert_eq!(lines.len(), 2, "got: {:?}", lines);
76 assert!(lines[0].starts_with("A "));
77 assert!(lines[0].contains("BB"));
78 assert!(lines[1].starts_with("-"));
79 assert!(lines[1].contains("--"));
80 }
81
82 #[test]
83 fn column_widths_grow_to_longest_cell() {
84 let mut buf: Vec<u8> = Vec::new();
85 let rows = vec![
86 vec!["short".to_string(), "x".to_string()],
87 vec!["a-very-long-cell".to_string(), "y".to_string()],
88 ];
89 print_table(&mut buf, &["K", "V"], &rows).unwrap();
90 let s = String::from_utf8(buf).unwrap();
91 let lines: Vec<&str> = s.lines().collect();
92 assert!(
95 lines[0].starts_with("K "),
96 "header pad wrong: {:?}",
97 lines[0]
98 );
99 assert!(
101 lines[1].starts_with(&"-".repeat(16)),
102 "sep wrong: {:?}",
103 lines[1]
104 );
105 assert!(lines[2].starts_with("short "));
107 assert!(lines[3].starts_with("a-very-long-cell y"));
108 }
109
110 #[test]
111 fn json_output_is_valid_json() {
112 #[derive(Serialize)]
113 struct X {
114 a: u32,
115 b: Vec<String>,
116 }
117 let mut buf: Vec<u8> = Vec::new();
118 let x = X {
119 a: 7,
120 b: vec!["hi".into(), "there".into()],
121 };
122 print_json(&mut buf, &x).unwrap();
123 let s = String::from_utf8(buf).unwrap();
124 let v: serde_json::Value = serde_json::from_str(&s).unwrap();
125 assert_eq!(v["a"], 7);
126 assert_eq!(v["b"][1], "there");
127 }
128}