//! # Stybulate : Tabulate with Style!
//! This library creates tables in ASCII with styled borders
//!
//! # References
//! It was inspired by the Python package <https://pypi.org/project/tabulate/>
//!
//! # Examples
//! ```
//! use stybulate::{Table, Style, Cell, Headers};
//! let result = Table::new(
//! Style::Fancy,
//! vec![
//! vec![Cell::from("answer"), Cell::Int(42)],
//! vec![Cell::from("pi"), Cell::Float(3.1415)],
//! ],
//! Some(Headers::from(vec!["strings", "numbers"])),
//! ).tabulate();
//! let expected = vec![
//! "╒═══════════╤═══════════╕",
//! "│ strings │ numbers │",
//! "╞═══════════╪═══════════╡",
//! "│ answer │ 42 │",
//! "├───────────┼───────────┤",
//! "│ pi │ 3.1415 │",
//! "╘═══════════╧═══════════╛",
//! ].join("\n");
//! assert_eq!(expected, result);
//! ```
#![warn(missing_docs)]
use std::cmp;
use std::collections::HashMap;
use unicode_width::UnicodeWidthStr;
mod style;
pub use style::{Align, Style};
mod unstyle;
pub use unstyle::{AsciiEscapedString, Unstyle};
mod cell;
pub use cell::Cell;
// constants
const MIN_PADDING: usize = 2;
/// The Headers structure is a list of headers (per column)
/// # Example
/// ```
/// use stybulate::{AsciiEscapedString, Headers};
/// // simple example with only strings
/// let simple = Headers::from(vec!["foo", "bar"]);
/// // more elaborated example with a mix of a styled string and a simple string
/// let mut with_style = Headers::with_capacity(2);
/// with_style
/// .push(AsciiEscapedString::from("\x1b[1;35mfoo\x1b[0m bar"))
/// .push(String::from("baz"));
/// ```
#[derive(Default)]
pub struct Headers {
headers: Vec<Box<dyn Unstyle>>,
}
impl Headers {
/// Headers constructor: creates an empty header
pub fn new() -> Self {
Default::default()
}
/// Headers constructor from a vec of `&str`
pub fn from(headers: Vec<&str>) -> Self {
let mut unstyle_headers: Vec<Box<dyn Unstyle>> = Vec::with_capacity(headers.len());
for header in headers.iter() {
unstyle_headers.push(Box::new(String::from(*header)));
}
Self {
headers: unstyle_headers,
}
}
/// Headers constructor with capacity
pub fn with_capacity(capacity: usize) -> Self {
Self {
headers: Vec::with_capacity(capacity),
}
}
/// Add a header to the Headers
pub fn push<H: Unstyle + 'static>(&mut self, header: H) -> &mut Self {
self.headers.push(Box::new(header));
self
}
fn len(&self) -> usize {
self.headers.len()
}
#[allow(clippy::borrowed_box)]
fn get(&self, i: usize) -> Option<&Box<dyn Unstyle>> {
self.headers.get(i)
}
#[allow(clippy::borrowed_box)]
fn to_ref_vec(&self) -> Vec<&Box<dyn Unstyle>> {
self.headers.iter().collect()
}
}
/// The Table structure
/// # Example
/// ```
/// use::stybulate::*;
/// let mut table = Table::new(
/// Style::Fancy,
/// vec![
/// vec![Cell::from("answer"), Cell::Int(42)],
/// vec![Cell::from("pi"), Cell::Float(3.1415)],
/// ],
/// Some(Headers::from(vec!["strings", "numbers"]))
/// );
/// table.set_align(Align::Center, Align::Right);
/// ```
pub struct Table<'a> {
style: Style,
str_align: Align,
num_align: Align,
contents: Vec<Vec<Cell<'a>>>,
headers: Option<Headers>,
#[cfg(feature = "ansi_term_style")]
border_style: Option<ansi_term::Style>,
}
impl<'a> Table<'a> {
/// Table constructor with default alignments (`Align::Left` for strings and `Align::Decimal` for numbers)
pub fn new(style: Style, contents: Vec<Vec<Cell<'a>>>, headers: Option<Headers>) -> Self {
Self {
style,
str_align: Align::Left,
num_align: Align::Decimal,
#[cfg(feature = "ansi_term_style")]
border_style: None,
contents,
headers,
}
}
/// Set the table alignments (defaults are `Align::Left` for strings and `Align::Decimal` for numbers)
/// # Panics
/// Panics if str_align is equal to `Align::Decimal`
pub fn set_align(&mut self, str_align: Align, num_align: Align) {
if str_align == Align::Decimal {
panic!("str_align should not be set to Decimal, only num_align can");
}
self.str_align = str_align;
self.num_align = num_align;
}
#[cfg(feature = "ansi_term_style")]
/// Set the borders style
/// # Feature
/// Needs feature `ansi_term_style`.
pub fn set_border_style(&mut self, style: ansi_term::Style) {
self.border_style = Some(style);
}
/// Creates the table as a `String`
pub fn tabulate(&self) -> String {
let style = &self.style;
let headers = &self.headers;
let contents = &self.contents;
let str_align = &self.str_align;
let num_align = &self.num_align;
#[allow(unused_mut)]
let mut fmt = style.to_format();
#[cfg(feature = "ansi_term_style")]
{
if let Some(style) = self.border_style {
fmt.apply_style(style);
}
}
// number of columns
let header_len = if let Some(h) = headers { h.len() } else { 0 };
let col_nb = cmp::max(
header_len,
*contents.iter().map(Vec::len).max().get_or_insert(0),
);
// column specs = [0]: true if only made of numbers & [1]: digits offset
let col_spec = get_col_specs(col_nb, contents);
// max width of the content of each column
let col_width = get_col_width(col_nb, headers, contents, &col_spec, num_align);
// Build the lines
let mut lines = vec![];
// lineabove
if !(headers.is_some() && fmt.hidelineaboveifheader) {
if let Some(lineabove) = fmt.lineabove {
lines.push(create_line(&lineabove, &col_width));
}
}
if let Some(headers) = headers {
// headerrow
let headers: Vec<&Box<dyn Unstyle>> = headers.to_ref_vec();
for data in create_data_lines(&headers, str_align, num_align, &col_width, &col_spec) {
lines.push(create_data_line(&fmt.headerrow, col_nb, &data));
}
// linebelowheader
if let Some(linebelowheader) = fmt.linebelowheader {
lines.push(create_line(&linebelowheader, &col_width));
}
}
// loop on contents
for (i, content) in contents.iter().enumerate() {
// linebetweenrows
if i != 0 {
if let Some(linebetweenrows) = fmt.linebetweenrows.clone() {
lines.push(create_line(&linebetweenrows, &col_width));
}
}
// datarow
let mut unstylable_content = Vec::with_capacity(content.len());
let mut temp_unstyle_store = HashMap::new();
let mut temp_strings_store = HashMap::new();
for (col, cell) in content.iter().enumerate() {
if let Some(u) = cell.to_unstylable() {
temp_unstyle_store.insert(col, u);
} else {
temp_strings_store.insert(
col,
Box::new(cell.to_string_with_precision(col_spec[col].1).unwrap())
as Box<dyn Unstyle>,
);
}
}
for col in 0..col_nb {
if let Some(u) = temp_unstyle_store.get(&col) {
unstylable_content.push(*u);
} else {
unstylable_content.push(temp_strings_store.get(&col).unwrap());
}
}
for data in create_data_lines(
&unstylable_content,
str_align,
num_align,
&col_width,
&col_spec,
) {
lines.push(create_data_line(&fmt.datarow, col_nb, &data));
}
}
// linebelow
if !(headers.is_some() && fmt.hidelinebelowifheader) {
if let Some(linebelow) = fmt.linebelow {
lines.push(create_line(&linebelow, &col_width));
}
}
// finally join all lines
lines.join("\n")
}
}
// --------------------------- Private ---------------------------
fn get_col_width<'a>(
col_nb: usize,
headers: &Option<Headers>,
contents: &[Vec<Cell<'a>>],
col_spec: &[(bool, usize)],
num_align: &Align,
) -> Vec<usize> {
let mut col_width = vec![0; col_nb];
for col in 0..col_nb {
let mut max = 0;
if let Some(headers) = headers {
if let Some(h) = headers.get(col) {
max = *h
.unstyle()
.split('\n')
.map(|s| UnicodeWidthStr::width(s as &str))
.max()
.get_or_insert(0)
+ MIN_PADDING;
}
}
for row in contents.iter() {
if let Some(c) = row.get(col) {
let width = if col_spec[col].0 /* a number */ && num_align == &Align::Decimal && col_spec[col].1 > 0
{
c.to_string_with_precision(col_spec[col].1).unwrap().len()
} else if let Some(u) = c.to_unstylable() {
*u.unstyle()
.split('\n')
.map(|s| UnicodeWidthStr::width(s as &str))
.max()
.get_or_insert(0)
} else {
c.to_string().unwrap().len()
};
max = cmp::max(width, max);
}
}
col_width[col] = max;
}
col_width
}
fn get_col_specs(col_nb: usize, contents: &[Vec<Cell>]) -> Vec<(bool, usize)> {
let mut col_spec = vec![(false, 0); col_nb];
for (col, spec) in col_spec.iter_mut().enumerate().take(col_nb) {
let mut max = 0;
let mut all = true;
for row in contents.iter() {
if let Some(cell) = row.get(col) {
if cell.is_a_number() {
max = cmp::max(max, cell.digits_len());
} else {
all = false;
}
}
}
*spec = (all, max);
}
col_spec
}
fn create_line(line: &style::Line, col_width: &[usize]) -> String {
(line.begin.clone()
+ &col_width
.iter()
.map(|w| line.hline.repeat(*w))
.collect::<Vec<String>>()
.join(&line.sep)
+ &line.end)
.trim_end()
.to_string()
}
fn create_data_line(row: &style::DataRow, col_nb: usize, content: &[String]) -> String {
let mut v = Vec::with_capacity(col_nb);
for col in 0..col_nb {
if let Some(c) = content.get(col) {
v.push(String::from(c));
} else {
v.push(String::from(""));
}
}
(row.begin.clone() + &v.join(&row.sep) + &row.end)
.trim_end()
.to_string()
}
#[allow(clippy::borrowed_box)]
fn format_unstylable<'a>(
word: &Box<dyn Unstyle + 'a>,
line_idx: usize,
align: &Align,
width: usize,
) -> String {
if let Some(unstyled_word) = word.unstyle().split('\n').nth(line_idx) {
let word = word.to_string();
let word = word
.split('\n')
.nth(line_idx)
.expect("unstyled word can't have more \\n than styled one");
let width =
width - (UnicodeWidthStr::width(unstyled_word as &str) - unstyled_word.chars().count());
let formatted = match align {
Align::Right => format!("{:>width$}", unstyled_word, width = width),
Align::Left => format!("{:<width$}", unstyled_word, width = width),
Align::Center => format!("{:^width$}", unstyled_word, width = width),
Align::Decimal => {
let mut out = format!("{:>width$}", unstyled_word, width = width);
if let Some(dot) = out.rfind('.') {
if out[(dot + 1)..].bytes().all(|c| c == b'0') {
out.replace_range(dot.., &" ".repeat(out.len() - dot));
} else {
for i in (dot + 1..out.len()).rev() {
if out.as_bytes()[i] == b'0' {
out.replace_range(i..i + 1, " ");
}
}
}
}
out
}
};
if unstyled_word != word {
formatted.replace(&unstyled_word, word)
} else {
formatted
}
} else {
" ".repeat(width)
}
}
#[allow(clippy::borrowed_box)]
fn create_data_lines<'a>(
content: &[&Box<dyn Unstyle + 'a>],
str_align: &Align,
num_align: &Align,
col_width: &[usize],
col_spec: &[(bool, usize)],
) -> Vec<Vec<String>> {
let lines_nb = content.iter().map(|u| u.nb_of_lines()).max().unwrap();
let mut lines = Vec::with_capacity(lines_nb);
for i in 0..lines_nb {
let formatted: Vec<_> = content
.iter()
.enumerate()
.map(|(col, text)| {
let align = if col_spec[col].0 {
// numbers only
&num_align
} else {
// strings only
&str_align
};
format_unstylable(text, i, align, col_width[col])
})
.collect();
lines.push(formatted);
}
lines
}
// --------------------------- Tests ---------------------------
#[cfg(test)]
mod tests {
use super::*;
fn headerless(style: Style) -> Table<'static> {
Table::new(
style,
vec![
vec![Cell::from("spam"), Cell::Float(41.9999)],
vec![Cell::from("eggs"), Cell::Int(451)],
],
None,
)
}
fn table(style: Style) -> Table<'static> {
Table::new(
style.clone(),
headerless(style).contents,
Some(Headers::from(vec!["strings", "numbers"])),
)
}
fn multiline_headerless(style: Style) -> Table<'static> {
let mut table = Table::new(
style,
vec![
vec![Cell::from("foo bar\nbaz\nbau"), Cell::from("hello")],
vec![Cell::from(""), Cell::from("multiline\nworld")],
],
None,
);
table.set_align(Align::Center, Align::Right);
table
}
fn multiline(style: Style) -> Table<'static> {
Table::new(
style,
vec![vec![Cell::Int(2), Cell::from("foo\nbar")]],
Some(Headers::from(vec!["more\nspam eggs", "more spam\n& eggs"])),
)
}
fn multiline_empty_cells(style: Style) -> Table<'static> {
Table::new(
style.clone(),
vec![
vec![Cell::Int(1), Cell::from(""), Cell::from("")],
vec![
Cell::Int(2),
Cell::from("very long data"),
Cell::from("fold\nthis"),
],
],
Some(Headers::from(vec!["hdr", "data", "fold"])),
)
}
fn multiline_empty_cells_headerless(style: Style) -> Table<'static> {
Table::new(
style,
vec![
vec![Cell::Int(0), Cell::from(""), Cell::from("")],
vec![Cell::Int(1), Cell::from(""), Cell::from("")],
vec![
Cell::Int(2),
Cell::from("very long data"),
Cell::from("fold\nthis"),
],
],
None,
)
}
#[test]
fn plain() {
//Output: plain with headers
let result = table(Style::Plain).tabulate();
let expected = vec![
"strings numbers",
"spam 41.9999",
"eggs 451",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn plain_headerless() {
//Output: plain without headers
let result = headerless(Style::Plain).tabulate();
let expected = vec!["spam 41.9999", "eggs 451"].join("\n");
assert_eq!(expected, result);
}
#[test]
fn plain_multiline_headerless() {
//Output: plain with multiline cells without headers
let result = multiline_headerless(Style::Plain).tabulate();
let expected = vec![
"foo bar hello",
" baz",
" bau",
" multiline",
" world",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn plain_multiline() {
//Output: plain with multiline cells with headers
let result = multiline(Style::Plain).tabulate();
let expected = vec![
" more more spam",
" spam eggs & eggs",
" 2 foo",
" bar",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn plain_multiline_with_empty_cells() {
//Output: plain with multiline cells and empty cells with headers
let result = multiline_empty_cells(Style::Plain).tabulate();
let expected = vec![
" hdr data fold",
" 1",
" 2 very long data fold",
" this",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn plain_multiline_with_empty_cells_headerless() {
//Output: plain with multiline cells and empty cells without headers
let result = multiline_empty_cells_headerless(Style::Plain).tabulate();
let expected = vec![
"0",
"1",
"2 very long data fold",
" this",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn simple() {
//Output: simple with headers
let result = table(Style::Simple).tabulate();
let expected = vec![
"strings numbers",
"--------- ---------",
"spam 41.9999",
"eggs 451",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn simple_multiline_2() {
//Output: simple with multiline cells
let mut table = Table::new(
Style::Simple,
vec![
vec![Cell::from("foo"), Cell::from("bar")],
vec![Cell::from("spam"), Cell::from("multiline\nworld")],
],
Some(Headers::from(vec!["key", "value"])),
);
table.set_align(Align::Center, Align::Right);
let result = table.tabulate();
let expected = vec![
" key value",
"----- ---------",
" foo bar",
"spam multiline",
" world",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn simple_headerless() {
//Output: simple without headers
let result = headerless(Style::Simple).tabulate();
let expected = vec![
"---- --------",
"spam 41.9999",
"eggs 451",
"---- --------",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn simple_multiline_headerless() {
//Output: simple with multiline cells without headers
let result = multiline_headerless(Style::Simple).tabulate();
let expected = vec![
"------- ---------",
"foo bar hello",
" baz",
" bau",
" multiline",
" world",
"------- ---------",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn simple_multiline() {
//Output: simple with multiline cells with headers
let result = multiline(Style::Simple).tabulate();
let expected = vec![
" more more spam",
" spam eggs & eggs",
"----------- -----------",
" 2 foo",
" bar",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn simple_multiline_ascii_escaped() {
//Output: simple with multiline headers and colors (ascii escape)
let mut headers = Headers::with_capacity(2);
headers
.push(AsciiEscapedString::from("more\nspam \x1b[31meggs\x1b[0m"))
.push(String::from("more spam\n& eggs"));
let result = Table::new(
Style::Simple,
vec![vec![Cell::Int(2), Cell::from("foo\nbar")]],
Some(headers),
)
.tabulate();
let expected = vec![
" more more spam",
" spam \x1b[31meggs\x1b[0m & eggs",
"----------- -----------",
" 2 foo",
" bar",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn simple_multiline_with_empty_cells() {
//Output: simple with multiline cells and empty cells with headers
let result = multiline_empty_cells(Style::Simple).tabulate();
let expected = vec![
" hdr data fold",
"----- -------------- ------",
" 1",
" 2 very long data fold",
" this",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn simple_multiline_with_empty_cells_headerless() {
//Output: simple with multiline cells and empty cells without headers
let result = multiline_empty_cells_headerless(Style::Simple).tabulate();
let expected = vec![
"- -------------- ----",
"0",
"1",
"2 very long data fold",
" this",
"- -------------- ----",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn github() {
//Output: github with headers
let result = table(Style::Github).tabulate();
let expected = vec![
"| strings | numbers |",
"|-----------|-----------|",
"| spam | 41.9999 |",
"| eggs | 451 |",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn grid() {
//Output: grid with headers
let result = table(Style::Grid).tabulate();
let expected = vec![
"+-----------+-----------+",
"| strings | numbers |",
"+===========+===========+",
"| spam | 41.9999 |",
"+-----------+-----------+",
"| eggs | 451 |",
"+-----------+-----------+",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn grid_wide_characters() {
//Output: grid with wide characters in headers
let headers = Headers::from(vec!["strings", "配列"]);
let contents = vec![
vec![
Cell::from("Ответ на главный вопрос жизни, вселенной и всего такого"),
Cell::Int(42),
],
vec![Cell::from("pi"), Cell::Float(3.1415)],
];
let result = Table::new(Style::Grid, contents, Some(headers)).tabulate();
let expected = vec![
"+---------------------------------------------------------+---------+",
"| strings | 配列 |",
"+=========================================================+=========+",
"| Ответ на главный вопрос жизни, вселенной и всего такого | 42 |",
"+---------------------------------------------------------+---------+",
"| pi | 3.1415 |",
"+---------------------------------------------------------+---------+",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn issue_14() {
let headers = Headers::from(vec!["Hello", "World"]);
let contents = vec![vec![Cell::from("✔"), Cell::from("foo")]];
let result = Table::new(Style::Grid, contents, Some(headers)).tabulate();
let expected = vec![
"+---------+---------+",
"| Hello | World |",
"+=========+=========+",
"| ✔ | foo |",
"+---------+---------+",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn grid_headerless() {
//Output: grid without headers
let result = headerless(Style::Grid).tabulate();
let expected = vec![
"+------+----------+",
"| spam | 41.9999 |",
"+------+----------+",
"| eggs | 451 |",
"+------+----------+",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn grid_multiline_headerless() {
//Output: grid with multiline cells without headers
let result = multiline_headerless(Style::Grid).tabulate();
let expected = vec![
"+---------+-----------+",
"| foo bar | hello |",
"| baz | |",
"| bau | |",
"+---------+-----------+",
"| | multiline |",
"| | world |",
"+---------+-----------+",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn grid_multiline() {
//Output: grid with multiline cells with headers
let result = multiline(Style::Grid).tabulate();
let expected = vec![
"+-------------+-------------+",
"| more | more spam |",
"| spam eggs | & eggs |",
"+=============+=============+",
"| 2 | foo |",
"| | bar |",
"+-------------+-------------+",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn grid_multiline_with_empty_cells() {
//Output: grid with multiline cells and empty cells with headers
let result = multiline_empty_cells(Style::Grid).tabulate();
let expected = vec![
"+-------+----------------+--------+",
"| hdr | data | fold |",
"+=======+================+========+",
"| 1 | | |",
"+-------+----------------+--------+",
"| 2 | very long data | fold |",
"| | | this |",
"+-------+----------------+--------+",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn grid_multiline_with_empty_cells_headerless() {
//Output: grid with multiline cells and empty cells without headers
let result = multiline_empty_cells_headerless(Style::Grid).tabulate();
let expected = vec![
"+---+----------------+------+",
"| 0 | | |",
"+---+----------------+------+",
"| 1 | | |",
"+---+----------------+------+",
"| 2 | very long data | fold |",
"| | | this |",
"+---+----------------+------+",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancy_grid() {
//Output: fancy_grid with headers
let result = table(Style::Fancy).tabulate();
let expected = vec![
"╒═══════════╤═══════════╕",
"│ strings │ numbers │",
"╞═══════════╪═══════════╡",
"│ spam │ 41.9999 │",
"├───────────┼───────────┤",
"│ eggs │ 451 │",
"╘═══════════╧═══════════╛",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancy_grid_headerless() {
//Output: fancy_grid without headers
let result = headerless(Style::Fancy).tabulate();
let expected = vec![
"╒══════╤══════════╕",
"│ spam │ 41.9999 │",
"├──────┼──────────┤",
"│ eggs │ 451 │",
"╘══════╧══════════╛",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancy_grid_multiline_headerless() {
//Output: fancy_grid with multiline cells without headers
let result = multiline_headerless(Style::Fancy).tabulate();
let expected = vec![
"╒═════════╤═══════════╕",
"│ foo bar │ hello │",
"│ baz │ │",
"│ bau │ │",
"├─────────┼───────────┤",
"│ │ multiline │",
"│ │ world │",
"╘═════════╧═══════════╛",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancy_grid_multiline() {
//Output: fancy_grid with multiline cells with headers
let result = multiline(Style::Fancy).tabulate();
let expected = vec![
"╒═════════════╤═════════════╕",
"│ more │ more spam │",
"│ spam eggs │ & eggs │",
"╞═════════════╪═════════════╡",
"│ 2 │ foo │",
"│ │ bar │",
"╘═════════════╧═════════════╛",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancy_grid_multiline_with_empty_cells() {
//Output: fancy_grid with multiline cells and empty cells with headers
let result = multiline_empty_cells(Style::Fancy).tabulate();
let expected = vec![
"╒═══════╤════════════════╤════════╕",
"│ hdr │ data │ fold │",
"╞═══════╪════════════════╪════════╡",
"│ 1 │ │ │",
"├───────┼────────────────┼────────┤",
"│ 2 │ very long data │ fold │",
"│ │ │ this │",
"╘═══════╧════════════════╧════════╛",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancy_grid_multiline_with_empty_cells_headerless() {
//Output: fancy_grid with multiline cells and empty cells without headers
let result = multiline_empty_cells_headerless(Style::Fancy).tabulate();
let expected = vec![
"╒═══╤════════════════╤══════╕",
"│ 0 │ │ │",
"├───┼────────────────┼──────┤",
"│ 1 │ │ │",
"├───┼────────────────┼──────┤",
"│ 2 │ very long data │ fold │",
"│ │ │ this │",
"╘═══╧════════════════╧══════╛",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn presto() {
//Output: presto with headers
let result = table(Style::Presto).tabulate();
let expected = vec![
" strings | numbers",
"-----------+-----------",
" spam | 41.9999",
" eggs | 451",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn presto_headerless() {
//Output: presto without headers
let result = headerless(Style::Presto).tabulate();
let expected = vec![" spam | 41.9999", " eggs | 451"].join("\n");
assert_eq!(expected, result);
}
#[test]
fn presto_multiline_headerless() {
//Output: presto with multiline cells without headers
let result = multiline_headerless(Style::Presto).tabulate();
let expected = vec![
" foo bar | hello",
" baz |",
" bau |",
" | multiline",
" | world",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn presto_multiline() {
//Output: presto with multiline cells with headers
let result = multiline(Style::Presto).tabulate();
let expected = vec![
" more | more spam",
" spam eggs | & eggs",
"-------------+-------------",
" 2 | foo",
" | bar",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn presto_multiline_with_empty_cells() {
//Output: presto with multiline cells and empty cells with headers
let result = multiline_empty_cells(Style::Presto).tabulate();
let expected = vec![
" hdr | data | fold",
"-------+----------------+--------",
" 1 | |",
" 2 | very long data | fold",
" | | this",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn presto_multiline_with_empty_cells_headerless() {
//Output: presto with multiline cells and empty cells without headers
let result = multiline_empty_cells_headerless(Style::Presto).tabulate();
let expected = vec![
" 0 | |",
" 1 | |",
" 2 | very long data | fold",
" | | this",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancygithub_grid() {
let result = table(Style::FancyGithub).tabulate();
let expected = vec![
"│ strings │ numbers │",
"├───────────┼───────────┤",
"│ spam │ 41.9999 │",
"│ eggs │ 451 │",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancygithub_grid_headerless() {
let result = headerless(Style::FancyGithub).tabulate();
let expected = vec!["│ spam │ 41.9999 │", "│ eggs │ 451 │"].join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancygithub_grid_multiline_headerless() {
let result = multiline_headerless(Style::FancyGithub).tabulate();
let expected = vec![
"│ foo bar │ hello │",
"│ baz │ │",
"│ bau │ │",
"│ │ multiline │",
"│ │ world │",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancygithub_grid_multiline() {
let result = multiline(Style::FancyGithub).tabulate();
let expected = vec![
"│ more │ more spam │",
"│ spam eggs │ & eggs │",
"├─────────────┼─────────────┤",
"│ 2 │ foo │",
"│ │ bar │",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancygithub_grid_multiline_with_empty_cells() {
let result = multiline_empty_cells(Style::FancyGithub).tabulate();
let expected = vec![
"│ hdr │ data │ fold │",
"├───────┼────────────────┼────────┤",
"│ 1 │ │ │",
"│ 2 │ very long data │ fold │",
"│ │ │ this │",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancygithub_grid_multiline_with_empty_cells_headerless() {
let result = multiline_empty_cells_headerless(Style::FancyGithub).tabulate();
let expected = vec![
"│ 0 │ │ │",
"│ 1 │ │ │",
"│ 2 │ very long data │ fold │",
"│ │ │ this │",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancypresto_grid() {
let result = table(Style::FancyPresto).tabulate();
let expected = vec![
"strings │ numbers",
"──────────┼──────────",
"spam │ 41.9999",
"eggs │ 451",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancypresto_grid_headerless() {
let result = headerless(Style::FancyPresto).tabulate();
let expected = vec!["spam │ 41.9999", "eggs │ 451"].join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancypresto_grid_multiline_headerless() {
let result = multiline_headerless(Style::FancyPresto).tabulate();
let expected = vec![
"foo bar │ hello",
" baz │",
" bau │",
" │ multiline",
" │ world",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancypresto_grid_multiline() {
let result = multiline(Style::FancyPresto).tabulate();
let expected = vec![
" more │ more spam",
" spam eggs │ & eggs",
"────────────┼────────────",
" 2 │ foo",
" │ bar",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancypresto_grid_multiline_with_empty_cells() {
let result = multiline_empty_cells(Style::FancyPresto).tabulate();
let expected = vec![
" hdr │ data │ fold",
"──────┼────────────────┼───────",
" 1 │ │",
" 2 │ very long data │ fold",
" │ │ this",
]
.join("\n");
assert_eq!(expected, result);
}
#[test]
fn fancypresto_grid_multiline_with_empty_cells_headerless() {
let result = multiline_empty_cells_headerless(Style::FancyPresto).tabulate();
let expected = vec![
"0 │ │",
"1 │ │",
"2 │ very long data │ fold",
" │ │ this",
]
.join("\n");
assert_eq!(expected, result);
}
#[cfg(feature = "ansi_term_style")]
#[test]
fn ansi_term_colored_content<'a>() {
use ansi_term::Colour::Red;
use ansi_term::{ANSIString, ANSIStrings};
let some_value = format!("{:b}", 42);
let strings: &[ANSIString<'a>] =
&[Red.paint("["), Red.bold().paint(some_value), Red.paint("]")];
let result = Table::new(
Style::Grid,
vec![vec![
Cell::Int(42),
Cell::Text(Box::new(ANSIStrings(&strings))),
]],
Some(Headers::from(vec!["Int", "Colored binary"])),
)
.tabulate();
let expected = vec![
"+-------+------------------+",
"| Int | Colored binary |",
"+=======+==================+",
"| 42 | \u{1b}[31m[\u{1b}[1m101010\u{1b}[0m\u{1b}[31m]\u{1b}[0m |",
"+-------+------------------+",
]
.join("\n");
assert_eq!(expected, result);
}
#[cfg(feature = "ansi_term_style")]
#[test]
fn ansi_styled_borders() {
let mut table = table(Style::Fancy);
table.set_border_style(ansi_term::Color::Green.bold());
let result = table.tabulate();
let expected = vec![
"\u{1b}[1;32m╒═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╤═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╕\u{1b}[0m",
"\u{1b}[1;32m│ \u{1b}[0mstrings \u{1b}[1;32m │ \u{1b}[0m numbers\u{1b}[1;32m │\u{1b}[0m",
"\u{1b}[1;32m╞═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╪═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╡\u{1b}[0m",
"\u{1b}[1;32m│ \u{1b}[0mspam \u{1b}[1;32m │ \u{1b}[0m 41.9999\u{1b}[1;32m │\u{1b}[0m",
"\u{1b}[1;32m├─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─┼─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─┤\u{1b}[0m",
"\u{1b}[1;32m│ \u{1b}[0meggs \u{1b}[1;32m │ \u{1b}[0m 451 \u{1b}[1;32m │\u{1b}[0m",
"\u{1b}[1;32m╘═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╧═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╛\u{1b}[0m"
].join("\n");
assert_eq!(expected, result);
}
}