use crate::detect;
use crate::theme;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Align {
Left,
Right,
}
pub struct Column {
pub header: String,
pub align: Align,
pub min_width: usize,
}
impl Column {
pub fn new(header: &str) -> Self {
Self {
header: header.to_string(),
align: Align::Left,
min_width: 0,
}
}
pub fn right(header: &str) -> Self {
Self {
header: header.to_string(),
align: Align::Right,
min_width: 0,
}
}
pub fn width(mut self, w: usize) -> Self {
self.min_width = w;
self
}
}
pub fn render_table(columns: &[Column], rows: &[Vec<String>]) {
if columns.is_empty() {
return;
}
let mut widths: Vec<usize> = columns
.iter()
.map(|c| c.header.len().max(c.min_width))
.collect();
for row in rows {
for (i, cell) in row.iter().enumerate() {
if i < widths.len() {
widths[i] = widths[i].max(cell.len());
}
}
}
let styled = detect::should_style();
let header_line: String = columns
.iter()
.enumerate()
.map(|(i, col)| pad(&col.header, widths[i], col.align))
.collect::<Vec<_>>()
.join(" ");
if styled {
let color = theme::COLOR_PRIMARY.resolve();
let code = color_to_fg(color);
eprintln!(" {code}\x1b[1m{header_line}\x1b[0m");
} else {
eprintln!(" {header_line}");
}
let sep: String = widths
.iter()
.map(|w| "─".repeat(*w))
.collect::<Vec<_>>()
.join("──");
if styled {
let dim = "\x1b[2m";
eprintln!(" {dim}{sep}\x1b[0m");
} else {
eprintln!(" {sep}");
}
for (row_idx, row) in rows.iter().enumerate() {
let line: String = columns
.iter()
.enumerate()
.map(|(i, col)| {
let cell = row.get(i).map(|s| s.as_str()).unwrap_or("");
pad(cell, widths[i], col.align)
})
.collect::<Vec<_>>()
.join(" ");
if styled && row_idx % 2 == 1 {
eprintln!(" \x1b[2m{line}\x1b[0m");
} else {
eprintln!(" {line}");
}
}
}
fn pad(text: &str, width: usize, align: Align) -> String {
match align {
Align::Left => format!("{:<width$}", text, width = width),
Align::Right => format!("{:>width$}", text, width = width),
}
}
fn color_to_fg(color: ratatui::style::Color) -> String {
use ratatui::style::Color;
match color {
Color::Rgb(r, g, b) => format!("\x1b[38;2;{r};{g};{b}m"),
Color::Reset => String::new(),
_ => String::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pad_left() {
assert_eq!(pad("hi", 5, Align::Left), "hi ");
}
#[test]
fn pad_right() {
assert_eq!(pad("hi", 5, Align::Right), " hi");
}
#[test]
fn empty_columns_no_panic() {
render_table(&[], &[]);
}
#[test]
fn column_builder() {
let c = Column::new("Name").width(10);
assert_eq!(c.header, "Name");
assert_eq!(c.min_width, 10);
assert_eq!(c.align, Align::Left);
}
#[test]
fn column_right() {
let c = Column::right("Size");
assert_eq!(c.align, Align::Right);
}
}