#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Padding {
left: usize,
right: usize,
fill: char,
}
impl Padding {
pub const fn new(left: usize, right: usize, fill: char) -> Self {
Self { left, right, fill }
}
pub const fn uniform(size: usize) -> Self {
Self { left: size, right: size, fill: ' ' }
}
pub const fn uniform_with_fill(size: usize, fill: char) -> Self {
Self { left: size, right: size, fill }
}
pub const fn with_left(mut self, left: usize) -> Self {
self.left = left;
self
}
pub const fn with_right(mut self, right: usize) -> Self {
self.right = right;
self
}
pub const fn with_fill(mut self, fill: char) -> Self {
self.fill = fill;
self
}
}
impl Default for Padding {
fn default() -> Self {
Self { left: 1, right: 1, fill: ' ', }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Borders {
horizontal: Option<char>,
vertical: Option<char>,
corner: Option<char>,
}
impl Borders {
pub fn new(horizontal: impl Into<Option<char>>, vertical: impl Into<Option<char>>, corner: impl Into<Option<char>>) -> Self {
Self { horizontal: horizontal.into(), vertical: vertical.into(), corner: corner.into() }
}
pub const EMPTY: Self = Self { horizontal: None, vertical: None, corner: None, };
pub const SINGLE: Self = Self { horizontal: Some('─'), vertical: Some('│'), corner: Some('┼'), };
pub const DOUBLE: Self = Self { horizontal: Some('═'), vertical: Some('║'), corner: Some('╬'), };
pub const DASHED: Self = Self { horizontal: Some('╌'), vertical: Some('╎'), corner: Some('┼'), };
pub const DOTTED: Self = Self { horizontal: Some('┄'), vertical: Some('┊'), corner: Some('┼'), };
pub const ASCII: Self = Self { horizontal: Some('-'), vertical: Some('|'), corner: Some('+'), };
pub fn horizontal(mut self, c: impl Into<Option<char>>) -> Self {
self.horizontal = c.into();
self
}
pub fn vertical(mut self, c: impl Into<Option<char>>) -> Self {
self.vertical = c.into();
self
}
pub fn corner(mut self, c: impl Into<Option<char>>) -> Self {
self.corner = c.into();
self
}
}
impl Default for Borders {
fn default() -> Self {
Self::SINGLE
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Alignment {
#[default]
Left,
Center,
Right,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Table {
headers: Vec<String>,
rows: Vec<Vec<String>>,
alignment: Alignment,
padding: Padding,
borders: Borders,
}
impl Table {
pub fn new(headers: Vec<&str>) -> Self {
Table {
headers: headers.into_iter().map(String::from).collect(),
rows: Vec::new(),
alignment: Alignment::Left,
padding: Padding::default(),
borders: Borders::ASCII,
}
}
pub fn with_alignment(mut self, alignment: Alignment) -> Self {
self.alignment = alignment;
self
}
pub fn with_borders(mut self, borders: Borders) -> Self {
self.borders = borders;
self
}
pub fn borderless(mut self) -> Self {
self.borders = Borders::EMPTY;
self
}
pub fn with_padding(mut self, padding: Padding) -> Self {
self.padding = padding;
self
}
pub fn add_row(&mut self, row: Vec<&str>) {
self.rows.push(row.into_iter().map(String::from).collect());
}
fn column_widths(&self) -> Vec<usize> {
let mut widths: Vec<usize> = self.headers.iter().map(|h| h.chars().count()).collect();
for row in &self.rows {
for (i, cell) in row.iter().enumerate() {
if i >= widths.len() {
widths.push(cell.chars().count());
} else {
widths[i] = widths[i].max(cell.chars().count());
}
}
}
widths
}
fn pad(&self, text: &str, width: usize) -> String {
let len = text.chars().count();
if len >= width {
return text.to_string();
}
let diff = width - len;
let fill = self.padding.fill.to_string();
match self.alignment {
Alignment::Left => format!("{}{}", text, fill.repeat(diff)),
Alignment::Right => format!("{}{}", fill.repeat(diff), text),
Alignment::Center => {
let left = diff / 2;
let right = diff - left;
format!("{}{}{}", fill.repeat(left), text, fill.repeat(right))
}
}
}
pub fn render(&self) -> String {
let widths = self.column_widths();
let mut out = String::new();
let render_separator = |out: &mut String, | {
if let Some(corner) = self.borders.corner {
out.push(corner);
}
for w in &widths {
if let Some(horizontal_border) = self.borders.horizontal {
out.push_str(&horizontal_border.to_string().repeat(w + self.padding.left + self.padding.right));
}
if let Some(corner) = self.borders.corner {
out.push(corner);
}
}
out.push('\n');
};
render_separator(&mut out);
if let Some(vertical_border) = self.borders.vertical {
out.push(vertical_border);
}
for (i, header) in self.headers.iter().enumerate() {
out.push_str(&self.padding.fill.to_string().repeat(self.padding.left));
out.push_str(&self.pad(header, widths[i]));
out.push_str(&self.padding.fill.to_string().repeat(self.padding.right));
if let Some(vertical_border) = self.borders.vertical {
out.push(vertical_border);
}
}
out.push('\n');
render_separator(&mut out);
for row in &self.rows {
if let Some(vertical_border) = self.borders.vertical {
out.push(vertical_border);
}
for (i, w) in widths.iter().enumerate() {
let cell = row.get(i).map(String::as_str).unwrap_or("");
out.push_str(&self.padding.fill.to_string().repeat(self.padding.left));
out.push_str(&self.pad(cell, *w));
out.push_str(&self.padding.fill.to_string().repeat(self.padding.right));
if let Some(vertical_border) = self.borders.vertical {
out.push(vertical_border);
}
}
out.push('\n');
}
render_separator(&mut out);
out
}
pub fn print(&self) {
print!("{}", self.render());
}
}
impl std::fmt::Display for Table {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.render())
}
}