#[macro_use]
extern crate lazy_static;
extern crate wcwidth;
extern crate regex;
pub mod table_cell;
pub mod row;
use row::Row;
use std::cmp::{max, min};
use std::collections::HashMap;
#[derive(Eq, PartialEq, Copy, Clone)]
pub enum RowPosition {
First,
Mid,
Last,
}
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 simple() -> TableStyle {
return 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: '-',
};
}
pub fn extended() -> TableStyle {
return 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: '═',
};
}
pub fn thin() -> TableStyle {
return 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: '─',
};
}
pub fn rounded() -> TableStyle {
return 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: '─',
};
}
pub fn elegant() -> TableStyle {
return 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: '─',
};
}
pub fn blank() -> TableStyle {
return TableStyle {
top_left_corner: '\0',
top_right_corner: '\0',
bottom_left_corner: '\0',
bottom_right_corner: '\0',
outer_left_vertical: '\0',
outer_right_vertical: '\0',
outer_bottom_horizontal: '\0',
outer_top_horizontal: '\0',
intersection: '\0',
vertical: '\0',
horizontal: '\0',
};
}
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);
}
}
}
pub struct Table<'data> {
pub rows: Vec<Row<'data>>,
pub style: TableStyle,
pub max_column_width: usize,
pub max_column_widths: HashMap<usize, usize>,
}
impl<'data> Table<'data> {
pub fn new() -> Table<'data> {
return Table {
rows: Vec::new(),
style: TableStyle::extended(),
max_column_width: std::usize::MAX,
max_column_widths: HashMap::new(),
};
}
pub fn set_max_column_width(&mut self, column_index: usize, width: usize) {
self.max_column_widths.insert(column_index, width);
}
pub fn set_max_column_widths(&mut self, index_width_pairs: Vec<(usize, usize)>) {
for pair in index_width_pairs {
self.max_column_widths.insert(pair.0, pair.1);
}
}
pub fn add_row(&mut self, row: Row<'data>) {
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 mut 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(),
);
Table::buffer_line(&mut print_buffer, &separator);
Table::buffer_line(
&mut print_buffer,
&self.rows[i].format(&max_widths, &self.style),
);
previous_separator = Some(separator.clone());
}
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] as usize, max_width);
max_widths[i] = min(max_width, max(max_widths[i], column_widths[i].0 as usize));
}
}
return max_widths;
}
fn buffer_line(buffer: &mut String, line: &str) {
buffer.push_str(format!("{}\n", line).as_str());
}
}
impl<'data> Default for Table<'data>{
fn default()->Self{
return Table::new();
}
}
impl<'data> ToString for Table<'data>{
fn to_string(&self)->String{
return self.render();
}
}
#[cfg(test)]
mod test {
use Table;
use TableStyle;
use table_cell::{Alignment, TableCell};
use row::Row;
#[test]
fn simple_table_style() {
let mut table = Table::new();
table.max_column_width = 40;
table.style = TableStyle::simple();
table.add_row(Row::new(vec![
TableCell::new_with_alignment("This is some centered text", 2, Alignment::Center),
]));
table.add_row(Row::new(vec![
TableCell::new("This is left aligned text"),
TableCell::new_with_alignment("This is right aligned text", 1, Alignment::Right),
]));
table.add_row(Row::new(vec![
TableCell::new("This is left aligned text"),
TableCell::new_with_alignment("This is right aligned text", 1, Alignment::Right),
]));
table.add_row(Row::new(vec![
TableCell::new_with_col_span("This is some really really really really really really really really really that is going to wrap to the next line", 2),
]));
let expected =
"+---------------------------------------------------------------------------------+
| This is some centered text |
+----------------------------------------+----------------------------------------+
| This is left aligned text | This is right aligned text |
+----------------------------------------+----------------------------------------+
| This is left aligned text | This is right aligned text |
+----------------------------------------+----------------------------------------+
| This is some really really really really really really really really really tha |
| t is going to wrap to the next line |
+---------------------------------------------------------------------------------+
";
println!("{}", table.render());
assert_eq!(expected, table.render());
}
#[test]
fn extended_table_style() {
let mut table = Table::new();
table.max_column_width = 40;
table.set_max_column_widths(vec![(0, 1), (1, 1)]);
table.style = TableStyle::extended();
table.add_row(Row::new(vec![
TableCell::new_with_alignment("This is some centered text", 2, Alignment::Center),
]));
table.add_row(Row::new(vec![
TableCell::new("This is left aligned text"),
TableCell::new_with_alignment("This is right aligned text", 1, Alignment::Right),
]));
table.add_row(Row::new(vec![
TableCell::new("This is left aligned text"),
TableCell::new_with_alignment("This is right aligned text", 1, Alignment::Right),
]));
table.add_row(Row::new(vec![
TableCell::new_with_col_span("This is some really really really really really really really really really that is going to wrap to the next line\n1\n2", 2),
]));
let expected =
"╔═══════╗
║ This ║
║ is so ║
║ me ce ║
║ ntere ║
║ d tex ║
║ t ║
╠═══╦═══╣
║ T ║ T ║
║ h ║ h ║
║ i ║ i ║
║ s ║ s ║
║ ║ ║
║ i ║ i ║
║ s ║ s ║
║ ║ ║
║ l ║ r ║
║ e ║ i ║
║ f ║ g ║
║ t ║ h ║
║ ║ t ║
║ a ║ ║
║ l ║ a ║
║ i ║ l ║
║ g ║ i ║
║ n ║ g ║
║ e ║ n ║
║ d ║ e ║
║ ║ d ║
║ t ║ ║
║ e ║ t ║
║ x ║ e ║
║ t ║ x ║
║ ║ t ║
╠═══╬═══╣
║ T ║ T ║
║ h ║ h ║
║ i ║ i ║
║ s ║ s ║
║ ║ ║
║ i ║ i ║
║ s ║ s ║
║ ║ ║
║ l ║ r ║
║ e ║ i ║
║ f ║ g ║
║ t ║ h ║
║ ║ t ║
║ a ║ ║
║ l ║ a ║
║ i ║ l ║
║ g ║ i ║
║ n ║ g ║
║ e ║ n ║
║ d ║ e ║
║ ║ d ║
║ t ║ ║
║ e ║ t ║
║ x ║ e ║
║ t ║ x ║
║ ║ t ║
╠═══╩═══╣
║ This ║
║ is so ║
║ me re ║
║ ally ║
║ reall ║
║ y rea ║
║ lly r ║
║ eally ║
║ real ║
║ ly re ║
║ ally ║
║ reall ║
║ y rea ║
║ lly r ║
║ eally ║
║ that ║
║ is g ║
║ oing ║
║ to wr ║
║ ap to ║
║ the ║
║ next ║
║ line ║
║ 1 ║
║ 2 ║
╚═══════╝
";
println!("{}", table.render());
assert_eq!(expected, table.render());
}
#[test]
fn blank_table_style() {
let mut table = Table::new();
table.max_column_width = 40;
table.style = TableStyle::blank();
table.add_row(Row::new(vec![
TableCell::new_with_alignment("This is some centered text", 2, Alignment::Center),
]));
table.add_row(Row::new(vec![
TableCell::new("This is left aligned text"),
TableCell::new_with_alignment("This is right aligned text", 1, Alignment::Right),
]));
table.add_row(Row::new(vec![
TableCell::new("This is left aligned text"),
TableCell::new_with_alignment("This is right aligned text", 1, Alignment::Right),
]));
table.add_row(Row::new(vec![
TableCell::new_with_col_span("This is some really really really really really really really really really that is going to wrap to the next line", 2),
]));
let expected =
"