Skip to main content

canic_host/
table.rs

1///
2/// WhitespaceTable
3///
4
5#[derive(Clone, Debug, Eq, PartialEq)]
6pub struct WhitespaceTable {
7    headers: Vec<String>,
8    rows: Vec<Vec<String>>,
9}
10
11impl WhitespaceTable {
12    /// Build a whitespace-aligned table from header labels.
13    #[must_use]
14    pub fn new(headers: impl IntoIterator<Item = impl Into<String>>) -> Self {
15        Self {
16            headers: headers.into_iter().map(Into::into).collect(),
17            rows: Vec::new(),
18        }
19    }
20
21    /// Append one row, padding missing cells as empty strings.
22    pub fn push_row(&mut self, row: impl IntoIterator<Item = impl Into<String>>) {
23        self.rows.push(row.into_iter().map(Into::into).collect());
24    }
25
26    /// Render the table using two spaces between columns.
27    #[must_use]
28    pub fn render(&self) -> String {
29        let widths = self.column_widths();
30        let mut lines = Vec::with_capacity(self.rows.len() + 1);
31        lines.push(render_row(&self.headers, &widths));
32        lines.extend(self.rows.iter().map(|row| render_row(row, &widths)));
33        lines.join("\n")
34    }
35
36    // Compute character widths so box-drawing tree prefixes do not over-pad columns.
37    fn column_widths(&self) -> Vec<usize> {
38        (0..self.headers.len())
39            .map(|index| {
40                std::iter::once(self.headers.get(index).map_or("", String::as_str))
41                    .chain(
42                        self.rows
43                            .iter()
44                            .map(move |row| row.get(index).map_or("", String::as_str)),
45                    )
46                    .map(display_width)
47                    .max()
48                    .unwrap_or(0)
49            })
50            .collect()
51    }
52}
53
54// Render one row with precomputed column widths.
55fn render_row(row: &[String], widths: &[usize]) -> String {
56    widths
57        .iter()
58        .enumerate()
59        .map(|(index, width)| {
60            let value = row.get(index).map_or("", String::as_str);
61            format!("{value:<width$}")
62        })
63        .collect::<Vec<_>>()
64        .join("  ")
65        .trim_end()
66        .to_string()
67}
68
69// Count display width by character for simple terminal-aligned tables.
70fn display_width(value: &str) -> usize {
71    value.chars().count()
72}