use std::fmt::{Display, Formatter, Result as FmtResult};
use numfmt::{Numeric, Precision};
pub struct DisplayTable {
column_names: Vec<String>,
column_widths: Vec<usize>,
row_names: Vec<String>,
data: Vec<Vec<String>>,
max_print: usize,
over_max: bool,
}
impl DisplayTable {
pub fn new<D: ToString + Numeric>(
column_names: Vec<String>,
row_names: Vec<String>,
data: Vec<Vec<D>>,
max_print: Option<usize>,
) -> Self {
let mut number_formatter = numfmt::Formatter::new().precision(Precision::Significance(5));
let max_print = max_print.unwrap_or(10);
let mut over_max = false;
let data = data
.into_iter()
.map(|d| {
d.into_iter()
.map(|v| number_formatter.fmt2(v).to_string())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let data = if data.len() > max_print {
over_max = true;
data.iter()
.take((max_print as f64 / 2.0).ceil() as usize)
.chain(
data.iter()
.rev()
.take((max_print as f64 / 2.0).floor() as usize)
.rev(),
)
.cloned()
.collect()
} else {
data.clone()
};
let row_names = if row_names.len() > max_print {
row_names
.iter()
.take((max_print as f64 / 2.0).ceil() as usize)
.chain(
row_names
.iter()
.rev()
.take((max_print as f64 / 2.0).floor() as usize)
.rev(),
)
.cloned()
.collect()
} else {
row_names.clone()
};
let mut column_widths = column_names.iter().map(|c| c.len()).collect::<Vec<_>>();
for row in &data {
for (column, width) in row.iter().zip(column_widths.iter_mut()) {
*width = (*width).max(column.len());
}
}
if !row_names.is_empty() {
column_widths.insert(0, row_names.iter().map(|n| n.len()).max().unwrap());
};
Self {
column_names,
column_widths,
row_names,
data,
max_print,
over_max,
}
}
fn write_table_bars(
&self,
formatter: &mut Formatter,
left_bar: &str,
middle_blank: &str,
middle_bar: &str,
right_bar: &str,
) -> FmtResult {
write!(formatter, "{left_bar}")?;
for (index, &len) in self.column_widths.iter().enumerate() {
if len > 0 {
for _ in 0..len + 2 {
write!(formatter, "{middle_blank}")?;
}
if index != self.column_widths.len() - 1 {
write!(formatter, "{middle_bar}")?;
}
}
}
writeln!(formatter, "{right_bar}")?;
Ok(())
}
fn write_data(&self, formatter: &mut Formatter, bar: &str) -> FmtResult {
for (row_index, row) in self.data.iter().enumerate() {
if self.over_max && row_index == (self.max_print as f64 / 2.0).floor() as usize {
for column_index in 0..self.column_widths.len() {
write!(formatter, "│ …")?;
for _ in 0..self.column_widths[column_index] - 1 {
write!(formatter, " ")?;
}
write!(formatter, " ")?;
}
writeln!(formatter, "│")?;
}
for (column_index, value) in row.iter().enumerate() {
write!(formatter, "{bar} ")?;
let column_index = if !self.row_names.is_empty() && column_index == 0 {
self.row_names[row_index].fmt(formatter)?;
for _ in 0..self.column_widths[column_index] - self.row_names[row_index].len() {
write!(formatter, " ")?;
}
write!(formatter, " {bar} ")?;
value.fmt(formatter)?;
column_index + 1
} else {
value.fmt(formatter)?;
if !self.row_names.is_empty() {
column_index + 1
} else {
column_index
}
};
if self.column_widths[column_index] > value.to_string().len() {
for _ in 0..self.column_widths[column_index] - value.to_string().len() {
write!(formatter, " ")?;
}
}
write!(formatter, " ")?;
}
writeln!(formatter, "{bar}")?;
}
Ok(())
}
}
impl Display for DisplayTable {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
self.write_table_bars(f, "┌", "─", "┬", "┐")?;
let mut column_names = self.column_names.clone();
if !self.row_names.is_empty() {
column_names.insert(0, "".to_string());
}
for (column_index, name) in column_names.iter().enumerate() {
write!(f, "│ ")?;
name.fmt(f)?;
if self.column_widths[column_index] > name.len() {
for _ in 0..self.column_widths[column_index] - name.len() {
write!(f, " ")?;
}
}
write!(f, " ")?;
}
writeln!(f, "│")?;
self.write_table_bars(f, "╞", "═", "╪", "╡")?;
self.write_data(f, "│")?;
self.write_table_bars(f, "└", "─", "┴", "┘")?;
Ok(())
}
}