use crate::{
column_spec::{parse_row_spec, row_spec_to_string, ColumnSpec},
error::Result,
row::{InternalRow, Row},
width_string::WidthString,
};
use std::fmt::{Debug, Display, Formatter};
#[derive(Clone)]
pub struct Table {
n_columns: usize,
format: Vec<ColumnSpec>,
rows: Vec<InternalRow>,
column_widths: Vec<usize>,
line_end: String,
}
const DEFAULT_LINE_END: &str = "\n";
impl Table {
pub fn new(row_spec: &str) -> Self {
Self::new_safe(row_spec)
.unwrap_or_else(|e: super::error::Error| panic!("tabular::Table::new: {}", e))
}
pub fn new_safe(row_spec: &str) -> Result<Self> {
let (format, n_columns) = parse_row_spec(row_spec)?;
Ok(Table {
n_columns,
format,
rows: vec![],
column_widths: vec![0; n_columns],
line_end: DEFAULT_LINE_END.to_owned(),
})
}
pub fn column_count(&self) -> usize {
self.n_columns
}
pub fn add_heading<S: Into<String>>(&mut self, heading: S) -> &mut Self {
self.rows.push(InternalRow::Heading(heading.into()));
self
}
#[must_use]
pub fn with_heading<S: Into<String>>(mut self, heading: S) -> Self {
self.add_heading(heading);
self
}
pub fn add_row(&mut self, row: Row) -> &mut Self {
let cells = row.0;
assert_eq!(
cells.len(),
self.n_columns,
"Number of columns in table and row don't match"
);
for (width, s) in self.column_widths.iter_mut().zip(cells.iter()) {
*width = ::std::cmp::max(*width, s.width());
}
self.rows.push(InternalRow::Cells(cells));
self
}
#[must_use]
pub fn with_row(mut self, row: Row) -> Self {
self.add_row(row);
self
}
#[must_use]
pub fn set_line_end<S: Into<String>>(mut self, line_end: S) -> Self {
self.line_end = line_end.into();
self
}
}
impl Debug for Table {
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
write!(f, "Table::new({:?})", row_spec_to_string(&self.format))?;
if self.line_end != DEFAULT_LINE_END {
write!(f, ".set_line_end({:?})", self.line_end)?;
}
for row in &self.rows {
match *row {
InternalRow::Cells(ref row) => write!(f, ".with_row({:?})", Row(row.clone()))?,
InternalRow::Heading(ref heading) => write!(f, ".with_heading({:?})", heading)?,
}
}
Ok(())
}
}
impl Display for Table {
fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
use crate::column_spec::{Alignment::*, ColumnSpec::*};
let max_column_width = self.column_widths.iter().cloned().max().unwrap_or(0);
let mut spaces = String::with_capacity(max_column_width);
for _ in 0..max_column_width {
spaces.push(' ');
}
let mt_width_string = WidthString::default();
let is_not_last = |field_index| field_index + 1 < self.format.len();
for row in &self.rows {
match *row {
InternalRow::Cells(ref cells) => {
let mut cw_iter = self.column_widths.iter().cloned();
let mut row_iter = cells.iter();
for field_index in 0..self.format.len() {
match self.format[field_index] {
Align(alignment) => {
let cw = cw_iter.next().unwrap();
let ws = row_iter.next().unwrap_or(&mt_width_string);
let needed = cw - ws.width();
let padding = &spaces[..needed];
match alignment {
Left => {
f.write_str(ws.as_str())?;
if is_not_last(field_index) {
f.write_str(padding)?;
}
}
Center => {
let (before, after) = padding.split_at(needed / 2);
f.write_str(before)?;
f.write_str(ws.as_str())?;
if is_not_last(field_index) {
f.write_str(after)?;
}
}
Right => {
f.write_str(padding)?;
f.write_str(ws.as_str())?;
}
}
}
Literal(ref s) => f.write_str(s)?,
}
}
}
InternalRow::Heading(ref s) => {
f.write_str(s)?;
}
}
f.write_str(&self.line_end)?;
}
Ok(())
}
}