ocpi_tariffs_cli/
print.rs

1use std::fmt::{self, Write as _};
2
3use console::{measure_text_width, style};
4use ocpi_tariffs::UnknownFields;
5
6/// A general purpose horizontal break
7const LINE: &str = "----------------------------------------------------------------";
8/// The inital amount of memory allocated for writting out the table.
9const TABLE_BUF_LEN: usize = 4096;
10
11/// Print the unknown fields of a CDR to stderr.
12pub fn unknown_cdr_fields(unknown_fields: UnknownFields) {
13    if unknown_fields.is_empty() {
14        return;
15    }
16
17    eprintln!(
18        "{}: {} Unknown fields found in the CDR",
19        style("WARN").yellow(),
20        unknown_fields.len(),
21    );
22
23    for field_name in unknown_fields {
24        eprintln!("  - {}", style(field_name).yellow());
25    }
26
27    let line = style(LINE).yellow();
28    eprintln!("{line}");
29}
30
31/// Print the unknown fields of a list of tariffs to stderr.
32pub fn unknown_tariff_fields(unknown_fields: Vec<(String, UnknownFields)>) {
33    if unknown_fields
34        .iter()
35        .all(|(_, unknown_fields)| unknown_fields.is_empty())
36    {
37        return;
38    }
39
40    let line = style(LINE).yellow();
41
42    eprintln!(
43        "{}: Unknown fields found in the tariffs",
44        style("WARN").yellow(),
45    );
46
47    for (tariff_id, unknown_fields) in unknown_fields {
48        if unknown_fields.is_empty() {
49            continue;
50        }
51
52        eprintln!(
53            "{}: {} Unknown fields found in the tariff with id: {}",
54            style("WARN").yellow(),
55            unknown_fields.len(),
56            style(tariff_id).yellow(),
57        );
58
59        for field_name in unknown_fields {
60            eprintln!("  - {}", style(field_name).yellow());
61        }
62
63        eprintln!("{line}");
64    }
65
66    eprintln!("{line}");
67}
68
69/// A helper for printing tables with fixed width cols.
70pub struct Table {
71    /// The widths given in the `header` fn.
72    widths: Vec<usize>,
73    /// The table is written into this buffer.
74    buf: String,
75}
76
77/// The config date for setting up a table column.
78pub(crate) struct Col<'a> {
79    pub(crate) label: &'a dyn fmt::Display,
80    pub(crate) width: usize,
81}
82
83impl Col<'_> {
84    pub(crate) fn empty(width: usize) -> Self {
85        Self { label: &"", width }
86    }
87}
88
89impl Table {
90    /// Print the table header and use the column widths for all the following rows.
91    pub fn header(header: &[Col<'_>]) -> Self {
92        let widths = header
93            .iter()
94            .map(|Col { label: _, width }| *width)
95            .collect::<Vec<_>>();
96        let mut buf = String::with_capacity(TABLE_BUF_LEN);
97        let labels = header
98            .iter()
99            .map(|Col { label, width: _ }| *label)
100            .collect::<Vec<_>>();
101
102        print_table_line(&mut buf, &widths);
103        print_table_row(&mut buf, &widths, &labels);
104        print_table_line(&mut buf, &widths);
105
106        Self { widths, buf }
107    }
108
109    /// Print a separating line.
110    pub fn print_line(&mut self) {
111        print_table_line(&mut self.buf, &self.widths);
112    }
113
114    /// Print a single row of values.
115    pub fn print_row(&mut self, values: &[&dyn fmt::Display]) {
116        print_table_row(&mut self.buf, &self.widths, values);
117    }
118
119    /// Print a single row with a label stylized based of the validity.
120    ///
121    /// If the row represents a valid value, the label is colored green.
122    /// Otherwise the lable is colored red.
123    pub fn print_valid_row(
124        &mut self,
125        is_valid: bool,
126        label: &'static str,
127        values: &[&dyn fmt::Display],
128    ) {
129        let label = if is_valid {
130            style(label).green()
131        } else {
132            style(label).red()
133        };
134        print_table_row_with_label(&mut self.buf, &self.widths, &label, values);
135    }
136
137    /// Print the bottom line of the table and return the buffer for printing.
138    pub fn finish(self) -> String {
139        let Self { widths, mut buf } = self;
140        print_table_line(&mut buf, &widths);
141        buf
142    }
143}
144
145/// Just like the std lib `write!` macro except that it suppresses in `fmt::Result`.
146///
147/// This should only be used if you are in control of the buffer you're writing to
148/// and the only way it can fail is if the OS allocator fails.
149///
150/// See: <https://doc.rust-lang.org/std/io/trait.Write.html#method.write_fmt>
151#[macro_export]
152macro_rules! write_or {
153    ($dst:expr, $($arg:tt)*) => {{
154        let _ignore_result = $dst.write_fmt(std::format_args!($($arg)*));
155    }};
156}
157
158/// Print a separation line for a table.
159fn print_table_line(buf: &mut String, widths: &[usize]) {
160    write_or!(buf, "+");
161
162    for width in widths {
163        write_or!(buf, "{0:->1$}+", "", width + 2);
164    }
165
166    write_or!(buf, "\n");
167}
168
169/// Print a single row to the buffer with a label.
170fn print_table_row(buf: &mut String, widths: &[usize], values: &[&dyn fmt::Display]) {
171    assert_eq!(
172        widths.len(),
173        values.len(),
174        "The widths and values amounts should be the same"
175    );
176    print_table_row_(buf, widths, values, None);
177}
178
179/// Print a single row to the buffer with a distinct label.
180///
181/// This fn is used to create a row with a stylized label.
182fn print_table_row_with_label(
183    buf: &mut String,
184    widths: &[usize],
185    label: &dyn fmt::Display,
186    values: &[&dyn fmt::Display],
187) {
188    print_table_row_(buf, widths, values, Some(label));
189}
190
191/// Print a single row to the buffer.
192fn print_table_row_(
193    buf: &mut String,
194    widths: &[usize],
195    values: &[&dyn fmt::Display],
196    label: Option<&dyn fmt::Display>,
197) {
198    write_or!(buf, "|");
199
200    if let Some(label) = label {
201        let mut widths = widths.iter();
202        let Some(width) = widths.next() else {
203            return;
204        };
205        print_col(buf, label, *width);
206
207        for (value, width) in values.iter().zip(widths) {
208            print_col(buf, *value, *width);
209        }
210    } else {
211        for (value, width) in values.iter().zip(widths) {
212            print_col(buf, *value, *width);
213        }
214    }
215
216    write_or!(buf, "\n");
217}
218
219/// Print a single column to the buffer
220fn print_col(buf: &mut String, value: &dyn fmt::Display, width: usize) {
221    write_or!(buf, " ");
222
223    // The value could contain ANSI escape codes and the `Display` impl of the type
224    // may not implement fill and alignment logic. So we need to implement left-aligned text ourselves.
225    let len_before = buf.len();
226    write_or!(buf, "{value}");
227    let len_after = buf.len();
228
229    // Use the len before and after to capture the str just written.
230    // And compute it's visible len in the terminal.
231    let s = &buf[len_before..len_after];
232    let len = measure_text_width(s);
233    // calculate the padding we need to apply at the end of the str.
234    let padding = width.saturating_sub(len);
235
236    // and apply the padding
237    for _ in 0..padding {
238        write_or!(buf, " ");
239    }
240
241    write_or!(buf, " |");
242}