prettytable-rs 0.10.0

A library for printing pretty formatted tables in terminal
Documentation
#![warn(
    missing_docs,
    unused_extern_crates,
    unused_import_braces,
    unused_qualifications
)]
//! A formatted and aligned table printer written in rust

#[macro_use]
extern crate lazy_static;

use std::fmt;
use std::io::{self, Error, Write};
use std::iter::{FromIterator, IntoIterator};
use std::ops::{Index, IndexMut};
use std::slice::{Iter, IterMut};

pub use term::{color, Attr};
pub(crate) use term::{stdout, Terminal};

mod cell;
pub mod format;
mod row;
mod utils;

#[cfg(feature = "csv")]
pub mod csv;

#[cfg(feature = "evcxr")]
pub mod evcxr;

pub use cell::Cell;
use format::{consts, LinePosition, TableFormat};
pub use row::Row;
use utils::StringWriter;

/// An owned printable table
#[derive(Default, Clone, Debug, Hash, PartialEq, Eq)]
pub struct Table {
    format: Box<TableFormat>,
    titles: Box<Option<Row>>,
    rows: Vec<Row>,
}

/// A borrowed immutable `Table` slice
/// A `TableSlice` is obtained by slicing a `Table` with the `Slice::slice` method.
///
/// # Examples
/// ```rust
/// # #[macro_use] extern crate prettytable;
/// use prettytable::{Table, Slice};
/// # fn main() {
/// let table = table![[1, 2, 3], [4, 5, 6], [7, 8, 9]];
/// let slice = table.slice(1..);
/// slice.printstd(); // Prints only rows 1 and 2
///
/// //Also supports other syntax :
/// table.slice(..);
/// table.slice(..2);
/// table.slice(1..3);
/// # }
/// ```
///
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct TableSlice<'a> {
    format: &'a TableFormat,
    titles: &'a Option<Row>,
    rows: &'a [Row],
}

impl<'a> TableSlice<'a> {
    /// Compute and return the number of column
    // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
    fn get_column_num(&self) -> usize {
        let mut cnum = match *self.titles {
            Some(ref t) => t.column_count(),
            None => 0,
        };
        for r in self.rows {
            let l = r.column_count();
            if l > cnum {
                cnum = l;
            }
        }
        cnum
    }

    /// Get the number of rows
    pub fn len(&self) -> usize {
        self.rows.len()
    }

    /// Check if the table slice is empty
    pub fn is_empty(&self) -> bool {
        self.rows.is_empty()
    }

    /// Get an immutable reference to a row
    pub fn get_row(&self, row: usize) -> Option<&Row> {
        self.rows.get(row)
    }

    /// Get the width of the column at position `col_idx`.
    /// Return 0 if the column does not exists;
    fn get_column_width(&self, col_idx: usize) -> usize {
        let mut width = match *self.titles {
            Some(ref t) => t.get_column_width(col_idx, self.format),
            None => 0,
        };
        for r in self.rows {
            let l = r.get_column_width(col_idx, self.format);
            if l > width {
                width = l;
            }
        }
        width
    }

    /// Get the width of all columns, and return a slice
    /// with the result for each column
    fn get_all_column_width(&self) -> Vec<usize> {
        let colnum = self.get_column_num();
        let mut col_width = vec![0usize; colnum];
        #[allow(clippy::needless_range_loop)]
        for i in 0..colnum {
            // TODO: calling "get_column_width()" in a loop is inefficient
            col_width[i] = self.get_column_width(i);
        }
        col_width
    }

    /// Returns an iterator over the immutable cells of the column specified by `column`
    pub fn column_iter(&self, column: usize) -> ColumnIter {
        ColumnIter(self.rows.iter(), column)
    }

    /// Returns an iterator over immutable rows
    pub fn row_iter(&self) -> Iter<Row> {
        self.rows.iter()
    }

    /// Internal only
    fn __print<T: Write + ?Sized, F>(&self, out: &mut T, f: F) -> Result<usize, Error>
    where
        F: Fn(&Row, &mut T, &TableFormat, &[usize]) -> Result<usize, Error>,
    {
        let mut height = 0;
        // Compute columns width
        let col_width = self.get_all_column_width();
        height += self
            .format
            .print_line_separator(out, &col_width, LinePosition::Top)?;
        if let Some(ref t) = *self.titles {
            height += f(t, out, self.format, &col_width)?;
            height += self
                .format
                .print_line_separator(out, &col_width, LinePosition::Title)?;
        }
        // Print rows
        let mut iter = self.rows.iter().peekable();
        while let Some(r) = iter.next() {
            height += f(r, out, self.format, &col_width)?;
            if iter.peek().is_some() {
                height +=
                    self.format
                        .print_line_separator(out, &col_width, LinePosition::Intern)?;
            }
        }
        height += self
            .format
            .print_line_separator(out, &col_width, LinePosition::Bottom)?;
        out.flush()?;
        Ok(height)
    }

    /// Print the table to `out` and returns the number of
    /// line printed, or an error
    pub fn print<T: Write + ?Sized>(&self, out: &mut T) -> Result<usize, Error> {
        self.__print(out, Row::print)
    }

    /// Print the table to terminal `out`, applying styles when needed and returns the number of
    /// line printed, or an error
    pub fn print_term<T: Terminal + ?Sized>(&self, out: &mut T) -> Result<usize, Error> {
        self.__print(out, Row::print_term)
    }

    /// Print the table to standard output. Colors won't be displayed unless
    /// stdout is a tty terminal, or `force_colorize` is set to `true`.
    /// In ANSI terminals, colors are displayed using ANSI escape characters. When for example the
    /// output is redirected to a file, or piped to another program, the output is considered
    /// as not beeing tty, and ANSI escape characters won't be displayed unless `force colorize`
    /// is set to `true`.
    /// # Returns
    /// A `Result` holding the number of lines printed, or an `io::Error` if any failure happens
    pub fn print_tty(&self, force_colorize: bool) -> Result<usize, Error> {
        use is_terminal::IsTerminal;
        match (stdout(), io::stdout().is_terminal() || force_colorize) {
            (Some(mut o), true) => self.print_term(&mut *o),
            _ => self.print(&mut io::stdout()),
        }
    }

    /// Print the table to standard output. Colors won't be displayed unless
    /// stdout is a tty terminal. This means that if stdout is redirected to a file, or piped
    /// to another program, no color will be displayed.
    /// To force colors rendering, use `print_tty()` method.
    /// Any failure to print is ignored. For better control, use `print_tty()`.
    /// Calling `printstd()` is equivalent to calling `print_tty(false)` and ignoring the result.
    pub fn printstd(&self) {
        let _ = self.print_tty(false); // Ignore result
    }

    /// Print table in HTML format to `out`.
    pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<(), Error> {
        // Compute column width
        let column_num = self.get_column_num();
        out.write_all(b"<table>")?;
        // Print titles / table header
        if let Some(ref t) = *self.titles {
            out.write_all(b"<th>")?;
            t.print_html(out, column_num)?;
            out.write_all(b"</th>")?;
        }
        // Print rows
        for r in self.rows {
            out.write_all(b"<tr>")?;
            r.print_html(out, column_num)?;
            out.write_all(b"</tr>")?;
        }
        out.write_all(b"</table>")?;
        out.flush()?;
        Ok(())
    }
}

impl<'a> IntoIterator for &'a TableSlice<'a> {
    type Item = &'a Row;
    type IntoIter = Iter<'a, Row>;
    fn into_iter(self) -> Self::IntoIter {
        self.row_iter()
    }
}

impl Table {
    /// Create an empty table
    pub fn new() -> Table {
        Self::init(Vec::new())
    }

    /// Create a table initialized with `rows`
    pub fn init(rows: Vec<Row>) -> Table {
        Table {
            rows,
            titles: Box::new(None),
            format: Box::new(*consts::FORMAT_DEFAULT),
        }
    }

    /// Change the table format. Eg : Separators
    pub fn set_format(&mut self, format: TableFormat) {
        *self.format = format;
    }

    /// Get a mutable reference to the internal format
    pub fn get_format(&mut self) -> &mut TableFormat {
        &mut self.format
    }

    /// Compute and return the number of column
    // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
    #[cfg(test)] // Only used for testing for now
    pub(crate) fn get_column_num(&self) -> usize {
        self.as_slice().get_column_num()
    }

    /// Get the number of rows
    pub fn len(&self) -> usize {
        self.rows.len()
    }

    /// Check if the table is empty
    pub fn is_empty(&self) -> bool {
        self.rows.is_empty()
    }

    /// Set the optional title lines
    pub fn set_titles(&mut self, titles: Row) {
        *self.titles = Some(titles);
    }

    /// Unset the title line
    pub fn unset_titles(&mut self) {
        *self.titles = None;
    }

    /// Get a mutable reference to a row
    pub fn get_mut_row(&mut self, row: usize) -> Option<&mut Row> {
        self.rows.get_mut(row)
    }

    /// Get an immutable reference to a row
    pub fn get_row(&self, row: usize) -> Option<&Row> {
        self.rows.get(row)
    }

    /// Append a row in the table, transferring ownership of this row to the table
    /// and returning a mutable reference to the row
    pub fn add_row(&mut self, row: Row) -> &mut Row {
        self.rows.push(row);
        let l = self.rows.len() - 1;
        &mut self.rows[l]
    }

    /// Append an empty row in the table. Return a mutable reference to this new row.
    pub fn add_empty_row(&mut self) -> &mut Row {
        self.add_row(Row::default())
    }

    /// Insert `row` at the position `index`, and return a mutable reference to this row.
    /// If index is higher than current numbers of rows, `row` is appended at the end of the table
    pub fn insert_row(&mut self, index: usize, row: Row) -> &mut Row {
        if index < self.rows.len() {
            self.rows.insert(index, row);
            &mut self.rows[index]
        } else {
            self.add_row(row)
        }
    }

    /// Modify a single element in the table
    pub fn set_element(&mut self, element: &str, column: usize, row: usize) -> Result<(), &str> {
        let rowline = self.get_mut_row(row).ok_or("Cannot find row")?;
        // TODO: If a cell already exist, copy it's alignment parameter
        rowline.set_cell(Cell::new(element), column)
    }

    /// Remove the row at position `index`. Silently skip if the row does not exist
    pub fn remove_row(&mut self, index: usize) {
        if index < self.rows.len() {
            self.rows.remove(index);
        }
    }

    /// Return an iterator over the immutable cells of the column specified by `column`
    pub fn column_iter(&self, column: usize) -> ColumnIter {
        ColumnIter(self.rows.iter(), column)
    }

    /// Return an iterator over the mutable cells of the column specified by `column`
    pub fn column_iter_mut(&mut self, column: usize) -> ColumnIterMut {
        ColumnIterMut(self.rows.iter_mut(), column)
    }

    /// Returns an iterator over immutable rows
    pub fn row_iter(&self) -> Iter<Row> {
        self.rows.iter()
    }

    /// Returns an iterator over mutable rows
    pub fn row_iter_mut(&mut self) -> IterMut<Row> {
        self.rows.iter_mut()
    }

    /// Print the table to `out` and returns the number
    /// of lines printed, or an error
    pub fn print<T: Write + ?Sized>(&self, out: &mut T) -> Result<usize, Error> {
        self.as_slice().print(out)
    }

    /// Print the table to terminal `out`, applying styles when needed and returns the number
    /// of lines printed, or an error
    pub fn print_term<T: Terminal + ?Sized>(&self, out: &mut T) -> Result<usize, Error> {
        self.as_slice().print_term(out)
    }

    /// Print the table to standard output. Colors won't be displayed unless
    /// stdout is a tty terminal, or `force_colorize` is set to `true`.
    /// In ANSI terminals, colors are displayed using ANSI escape characters. When for example the
    /// output is redirected to a file, or piped to another program, the output is considered
    /// as not beeing tty, and ANSI escape characters won't be displayed unless `force colorize`
    /// is set to `true`.
    /// # Returns
    /// A `Result` holding the number of lines printed, or an `io::Error` if any failure happens
    pub fn print_tty(&self, force_colorize: bool) -> Result<usize, Error> {
        self.as_slice().print_tty(force_colorize)
    }

    /// Print the table to standard output. Colors won't be displayed unless
    /// stdout is a tty terminal. This means that if stdout is redirected to a file, or piped
    /// to another program, no color will be displayed.
    /// To force colors rendering, use `print_tty()` method.
    /// Any failure to print is ignored. For better control, use `print_tty()`.
    /// Calling `printstd()` is equivalent to calling `print_tty(false)` and ignoring the result.
    pub fn printstd(&self) {
        self.as_slice().printstd()
    }

    /// Print table in HTML format to `out`.
    pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<(), Error> {
        self.as_slice().print_html(out)
    }
}

/// Trait implemented by types which can be sliced
pub trait AsTableSlice {
    /// Get a slice from self
    fn as_slice(&self) -> TableSlice<'_>;
}

impl AsTableSlice for Table {
    fn as_slice(&self) -> TableSlice<'_> {
        TableSlice {
            format: &self.format,
            titles: &self.titles,
            rows: &self.rows,
        }
    }
}

impl<T> AsTableSlice for T
where
    T: AsRef<Table>,
{
    fn as_slice(&self) -> TableSlice<'_> {
        self.as_ref().as_slice()
    }
}

impl Index<usize> for Table {
    type Output = Row;
    fn index(&self, idx: usize) -> &Self::Output {
        &self.rows[idx]
    }
}

impl<'a> Index<usize> for TableSlice<'a> {
    type Output = Row;
    fn index(&self, idx: usize) -> &Self::Output {
        &self.rows[idx]
    }
}

impl IndexMut<usize> for Table {
    fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
        &mut self.rows[idx]
    }
}

impl fmt::Display for Table {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        self.as_slice().fmt(fmt)
    }
}

impl<'a> fmt::Display for TableSlice<'a> {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        let mut writer = StringWriter::new();
        if self.print(&mut writer).is_err() {
            return Err(fmt::Error);
        }
        fmt.write_str(writer.as_string())
    }
}

impl<B: ToString, A: IntoIterator<Item = B>> FromIterator<A> for Table {
    fn from_iter<T>(iterator: T) -> Table
    where
        T: IntoIterator<Item = A>,
    {
        Self::init(iterator.into_iter().map(Row::from).collect())
    }
}

impl FromIterator<Row> for Table {
    fn from_iter<T>(iterator: T) -> Table
    where
        T: IntoIterator<Item = Row>,
    {
        Self::init(iterator.into_iter().collect())
    }
}

impl<T, A, B> From<T> for Table
where
    B: ToString,
    A: IntoIterator<Item = B>,
    T: IntoIterator<Item = A>,
{
    fn from(it: T) -> Table {
        Self::from_iter(it)
    }
}

impl<'a> IntoIterator for &'a Table {
    type Item = &'a Row;
    type IntoIter = Iter<'a, Row>;
    fn into_iter(self) -> Self::IntoIter {
        self.row_iter()
    }
}

impl<'a> IntoIterator for &'a mut Table {
    type Item = &'a mut Row;
    type IntoIter = IterMut<'a, Row>;
    fn into_iter(self) -> Self::IntoIter {
        self.row_iter_mut()
    }
}

// impl IntoIterator for Table {
//     type Item = Row;
//     type IntoIter = std::vec::IntoIter<Self::Item>;
//     fn into_iter(self) -> Self::IntoIter {
//         self.rows.into_iter()
//     }
// }

impl<A: Into<Row>> Extend<A> for Table {
    fn extend<T: IntoIterator<Item = A>>(&mut self, iter: T) {
        self.rows.extend(iter.into_iter().map(|r| r.into()));
    }
}

/// Iterator over immutable cells in a column
pub struct ColumnIter<'a>(Iter<'a, Row>, usize);

impl<'a> Iterator for ColumnIter<'a> {
    type Item = &'a Cell;
    fn next(&mut self) -> Option<&'a Cell> {
        self.0.next().and_then(|row| row.get_cell(self.1))
    }
}

/// Iterator over mutable cells in a column
pub struct ColumnIterMut<'a>(IterMut<'a, Row>, usize);

impl<'a> Iterator for ColumnIterMut<'a> {
    type Item = &'a mut Cell;
    fn next(&mut self) -> Option<&'a mut Cell> {
        self.0.next().and_then(|row| row.get_mut_cell(self.1))
    }
}

impl<'a> AsTableSlice for TableSlice<'a> {
    fn as_slice(&self) -> TableSlice<'_> {
        *self
    }
}

impl<'a> AsRef<TableSlice<'a>> for TableSlice<'a> {
    fn as_ref(&self) -> &TableSlice<'a> {
        self
    }
}

/// Trait implemented by types which can be sliced
pub trait Slice<'a, E> {
    /// Type output after slicing
    type Output: 'a;
    /// Get a slice from self
    fn slice(&'a self, arg: E) -> Self::Output;
}

impl<'a, T, E> Slice<'a, E> for T
where
    T: AsTableSlice,
    [Row]: Index<E, Output = [Row]>,
{
    type Output = TableSlice<'a>;
    fn slice(&'a self, arg: E) -> Self::Output {
        let mut sl = self.as_slice();
        sl.rows = sl.rows.index(arg);
        sl
    }
}

/// Create a table filled with some values
///
/// All the arguments used for elements must implement the `std::string::ToString` trait
/// # Syntax
/// ```text
/// table!([Element1_ row1, Element2_ row1, ...], [Element1_row2, ...], ...);
/// ```
///
/// # Example
/// ```
/// # #[macro_use] extern crate prettytable;
/// # fn main() {
/// // Create a table initialized with some rows :
/// let tab = table!(["Element1", "Element2", "Element3"],
///                  [1, 2, 3],
///                  ["A", "B", "C"]
///                 );
/// # drop(tab);
/// # }
/// ```
///
/// Some style can also be given in table creation
///
/// ```
/// # #[macro_use] extern crate prettytable;
/// # fn main() {
/// let tab = table!([FrByl->"Element1", Fgc->"Element2", "Element3"],
///                  [FrBy => 1, 2, 3],
///                  ["A", "B", "C"]
///                 );
/// # drop(tab);
/// # }
/// ```
///
/// For details about style specifier syntax, check doc for [`Cell::style_spec`](cell/struct.Cell.html#method.style_spec) method
#[macro_export]
macro_rules! table {
    ($([$($content:tt)*]), *) => (
        $crate::Table::init(vec![$($crate::row![$($content)*]), *])
    );
}

/// Create a table with `table!` macro, print it to standard output, then return this table for future usage.
///
/// The syntax is the same that the one for the `table!` macro
#[macro_export]
macro_rules! ptable {
    ($($content:tt)*) => (
        {
            let tab = $crate::table!($($content)*);
            tab.printstd();
            tab
        }
    );
}

#[cfg(test)]
mod tests {
    use crate::utils::StringWriter;
    use crate::{format, AsTableSlice, Cell, Row, Slice, Table};
    use format::consts::{
        FORMAT_BOX_CHARS, FORMAT_CLEAN, FORMAT_DEFAULT, FORMAT_NO_COLSEP, FORMAT_NO_LINESEP,
    };

    #[test]
    fn table() {
        let mut table = Table::new();
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        table.set_titles(Row::new(vec![
            Cell::new("t1"),
            Cell::new("t2"),
            Cell::new("t3"),
        ]));
        let out = "\
+-----+----+-----+
| t1  | t2 | t3  |
+=====+====+=====+
| a   | bc | def |
+-----+----+-----+
| def | bc | a   |
+-----+----+-----+
";
        assert_eq!(table.to_string().replace("\r\n", "\n"), out);
        assert_eq!(7, table.print(&mut StringWriter::new()).unwrap());
        table.unset_titles();
        let out = "\
+-----+----+-----+
| a   | bc | def |
+-----+----+-----+
| def | bc | a   |
+-----+----+-----+
";
        assert_eq!(table.to_string().replace("\r\n", "\n"), out);
        assert_eq!(5, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn index() {
        let mut table = Table::new();
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        table.set_titles(Row::new(vec![
            Cell::new("t1"),
            Cell::new("t2"),
            Cell::new("t3"),
        ]));
        assert_eq!(table[1][1].get_content(), "bc");

        table[1][1] = Cell::new("newval");
        assert_eq!(table[1][1].get_content(), "newval");

        let out = "\
+-----+--------+-----+
| t1  | t2     | t3  |
+=====+========+=====+
| a   | bc     | def |
+-----+--------+-----+
| def | newval | a   |
+-----+--------+-----+
";
        assert_eq!(table.to_string().replace("\r\n", "\n"), out);
        assert_eq!(7, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn table_size() {
        let mut table = Table::new();
        assert!(table.is_empty());
        assert!(table.as_slice().is_empty());
        assert_eq!(table.len(), 0);
        assert_eq!(table.as_slice().len(), 0);
        assert_eq!(table.get_column_num(), 0);
        assert_eq!(table.as_slice().get_column_num(), 0);
        table.add_empty_row();
        assert!(!table.is_empty());
        assert!(!table.as_slice().is_empty());
        assert_eq!(table.len(), 1);
        assert_eq!(table.as_slice().len(), 1);
        assert_eq!(table.get_column_num(), 0);
        assert_eq!(table.as_slice().get_column_num(), 0);
        table[0].add_cell(Cell::default());
        assert_eq!(table.get_column_num(), 1);
        assert_eq!(table.as_slice().get_column_num(), 1);
    }

    #[test]
    fn get_row() {
        let mut table = Table::new();
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        assert!(table.get_row(12).is_none());
        assert!(table.get_row(1).is_some());
        assert_eq!(table.get_row(1).unwrap()[0].get_content(), "def");
        assert!(table.get_mut_row(12).is_none());
        assert!(table.get_mut_row(1).is_some());
        table.get_mut_row(1).unwrap().add_cell(Cell::new("z"));
        assert_eq!(table.get_row(1).unwrap()[3].get_content(), "z");
    }

    #[test]
    fn add_empty_row() {
        let mut table = Table::new();
        assert_eq!(table.len(), 0);
        table.add_empty_row();
        assert_eq!(table.len(), 1);
        assert_eq!(table[0].len(), 0);
    }

    #[test]
    fn remove_row() {
        let mut table = Table::new();
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        table.remove_row(12);
        assert_eq!(table.len(), 2);
        table.remove_row(0);
        assert_eq!(table.len(), 1);
        assert_eq!(table[0][0].get_content(), "def");
    }

    #[test]
    fn insert_row() {
        let mut table = Table::new();
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        table.insert_row(
            12,
            Row::new(vec![Cell::new("1"), Cell::new("2"), Cell::new("3")]),
        );
        assert_eq!(table.len(), 3);
        assert_eq!(table[2][1].get_content(), "2");
        table.insert_row(
            1,
            Row::new(vec![Cell::new("3"), Cell::new("4"), Cell::new("5")]),
        );
        assert_eq!(table.len(), 4);
        assert_eq!(table[1][1].get_content(), "4");
        assert_eq!(table[2][1].get_content(), "bc");
    }

    #[test]
    fn set_element() {
        let mut table = Table::new();
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        assert!(table.set_element("foo", 12, 12).is_err());
        assert!(table.set_element("foo", 1, 1).is_ok());
        assert_eq!(table[1][1].get_content(), "foo");
    }

    #[test]
    fn no_linesep() {
        let mut table = Table::new();
        table.set_format(*FORMAT_NO_LINESEP);
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        table.set_titles(Row::new(vec![
            Cell::new("t1"),
            Cell::new("t2"),
            Cell::new("t3"),
        ]));
        assert_eq!(table[1][1].get_content(), "bc");

        table[1][1] = Cell::new("newval");
        assert_eq!(table[1][1].get_content(), "newval");

        let out = "\
+-----+--------+-----+
| t1  | t2     | t3  |
| a   | bc     | def |
| def | newval | a   |
+-----+--------+-----+
";
        assert_eq!(table.to_string().replace("\r\n", "\n"), out);
        assert_eq!(5, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn no_colsep() {
        let mut table = Table::new();
        table.set_format(*FORMAT_NO_COLSEP);
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        table.set_titles(Row::new(vec![
            Cell::new("t1"),
            Cell::new("t2"),
            Cell::new("t3"),
        ]));
        assert_eq!(table[1][1].get_content(), "bc");

        table[1][1] = Cell::new("newval");
        assert_eq!(table[1][1].get_content(), "newval");

        let out = "\
------------------
 t1   t2      t3 \n\
==================
 a    bc      def \n\
------------------
 def  newval  a \n\
------------------
";
        println!("{}", out);
        println!("____");
        println!("{}", table.to_string().replace("\r\n", "\n"));
        assert_eq!(table.to_string().replace("\r\n", "\n"), out);
        assert_eq!(7, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn clean() {
        let mut table = Table::new();
        table.set_format(*FORMAT_CLEAN);
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        table.set_titles(Row::new(vec![
            Cell::new("t1"),
            Cell::new("t2"),
            Cell::new("t3"),
        ]));
        assert_eq!(table[1][1].get_content(), "bc");

        table[1][1] = Cell::new("newval");
        assert_eq!(table[1][1].get_content(), "newval");

        let out = "\
\u{0020}t1   t2      t3 \n\
\u{0020}a    bc      def \n\
\u{0020}def  newval  a \n\
";
        println!("{}", out);
        println!("____");
        println!("{}", table.to_string().replace("\r\n", "\n"));
        assert_eq!(out, table.to_string().replace("\r\n", "\n"));
        assert_eq!(3, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn padding() {
        let mut table = Table::new();
        let mut format = *FORMAT_DEFAULT;
        format.padding(2, 2);
        table.set_format(format);
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        table.set_titles(Row::new(vec![
            Cell::new("t1"),
            Cell::new("t2"),
            Cell::new("t3"),
        ]));
        assert_eq!(table[1][1].get_content(), "bc");

        table[1][1] = Cell::new("newval");
        assert_eq!(table[1][1].get_content(), "newval");

        let out = "\
+-------+----------+-------+
|  t1   |  t2      |  t3   |
+=======+==========+=======+
|  a    |  bc      |  def  |
+-------+----------+-------+
|  def  |  newval  |  a    |
+-------+----------+-------+
";
        println!("{}", out);
        println!("____");
        println!("{}", table.to_string().replace("\r\n", "\n"));
        assert_eq!(out, table.to_string().replace("\r\n", "\n"));
        assert_eq!(7, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn indent() {
        let mut table = Table::new();
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        table.set_titles(Row::new(vec![
            Cell::new("t1"),
            Cell::new("t2"),
            Cell::new("t3"),
        ]));
        table.get_format().indent(8);
        let out = "        +-----+----+-----+
        | t1  | t2 | t3  |
        +=====+====+=====+
        | a   | bc | def |
        +-----+----+-----+
        | def | bc | a   |
        +-----+----+-----+
";
        assert_eq!(table.to_string().replace("\r\n", "\n"), out);
        assert_eq!(7, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn slices() {
        let mut table = Table::new();
        table.set_titles(Row::new(vec![
            Cell::new("t1"),
            Cell::new("t2"),
            Cell::new("t3"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("0"),
            Cell::new("0"),
            Cell::new("0"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("1"),
            Cell::new("1"),
            Cell::new("1"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("2"),
            Cell::new("2"),
            Cell::new("2"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("3"),
            Cell::new("3"),
            Cell::new("3"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("4"),
            Cell::new("4"),
            Cell::new("4"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("5"),
            Cell::new("5"),
            Cell::new("5"),
        ]));
        let out = "\
+----+----+----+
| t1 | t2 | t3 |
+====+====+====+
| 1  | 1  | 1  |
+----+----+----+
| 2  | 2  | 2  |
+----+----+----+
| 3  | 3  | 3  |
+----+----+----+
";
        let slice = table.slice(..);
        let slice = slice.slice(1..);
        let slice = slice.slice(..3);
        assert_eq!(out, slice.to_string().replace("\r\n", "\n"));
        assert_eq!(9, slice.print(&mut StringWriter::new()).unwrap());
        assert_eq!(out, table.slice(1..4).to_string().replace("\r\n", "\n"));
        assert_eq!(
            9,
            table.slice(1..4).print(&mut StringWriter::new()).unwrap()
        );
    }

    #[test]
    fn test_unicode_separators() {
        let mut table = Table::new();
        table.set_format(*FORMAT_BOX_CHARS);
        table.add_row(Row::new(vec![
            Cell::new("1"),
            Cell::new("1"),
            Cell::new("1"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("2"),
            Cell::new("2"),
            Cell::new("2"),
        ]));
        table.set_titles(Row::new(vec![
            Cell::new("t1"),
            Cell::new("t2"),
            Cell::new("t3"),
        ]));
        let out = "\
┌────┬────┬────┐
│ t1 │ t2 │ t3 │
├────┼────┼────┤
│ 1  │ 1  │ 1  │
├────┼────┼────┤
│ 2  │ 2  │ 2  │
└────┴────┴────┘
";
        println!("{}", out);
        println!("____");
        println!("{}", table.to_string().replace("\r\n", "\n"));
        assert_eq!(out, table.to_string().replace("\r\n", "\n"));
        assert_eq!(7, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn test_readme_format() {
        // The below is lifted from the README

        let mut table = Table::new();
        let format = format::FormatBuilder::new()
            .column_separator('|')
            .borders('|')
            .separators(
                &[format::LinePosition::Top, format::LinePosition::Bottom],
                format::LineSeparator::new('-', '+', '+', '+'),
            )
            .padding(1, 1)
            .build();
        table.set_format(format);

        table.set_titles(Row::new(vec![Cell::new("Title 1"), Cell::new("Title 2")]));
        table.add_row(Row::new(vec![Cell::new("Value 1"), Cell::new("Value 2")]));
        table.add_row(Row::new(vec![
            Cell::new("Value three"),
            Cell::new("Value four"),
        ]));

        let out = "\
+-------------+------------+
| Title 1     | Title 2    |
| Value 1     | Value 2    |
| Value three | Value four |
+-------------+------------+
";

        println!("{}", out);
        println!("____");
        println!("{}", table.to_string().replace("\r\n", "\n"));
        assert_eq!(out, table.to_string().replace("\r\n", "\n"));
        assert_eq!(5, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn test_readme_format_with_title() {
        let mut table = Table::new();
        table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE);

        table.set_titles(Row::new(vec![Cell::new("Title 1"), Cell::new("Title 2")]));
        table.add_row(Row::new(vec![Cell::new("Value 1"), Cell::new("Value 2")]));
        table.add_row(Row::new(vec![
            Cell::new("Value three"),
            Cell::new("Value four"),
        ]));

        let out = "\
+-------------+------------+
| Title 1     | Title 2    |
+-------------+------------+
| Value 1     | Value 2    |
| Value three | Value four |
+-------------+------------+
";
        println!("{}", out);
        println!("____");
        println!("{}", table.to_string().replace("\r\n", "\n"));
        assert_eq!(out, table.to_string().replace("\r\n", "\n"));
        assert_eq!(6, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn test_empty_table_with_title() {
        let mut table = Table::new();
        table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE);

        table.set_titles(Row::new(vec![Cell::new("Title 1"), Cell::new("Title 2")]));

        let out = "\
+---------+---------+
| Title 1 | Title 2 |
+---------+---------+
+---------+---------+
";
        println!("{}", out);
        println!("____");
        println!("{}", table.to_string().replace("\r\n", "\n"));
        assert_eq!(out, table.to_string().replace("\r\n", "\n"));
        assert_eq!(4, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn test_horizontal_span() {
        let mut table = Table::new();
        table.set_titles(Row::new(vec![
            Cell::new("t1"),
            Cell::new("t2").with_hspan(2),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def").style_spec("H02c"),
            Cell::new("a"),
        ]));
        let out = "\
+----+----+-----+
| t1 | t2       |
+====+====+=====+
| a  | bc | def |
+----+----+-----+
|   def   | a   |
+----+----+-----+
";
        println!("{}", out);
        println!("____");
        println!("{}", table.to_string().replace("\r\n", "\n"));
        assert_eq!(out, table.to_string().replace("\r\n", "\n"));
        assert_eq!(7, table.print(&mut StringWriter::new()).unwrap());
    }

    #[test]
    fn table_html() {
        let mut table = Table::new();
        table.add_row(Row::new(vec![
            Cell::new("a"),
            Cell::new("bc"),
            Cell::new("def"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("def"),
            Cell::new("bc"),
            Cell::new("a"),
        ]));
        table.set_titles(Row::new(vec![
            Cell::new("t1"),
            Cell::new("t2"),
            Cell::new("t3"),
        ]));
        let out = "\
<table>\
<th><td style=\"text-align: left;\">t1</td><td style=\"text-align: left;\">t2</td><td style=\"text-align: left;\">t3</td></th>\
<tr><td style=\"text-align: left;\">a</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">def</td></tr>\
<tr><td style=\"text-align: left;\">def</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">a</td></tr>\
</table>";
        let mut writer = StringWriter::new();
        assert!(table.print_html(&mut writer).is_ok());
        assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
        table.unset_titles();
        let out = "\
<table>\
<tr><td style=\"text-align: left;\">a</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">def</td></tr>\
<tr><td style=\"text-align: left;\">def</td><td style=\"text-align: left;\">bc</td><td style=\"text-align: left;\">a</td></tr>\
</table>";
        let mut writer = StringWriter::new();
        assert!(table.print_html(&mut writer).is_ok());
        assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
    }

    #[test]
    fn table_html_colors() {
        let mut table = Table::new();
        table.add_row(Row::new(vec![
            Cell::new("bold").style_spec("b"),
            Cell::new("italic").style_spec("i"),
            Cell::new("underline").style_spec("u"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("left").style_spec("l"),
            Cell::new("center").style_spec("c"),
            Cell::new("right").style_spec("r"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("red").style_spec("Fr"),
            Cell::new("black").style_spec("Fd"),
            Cell::new("yellow").style_spec("Fy"),
        ]));
        table.add_row(Row::new(vec![
            Cell::new("bright magenta on cyan").style_spec("FMBc"),
            Cell::new("white on bright green").style_spec("FwBG"),
            Cell::new("default on blue").style_spec("Bb"),
        ]));
        table.set_titles(Row::new(
            vec![Cell::new("span horizontal").style_spec("H3")],
        ));
        let out = "\
<table>\
<th><td colspan=\"3\" style=\"text-align: left;\">span horizontal</td></th>\
<tr><td style=\"font-weight: bold;text-align: left;\">bold</td><td style=\"font-style: italic;text-align: left;\">italic</td><td style=\"text-decoration: underline;text-align: left;\">underline</td></tr>\
<tr><td style=\"text-align: left;\">left</td><td style=\"text-align: center;\">center</td><td style=\"text-align: right;\">right</td></tr>\
<tr><td style=\"color: #aa0000;text-align: left;\">red</td><td style=\"color: #000000;text-align: left;\">black</td><td style=\"color: #aa5500;text-align: left;\">yellow</td></tr>\
<tr><td style=\"color: #ff55ff;background-color: #00aaaa;text-align: left;\">bright magenta on cyan</td><td style=\"color: #aaaaaa;background-color: #55ff55;text-align: left;\">white on bright green</td><td style=\"background-color: #0000aa;text-align: left;\">default on blue</td></tr>\
</table>";
        let mut writer = StringWriter::new();
        assert!(table.print_html(&mut writer).is_ok());
        assert_eq!(writer.as_string().replace("\r\n", "\n"), out);
    }

    #[test]
    fn test_panic() {
        let mut table = Table::new();

        table.add_row(Row::new(vec![Cell::new("\u{1b}[\u{1b}\u{0}\u{0}")]));

        let out = "+--+
| \u{1b}[\u{1b}\u{0}\u{0} |
+--+
";

        assert_eq!(table.to_string().replace("\r\n", "\n"), out);
        assert_eq!(3, table.print(&mut StringWriter::new()).unwrap());
    }
}