prettytable-rs 0.8.0

A library for printing pretty formatted tables in terminal
Documentation
//! This module contains definition of table rows stuff
use std::io::{Write, Error};
use std::iter::FromIterator;
use std::slice::{Iter, IterMut};
// use std::vec::IntoIter;
use std::ops::{Index, IndexMut};

use super::Terminal;

use super::utils::NEWLINE;
use super::Cell;
use super::format::{TableFormat, ColumnPosition};

/// Represent a table row made of cells
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Row {
    cells: Vec<Cell>,
}

impl Row {
    /// Create a new `Row` backed with `cells` vector
    pub fn new(cells: Vec<Cell>) -> Row {
        Row { cells: cells }
    }

    /// Create an row of length `size`, with empty strings stored
    pub fn empty() -> Row {
        Self::new(vec![Cell::default(); 0])
    }

    /// Count the number of column required in the table grid.
    /// It takes into account horizontal spanning of cells. For
    /// example, a cell with an hspan of 3 will add 3 column to the grid
    #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
    pub fn column_count(&self) -> usize {
        self.cells.iter().map(|c| c.get_hspan()).sum()
    }

    /// Get the number of cells in this row
    pub fn len(&self) -> usize {
        self.cells.len()
        // self.cells.iter().map(|c| c.get_hspan()).sum()
    }

    /// Check if the row is empty (has no cell)
    pub fn is_empty(&self) -> bool {
        self.cells.is_empty()
    }

    /// Get the height of this row
    #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
    pub fn get_height(&self) -> usize {
        let mut height = 1; // Minimum height must be 1 to print empty rows
        for cell in &self.cells {
            let h = cell.get_height();
            if h > height {
                height = h;
            }
        }
        height
    }

    /// Get the minimum width required by the cell in the column `column`.
    /// Return 0 if the cell does not exist in this row
    #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
    pub fn get_column_width(&self, column: usize, format: &TableFormat) -> usize {
        let mut i = 0;
        for c in &self.cells {
            if i + c.get_hspan()-1 >= column {
                if c.get_hspan() == 1 {
                    return c.get_width();
                }
                let (lp, rp) = format.get_padding();
                let sep = format.get_column_separator(ColumnPosition::Intern).map(|_| 1).unwrap_or_default();
                let rem = lp + rp +sep;
                let mut w = c.get_width();
                if w > rem {
                    w -= rem;
                } else {
                    w = 0;
                }
                return (w as f64 / c.get_hspan() as f64).ceil() as usize;
            }
            i += c.get_hspan();
        }
        0
    }

    /// Get the cell at index `idx`
    pub fn get_cell(&self, idx: usize) -> Option<&Cell> {
        self.cells.get(idx)
    }

    /// Get the mutable cell at index `idx`
    pub fn get_mut_cell(&mut self, idx: usize) -> Option<&mut Cell> {
        self.cells.get_mut(idx)
    }

    /// Set the `cell` in the row at the given `idx` index
    pub fn set_cell(&mut self, cell: Cell, idx: usize) -> Result<(), &str> {
        if idx >= self.len() {
            return Err("Cannot find cell");
        }
        self.cells[idx] = cell;
        Ok(())
    }

    /// Append a `cell` at the end of the row
    pub fn add_cell(&mut self, cell: Cell) {
        self.cells.push(cell);
    }

    /// Insert `cell` at position `index`. If `index` is higher than the row length,
    /// the cell will be appended at the end
    pub fn insert_cell(&mut self, index: usize, cell: Cell) {
        if index < self.cells.len() {
            self.cells.insert(index, cell);
        } else {
            self.add_cell(cell);
        }
    }

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

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

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

    /// Internal only
    fn __print<T: Write + ?Sized, F>(&self,
                                     out: &mut T,
                                     format: &TableFormat,
                                     col_width: &[usize],
                                     f: F)
                                     -> Result<usize, Error>
        where F: Fn(&Cell, &mut T, usize, usize, bool) -> Result<(), Error>
    {
        let height = self.get_height();
        for i in 0..height {
            //TODO: Wrap this into dedicated function one day
            out.write_all(&vec![b' '; format.get_indent()])?;
            format.print_column_separator(out, ColumnPosition::Left)?;
            let (lp, rp) = format.get_padding();
            let mut j = 0;
            let mut hspan = 0; // The additional offset caused by cell's horizontal spanning
            while j+hspan < col_width.len() {
                out.write_all(&vec![b' '; lp])?; // Left padding
                // skip_r_fill skip filling the end of the last cell if there's no character
                // delimiting the end of the table
                let skip_r_fill = (j == col_width.len() - 1) &&
                                  format.get_column_separator(ColumnPosition::Right).is_none();
                match self.get_cell(j) {
                    Some(c) => {
                        // In case of horizontal spanning, width is the sum of all spanned columns' width
                        let mut w = col_width[j+hspan..j+hspan+c.get_hspan()].iter().sum();
                        let real_span = c.get_hspan()-1;
                        w += real_span * (lp + rp) + real_span * format.get_column_separator(ColumnPosition::Intern).map(|_| 1).unwrap_or_default();
                        // Print cell content
                        f(c, out, i, w, skip_r_fill)?;
                        hspan += real_span; // Add span to offset
                    },
                    None => f(&Cell::default(), out, i, col_width[j+hspan], skip_r_fill)?,
                };
                out.write_all(&vec![b' '; rp])?; // Right padding
                if j+hspan < col_width.len() - 1 {
                    format.print_column_separator(out, ColumnPosition::Intern)?;
                }
                j+=1;
            }
            format.print_column_separator(out, ColumnPosition::Right)?;
            out.write_all(NEWLINE)?;
        }
        Ok(height)
    }

    /// Print the row to `out`, with `separator` as column separator, and `col_width`
    /// specifying the width of each columns. Returns the number of printed lines
    #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
    pub fn print<T: Write + ?Sized>(&self,
                                    out: &mut T,
                                    format: &TableFormat,
                                    col_width: &[usize])
                                    -> Result<usize, Error> {
        self.__print(out, format, col_width, Cell::print)
    }

    /// Print the row to terminal `out`, with `separator` as column separator, and `col_width`
    /// specifying the width of each columns. Apply style when needed. returns the number of printed lines
    #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
    pub fn print_term<T: Terminal + ?Sized>(&self,
                                            out: &mut T,
                                            format: &TableFormat,
                                            col_width: &[usize])
                                            -> Result<usize, Error> {
        self.__print(out, format, col_width, Cell::print_term)
    }
}

impl Default for Row {
    fn default() -> Row {
        Row::empty()
    }
}

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

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

impl<A: ToString> FromIterator<A> for Row {
    fn from_iter<T>(iterator: T) -> Row
        where T: IntoIterator<Item = A>
    {
        Self::new(iterator.into_iter().map(|ref e| Cell::from(e)).collect())
    }
}

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

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

// impl IntoIterator for Row {
//     type Item = Cell;
//     type IntoIter = IntoIter<Cell>;
//     fn into_iter(self) -> Self::IntoIter {
//         self.cells.into_iter()
//     }
// }

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

impl <S: ToString> Extend<S> for Row {
    fn extend<T: IntoIterator<Item=S>>(&mut self, iter: T) {
        self.cells.extend(iter.into_iter().map(|s| Cell::new(&s.to_string())));
    }
}

// impl <S: Into<Cell>> Extend<S> for Row {
//     fn extend<T: IntoIterator<Item=S>>(&mut self, iter: T) {
//         self.cells.extend(iter.into_iter().map(|s| s.into()));
//     }
// }

/// This macro simplifies `Row` creation
///
/// The syntax support style spec
/// # Example
/// ```
/// # #[macro_use] extern crate prettytable;
/// # fn main() {
/// // Create a normal row
/// let row1 = row!["Element 1", "Element 2", "Element 3"];
/// // Create a row with all cells formatted with red foreground color, yellow background color
/// // bold, italic, align in the center of the cell
/// let row2 = row![FrBybic => "Element 1", "Element 2", "Element 3"];
/// // Create a row with first cell in blue, second one in red, and last one with default style
/// let row3 = row![Fb->"blue", Fr->"red", "normal"];
/// // Do something with rows
/// # drop(row1);
/// # drop(row2);
/// # drop(row3);
/// # }
/// ```
///
/// For details about style specifier syntax, check doc for [`Cell::style_spec`](cell/struct.Cell.html#method.style_spec) method
#[macro_export]
macro_rules! row {
    (($($out:tt)*);) => (vec![$($out)*]);
    (($($out:tt)*); $value:expr) => (vec![$($out)* cell!($value)]);
    (($($out:tt)*); $value:expr, $($n:tt)*) => (row!(($($out)* cell!($value),); $($n)*));
    (($($out:tt)*); $style:ident -> $value:expr) => (vec![$($out)* cell!($style -> $value)]);
    (($($out:tt)*); $style:ident -> $value:expr, $($n: tt)*) => (row!(($($out)* cell!($style -> $value),); $($n)*));

    ($($content:expr), *) => ($crate::Row::new(vec![$(cell!($content)), *])); // This line may not be needed starting from Rust 1.20
    ($style:ident => $($content:expr), *) => ($crate::Row::new(vec![$(cell!($style -> $content)), *]));
    ($style:ident => $($content:expr,) *) => ($crate::Row::new(vec![$(cell!($style -> $content)), *]));
    ($($content:tt)*) => ($crate::Row::new(row!((); $($content)*)));
}

#[cfg(test)]
mod tests {
    use super::*;
    use Cell;

    #[test]
    fn row_default_empty() {
        let row1 = Row::default();
        assert_eq!(row1.len(), 0);
        assert!(row1.is_empty());
    }

    #[test]
    fn get_add_set_cell() {
        let mut row = Row::from(vec!["foo", "bar", "foobar"]);
        assert_eq!(row.len(), 3);
        assert!(row.get_mut_cell(12).is_none());
        let c1 = row.get_mut_cell(0).unwrap().clone();
        assert_eq!(c1.get_content(), "foo");

        let c1 = Cell::from(&"baz");
        assert!(row.set_cell(c1.clone(), 1000).is_err());
        assert!(row.set_cell(c1.clone(), 0).is_ok());
        assert_eq!(row.get_cell(0).unwrap().get_content(), "baz");

        row.add_cell(c1.clone());
        assert_eq!(row.len(), 4);
        assert_eq!(row.get_cell(3).unwrap().get_content(), "baz");
    }

    #[test]
    fn insert_cell() {
        let mut row = Row::from(vec!["foo", "bar", "foobar"]);
        assert_eq!(row.len(), 3);
        let cell = Cell::new("baz");
        row.insert_cell(1000, cell.clone());
        assert_eq!(row.len(), 4);
        assert_eq!(row.get_cell(3).unwrap().get_content(), "baz");
        row.insert_cell(1, cell.clone());
        assert_eq!(row.len(), 5);
        assert_eq!(row.get_cell(1).unwrap().get_content(), "baz");
    }

    #[test]
    fn remove_cell() {
        let mut row = Row::from(vec!["foo", "bar", "foobar"]);
        assert_eq!(row.len(), 3);
        row.remove_cell(1000);
        assert_eq!(row.len(), 3);
        row.remove_cell(1);
        assert_eq!(row.len(), 2);
        assert_eq!(row.get_cell(0).unwrap().get_content(), "foo");
        assert_eq!(row.get_cell(1).unwrap().get_content(), "foobar");
    }

    #[test]
    fn extend_row() {
        let mut row = Row::from(vec!["foo", "bar", "foobar"]);
        row.extend(vec!["A", "B", "C"]);
        assert_eq!(row.len(), 6);
        assert_eq!(row.get_cell(3).unwrap().get_content(), "A");
        assert_eq!(row.get_cell(4).unwrap().get_content(), "B");
        assert_eq!(row.get_cell(5).unwrap().get_content(), "C");
    }
}