//! This module contains definition of table/row cells stuff use super::format::Alignment; use super::utils::display_width; use super::utils::print_align; use super::{color, Attr, Terminal}; use std::io::{Error, Write}; use std::string::ToString; use std::str::FromStr; /// Represent a table cell containing a string. /// /// Once created, a cell's content cannot be modified. /// The cell would have to be replaced by another one #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct Cell { content: Vec, width: usize, align: Alignment, style: Vec, hspan: usize, } impl Cell { /// Create a new `Cell` initialized with content from `string`. /// Text alignment in cell is configurable with the `align` argument pub fn new_align(string: &str, align: Alignment) -> Cell { let content: Vec = string.lines().map(|x| x.to_string()).collect(); let mut width = 0; for cont in &content { let l = display_width(&cont[..]); if l > width { width = l; } } Cell { content: content, width: width, align: align, style: Vec::new(), hspan: 1, } } /// Create a new `Cell` initialized with content from `string`. /// By default, content is align to `LEFT` pub fn new(string: &str) -> Cell { Cell::new_align(string, Alignment::LEFT) } /// Set text alignment in the cell pub fn align(&mut self, align: Alignment) { self.align = align; } /// Add a style attribute to the cell pub fn style(&mut self, attr: Attr) { self.style.push(attr); } /// Add a style attribute to the cell. Can be chained pub fn with_style(mut self, attr: Attr) -> Cell { self.style(attr); self } /// Add horizontal spanning to the cell pub fn with_hspan(mut self, hspan: usize) -> Cell { self.set_hspan(hspan); self } /// Remove all style attributes and reset alignment to default (LEFT) pub fn reset_style(&mut self) { self.style.clear(); self.align(Alignment::LEFT); } /// Set the cell's style by applying the given specifier string /// /// # Style spec syntax /// /// The syntax for the style specifier looks like this : /// **FrBybl** which means **F**oreground **r**ed **B**ackground **y**ellow **b**old **l**eft /// /// ### List of supported specifiers : /// /// * **F** : **F**oreground (must be followed by a color specifier) /// * **B** : **B**ackground (must be followed by a color specifier) /// * **H** : **H**orizontal span (must be followed by a number) /// * **b** : **b**old /// * **i** : **i**talic /// * **u** : **u**nderline /// * **c** : Align **c**enter /// * **l** : Align **l**eft /// * **r** : Align **r**ight /// * **d** : **d**efault style /// /// ### List of color specifiers : /// /// * **r** : Red /// * **b** : Blue /// * **g** : Green /// * **y** : Yellow /// * **c** : Cyan /// * **m** : Magenta /// * **w** : White /// * **d** : Black /// /// And capital letters are for **bright** colors. /// Eg : /// /// * **R** : Bright Red /// * **B** : Bright Blue /// * ... and so on ... pub fn style_spec(mut self, spec: &str) -> Cell { self.reset_style(); let mut foreground = false; let mut background = false; let mut it = spec.chars().peekable(); while let Some(c) = it.next() { if foreground || background { let color = match c { 'r' => color::RED, 'R' => color::BRIGHT_RED, 'b' => color::BLUE, 'B' => color::BRIGHT_BLUE, 'g' => color::GREEN, 'G' => color::BRIGHT_GREEN, 'y' => color::YELLOW, 'Y' => color::BRIGHT_YELLOW, 'c' => color::CYAN, 'C' => color::BRIGHT_CYAN, 'm' => color::MAGENTA, 'M' => color::BRIGHT_MAGENTA, 'w' => color::WHITE, 'W' => color::BRIGHT_WHITE, 'd' => color::BLACK, 'D' => color::BRIGHT_BLACK, _ => { // Silently ignore unknown tags foreground = false; background = false; continue; } }; if foreground { self.style(Attr::ForegroundColor(color)); } else if background { self.style(Attr::BackgroundColor(color)); } foreground = false; background = false; } else { match c { 'F' => foreground = true, 'B' => background = true, 'b' => self.style(Attr::Bold), 'i' => self.style(Attr::Italic(true)), 'u' => self.style(Attr::Underline(true)), 'c' => self.align(Alignment::CENTER), 'l' => self.align(Alignment::LEFT), 'r' => self.align(Alignment::RIGHT), 'H' => { let mut span_s = String::new(); while let Some('0'..='9') = it.peek() { span_s.push(it.next().unwrap()); } let span = usize::from_str(&span_s).unwrap(); self.set_hspan(span); } _ => { /* Silently ignore unknown tags */ } } } } self } /// Return the height of the cell #[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 { self.content.len() } /// Return the width of the cell #[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_width(&self) -> usize { self.width } /// Set horizontal span for this cell (must be > 0) pub fn set_hspan(&mut self, hspan: usize) { self.hspan = if hspan == 0 {1} else {hspan}; } /// Get horizontal span of this cell (> 0) pub fn get_hspan(&self) -> usize { self.hspan } /// Return a copy of the full string contained in the cell pub fn get_content(&self) -> String { self.content.join("\n") } /// Print a partial cell to `out`. Since the cell may be multi-lined, /// `idx` is the line index to print. `col_width` is the column width used to /// fill the cells with blanks so it fits in the table. /// If `ìdx` is higher than this cell's height, it will print empty content #[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( &self, out: &mut T, idx: usize, col_width: usize, skip_right_fill: bool, ) -> Result<(), Error> { let c = self.content.get(idx).map(|s| s.as_ref()).unwrap_or(""); print_align(out, self.align, c, ' ', col_width, skip_right_fill) } /// Apply style then call `print` to print the cell into a terminal #[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( &self, out: &mut T, idx: usize, col_width: usize, skip_right_fill: bool, ) -> Result<(), Error> { for a in &self.style { match out.attr(*a) { Ok(..) | Err(::term::Error::NotSupported) | Err(::term::Error::ColorOutOfRange) => { () } // Ignore unsupported atrributes Err(e) => return Err(term_error_to_io_error(e)), }; } self.print(out, idx, col_width, skip_right_fill)?; match out.reset() { Ok(..) | Err(::term::Error::NotSupported) | Err(::term::Error::ColorOutOfRange) => { Ok(()) } Err(e) => Err(term_error_to_io_error(e)), } } } fn term_error_to_io_error(te: ::term::Error) -> Error { match te { ::term::Error::Io(why) => why, _ => Error::new(::std::io::ErrorKind::Other, te), } } impl<'a, T: ToString> From<&'a T> for Cell { fn from(f: &T) -> Cell { Cell::new(&f.to_string()) } } impl ToString for Cell { fn to_string(&self) -> String { self.get_content() } } impl Default for Cell { /// Return a cell initialized with a single empty `String`, with LEFT alignment fn default() -> Cell { Cell { content: vec!["".to_string(); 1], width: 0, align: Alignment::LEFT, style: Vec::new(), hspan: 1, } } } /// This macro simplifies `Cell` creation /// /// Support 2 syntax : With and without style specification. /// # Syntax /// ```text /// cell!(value); /// ``` /// or /// /// ```text /// cell!(spec->value); /// ``` /// Value must implement the `std::string::ToString` trait /// /// For details about style specifier syntax, check doc for [`Cell::style_spec`](cell/struct.Cell.html#method.style_spec) method /// # Example /// ``` /// # #[macro_use] extern crate prettytable; /// # fn main() { /// let cell = cell!("value"); /// // Do something with the cell /// # drop(cell); /// // Create a cell with style (Red foreground, Bold, aligned to left); /// let styled = cell!(Frbl->"value"); /// # drop(styled); /// # } /// ``` #[macro_export] macro_rules! cell { () => { $crate::Cell::default() }; ($value:expr) => { $crate::Cell::new(&$value.to_string()) }; ($style:ident -> $value:expr) => { $crate::cell!($value).style_spec(stringify!($style)) }; } #[cfg(test)] mod tests { use super::Cell; use crate::format::Alignment; use term::{color, Attr}; use crate::utils::StringWriter; #[test] fn get_content() { let cell = Cell::new("test"); assert_eq!(cell.get_content(), "test"); } #[test] fn print_ascii() { let ascii_cell = Cell::new("hello"); assert_eq!(ascii_cell.get_width(), 5); let mut out = StringWriter::new(); let _ = ascii_cell.print(&mut out, 0, 10, false); assert_eq!(out.as_string(), "hello "); } #[test] fn print_unicode() { let unicode_cell = Cell::new("привет"); assert_eq!(unicode_cell.get_width(), 6); let mut out = StringWriter::new(); let _ = unicode_cell.print(&mut out, 0, 10, false); assert_eq!(out.as_string(), "привет "); } #[test] fn print_cjk() { let unicode_cell = Cell::new("由系统自动更新"); assert_eq!(unicode_cell.get_width(), 14); let mut out = StringWriter::new(); let _ = unicode_cell.print(&mut out, 0, 20, false); assert_eq!(out.as_string(), "由系统自动更新 "); } #[test] fn align_left() { let cell = Cell::new_align("test", Alignment::LEFT); let mut out = StringWriter::new(); let _ = cell.print(&mut out, 0, 10, false); assert_eq!(out.as_string(), "test "); } #[test] fn align_center() { let cell = Cell::new_align("test", Alignment::CENTER); let mut out = StringWriter::new(); let _ = cell.print(&mut out, 0, 10, false); assert_eq!(out.as_string(), " test "); } #[test] fn align_right() { let cell = Cell::new_align("test", Alignment::RIGHT); let mut out = StringWriter::new(); let _ = cell.print(&mut out, 0, 10, false); assert_eq!(out.as_string(), " test"); } #[test] fn style_spec() { let mut cell = Cell::new("test").style_spec("FrBBbuic"); assert_eq!(cell.style.len(), 5); assert!(cell.style.contains(&Attr::Underline(true))); assert!(cell.style.contains(&Attr::Italic(true))); assert!(cell.style.contains(&Attr::Bold)); assert!(cell.style.contains(&Attr::ForegroundColor(color::RED))); assert!( cell.style .contains(&Attr::BackgroundColor(color::BRIGHT_BLUE)) ); assert_eq!(cell.align, Alignment::CENTER); cell = cell.style_spec("FDBwr"); assert_eq!(cell.style.len(), 2); assert!( cell.style .contains(&Attr::ForegroundColor(color::BRIGHT_BLACK)) ); assert!(cell.style.contains(&Attr::BackgroundColor(color::WHITE))); assert_eq!(cell.align, Alignment::RIGHT); // Test with invalid sepcifier chars cell = cell.clone(); cell = cell.style_spec("FzBr"); assert!(cell.style.contains(&Attr::BackgroundColor(color::RED))); assert_eq!(cell.style.len(), 1); cell = cell.style_spec("zzz"); assert!(cell.style.is_empty()); assert_eq!(cell.get_hspan(), 1); cell = cell.style_spec("FDBwH03r"); assert_eq!(cell.get_hspan(), 3); } #[test] fn reset_style() { let mut cell = Cell::new("test") .with_style(Attr::ForegroundColor(color::BRIGHT_BLACK)) .with_style(Attr::BackgroundColor(color::WHITE)); cell.align(Alignment::RIGHT); //style_spec("FDBwr"); assert_eq!(cell.style.len(), 2); assert_eq!(cell.align, Alignment::RIGHT); cell.reset_style(); assert_eq!(cell.style.len(), 0); assert_eq!(cell.align, Alignment::LEFT); } #[test] fn default_empty_cell() { let cell = Cell::default(); assert_eq!(cell.align, Alignment::LEFT); assert!(cell.style.is_empty()); assert_eq!(cell.get_content(), ""); assert_eq!(cell.to_string(), ""); assert_eq!(cell.get_height(), 1); assert_eq!(cell.get_width(), 0); } }