repo_description: A rust library to print aligned and formatted tables
repo_topics: cli crates formatting rust
code: //! This module contains definition of table/row cells stuff
  
  use super::format::Alignment;
  use super::utils::{display_width, print_align, HtmlEscape};
  use super::{color, Attr, Terminal};
  use std::io::{Error, Write};
  use std::str::FromStr;
  use std::string::ToString;
  
  /// 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<String>,
      width: usize,
      align: Alignment,
      style: Vec<Attr>,
      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> = 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,
              width,
              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(crate) 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(crate) 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(crate) fn print<T: Write + ?Sized>(
          &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(crate) fn print_term<T: Terminal + ?Sized>(
          &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 attributes
                  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)),
          }
      }
  
      /// Print the cell in HTML format to `out`.
      pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<usize, Error> {
          /// Convert the color to a hex value useful in CSS
          fn color2hex(color: color::Color) -> &'static str {
              match color {
                  color::BLACK => "#000000",
                  color::RED => "#aa0000",
                  color::GREEN => "#00aa00",
                  color::YELLOW => "#aa5500",
                  color::BLUE => "#0000aa",
                  color::MAGENTA => "#aa00aa",
                  color::CYAN => "#00aaaa",
                  color::WHITE => "#aaaaaa",
                  color::BRIGHT_BLACK => "#555555",
                  color::BRIGHT_RED => "#ff5555",
                  color::BRIGHT_GREEN => "#55ff55",
                  color::BRIGHT_YELLOW => "#ffff55",
                  color::BRIGHT_BLUE => "#5555ff",
                  color::BRIGHT_MAGENTA => "#ff55ff",
                  color::BRIGHT_CYAN => "#55ffff",
                  color::BRIGHT_WHITE => "#ffffff",
  
                  // Unknown colors, fallback to blakc
                  _ => "#000000",
              }
          }
  
          let colspan = if self.hspan > 1 {
              format!(" colspan=\"{}\"", self.hspan)
          } else {
              String::new()
          };
  
          // Process style properties like color
          let mut styles = String::new();
          for style in &self.style {
              match style {
                  Attr::Bold => styles += "font-weight: bold;",
                  Attr::Italic(true) => styles += "font-style: italic;",
                  Attr::Underline(true) => styles += "text-decoration: underline;",
                  Attr::ForegroundColor(c) => {
                      styles += "color: ";
                      styles += color2hex(*c);
                      styles += ";";
                  }
                  Attr::BackgroundColor(c) => {
                      styles += "background-color: ";
                      styles += color2hex(*c);
                      styles += ";";
                  }
                  _ => {}
              }
          }
          // Process alignment
          match self.align {
              Alignment::LEFT => styles += "text-align: left;",
              Alignment::CENTER => styles += "text-align: center;",
              Alignment::RIGHT => styles += "text-align: right;",
          }
  
          let content = self.content.join("<br />");
          out.write_all(
              format!(
                  "<td{1} style=\"{2}\">{0}</td>",
                  HtmlEscape(&content),
                  colspan,
                  styles
              )
              .as_bytes(),
          )?;
          Ok(self.hspan)
      }
  }
  
  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 crate::utils::StringWriter;
      use term::{color, Attr};
  
      #[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 print_ascii_html() {
          let ascii_cell = Cell::new("hello");
          assert_eq!(ascii_cell.get_width(), 5);
  
          let mut out = StringWriter::new();
          let _ = ascii_cell.print_html(&mut out);
          assert_eq!(
              out.as_string(),
              r#"<td style="text-align: left;">hello</td>"#
          );
      }
  
      #[test]
      fn print_html_special_chars() {
          let ascii_cell = Cell::new("<abc\">&'");
  
          let mut out = StringWriter::new();
          let _ = ascii_cell.print_html(&mut out);
          assert_eq!(
              out.as_string(),
              r#"<td style="text-align: left;">&lt;abc&quot;&gt;&amp;&#39;</td>"#
          );
      }
  
      #[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);
      }
  }
  