1use std::fmt::{self, Write as _};
4
5use console::{measure_text_width, style};
6use ocpi_tariffs::{
7 json::{Component, Path},
8 price::TariffReport,
9 timezone, warning, Warning,
10};
11use tracing::error;
12
13const LINE: &str = "----------------------------------------------------------------";
15const TABLE_BUF_LEN: usize = 4096;
17
18pub struct Optional<T>(pub Option<T>)
20where
21 T: fmt::Display;
22
23impl<T> fmt::Display for Optional<T>
24where
25 T: fmt::Display,
26{
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match &self.0 {
29 Some(v) => fmt::Display::fmt(v, f),
30 None => f.write_str("-"),
31 }
32 }
33}
34
35pub struct DisplayPath<'a>(pub &'a Path);
41
42impl fmt::Display for DisplayPath<'_> {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(f, "{}", style('$').dim())?;
45
46 for component in self.0.components() {
47 match component {
48 Component::Member(name) => {
49 write!(f, "{}{}", style('.').dim(), style(name).cyan())?;
50 }
51 Component::Index(index) => {
52 write!(
53 f,
54 "{}{}{}",
55 style('[').dim(),
56 style(index).yellow(),
57 style(']').dim(),
58 )?;
59 }
60 }
61 }
62
63 Ok(())
64 }
65}
66
67pub fn timezone_error(error: &warning::Error<timezone::Warning>) {
69 eprintln!(
70 "{}: Unable to find timezone due to error at path `{}`: {}",
71 style("ERR").red(),
72 DisplayPath(&error.element().path),
73 error.warning()
74 );
75}
76
77pub fn warning_set<W: Warning>(feature: &'static str, warnings: &warning::Set<W>) {
83 if warnings.is_empty() {
84 return;
85 }
86
87 eprintln!(
88 "{}: {} warnings from {}",
89 style("WARN").yellow(),
90 warnings.len_warnings(),
91 feature
92 );
93
94 for group in warnings {
95 let (element, warnings) = group.to_parts();
96 for warning in warnings {
97 eprintln!(
98 " - path: {}: {}",
99 DisplayPath(&element.path),
100 style(warning).yellow()
101 );
102 }
103 }
104
105 let line = style(LINE).yellow();
106 eprintln!("{line}");
107}
108
109pub fn tariff_reports(reports: &[TariffReport]) {
111 if reports.iter().all(|report| report.warnings.is_empty()) {
112 return;
113 }
114
115 let line = style(LINE).yellow();
116
117 eprintln!("{}: warnings found in tariffs", style("WARN").yellow());
118
119 for report in reports {
120 let TariffReport { origin, warnings } = report;
121
122 if warnings.is_empty() {
123 continue;
124 }
125
126 eprintln!(
127 "{}: {} warnings from tariff with id: {}",
128 style("WARN").yellow(),
129 warnings.len(),
130 style(&origin.id).yellow(),
131 );
132
133 for (elem_path, warnings) in warnings {
134 eprintln!(" {}", DisplayPath(elem_path));
135
136 for warning in warnings {
137 eprintln!(" - {}", style(warning).yellow());
138 }
139 }
140
141 eprintln!("{line}");
142 }
143
144 eprintln!("{line}");
145}
146
147pub struct Table {
149 widths: Vec<usize>,
151
152 buf: String,
154}
155
156pub struct Col<'caller> {
158 pub label: &'caller dyn fmt::Display,
160
161 pub width: usize,
163}
164
165impl Col<'_> {
166 pub fn empty(width: usize) -> Self {
168 Self { label: &"", width }
169 }
170}
171
172impl Table {
173 pub fn header(header: &[Col<'_>]) -> Self {
175 let widths = header
176 .iter()
177 .map(|Col { label: _, width }| *width)
178 .collect::<Vec<_>>();
179 let mut buf = String::with_capacity(TABLE_BUF_LEN);
180 let labels = header
181 .iter()
182 .map(|Col { label, width: _ }| *label)
183 .collect::<Vec<_>>();
184
185 print_table_line(&mut buf, &widths);
186 print_table_row(&mut buf, &widths, &labels);
187 print_table_line(&mut buf, &widths);
188
189 Self { widths, buf }
190 }
191
192 pub fn print_line(&mut self) {
194 print_table_line(&mut self.buf, &self.widths);
195 }
196
197 pub fn print_row(&mut self, values: &[&dyn fmt::Display]) {
199 print_table_row(&mut self.buf, &self.widths, values);
200 }
201
202 pub fn print_valid_row(
207 &mut self,
208 is_valid: bool,
209 label: &'static str,
210 values: &[&dyn fmt::Display],
211 ) {
212 let label = if is_valid {
213 style(label).green()
214 } else {
215 style(label).red()
216 };
217 print_table_row_with_label(&mut self.buf, &self.widths, &label, values);
218 }
219
220 pub fn finish(self) -> String {
222 let Self { widths, mut buf } = self;
223 print_table_line(&mut buf, &widths);
224 buf
225 }
226}
227
228#[macro_export]
235macro_rules! write_or {
236 ($dst:expr, $($arg:tt)*) => {{
237 let _ignore_result = $dst.write_fmt(std::format_args!($($arg)*));
238 }};
239}
240
241fn print_table_line(buf: &mut String, widths: &[usize]) {
243 write_or!(buf, "+");
244
245 for width in widths {
246 write_or!(
247 buf,
248 "{0:->1$}+",
249 "",
250 width.checked_add(2).unwrap_or_default()
251 );
252 }
253
254 write_or!(buf, "\n");
255}
256
257fn print_table_row(buf: &mut String, widths: &[usize], values: &[&dyn fmt::Display]) {
259 assert_eq!(
260 widths.len(),
261 values.len(),
262 "The widths and values amounts should be the same"
263 );
264 print_table_row_(buf, widths, values, None);
265}
266
267fn print_table_row_with_label(
271 buf: &mut String,
272 widths: &[usize],
273 label: &dyn fmt::Display,
274 values: &[&dyn fmt::Display],
275) {
276 print_table_row_(buf, widths, values, Some(label));
277}
278
279fn print_table_row_(
281 buf: &mut String,
282 widths: &[usize],
283 values: &[&dyn fmt::Display],
284 label: Option<&dyn fmt::Display>,
285) {
286 write_or!(buf, "|");
287
288 if let Some(label) = label {
289 let mut widths = widths.iter();
290 let Some(width) = widths.next() else {
291 return;
292 };
293 print_col(buf, label, *width);
294
295 for (value, width) in values.iter().zip(widths) {
296 print_col(buf, *value, *width);
297 }
298 } else {
299 for (value, width) in values.iter().zip(widths) {
300 print_col(buf, *value, *width);
301 }
302 }
303
304 write_or!(buf, "\n");
305}
306
307fn print_col(buf: &mut String, value: &dyn fmt::Display, width: usize) {
309 write_or!(buf, " ");
310
311 let len_before = buf.len();
314 write_or!(buf, "{value}");
315 let len_after = buf.len();
316
317 let Some(s) = &buf.get(len_before..len_after) else {
320 error!("Non UTF8 values were written as a column value");
321 return;
322 };
323
324 let len = measure_text_width(s);
325 let padding = width.saturating_sub(len);
327
328 for _ in 0..padding {
330 write_or!(buf, " ");
331 }
332
333 write_or!(buf, " |");
334}