use std::io::{Result, Write};
use termcolor::{BufferWriter, ColorChoice, WriteColor};
use crate::{
format::{HorizontalLine, TableFormat, VerticalLine},
Row,
};
pub struct Table {
rows: Vec<Row>,
format: TableFormat,
widths: Vec<usize>,
}
impl Table {
pub fn new(rows: Vec<Row>, format: TableFormat) -> Table {
validate_equal_columns(&rows);
let widths = get_widths(&rows);
Table {
rows,
format,
widths,
}
}
pub fn print_stdout(&self) -> Result<()> {
self.print_writer(BufferWriter::stdout(ColorChoice::Always))
}
pub fn print_stderr(&self) -> Result<()> {
self.print_writer(BufferWriter::stderr(ColorChoice::Always))
}
fn print_writer(&self, writer: BufferWriter) -> Result<()> {
self.print_horizontal_line(&writer, self.format.border.top.as_ref())?;
let mut rows = self.rows.iter().peekable();
let mut first = true;
while let Some(row) = rows.next() {
let buffers = row.buffers(&writer, &self.widths)?;
for line in buffers.into_iter() {
self.print_vertical_line(&writer, self.format.border.left.as_ref())?;
let mut line_buffers = line.into_iter().peekable();
while let Some(buffer) = line_buffers.next() {
print_char(&writer, ' ')?;
writer.print(&buffer)?;
print_char(&writer, ' ')?;
match line_buffers.peek() {
Some(_) => self
.print_vertical_line(&writer, self.format.separator.column.as_ref())?,
None => {
self.print_vertical_line(&writer, self.format.border.right.as_ref())?
}
}
}
println_str(&writer, "")?;
}
match rows.peek() {
Some(_) => {
if first {
if self.format.separator.title.is_some() {
self.print_horizontal_line(
&writer,
self.format.separator.title.as_ref(),
)?
} else {
self.print_horizontal_line(&writer, self.format.separator.row.as_ref())?
}
} else {
self.print_horizontal_line(&writer, self.format.separator.row.as_ref())?
}
first = false;
}
None => self.print_horizontal_line(&writer, self.format.border.bottom.as_ref())?,
}
}
Ok(())
}
fn print_horizontal_line(
&self,
writer: &BufferWriter,
line: Option<&HorizontalLine>,
) -> Result<()> {
if let Some(line) = line {
if self.format.border.left.is_some() {
print_char(writer, line.left_end)?;
}
let mut widths = self.widths.iter().peekable();
while let Some(width) = widths.next() {
let s = std::iter::repeat(line.filler)
.take(width + 2)
.collect::<String>();
print_str(writer, &s)?;
match widths.peek() {
Some(_) => {
if self.format.separator.column.is_some() {
print_char(writer, line.junction)?
}
}
None => {
if self.format.border.right.is_some() {
println_char(writer, line.right_end)?;
} else {
println_str(writer, "")?;
}
}
}
}
}
Ok(())
}
fn print_vertical_line(
&self,
writer: &BufferWriter,
line: Option<&VerticalLine>,
) -> Result<()> {
if let Some(line) = line {
print_char(writer, line.filler)?;
}
Ok(())
}
}
fn print_str(writer: &BufferWriter, s: &str) -> Result<()> {
let mut buffer = writer.buffer();
buffer.reset()?;
write!(&mut buffer, "{}", s)?;
writer.print(&buffer)?;
Ok(())
}
fn println_str(writer: &BufferWriter, s: &str) -> Result<()> {
let mut buffer = writer.buffer();
buffer.reset()?;
writeln!(&mut buffer, "{}", s)?;
writer.print(&buffer)?;
Ok(())
}
fn print_char(writer: &BufferWriter, c: char) -> Result<()> {
let mut buffer = writer.buffer();
buffer.reset()?;
write!(&mut buffer, "{}", c)?;
writer.print(&buffer)?;
Ok(())
}
fn println_char(writer: &BufferWriter, c: char) -> Result<()> {
let mut buffer = writer.buffer();
buffer.reset()?;
writeln!(&mut buffer, "{}", c)?;
writer.print(&buffer)?;
Ok(())
}
fn validate_equal_columns(rows: &[Row]) {
if rows.len() <= 1 {
return;
}
let columns = rows[0].columns();
for row in rows.iter().skip(1) {
if columns != row.columns() {
panic!("Mismatch column numbers in different rows");
}
}
}
fn get_widths(rows: &[Row]) -> Vec<usize> {
if rows.is_empty() {
return Vec::new();
}
let mut widths = rows[0].widths();
for row in rows.iter().skip(1) {
let new_widths = row.widths();
for (width, new_width) in widths.iter_mut().zip(new_widths.into_iter()) {
*width = std::cmp::max(new_width, *width);
}
}
widths
}