#![allow(dead_code)]
use std::fmt;
#[derive(Debug, Clone, Copy, Default)]
pub enum Alignment {
#[default]
Left,
Right,
Center,
}
#[derive(Debug, Clone, Copy, Default)]
pub enum TableStyle {
#[default]
Simple,
Bordered,
Markdown,
Compact,
}
#[derive(Debug, Clone)]
pub struct Column {
pub header: String,
pub alignment: Alignment,
pub min_width: Option<usize>,
pub max_width: Option<usize>,
}
impl Column {
pub fn new(header: &str) -> Self {
Self {
header: header.to_string(),
alignment: Alignment::Left,
min_width: None,
max_width: None,
}
}
pub fn with_alignment(mut self, alignment: Alignment) -> Self {
self.alignment = alignment;
self
}
pub fn with_min_width(mut self, width: usize) -> Self {
self.min_width = Some(width);
self
}
pub fn with_max_width(mut self, width: usize) -> Self {
self.max_width = Some(width);
self
}
}
pub struct Table {
columns: Vec<Column>,
rows: Vec<Vec<String>>,
style: TableStyle,
}
impl Table {
pub fn new(columns: Vec<Column>) -> Self {
Self { columns, rows: Vec::new(), style: TableStyle::default() }
}
pub fn from_headers(headers: &[&str]) -> Self {
let columns = headers.iter().map(|h| Column::new(h)).collect();
Self::new(columns)
}
pub fn with_style(mut self, style: TableStyle) -> Self {
self.style = style;
self
}
pub fn add_row<I, S>(&mut self, row: I)
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let row_vec: Vec<String> = row.into_iter().map(|s| s.as_ref().to_string()).collect();
self.rows.push(row_vec);
}
pub fn row<I, S>(mut self, row: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.add_row(row);
self
}
fn calculate_widths(&self) -> Vec<usize> {
let mut widths: Vec<usize> = self.columns.iter().map(|c| c.header.len()).collect();
for row in &self.rows {
for (i, cell) in row.iter().enumerate() {
if i < widths.len() {
widths[i] = widths[i].max(cell.len());
}
}
}
for (i, col) in self.columns.iter().enumerate() {
if let Some(min) = col.min_width {
widths[i] = widths[i].max(min);
}
if let Some(max) = col.max_width {
widths[i] = widths[i].min(max);
}
}
widths
}
fn format_cell(&self, content: &str, width: usize, alignment: Alignment) -> String {
let content = if content.len() > width {
format!("{}...", &content[..width.saturating_sub(3)])
} else {
content.to_string()
};
match alignment {
Alignment::Left => format!("{:<width$}", content, width = width),
Alignment::Right => format!("{:>width$}", content, width = width),
Alignment::Center => format!("{:^width$}", content, width = width),
}
}
pub fn render(&self) -> String {
let widths = self.calculate_widths();
let mut output = String::new();
match self.style {
TableStyle::Simple => self.render_simple(&widths, &mut output),
TableStyle::Bordered => self.render_bordered(&widths, &mut output),
TableStyle::Markdown => self.render_markdown(&widths, &mut output),
TableStyle::Compact => self.render_compact(&widths, &mut output),
}
output
}
fn render_simple(&self, widths: &[usize], output: &mut String) {
let header: Vec<String> = self
.columns
.iter()
.enumerate()
.map(|(i, col)| self.format_cell(&col.header, widths[i], col.alignment))
.collect();
output.push_str(&header.join(" "));
output.push('\n');
let separator: Vec<String> = widths.iter().map(|&w| "-".repeat(w)).collect();
output.push_str(&separator.join(" "));
output.push('\n');
for row in &self.rows {
let formatted: Vec<String> = row
.iter()
.enumerate()
.map(|(i, cell)| {
let alignment = self.columns.get(i).map(|c| c.alignment).unwrap_or_default();
let width = *widths.get(i).unwrap_or(&0);
self.format_cell(cell, width, alignment)
})
.collect();
output.push_str(&formatted.join(" "));
output.push('\n');
}
}
fn render_bordered(&self, widths: &[usize], output: &mut String) {
let total_width: usize = widths.iter().sum::<usize>() + widths.len() * 3 + 1;
output.push_str(&"─".repeat(total_width));
output.push('\n');
output.push('│');
for (i, col) in self.columns.iter().enumerate() {
output.push(' ');
output.push_str(&self.format_cell(&col.header, widths[i], col.alignment));
output.push_str(" │");
}
output.push('\n');
output.push_str(&"─".repeat(total_width));
output.push('\n');
for row in &self.rows {
output.push('│');
for (i, cell) in row.iter().enumerate() {
let alignment = self.columns.get(i).map(|c| c.alignment).unwrap_or_default();
let width = *widths.get(i).unwrap_or(&0);
output.push(' ');
output.push_str(&self.format_cell(cell, width, alignment));
output.push_str(" │");
}
output.push('\n');
}
output.push_str(&"─".repeat(total_width));
output.push('\n');
}
fn render_markdown(&self, widths: &[usize], output: &mut String) {
output.push('|');
for (i, col) in self.columns.iter().enumerate() {
output.push(' ');
output.push_str(&self.format_cell(&col.header, widths[i], col.alignment));
output.push_str(" |");
}
output.push('\n');
output.push('|');
for (i, col) in self.columns.iter().enumerate() {
let width = widths[i];
let sep = match col.alignment {
Alignment::Left => format!(":{}-", "-".repeat(width)),
Alignment::Right => format!("-{}:", "-".repeat(width)),
Alignment::Center => format!(":{}:", "-".repeat(width)),
};
output.push_str(&sep);
output.push('|');
}
output.push('\n');
for row in &self.rows {
output.push('|');
for (i, cell) in row.iter().enumerate() {
let alignment = self.columns.get(i).map(|c| c.alignment).unwrap_or_default();
let width = *widths.get(i).unwrap_or(&0);
output.push(' ');
output.push_str(&self.format_cell(cell, width, alignment));
output.push_str(" |");
}
output.push('\n');
}
}
fn render_compact(&self, widths: &[usize], output: &mut String) {
let header: Vec<String> = self
.columns
.iter()
.enumerate()
.map(|(i, col)| self.format_cell(&col.header, widths[i], col.alignment))
.collect();
output.push_str(&header.join(" "));
output.push('\n');
for row in &self.rows {
let formatted: Vec<String> = row
.iter()
.enumerate()
.map(|(i, cell)| {
let alignment = self.columns.get(i).map(|c| c.alignment).unwrap_or_default();
let width = *widths.get(i).unwrap_or(&0);
self.format_cell(cell, width, alignment)
})
.collect();
output.push_str(&formatted.join(" "));
output.push('\n');
}
}
pub fn row_count(&self) -> usize {
self.rows.len()
}
pub fn column_count(&self) -> usize {
self.columns.len()
}
}
impl fmt::Display for Table {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.render())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_table() {
let mut table = Table::from_headers(&["Name", "Age", "City"]);
table.add_row(["Alice", "25", "Beijing"]);
table.add_row(["Bob", "30", "Shanghai"]);
let output = table.render();
assert!(output.contains("Name"));
assert!(output.contains("Alice"));
assert!(output.contains("Bob"));
}
#[test]
fn test_markdown_table() {
let table = Table::from_headers(&["Col1", "Col2"])
.with_style(TableStyle::Markdown)
.row(["A", "B"])
.row(["C", "D"]);
let output = table.render();
assert!(output.contains("|"));
assert!(output.contains("---"));
}
#[test]
fn test_alignment() {
let columns = vec![
Column::new("Left").with_alignment(Alignment::Left),
Column::new("Right").with_alignment(Alignment::Right),
Column::new("Center").with_alignment(Alignment::Center),
];
let mut table = Table::new(columns);
table.add_row(["A", "B", "C"]);
let _ = table.render();
}
#[test]
fn test_chain_methods() {
let table = Table::from_headers(&["X", "Y"])
.with_style(TableStyle::Simple)
.row(["1", "2"])
.row(["3", "4"]);
assert_eq!(table.row_count(), 2);
assert_eq!(table.column_count(), 2);
}
}