use std::io::Result;
use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec};
use crate::{
buffers::Buffers,
display::TableDisplay,
row::{Dimension as RowDimension, Row, RowStruct},
style::{Style, StyleStruct},
utils::*,
};
pub struct TableStruct {
title: Option<RowStruct>,
rows: Vec<RowStruct>,
format: TableFormat,
style: StyleStruct,
color_choice: ColorChoice,
}
impl TableStruct {
pub fn title<T: Row>(mut self, title: T) -> Self {
self.title = Some(title.row());
self
}
pub fn border(mut self, border: Border) -> Self {
self.format.border = border;
self
}
pub fn separator(mut self, separator: Separator) -> Self {
self.format.separator = separator;
self
}
pub fn color_choice(mut self, color_choice: ColorChoice) -> Self {
self.color_choice = color_choice;
self
}
pub fn display(&self) -> Result<TableDisplay> {
let writer = BufferWriter::stdout(self.color_choice);
let buffers = self.buffers(&writer)?;
let mut output = Vec::new();
for buffer in buffers {
output.append(&mut buffer.into_inner());
}
Ok(TableDisplay::new(output))
}
pub(crate) fn print_stdout(&self) -> Result<()> {
self.print_writer(BufferWriter::stdout(self.color_choice))
}
pub(crate) fn print_stderr(&self) -> Result<()> {
self.print_writer(BufferWriter::stderr(self.color_choice))
}
fn color_spec(&self) -> ColorSpec {
self.style.color_spec()
}
fn required_dimension(&self) -> Dimension {
let mut heights = Vec::with_capacity(self.rows.len() + 1);
let mut widths = Vec::new();
let title_dimension = self.title.as_ref().map(RowStruct::required_dimension);
if let Some(title_dimension) = title_dimension {
widths = title_dimension.widths;
heights.push(title_dimension.height);
}
for row in self.rows.iter() {
let row_dimension = row.required_dimension();
heights.push(row_dimension.height);
let new_widths = row_dimension.widths;
if widths.is_empty() {
widths = new_widths;
} else {
for (width, new_width) in widths.iter_mut().zip(new_widths.into_iter()) {
*width = std::cmp::max(new_width, *width);
}
}
}
Dimension { widths, heights }
}
fn buffers(&self, writer: &BufferWriter) -> Result<Vec<Buffer>> {
let table_dimension = self.required_dimension();
let row_dimensions: Vec<RowDimension> = table_dimension.clone().into();
let mut row_dimensions = row_dimensions.into_iter();
let color_spec = self.color_spec();
let mut buffers = Buffers::new(writer);
print_horizontal_line(
&mut buffers,
self.format.border.top.as_ref(),
&table_dimension,
&self.format,
&color_spec,
)?;
if let Some(ref title) = self.title {
let title_dimension = row_dimensions.next().unwrap();
let mut title_buffers =
title.buffers(writer, title_dimension, &self.format, &color_spec)?;
buffers.append(&mut title_buffers)?;
if self.format.separator.title.is_some() {
print_horizontal_line(
&mut buffers,
self.format.separator.title.as_ref(),
&table_dimension,
&self.format,
&color_spec,
)?
} else {
print_horizontal_line(
&mut buffers,
self.format.separator.row.as_ref(),
&table_dimension,
&self.format,
&color_spec,
)?
}
}
let mut rows = self.rows.iter().zip(row_dimensions).peekable();
while let Some((row, row_dimension)) = rows.next() {
let mut row_buffers = row.buffers(writer, row_dimension, &self.format, &color_spec)?;
buffers.append(&mut row_buffers)?;
match rows.peek() {
Some(_) => print_horizontal_line(
&mut buffers,
self.format.separator.row.as_ref(),
&table_dimension,
&self.format,
&color_spec,
)?,
None => print_horizontal_line(
&mut buffers,
self.format.border.bottom.as_ref(),
&table_dimension,
&self.format,
&color_spec,
)?,
}
}
buffers.into_vec()
}
fn print_writer(&self, writer: BufferWriter) -> Result<()> {
let buffers = self.buffers(&writer)?;
for buffer in buffers.iter() {
writer.print(buffer)?;
}
Ok(())
}
}
pub trait Table {
fn table(self) -> TableStruct;
}
impl<T, R> Table for T
where
T: IntoIterator<Item = R>,
R: Row,
{
fn table(self) -> TableStruct {
let rows = self.into_iter().map(Row::row).collect();
TableStruct {
title: Default::default(),
rows,
format: Default::default(),
style: Default::default(),
color_choice: ColorChoice::Always,
}
}
}
impl Table for TableStruct {
fn table(self) -> TableStruct {
self
}
}
impl Style for TableStruct {
fn foreground_color(mut self, foreground_color: Option<Color>) -> Self {
self.style = self.style.foreground_color(foreground_color);
self
}
fn background_color(mut self, background_color: Option<Color>) -> Self {
self.style = self.style.background_color(background_color);
self
}
fn bold(mut self, bold: bool) -> Self {
self.style = self.style.bold(bold);
self
}
fn underline(mut self, underline: bool) -> Self {
self.style = self.style.underline(underline);
self
}
fn italic(mut self, italic: bool) -> Self {
self.style = self.style.italic(italic);
self
}
fn intense(mut self, intense: bool) -> Self {
self.style = self.style.intense(intense);
self
}
fn dimmed(mut self, dimmed: bool) -> Self {
self.style = self.style.dimmed(dimmed);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VerticalLine {
pub(crate) filler: char,
}
impl Default for VerticalLine {
fn default() -> Self {
Self { filler: '|' }
}
}
impl VerticalLine {
pub fn new(filler: char) -> Self {
Self { filler }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HorizontalLine {
pub(crate) left_end: char,
pub(crate) right_end: char,
pub(crate) junction: char,
pub(crate) filler: char,
}
impl Default for HorizontalLine {
fn default() -> Self {
Self {
left_end: '+',
right_end: '+',
junction: '+',
filler: '-',
}
}
}
impl HorizontalLine {
pub fn new(left_end: char, right_end: char, junction: char, filler: char) -> Self {
Self {
left_end,
right_end,
junction,
filler,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Border {
pub(crate) top: Option<HorizontalLine>,
pub(crate) bottom: Option<HorizontalLine>,
pub(crate) left: Option<VerticalLine>,
pub(crate) right: Option<VerticalLine>,
}
impl Border {
pub fn builder() -> BorderBuilder {
BorderBuilder(Border {
top: None,
bottom: None,
left: None,
right: None,
})
}
}
impl Default for Border {
fn default() -> Self {
Self {
top: Some(Default::default()),
bottom: Some(Default::default()),
left: Some(Default::default()),
right: Some(Default::default()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BorderBuilder(Border);
impl BorderBuilder {
pub fn top(mut self, top: HorizontalLine) -> Self {
self.0.top = Some(top);
self
}
pub fn bottom(mut self, bottom: HorizontalLine) -> Self {
self.0.bottom = Some(bottom);
self
}
pub fn left(mut self, left: VerticalLine) -> Self {
self.0.left = Some(left);
self
}
pub fn right(mut self, right: VerticalLine) -> Self {
self.0.right = Some(right);
self
}
pub fn build(self) -> Border {
self.0
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Separator {
pub(crate) column: Option<VerticalLine>,
pub(crate) row: Option<HorizontalLine>,
pub(crate) title: Option<HorizontalLine>,
}
impl Separator {
pub fn builder() -> SeparatorBuilder {
SeparatorBuilder(Separator {
column: None,
row: None,
title: None,
})
}
}
impl Default for Separator {
fn default() -> Self {
Self {
column: Some(Default::default()),
row: Some(Default::default()),
title: None,
}
}
}
#[derive(Debug)]
pub struct SeparatorBuilder(Separator);
impl SeparatorBuilder {
pub fn column(mut self, column: Option<VerticalLine>) -> Self {
self.0.column = column;
self
}
pub fn row(mut self, row: Option<HorizontalLine>) -> Self {
self.0.row = row;
self
}
pub fn title(mut self, title: Option<HorizontalLine>) -> Self {
self.0.title = title;
self
}
pub fn build(self) -> Separator {
self.0
}
}
#[derive(Debug, Default, Copy, Clone)]
pub(crate) struct TableFormat {
pub(crate) border: Border,
pub(crate) separator: Separator,
}
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub(crate) struct Dimension {
pub(crate) widths: Vec<usize>,
pub(crate) heights: Vec<usize>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_row_from_str_arr() {
let table: TableStruct = vec![&["Hello", "World"], &["Scooby", "Doo"]].table();
assert_eq!(2, table.rows.len());
assert_eq!(2, table.rows[0].cells.len());
assert_eq!(2, table.rows[1].cells.len());
}
}