pub mod style;
use std::io;
use std::fmt;
use std::io::Write;
use std::fmt::Display;
use std::error::Error;
use std::cell::RefCell;
use crate::style::StyleOpt;
use crate::style::stylize;
pub struct GridPrinter {
rows: usize,
cols: usize,
max_widths: RefCell<Vec<usize>>,
col_spacing: usize,
col_styles: Option<Vec<Option<StyleOpt>>>,
}
impl GridPrinter {
pub fn new(rows: usize, cols: usize) -> Self {
Self {
rows,
cols,
..GridPrinterBuilder::new(rows, cols).build()
}
}
pub fn builder(rows: usize, cols: usize) -> GridPrinterBuilder {
GridPrinterBuilder::new(rows, cols)
}
fn pad(n: usize) -> String {
vec![' '; n].into_iter().collect()
}
#[allow(clippy::print_with_newline)]
pub fn print_cell(&self, cell: &str, col_idx: usize, style_opt: Option<&StyleOpt>) {
let mut s = cell.to_string();
if let Some(style_opt) = style_opt {
s = stylize(cell, style_opt);
}
let col_width = self.max_widths.borrow()[col_idx];
let pad = GridPrinter::pad(col_width - cell.len() + self.col_spacing);
print!("{}{}", s, pad);
}
#[allow(clippy::print_with_newline)]
pub fn print<F: Display>(&self, source: &[Vec<F>]) {
let mut buff: Vec<String> = Vec::new();
for i in 0..self.rows {
let row = source.get(i);
for j in 0..self.cols {
let cell = match row {
None => "".to_string(),
Some(row) => match row.get(j) {
None => "".to_string(),
Some(el) => format!("{}", el),
}
};
let len = cell.len();
if len > self.max_widths.borrow()[j] {
self.max_widths.borrow_mut()[j] = len;
}
buff.push(cell);
}
}
for (i, cell) in buff.iter().enumerate() {
let col_idx = i % self.cols;
let _row_idx = i / self.rows;
let style_opt = match self.col_styles.as_ref() {
None => None,
Some(col_styles) => match col_styles.get(col_idx) {
None => None,
Some(style_opt) => style_opt.as_ref(),
}
};
self.print_cell(cell, col_idx, style_opt);
if (i + 1) % self.cols == 0 {
print!("\n");
io::stdout().flush().unwrap();
}
}
}
}
#[derive(Debug)]
pub struct GridPrinterBuilder {
rows: usize,
cols: usize,
col_spacing: usize,
col_styles: Option<Vec<Option<StyleOpt>>>,
}
impl Default for GridPrinterBuilder {
fn default() -> Self {
Self {
rows: 1,
cols: 1,
col_spacing: 2,
col_styles: None,
}
}
}
impl GridPrinterBuilder {
pub fn new(rows: usize, cols: usize) -> Self {
GridPrinterBuilder {
rows,
cols,
..Default::default()
}
}
pub fn col_spacing(mut self, col_spacing: usize) -> Self {
self.col_spacing = col_spacing;
self
}
pub fn col_styles(mut self, col_styles: Vec<Option<StyleOpt>>) -> Result<Self, GridPrinterErr> {
match col_styles.len() == self.cols {
false => Err(GridPrinterErr::DimensionErr),
true => {
self.col_styles = Some(col_styles);
Ok(self)
}
}
}
pub fn col_style(mut self, idx: usize, opt: StyleOpt) -> Result<Self, GridPrinterErr> {
if idx >= self.cols {
return Err(GridPrinterErr::DimensionErr);
}
let col_styles = self.col_styles.get_or_insert(vec![None; self.cols]);
let col_style = col_styles.get_mut(idx)
.ok_or(GridPrinterErr::DimensionErr)?;
*col_style = Some(opt);
Ok(self)
}
pub fn build(self) -> GridPrinter {
GridPrinter {
rows: self.rows,
cols: self.cols,
max_widths: RefCell::new(vec![0; self.cols]),
col_spacing: self.col_spacing,
col_styles: self.col_styles,
}
}
}
#[derive(Debug)]
pub enum GridPrinterErr {
DimensionErr,
}
impl Display for GridPrinterErr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GridPrinterErr::DimensionErr => {
write!(f, "DimensionErr. Caused by mismatch in dimension size between method calls.")
},
}
}
}
impl Error for GridPrinterErr {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_2d_arr() {
let v = vec![
vec![1, 20, 3, ],
vec![40, 5, 6, ],
vec![7, 800, 9, ],
];
let rows = v.len();
let cols = v[0].len();
let printer = GridPrinterBuilder::new(rows, cols)
.col_spacing(20)
.build();
printer.print(&v);
}
}