blob: bc25cec0d31e5437388a28173d8fadffa1c7ac08 [file] [log] [blame]
//! 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<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: 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<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 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 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) => {
cell!($value).style_spec(stringify!($style))
};
}
#[cfg(test)]
mod tests {
use Cell;
use format::Alignment;
use term::{color, Attr};
use 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);
}
}