use std::{
io::{Error, ErrorKind, Write},
iter::repeat,
str::FromStr,
};
use colored::{Color, Colorize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Alignment {
Left,
Right,
Center,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TableConfig {
pub col_width: Vec<usize>,
pub padding: (usize, usize),
pub align: Alignment,
}
impl TableConfig {
pub fn new(col_width: Vec<usize>, padding: (usize, usize), align: Alignment) -> TableConfig {
TableConfig {
col_width,
padding,
align,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Table {
config: TableConfig,
}
impl Table {
pub fn new(config: TableConfig) -> Table {
Table { config }
}
pub fn display_width(text: &str) -> usize {
let width = text.len();
let mut state = 0;
let mut hidden = 0;
for c in text.chars() {
state = match (state, c) {
(0, '\u{1b}') => 1,
(1, '[') => 2,
(1, _) => 0,
(2, 'm') => 3,
_ => state,
};
if state > 1 {
hidden += 1;
}
if state == 3 {
state = 0;
}
}
width - hidden
}
fn align<T: Write + ?Sized>(
out: &mut T,
align: Alignment,
text: &str,
fill: char,
size: usize,
) -> Result<(), Error> {
let text_len = Table::display_width(text);
let mut nfill = if text_len < size { size - text_len } else { 0 };
let n = match align {
Alignment::Left => 0,
Alignment::Right => nfill,
Alignment::Center => nfill / 2,
};
if n > 0 {
out.write_all(repeat(fill).take(n).collect::<String>().as_bytes())?;
nfill -= n;
}
out.write_all(text.as_bytes())?;
if nfill > 0 {
out.write_all(repeat(fill).take(nfill).collect::<String>().as_bytes())?;
}
Ok(())
}
fn render_value_inner<T: Write + ?Sized, F: ToString>(
&self,
out: &mut T,
value: Vec<F>,
col_width: Vec<usize>,
padding: (usize, usize),
align: Alignment,
colored: bool,
) -> Result<usize, Error> {
assert_eq!(value.len(), col_width.len());
let col_width = if colored {
col_width.iter().map(|x| x + 2).collect()
} else {
col_width
};
let mut iter = value.iter().zip(col_width.iter()).peekable();
while let Some((v, w)) = iter.next() {
Table::align(out, align, &v.to_string(), ' ', *w + padding.0 + padding.1)?;
}
Ok(1)
}
fn render_value<T: Write + ?Sized, F: ToString>(
&self,
out: &mut T,
value: Vec<F>,
colored: bool,
) -> Result<usize, Error> {
self.render_value_inner(
out,
value,
self.config.col_width.clone(),
self.config.padding,
self.config.align,
colored,
)
}
pub fn render(&self, values: Vec<&str>, color: Option<Color>) -> String {
let mut writer = StringWriter::new();
if let Some(c) = color {
let values = match c {
Color::Blue => values.iter().map(|v| v.bold().blue()).collect(),
Color::Cyan => values.iter().map(|v| v.bold().blue()).collect(),
Color::Red => values.iter().map(|v| v.bold().red()).collect(),
Color::Green => values.iter().map(|v| v.bold().green()).collect(),
_ => values.iter().map(|v| v.bold()).collect(),
};
self.render_value(&mut writer, values, true).unwrap();
} else {
self.render_value(&mut writer, values, false).unwrap();
}
String::from_str(writer.as_string()).unwrap()
}
}
struct StringWriter {
string: String,
}
impl StringWriter {
pub fn new() -> StringWriter {
StringWriter {
string: String::new(),
}
}
pub fn as_string(&self) -> &str {
&self.string
}
}
impl Write for StringWriter {
fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> {
let string = match std::str::from_utf8(data) {
Ok(s) => s,
Err(e) => {
return Err(std::io::Error::new(
ErrorKind::Other,
format!("Cannot decode utf8 string : {}", e),
))
}
};
self.string.push_str(string);
Ok(data.len())
}
fn flush(&mut self) -> Result<(), std::io::Error> {
Ok(())
}
}