1pub 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#[derive(Clone, Copy)]
27pub(crate) enum Align {
28 Left,
29 Right,
30}
31
32pub(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}