Skip to main content

shellist/output/
mod.rs

1//! Output formatting: tables, JSON, CSV, stats, and trend charts.
2
3pub mod csv;
4pub mod json;
5pub mod stats;
6pub mod table;
7pub mod trend;
8
9pub use csv::format_csv;
10pub use json::format_json;
11pub use stats::{Stats, compute_stats, format_stats};
12pub use table::{TableOptions, format_table};
13pub use trend::{compute_trend, format_trend};
14
15pub(crate) const BAR_MAX: usize = 30;
16
17pub(crate) fn bar_len(count: usize, max: usize) -> usize {
18    if max == 0 {
19        0
20    } else {
21        ((count as f64 * BAR_MAX as f64) / max as f64).round() as usize
22    }
23}
24
25/// Column alignment for the shared table renderer.
26#[derive(Clone, Copy)]
27pub(crate) enum Align {
28    Left,
29    Right,
30}
31
32/// Render headers + rows into an aligned, column-padded text table.
33pub(crate) fn render_table(headers: &[String], rows: &[Vec<String>], aligns: &[Align]) -> String {
34    let cols = headers.len();
35    let mut widths = vec![0usize; cols];
36    for (i, h) in headers.iter().enumerate() {
37        widths[i] = widths[i].max(h.chars().count());
38    }
39    for row in rows {
40        for (i, width) in widths.iter_mut().enumerate() {
41            let len = row.get(i).map(|s| s.chars().count()).unwrap_or(0);
42            *width = (*width).max(len);
43        }
44    }
45
46    let pad = |cells: &[String], fill: char| -> String {
47        let mut line = String::new();
48        for i in 0..cols {
49            if i > 0 {
50                line.push_str("  ");
51            }
52            let cell = cells.get(i).map(String::as_str).unwrap_or("");
53            if fill == '-' {
54                for _ in 0..widths[i] {
55                    line.push('-');
56                }
57            } else {
58                let pad = widths[i].saturating_sub(cell.chars().count());
59                match aligns[i] {
60                    Align::Left => {
61                        line.push_str(cell);
62                        for _ in 0..pad {
63                            line.push(' ');
64                        }
65                    }
66                    Align::Right => {
67                        for _ in 0..pad {
68                            line.push(' ');
69                        }
70                        line.push_str(cell);
71                    }
72                }
73            }
74        }
75        line
76    };
77
78    let mut out = String::new();
79    out.push_str(&pad(headers, ' '));
80    out.push('\n');
81    out.push_str(&pad(&vec![String::new(); cols], '-'));
82    out.push('\n');
83    for row in rows {
84        out.push_str(&pad(row, ' '));
85        out.push('\n');
86    }
87    out
88}