#![allow(clippy::needless_range_loop)]
mod row;
mod table_cell;
pub use self::row::Row;
pub use self::table_cell::TableCell;
use self::table_cell::Alignment;
use std::cmp::{max, min};
use std::collections::HashMap;
#[derive(Eq, PartialEq, Copy, Clone)]
pub enum RowPosition {
First,
Mid,
Last,
}
#[derive(Debug, Clone, Copy)]
pub struct TableStyle {
pub top_left_corner: char,
pub top_right_corner: char,
pub bottom_left_corner: char,
pub bottom_right_corner: char,
pub outer_left_vertical: char,
pub outer_right_vertical: char,
pub outer_bottom_horizontal: char,
pub outer_top_horizontal: char,
pub intersection: char,
pub vertical: char,
pub horizontal: char,
}
impl TableStyle {
pub fn extended() -> TableStyle {
TableStyle {
top_left_corner: '╔',
top_right_corner: '╗',
bottom_left_corner: '╚',
bottom_right_corner: '╝',
outer_left_vertical: '╠',
outer_right_vertical: '╣',
outer_bottom_horizontal: '╩',
outer_top_horizontal: '╦',
intersection: '╬',
vertical: '║',
horizontal: '═',
}
}
fn start_for_position(&self, pos: RowPosition) -> char {
match pos {
RowPosition::First => self.top_left_corner,
RowPosition::Mid => self.outer_left_vertical,
RowPosition::Last => self.bottom_left_corner,
}
}
fn end_for_position(&self, pos: RowPosition) -> char {
match pos {
RowPosition::First => self.top_right_corner,
RowPosition::Mid => self.outer_right_vertical,
RowPosition::Last => self.bottom_right_corner,
}
}
fn intersect_for_position(&self, pos: RowPosition) -> char {
match pos {
RowPosition::First => self.outer_top_horizontal,
RowPosition::Mid => self.intersection,
RowPosition::Last => self.outer_bottom_horizontal,
}
}
fn merge_intersection_for_position(&self, top: char, bottom: char, pos: RowPosition) -> char {
if (top == self.horizontal || top == self.outer_bottom_horizontal)
&& bottom == self.intersection
{
return self.outer_top_horizontal;
} else if (top == self.intersection || top == self.outer_top_horizontal)
&& bottom == self.horizontal
{
return self.outer_bottom_horizontal;
} else if top == self.outer_bottom_horizontal && bottom == self.horizontal {
return self.horizontal;
} else {
return self.intersect_for_position(pos);
}
}
}
#[derive(Clone, Debug)]
pub struct Table {
pub rows: Vec<Row>,
pub style: TableStyle,
pub max_column_width: usize,
pub max_column_widths: HashMap<usize, usize>,
pub separate_rows: bool,
pub has_top_boarder: bool,
pub has_bottom_boarder: bool,
}
impl Table {
pub fn new() -> Table {
Self {
rows: Vec::new(),
style: TableStyle::extended(),
max_column_width: usize::MAX,
max_column_widths: HashMap::new(),
separate_rows: true,
has_top_boarder: true,
has_bottom_boarder: true,
}
}
pub fn add_row(&mut self, row: Row) {
self.rows.push(row);
}
pub fn render(&self) -> String {
let mut print_buffer = String::new();
let max_widths = self.calculate_max_column_widths();
let mut previous_separator = None;
if !self.rows.is_empty() {
for i in 0..self.rows.len() {
let row_pos = if i == 0 {
RowPosition::First
} else {
RowPosition::Mid
};
let separator = self.rows[i].gen_separator(
&max_widths,
&self.style,
row_pos,
previous_separator.clone(),
);
previous_separator = Some(separator.clone());
if self.rows[i].has_separator
&& ((i == 0 && self.has_top_boarder) || i != 0 && self.separate_rows)
{
Table::buffer_line(&mut print_buffer, &separator);
}
Table::buffer_line(
&mut print_buffer,
&self.rows[i].format(&max_widths, &self.style),
);
}
if self.has_bottom_boarder {
let separator = self.rows.last().unwrap().gen_separator(
&max_widths,
&self.style,
RowPosition::Last,
None,
);
Table::buffer_line(&mut print_buffer, &separator);
}
}
return print_buffer;
}
fn calculate_max_column_widths(&self) -> Vec<usize> {
let mut num_columns = 0;
for row in &self.rows {
num_columns = max(row.num_columns(), num_columns);
}
let mut max_widths: Vec<usize> = vec![0; num_columns];
let mut min_widths: Vec<usize> = vec![0; num_columns];
for row in &self.rows {
let column_widths = row.split_column_widths();
for i in 0..column_widths.len() {
min_widths[i] = max(min_widths[i], column_widths[i].1);
let mut max_width = *self
.max_column_widths
.get(&i)
.unwrap_or(&self.max_column_width);
max_width = max(min_widths[i], max_width);
max_widths[i] = min(max_width, max(max_widths[i], column_widths[i].0 as usize));
}
}
for row in &self.rows {
let mut col_index = 0;
for cell in row.cells.iter() {
let mut total_col_width = 0;
for i in col_index..col_index + cell.col_span {
total_col_width += max_widths[i];
}
if cell.width() != total_col_width
&& cell.alignment == Alignment::Center
&& total_col_width as f32 % 2.0 <= 0.001
{
let mut max_col_width = self.max_column_width;
if let Some(specific_width) = self.max_column_widths.get(&col_index) {
max_col_width = *specific_width;
}
if max_widths[col_index] < max_col_width {
max_widths[col_index] += 1;
}
}
if cell.col_span > 1 {
col_index += cell.col_span - 1;
} else {
col_index += 1;
}
}
}
return max_widths;
}
fn buffer_line(buffer: &mut String, line: &str) {
buffer.push_str(format!("{}\n", line).as_str());
}
}