use std::{
cmp,
fmt::{Display, Write},
io,
};
const CORNER_STR: &'static str = "+";
const HORIZ_BORDER_STR: &'static str = "-";
const VERT_BORDER_STR: &'static str = "|";
const SPACE_STR: &'static str = " ";
const NEW_LINE_STR: &'static str = "\n";
pub fn render<W, T, R, C>(writer: &mut W, data: T) -> io::Result<()>
where
W: io::Write,
T: AsRef<[R]>,
R: AsRef<[C]>,
C: Display,
{
let widths = widths(&data);
let data = data.as_ref();
render_border_line(writer, &widths)?;
for row in data.iter() {
let row = row.as_ref();
render_text_line(writer, &widths, row)?;
render_border_line(writer, &widths)?;
}
Ok(())
}
fn widths<T, R, C>(data: T) -> Vec<usize>
where
T: AsRef<[R]>,
R: AsRef<[C]>,
C: Display,
{
let mut string_buf = String::new();
let data = data.as_ref();
if data.len() == 0 {
return vec![];
}
let row_len = data[0].as_ref().len();
let mut widths = vec![0; row_len];
for row in data.iter() {
let row = row.as_ref();
if row_len != row.len() {
panic!("rows must be the same length");
}
for (idx, cell) in row.iter().enumerate() {
string_buf.clear();
write!(string_buf, "{}", cell).unwrap(); widths[idx] = cmp::max(widths[idx], string_buf.len());
}
}
widths
}
fn render_border_line<W: io::Write>(writer: &mut W, lengths: &[usize]) -> io::Result<()> {
if lengths.len() == 0 || lengths[0] == 0 {
return Ok(());
}
write!(writer, "{}", CORNER_STR)?;
for len in lengths {
for _ in 0..(*len + 2) {
write!(writer, "{}", HORIZ_BORDER_STR)?;
}
write!(writer, "{}", CORNER_STR)?;
}
write!(writer, "\n")
}
fn render_text_line<W, C>(writer: &mut W, lengths: &[usize], row: &[C]) -> io::Result<()>
where
W: io::Write,
C: Display,
{
if lengths.len() == 0 || lengths[0] == 0 {
return Ok(());
}
let mut string_buf = String::new();
write!(writer, "{}", VERT_BORDER_STR)?;
for (cell, len) in row.iter().zip(lengths.iter()) {
string_buf.clear();
write!(string_buf, "{}", cell).unwrap(); let extra = len - string_buf.len();
write!(writer, "{}{}", SPACE_STR, string_buf)?;
for _ in 0..extra + 1 {
write!(writer, "{}", SPACE_STR)?;
}
write!(writer, "{}", VERT_BORDER_STR)?;
}
write!(writer, "{}", NEW_LINE_STR)?;
Ok(())
}
#[cfg(test)]
mod tests {
#[test]
fn render() {
let tables = vec![
(vec![], &b""[..]),
(vec![vec![]], &b""[..]),
(
vec![vec!["single", "line", "a"], vec!["second", "lines", "a"]],
&b"\
+--------+-------+---+
| single | line | a |
+--------+-------+---+
| second | lines | a |
+--------+-------+---+
"[..],
),
];
for (table, result) in tables {
let mut out = Vec::new();
super::render(&mut out, &table).unwrap();
assert_eq!(out, &result[..], "{:#?}", table);
}
}
}