#![warn(missing_docs, unused_extern_crates, unused_import_braces, unused_qualifications)] //! A formatted and aligned table printer written in rust extern crate unicode_width; extern crate term; extern crate atty; #[cfg(feature = "csv")] extern crate csv; #[macro_use] extern crate lazy_static; extern crate encode_unicode; use std::io::{self, Write, Error}; #[cfg(feature = "csv")] use std::io::Read; use std::fmt; #[cfg(feature = "csv")] use std::path::Path; use std::iter::{FromIterator, IntoIterator}; use std::slice::{Iter, IterMut}; use std::ops::{Index, IndexMut}; use std::mem::transmute; pub use term::{Attr, color}; pub(crate) use term::{Terminal, stdout}; pub mod cell; pub mod row; pub mod format; mod utils; use row::Row; use cell::Cell; use format::{TableFormat, LinePosition, consts}; use utils::StringWriter; /// An owned printable table #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct Table { format: Box, titles: Box>, rows: Vec, } /// 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(Clone, Debug, Hash, PartialEq, Eq)] pub struct TableSlice<'a> { format: &'a TableFormat, titles: &'a Option, rows: &'a [Row], } impl<'a> TableSlice<'a> { /// Compute and return the number of column pub fn get_column_num(&self) -> usize { let mut cnum = 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 { let colnum = self.get_column_num(); let mut col_width = vec![0usize; colnum]; 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 { self.rows.iter() } /// Internal only fn __print(&self, out: &mut T, f: F) -> Result<(), Error> where F: Fn(&Row, &mut T, &TableFormat, &[usize]) -> Result<(), Error> { // Compute columns width let col_width = self.get_all_column_width(); self.format .print_line_separator(out, &col_width, LinePosition::Top)?; if let Some(ref t) = *self.titles { f(t, out, self.format, &col_width)?; self.format .print_line_separator(out, &col_width, LinePosition::Title)?; } // Print rows let mut iter = self.rows.into_iter().peekable(); while let Some(r) = iter.next() { f(r, out, self.format, &col_width)?; if iter.peek().is_some() { self.format .print_line_separator(out, &col_width, LinePosition::Intern)?; } } self.format .print_line_separator(out, &col_width, LinePosition::Bottom)?; out.flush() } /// Print the table to `out` pub fn print(&self, out: &mut T) -> Result<(), Error> { self.__print(out, Row::print) } /// Print the table to terminal `out`, applying styles when needed pub fn print_term(&self, out: &mut T) -> Result<(), 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`. /// # Panic /// Panic if writing to standard output fails pub fn print_tty(&self, force_colorize: bool) { let r = match (stdout(), atty::is(atty::Stream::Stdout) || force_colorize) { (Some(mut o), true) => self.print_term(&mut *o), _ => self.print(&mut io::stdout()), }; if let Err(e) = r { panic!("Cannot print table to standard output : {}", e); } } /// 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. /// Calling `printstd()` is equivalent to calling `print_tty(false)` /// # Panic /// Panic if writing to standard output fails pub fn printstd(&self) { self.print_tty(false); } /// Write the table to the specified writer. #[cfg(feature = "csv")] pub fn to_csv(&self, w: W) -> csv::Result> { self.to_csv_writer(csv::Writer::from_writer(w)) } /// Write the table to the specified writer. /// /// This allows for format customisation. #[cfg(feature = "csv")] pub fn to_csv_writer(&self, mut writer: csv::Writer) -> csv::Result> { for title in self.titles { writer.write_record(title.iter().map(|c| c.get_content()))?; } for row in self.rows { writer.write_record(row.iter().map(|c| c.get_content()))?; } writer.flush()?; Ok(writer) } } 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) -> Table { Table { rows: rows, titles: Box::new(None), format: Box::new(*consts::FORMAT_DEFAULT), } } /// Create a table from a CSV string /// /// For more customisability use `from_csv()` #[cfg(feature = "csv")] pub fn from_csv_string(csv_s: &str) -> csv::Result { Ok(Table::from_csv( &mut csv::ReaderBuilder::new() .has_headers(false) .from_reader(csv_s.as_bytes()))) } /// Create a table from a CSV file /// /// For more customisability use `from_csv()` #[cfg(feature = "csv")] pub fn from_csv_file>(csv_p: P) -> csv::Result
{ Ok(Table::from_csv( &mut csv::ReaderBuilder::new() .has_headers(false) .from_path(csv_p)?)) } /// Create a table from a CSV reader #[cfg(feature = "csv")] pub fn from_csv(reader: &mut csv::Reader) -> Table { Table::init(reader .records() .map(|row| { Row::new(row.unwrap() .into_iter() .map(|cell| Cell::new(&cell)) .collect()) }) .collect()) } /// 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 pub fn get_column_num(&self) -> usize { self.as_ref().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 { self.rows.iter() } /// Returns an iterator over mutable rows pub fn row_iter_mut(&mut self) -> IterMut { self.rows.iter_mut() } /// Print the table to `out` pub fn print(&self, out: &mut T) -> Result<(), Error> { self.as_ref().print(out) } /// Print the table to terminal `out`, applying styles when needed pub fn print_term(&self, out: &mut T) -> Result<(), Error> { self.as_ref().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`. /// # Panic /// Panic if writing to standard output fails pub fn print_tty(&self, force_colorize: bool) { self.as_ref().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. /// Calling `printstd()` is equivalent to calling `print_tty(false)` /// # Panic /// Panic if writing to standard output fails pub fn printstd(&self) { self.as_ref().printstd(); } /// Write the table to the specified writer. #[cfg(feature = "csv")] pub fn to_csv(&self, w: W) -> csv::Result> { self.as_ref().to_csv(w) } /// Write the table to the specified writer. /// /// This allows for format customisation. #[cfg(feature = "csv")] pub fn to_csv_writer(&self, writer: csv::Writer) -> csv::Result> { self.as_ref().to_csv_writer(writer) } } impl Index for Table { type Output = Row; fn index(&self, idx: usize) -> &Self::Output { &self.rows[idx] } } impl<'a> Index for TableSlice<'a> { type Output = Row; fn index(&self, idx: usize) -> &Self::Output { &self.rows[idx] } } impl IndexMut 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_ref().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> FromIterator for Table { fn from_iter(iterator: T) -> Table where T: IntoIterator { Self::init(iterator.into_iter().map(Row::from).collect()) } } impl FromIterator for Table { fn from_iter(iterator: T) -> Table where T: IntoIterator { Self::init(iterator.into_iter().collect()) } } impl From for Table where B: ToString, A: IntoIterator, T: IntoIterator { 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.as_ref().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; // fn into_iter(self) -> Self::IntoIter { // self.rows.into_iter() // } // } impl > Extend for Table { fn extend>(&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> AsRef> for TableSlice<'a> { fn as_ref(&self) -> &TableSlice<'a> { self } } impl<'a> AsRef> for Table { fn as_ref(&self) -> &TableSlice<'a> { unsafe { // All this is a bit hacky. Let's try to find something else let s = &mut *((self as *const Table) as *mut Table); s.rows.shrink_to_fit(); transmute(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: AsRef>, [Row]: Index { type Output = TableSlice<'a>; fn slice(&'a self, arg: E) -> Self::Output { let sl = self.as_ref(); TableSlice { format: sl.format, titles: sl.titles, rows: sl.rows.index(arg), } } } /// 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![$(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 = table!($($content)*); tab.printstd(); tab } ); } #[cfg(test)] mod tests { use Table; use Slice; use row::Row; use cell::Cell; use format; use format::consts::{FORMAT_DEFAULT, FORMAT_NO_LINESEP, FORMAT_NO_COLSEP, FORMAT_CLEAN}; #[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); table.unset_titles(); let out = "\ +-----+----+-----+ | a | bc | def | +-----+----+-----+ | def | bc | a | +-----+----+-----+ "; assert_eq!(table.to_string().replace("\r\n", "\n"), out); } #[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); } #[test] fn table_size() { let mut table = Table::new(); assert!(table.is_empty()); assert!(table.as_ref().is_empty()); assert_eq!(table.len(), 0); assert_eq!(table.as_ref().len(), 0); assert_eq!(table.get_column_num(), 0); assert_eq!(table.as_ref().get_column_num(), 0); table.add_empty_row(); assert!(!table.is_empty()); assert!(!table.as_ref().is_empty()); assert_eq!(table.len(), 1); assert_eq!(table.as_ref().len(), 1); assert_eq!(table.get_column_num(), 0); assert_eq!(table.as_ref().get_column_num(), 0); table[0].add_cell(Cell::default()); assert_eq!(table.get_column_num(), 1); assert_eq!(table.as_ref().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); } #[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); } #[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")); } #[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")); } #[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); } #[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!(out, table.slice(1..4).to_string().replace("\r\n", "\n")); } #[test] fn test_unicode_separators() { let mut table = Table::new(); table.set_format(format::FormatBuilder::new() .column_separator('│') .borders('│') .separators(&[format::LinePosition::Top], format::LineSeparator::new('─', '┬', '┌', '┐')) .separators(&[format::LinePosition::Intern], format::LineSeparator::new('─', '┼', '├', '┤')) .separators(&[format::LinePosition::Bottom], format::LineSeparator::new('─', '┴', '└', '┘')) .padding(1, 1) .build()); 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")); } #[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")); } #[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")); } #[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")); } #[cfg(feature = "csv")] mod csv { use Table; use row::Row; use cell::Cell; static CSV_S: &'static str = "ABC,DEFG,HIJKLMN\n\ foobar,bar,foo\n\ foobar2,bar2,foo2\n"; fn test_table() -> Table { let mut table = Table::new(); table .add_row(Row::new(vec![Cell::new("ABC"), Cell::new("DEFG"), Cell::new("HIJKLMN")])); table.add_row(Row::new(vec![Cell::new("foobar"), Cell::new("bar"), Cell::new("foo")])); table.add_row(Row::new(vec![Cell::new("foobar2"), Cell::new("bar2"), Cell::new("foo2")])); table } #[test] fn from() { assert_eq!(test_table().to_string().replace("\r\n", "\n"), Table::from_csv_string(CSV_S) .unwrap() .to_string() .replace("\r\n", "\n")); } #[test] fn to() { assert_eq!( String::from_utf8( test_table() .to_csv(Vec::new()) .unwrap() .into_inner() .unwrap() ).unwrap(), CSV_S); } #[test] fn trans() { assert_eq!( Table::from_csv_string( &String::from_utf8( test_table() .to_csv(Vec::new()) .unwrap() .into_inner() .unwrap() ).unwrap() ).unwrap() .to_string() .replace("\r\n", "\n"), test_table().to_string().replace("\r\n", "\n")); } #[test] fn extend_table() { let mut table = Table::new(); table.add_row(Row::new(vec![Cell::new("ABC"), Cell::new("DEFG"), Cell::new("HIJKLMN")])); table.extend(vec![vec!["A", "B", "C"]]); let t2 = table.clone(); table.extend(t2.rows); assert_eq!(table.get_row(1).unwrap().get_cell(2).unwrap().get_content(), "C"); assert_eq!(table.get_row(2).unwrap().get_cell(1).unwrap().get_content(), "DEFG"); } } }