use crate::style::{Color, Style};
use crate::unicode::display_width;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoxStyle {
Single,
Double,
Heavy,
Rounded,
Ascii,
}
impl BoxStyle {
fn chars(&self) -> BoxChars {
match self {
BoxStyle::Single => BoxChars {
top_left: '┌',
top_right: '┐',
bottom_left: '└',
bottom_right: '┘',
horizontal: '─',
vertical: '│',
cross: '┼',
t_down: '┬',
t_up: '┴',
t_right: '├',
t_left: '┤',
},
BoxStyle::Double => BoxChars {
top_left: '╔',
top_right: '╗',
bottom_left: '╚',
bottom_right: '╝',
horizontal: '═',
vertical: '║',
cross: '╬',
t_down: '╦',
t_up: '╩',
t_right: '╠',
t_left: '╣',
},
BoxStyle::Heavy => BoxChars {
top_left: '┏',
top_right: '┓',
bottom_left: '┗',
bottom_right: '┛',
horizontal: '━',
vertical: '┃',
cross: '╋',
t_down: '┳',
t_up: '┻',
t_right: '┣',
t_left: '┫',
},
BoxStyle::Rounded => BoxChars {
top_left: '╭',
top_right: '╮',
bottom_left: '╰',
bottom_right: '╯',
horizontal: '─',
vertical: '│',
cross: '┼',
t_down: '┬',
t_up: '┴',
t_right: '├',
t_left: '┤',
},
BoxStyle::Ascii => BoxChars {
top_left: '+',
top_right: '+',
bottom_left: '+',
bottom_right: '+',
horizontal: '-',
vertical: '|',
cross: '+',
t_down: '+',
t_up: '+',
t_right: '+',
t_left: '+',
},
}
}
}
#[derive(Debug, Clone, Copy)]
struct BoxChars {
top_left: char,
top_right: char,
bottom_left: char,
bottom_right: char,
horizontal: char,
vertical: char,
cross: char,
t_down: char,
t_up: char,
t_right: char,
t_left: char,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Alignment {
Left,
Right,
Center,
}
#[derive(Debug)]
pub struct Table {
headers: Vec<String>,
rows: Vec<Vec<String>>,
col_widths: Vec<usize>,
col_alignments: Vec<Alignment>,
box_style: BoxStyle,
indent: usize,
has_header_separator: bool,
has_footer_separator: bool,
}
impl Table {
pub fn new(headers: Vec<&str>) -> Self {
let col_widths: Vec<usize> = headers.iter().map(|h| display_width(h)).collect();
let headers: Vec<String> = headers.into_iter().map(|h| h.to_string()).collect();
let col_count = col_widths.len();
Table {
headers,
rows: Vec::new(),
col_widths,
col_alignments: vec![Alignment::Left; col_count],
box_style: BoxStyle::Single,
indent: 3,
has_header_separator: true,
has_footer_separator: false,
}
}
pub fn add_row(&mut self, row: Vec<&str>) {
for (i, cell) in row.iter().enumerate() {
if i < self.col_widths.len() {
self.col_widths[i] = self.col_widths[i].max(display_width(cell));
}
}
self.rows
.push(row.into_iter().map(|s| s.to_string()).collect());
}
pub fn set_box_style(&mut self, style: BoxStyle) -> &mut Self {
self.box_style = style;
self
}
pub fn set_indent(&mut self, indent: usize) -> &mut Self {
self.indent = indent;
self
}
pub fn set_column_alignment(&mut self, col_index: usize, alignment: Alignment) -> &mut Self {
if col_index < self.col_alignments.len() {
self.col_alignments[col_index] = alignment;
}
self
}
pub fn set_header_separator(&mut self, enabled: bool) -> &mut Self {
self.has_header_separator = enabled;
self
}
pub fn set_footer_separator(&mut self, enabled: bool) -> &mut Self {
self.has_footer_separator = enabled;
self
}
fn format_cell(&self, text: &str, width: usize, alignment: Alignment) -> String {
let text_width = display_width(text);
let padding = width.saturating_sub(text_width);
match alignment {
Alignment::Left => {
format!("{}{}", text, " ".repeat(padding))
}
Alignment::Right => {
format!("{}{}", " ".repeat(padding), text)
}
Alignment::Center => {
let left_pad = padding / 2;
let right_pad = padding - left_pad;
format!("{}{}{}", " ".repeat(left_pad), text, " ".repeat(right_pad))
}
}
}
fn print_line(&self, left: char, _mid: char, right: char, junction: char) {
let chars = self.box_style.chars();
print!("{}", " ".repeat(self.indent));
print!("{}", left);
for (i, width) in self.col_widths.iter().enumerate() {
print!("{}", chars.horizontal.to_string().repeat(width + 2));
if i < self.col_widths.len() - 1 {
print!("{}", junction);
}
}
println!("{}", right);
}
pub fn print(&self) {
let chars = self.box_style.chars();
self.print_line(chars.top_left, chars.t_down, chars.top_right, chars.t_down);
print!("{}", " ".repeat(self.indent));
print!("{}", chars.vertical);
for (i, (header, width)) in self.headers.iter().zip(&self.col_widths).enumerate() {
let formatted = self.format_cell(header, *width, self.col_alignments[i]);
print!(" {} ", formatted);
print!("{}", chars.vertical);
}
println!();
if self.has_header_separator {
self.print_line(chars.t_right, chars.cross, chars.t_left, chars.cross);
}
for (idx, row) in self.rows.iter().enumerate() {
print!("{}", " ".repeat(self.indent));
print!("{}", chars.vertical);
for (i, (cell, width)) in row.iter().zip(&self.col_widths).enumerate() {
let formatted = self.format_cell(cell, *width, self.col_alignments[i]);
print!(" {} ", formatted);
print!("{}", chars.vertical);
}
println!();
if self.has_footer_separator && idx == self.rows.len() - 2 {
self.print_line(chars.t_right, chars.cross, chars.t_left, chars.cross);
}
}
self.print_line(
chars.bottom_left,
chars.t_up,
chars.bottom_right,
chars.t_up,
);
}
}
pub fn draw_box(text: &str, style: BoxStyle, color: Color) {
let chars = style.chars();
let width = display_width(text);
let top = format!(
"{}{}{}",
chars.top_left,
chars.horizontal.to_string().repeat(width + 2),
chars.top_right
);
let middle = format!("{} {} {}", chars.vertical, text, chars.vertical);
let bottom = format!(
"{}{}{}",
chars.bottom_left,
chars.horizontal.to_string().repeat(width + 2),
chars.bottom_right
);
println!("{}", color.paint(&top).style(Style::Bold));
println!("{}", color.paint(&middle).style(Style::Bold));
println!("{}", color.paint(&bottom).style(Style::Bold));
}
pub fn draw_separator(width: usize, char: &str, color: Color) {
println!("{}", color.paint(char.repeat(width)).style(Style::Bold));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_table_creation() {
let table = Table::new(vec!["Col1", "Col2", "Col3"]);
assert_eq!(table.headers.len(), 3);
assert_eq!(table.col_widths.len(), 3);
assert_eq!(table.rows.len(), 0);
}
#[test]
fn test_add_row() {
let mut table = Table::new(vec!["Name", "Age"]);
table.add_row(vec!["Alice", "25"]);
table.add_row(vec!["Bob", "30"]);
assert_eq!(table.rows.len(), 2);
}
#[test]
fn test_column_width_calculation() {
let mut table = Table::new(vec!["Name", "Age"]);
assert_eq!(table.col_widths[0], 4);
table.add_row(vec!["Alexander", "25"]);
assert_eq!(table.col_widths[0], 9); }
#[test]
fn test_box_style_setting() {
let mut table = Table::new(vec!["Col1"]);
table.set_box_style(BoxStyle::Double);
assert_eq!(table.box_style, BoxStyle::Double);
}
#[test]
fn test_alignment_setting() {
let mut table = Table::new(vec!["Name", "Age", "Salary"]);
table.set_column_alignment(1, Alignment::Right);
assert_eq!(table.col_alignments[1], Alignment::Right);
}
#[test]
fn test_cell_formatting_left() {
let table = Table::new(vec!["Test"]);
let result = table.format_cell("Hi", 5, Alignment::Left);
assert_eq!(result, "Hi ");
}
#[test]
fn test_cell_formatting_right() {
let table = Table::new(vec!["Test"]);
let result = table.format_cell("Hi", 5, Alignment::Right);
assert_eq!(result, " Hi");
}
#[test]
fn test_cell_formatting_center() {
let table = Table::new(vec!["Test"]);
let result = table.format_cell("Hi", 6, Alignment::Center);
assert_eq!(result, " Hi ");
}
#[test]
fn test_box_chars_single() {
let chars = BoxStyle::Single.chars();
assert_eq!(chars.top_left, '┌');
assert_eq!(chars.horizontal, '─');
}
#[test]
fn test_box_chars_double() {
let chars = BoxStyle::Double.chars();
assert_eq!(chars.top_left, '╔');
assert_eq!(chars.horizontal, '═');
}
#[test]
fn test_box_chars_ascii() {
let chars = BoxStyle::Ascii.chars();
assert_eq!(chars.top_left, '+');
assert_eq!(chars.horizontal, '-');
}
}