use std::fmt::{self, Write as _};
use console::{measure_text_width, style};
use ocpi_tariffs::{
json,
price::{self, TariffReport},
timezone, warning, Warning,
};
use tracing::error;
use crate::ObjectKind;
const LINE: &str = "----------------------------------------------------------------";
const TABLE_BUF_LEN: usize = 4096;
pub struct Optional<T>(pub Option<T>)
where
T: fmt::Display;
impl<T> fmt::Display for Optional<T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
Some(v) => fmt::Display::fmt(v, f),
None => f.write_str("-"),
}
}
}
pub fn timezone_error(error: &warning::Error<timezone::Warning>) {
eprintln!(
"{}: Unable to find timezone due to error at path `{}`: {}",
style("ERR").red(),
error.element().path(),
error.warning()
);
}
pub fn timezone_warnings(warnings: &warning::Set<timezone::Warning>) {
if warnings.is_empty() {
return;
}
eprintln!(
"{}: {} warnings from the timezone search",
style("WARN").yellow(),
warnings.len_warnings(),
);
warning_set(warnings);
}
pub fn warning_set<W: Warning>(warnings: &warning::Set<W>) {
if warnings.is_empty() {
return;
}
eprintln!(
"{}: {} warnings from the timezone search",
style("WARN").yellow(),
warnings.len_warnings(),
);
for group in warnings {
let (element, warnings) = group.to_parts();
for warning in warnings {
eprintln!(
" - path: {}: {}",
style(&element.path()).green(),
style(warning).yellow()
);
}
}
let line = style(LINE).yellow();
eprintln!("{line}");
}
pub fn unexpected_fields(object: ObjectKind, unexpected_fields: &json::UnexpectedFields<'_>) {
if unexpected_fields.is_empty() {
return;
}
eprintln!(
"{}: {} Unknown fields found in the {}",
style("WARN").yellow(),
unexpected_fields.len(),
style(object).green()
);
for field_path in unexpected_fields {
eprintln!(" - {}", style(field_path).yellow());
}
let line = style(LINE).yellow();
eprintln!("{line}");
}
pub fn cdr_warnings(warnings: &warning::Set<price::Warning>) {
if warnings.is_empty() {
return;
}
eprintln!(
"{}: {} warnings for the CDR",
style("WARN").yellow(),
warnings.len_warnings(),
);
warning_set(warnings);
}
pub fn tariff_reports(reports: &[TariffReport]) {
if reports.iter().all(|report| report.warnings.is_empty()) {
return;
}
let line = style(LINE).yellow();
eprintln!("{}: warnings found in tariffs", style("WARN").yellow(),);
for report in reports {
let TariffReport { origin, warnings } = report;
if warnings.is_empty() {
continue;
}
eprintln!(
"{}: {} warnings from tariff with id: {}",
style("WARN").yellow(),
warnings.len(),
style(&origin.id).yellow(),
);
for (elem_path, warnings) in warnings {
eprintln!(" {}", style(elem_path).green());
for warning in warnings {
eprintln!(" - {}", style(warning).yellow());
}
}
eprintln!("{line}");
}
eprintln!("{line}");
}
pub struct Table {
widths: Vec<usize>,
buf: String,
}
pub struct Col<'a> {
pub label: &'a dyn fmt::Display,
pub width: usize,
}
impl Col<'_> {
pub fn empty(width: usize) -> Self {
Self { label: &"", width }
}
}
impl Table {
pub fn header(header: &[Col<'_>]) -> Self {
let widths = header
.iter()
.map(|Col { label: _, width }| *width)
.collect::<Vec<_>>();
let mut buf = String::with_capacity(TABLE_BUF_LEN);
let labels = header
.iter()
.map(|Col { label, width: _ }| *label)
.collect::<Vec<_>>();
print_table_line(&mut buf, &widths);
print_table_row(&mut buf, &widths, &labels);
print_table_line(&mut buf, &widths);
Self { widths, buf }
}
pub fn print_line(&mut self) {
print_table_line(&mut self.buf, &self.widths);
}
pub fn print_row(&mut self, values: &[&dyn fmt::Display]) {
print_table_row(&mut self.buf, &self.widths, values);
}
pub fn print_valid_row(
&mut self,
is_valid: bool,
label: &'static str,
values: &[&dyn fmt::Display],
) {
let label = if is_valid {
style(label).green()
} else {
style(label).red()
};
print_table_row_with_label(&mut self.buf, &self.widths, &label, values);
}
pub fn finish(self) -> String {
let Self { widths, mut buf } = self;
print_table_line(&mut buf, &widths);
buf
}
}
#[macro_export]
macro_rules! write_or {
($dst:expr, $($arg:tt)*) => {{
let _ignore_result = $dst.write_fmt(std::format_args!($($arg)*));
}};
}
fn print_table_line(buf: &mut String, widths: &[usize]) {
write_or!(buf, "+");
for width in widths {
write_or!(
buf,
"{0:->1$}+",
"",
width.checked_add(2).unwrap_or_default()
);
}
write_or!(buf, "\n");
}
fn print_table_row(buf: &mut String, widths: &[usize], values: &[&dyn fmt::Display]) {
assert_eq!(
widths.len(),
values.len(),
"The widths and values amounts should be the same"
);
print_table_row_(buf, widths, values, None);
}
fn print_table_row_with_label(
buf: &mut String,
widths: &[usize],
label: &dyn fmt::Display,
values: &[&dyn fmt::Display],
) {
print_table_row_(buf, widths, values, Some(label));
}
fn print_table_row_(
buf: &mut String,
widths: &[usize],
values: &[&dyn fmt::Display],
label: Option<&dyn fmt::Display>,
) {
write_or!(buf, "|");
if let Some(label) = label {
let mut widths = widths.iter();
let Some(width) = widths.next() else {
return;
};
print_col(buf, label, *width);
for (value, width) in values.iter().zip(widths) {
print_col(buf, *value, *width);
}
} else {
for (value, width) in values.iter().zip(widths) {
print_col(buf, *value, *width);
}
}
write_or!(buf, "\n");
}
fn print_col(buf: &mut String, value: &dyn fmt::Display, width: usize) {
write_or!(buf, " ");
let len_before = buf.len();
write_or!(buf, "{value}");
let len_after = buf.len();
let Some(s) = &buf.get(len_before..len_after) else {
error!("Non UTF8 values were written as a column value");
return;
};
let len = measure_text_width(s);
let padding = width.saturating_sub(len);
for _ in 0..padding {
write_or!(buf, " ");
}
write_or!(buf, " |");
}