Skip to main content

canic_host/
table.rs

1const COLUMN_GAP: &str = "   ";
2
3///
4/// ColumnAlign
5///
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum ColumnAlign {
9    Left,
10    Right,
11}
12
13/// Render a whitespace-aligned table with an underlined header row.
14#[must_use]
15pub fn render_table<const N: usize>(
16    headers: &[&str; N],
17    rows: &[[String; N]],
18    alignments: &[ColumnAlign; N],
19) -> String {
20    let widths = table_widths(headers, rows);
21    let mut lines = Vec::with_capacity(rows.len() + 2);
22    lines.push(render_table_row(headers, &widths, alignments));
23    lines.push(render_separator(&widths));
24    lines.extend(
25        rows.iter()
26            .map(|row| render_table_row(row, &widths, alignments)),
27    );
28    lines.join("\n")
29}
30
31/// Compute per-column display widths from headers and rows.
32#[must_use]
33pub fn table_widths<const N: usize>(headers: &[&str; N], rows: &[[String; N]]) -> [usize; N] {
34    let mut widths = headers.map(str::chars).map(Iterator::count);
35
36    for row in rows {
37        for (index, cell) in row.iter().enumerate() {
38            widths[index] = widths[index].max(cell.chars().count());
39        }
40    }
41
42    widths
43}
44
45/// Render one whitespace-aligned table row.
46#[must_use]
47pub fn render_table_row<const N: usize>(
48    row: &[impl AsRef<str>],
49    widths: &[usize; N],
50    alignments: &[ColumnAlign; N],
51) -> String {
52    widths
53        .iter()
54        .zip(alignments)
55        .enumerate()
56        .map(|(index, (width, alignment))| {
57            let value = row.get(index).map_or("", AsRef::as_ref);
58            match alignment {
59                ColumnAlign::Left => format!("{value:<width$}"),
60                ColumnAlign::Right => format!("{value:>width$}"),
61            }
62        })
63        .collect::<Vec<_>>()
64        .join(COLUMN_GAP)
65        .trim_end()
66        .to_string()
67}
68
69/// Render the underline row for a whitespace-aligned table.
70#[must_use]
71pub fn render_separator<const N: usize>(widths: &[usize; N]) -> String {
72    widths
73        .iter()
74        .map(|width| "-".repeat(*width))
75        .collect::<Vec<_>>()
76        .join(COLUMN_GAP)
77}