prettytable/
cell.rs

1//! This module contains definition of table/row cells stuff
2
3use super::format::Alignment;
4use super::utils::{display_width, print_align, HtmlEscape};
5use super::{color, Attr, Terminal};
6use std::io::{Error, Write};
7use std::str::FromStr;
8use std::string::ToString;
9
10/// Represent a table cell containing a string.
11///
12/// Once created, a cell's content cannot be modified.
13/// The cell would have to be replaced by another one
14#[derive(Clone, Debug, Hash, PartialEq, Eq)]
15pub struct Cell {
16    content: Vec<String>,
17    width: usize,
18    align: Alignment,
19    style: Vec<Attr>,
20    hspan: usize,
21}
22
23impl Cell {
24    /// Create a new `Cell` initialized with content from `string`.
25    /// Text alignment in cell is configurable with the `align` argument
26    pub fn new_align(string: &str, align: Alignment) -> Cell {
27        let content: Vec<String> = string.lines().map(|x| x.to_string()).collect();
28        let mut width = 0;
29        for cont in &content {
30            let l = display_width(&cont[..]);
31            if l > width {
32                width = l;
33            }
34        }
35        Cell {
36            content,
37            width,
38            align,
39            style: Vec::new(),
40            hspan: 1,
41        }
42    }
43
44    /// Create a new `Cell` initialized with content from `string`.
45    /// By default, content is align to `LEFT`
46    pub fn new(string: &str) -> Cell {
47        Cell::new_align(string, Alignment::LEFT)
48    }
49
50    /// Set text alignment in the cell
51    pub fn align(&mut self, align: Alignment) {
52        self.align = align;
53    }
54
55    /// Add a style attribute to the cell
56    pub fn style(&mut self, attr: Attr) {
57        self.style.push(attr);
58    }
59
60    /// Add a style attribute to the cell. Can be chained
61    pub fn with_style(mut self, attr: Attr) -> Cell {
62        self.style(attr);
63        self
64    }
65
66    /// Add horizontal spanning to the cell
67    pub fn with_hspan(mut self, hspan: usize) -> Cell {
68        self.set_hspan(hspan);
69        self
70    }
71
72    /// Remove all style attributes and reset alignment to default (LEFT)
73    pub fn reset_style(&mut self) {
74        self.style.clear();
75        self.align(Alignment::LEFT);
76    }
77
78    /// Set the cell's style by applying the given specifier string
79    ///
80    /// # Style spec syntax
81    ///
82    /// The syntax for the style specifier looks like this :
83    /// **FrBybl** which means **F**oreground **r**ed **B**ackground **y**ellow **b**old **l**eft
84    ///
85    /// ### List of supported specifiers :
86    ///
87    /// * **F** : **F**oreground (must be followed by a color specifier)
88    /// * **B** : **B**ackground (must be followed by a color specifier)
89    /// * **H** : **H**orizontal span (must be followed by a number)
90    /// * **b** : **b**old
91    /// * **i** : **i**talic
92    /// * **u** : **u**nderline
93    /// * **c** : Align **c**enter
94    /// * **l** : Align **l**eft
95    /// * **r** : Align **r**ight
96    /// * **d** : **d**efault style
97    ///
98    /// ### List of color specifiers :
99    ///
100    /// * **r** : Red
101    /// * **b** : Blue
102    /// * **g** : Green
103    /// * **y** : Yellow
104    /// * **c** : Cyan
105    /// * **m** : Magenta
106    /// * **w** : White
107    /// * **d** : Black
108    ///
109    /// And capital letters are for **bright** colors.
110    /// Eg :
111    ///
112    /// * **R** : Bright Red
113    /// * **B** : Bright Blue
114    /// * ... and so on ...
115    pub fn style_spec(mut self, spec: &str) -> Cell {
116        self.reset_style();
117        let mut foreground = false;
118        let mut background = false;
119        let mut it = spec.chars().peekable();
120        while let Some(c) = it.next() {
121            if foreground || background {
122                let color = match c {
123                    'r' => color::RED,
124                    'R' => color::BRIGHT_RED,
125                    'b' => color::BLUE,
126                    'B' => color::BRIGHT_BLUE,
127                    'g' => color::GREEN,
128                    'G' => color::BRIGHT_GREEN,
129                    'y' => color::YELLOW,
130                    'Y' => color::BRIGHT_YELLOW,
131                    'c' => color::CYAN,
132                    'C' => color::BRIGHT_CYAN,
133                    'm' => color::MAGENTA,
134                    'M' => color::BRIGHT_MAGENTA,
135                    'w' => color::WHITE,
136                    'W' => color::BRIGHT_WHITE,
137                    'd' => color::BLACK,
138                    'D' => color::BRIGHT_BLACK,
139                    _ => {
140                        // Silently ignore unknown tags
141                        foreground = false;
142                        background = false;
143                        continue;
144                    }
145                };
146                if foreground {
147                    self.style(Attr::ForegroundColor(color));
148                } else if background {
149                    self.style(Attr::BackgroundColor(color));
150                }
151                foreground = false;
152                background = false;
153            } else {
154                match c {
155                    'F' => foreground = true,
156                    'B' => background = true,
157                    'b' => self.style(Attr::Bold),
158                    'i' => self.style(Attr::Italic(true)),
159                    'u' => self.style(Attr::Underline(true)),
160                    'c' => self.align(Alignment::CENTER),
161                    'l' => self.align(Alignment::LEFT),
162                    'r' => self.align(Alignment::RIGHT),
163                    'H' => {
164                        let mut span_s = String::new();
165                        while let Some('0'..='9') = it.peek() {
166                            span_s.push(it.next().unwrap());
167                        }
168                        let span = usize::from_str(&span_s).unwrap();
169                        self.set_hspan(span);
170                    }
171                    _ => { /* Silently ignore unknown tags */ }
172                }
173            }
174        }
175        self
176    }
177
178    /// Return the height of the cell
179    // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
180    pub(crate) fn get_height(&self) -> usize {
181        self.content.len()
182    }
183
184    /// Return the width of the cell
185    // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
186    pub(crate) fn get_width(&self) -> usize {
187        self.width
188    }
189
190    /// Set horizontal span for this cell (must be > 0)
191    pub fn set_hspan(&mut self, hspan: usize) {
192        self.hspan = if hspan == 0 { 1 } else { hspan };
193    }
194
195    /// Get horizontal span of this cell (> 0)
196    pub fn get_hspan(&self) -> usize {
197        self.hspan
198    }
199
200    /// Return a copy of the full string contained in the cell
201    pub fn get_content(&self) -> String {
202        self.content.join("\n")
203    }
204
205    /// Print a partial cell to `out`. Since the cell may be multi-lined,
206    /// `idx` is the line index to print. `col_width` is the column width used to
207    /// fill the cells with blanks so it fits in the table.
208    /// If `ìdx` is higher than this cell's height, it will print empty content
209    // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
210    pub(crate) fn print<T: Write + ?Sized>(
211        &self,
212        out: &mut T,
213        idx: usize,
214        col_width: usize,
215        skip_right_fill: bool,
216    ) -> Result<(), Error> {
217        let c = self.content.get(idx).map(|s| s.as_ref()).unwrap_or("");
218        print_align(out, self.align, c, ' ', col_width, skip_right_fill)
219    }
220
221    /// Apply style then call `print` to print the cell into a terminal
222    // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")]
223    pub(crate) fn print_term<T: Terminal + ?Sized>(
224        &self,
225        out: &mut T,
226        idx: usize,
227        col_width: usize,
228        skip_right_fill: bool,
229    ) -> Result<(), Error> {
230        for a in &self.style {
231            match out.attr(*a) {
232                Ok(..) | Err(::term::Error::NotSupported) | Err(::term::Error::ColorOutOfRange) => {
233                } // Ignore unsupported attributes
234                Err(e) => return Err(term_error_to_io_error(e)),
235            };
236        }
237        self.print(out, idx, col_width, skip_right_fill)?;
238        match out.reset() {
239            Ok(..) | Err(::term::Error::NotSupported) | Err(::term::Error::ColorOutOfRange) => {
240                Ok(())
241            }
242            Err(e) => Err(term_error_to_io_error(e)),
243        }
244    }
245
246    /// Print the cell in HTML format to `out`.
247    pub fn print_html<T: Write + ?Sized>(&self, out: &mut T) -> Result<usize, Error> {
248        /// Convert the color to a hex value useful in CSS
249        fn color2hex(color: color::Color) -> &'static str {
250            match color {
251                color::BLACK => "#000000",
252                color::RED => "#aa0000",
253                color::GREEN => "#00aa00",
254                color::YELLOW => "#aa5500",
255                color::BLUE => "#0000aa",
256                color::MAGENTA => "#aa00aa",
257                color::CYAN => "#00aaaa",
258                color::WHITE => "#aaaaaa",
259                color::BRIGHT_BLACK => "#555555",
260                color::BRIGHT_RED => "#ff5555",
261                color::BRIGHT_GREEN => "#55ff55",
262                color::BRIGHT_YELLOW => "#ffff55",
263                color::BRIGHT_BLUE => "#5555ff",
264                color::BRIGHT_MAGENTA => "#ff55ff",
265                color::BRIGHT_CYAN => "#55ffff",
266                color::BRIGHT_WHITE => "#ffffff",
267
268                // Unknown colors, fallback to blakc
269                _ => "#000000",
270            }
271        }
272
273        let colspan = if self.hspan > 1 {
274            format!(" colspan=\"{}\"", self.hspan)
275        } else {
276            String::new()
277        };
278
279        // Process style properties like color
280        let mut styles = String::new();
281        for style in &self.style {
282            match style {
283                Attr::Bold => styles += "font-weight: bold;",
284                Attr::Italic(true) => styles += "font-style: italic;",
285                Attr::Underline(true) => styles += "text-decoration: underline;",
286                Attr::ForegroundColor(c) => {
287                    styles += "color: ";
288                    styles += color2hex(*c);
289                    styles += ";";
290                }
291                Attr::BackgroundColor(c) => {
292                    styles += "background-color: ";
293                    styles += color2hex(*c);
294                    styles += ";";
295                }
296                _ => {}
297            }
298        }
299        // Process alignment
300        match self.align {
301            Alignment::LEFT => styles += "text-align: left;",
302            Alignment::CENTER => styles += "text-align: center;",
303            Alignment::RIGHT => styles += "text-align: right;",
304        }
305
306        let content = self.content.join("<br />");
307        out.write_all(
308            format!(
309                "<td{1} style=\"{2}\">{0}</td>",
310                HtmlEscape(&content),
311                colspan,
312                styles
313            )
314            .as_bytes(),
315        )?;
316        Ok(self.hspan)
317    }
318}
319
320fn term_error_to_io_error(te: ::term::Error) -> Error {
321    match te {
322        ::term::Error::Io(why) => why,
323        _ => Error::new(::std::io::ErrorKind::Other, te),
324    }
325}
326
327impl<'a, T: ToString> From<&'a T> for Cell {
328    fn from(f: &T) -> Cell {
329        Cell::new(&f.to_string())
330    }
331}
332
333impl ToString for Cell {
334    fn to_string(&self) -> String {
335        self.get_content()
336    }
337}
338
339impl Default for Cell {
340    /// Return a cell initialized with a single empty `String`, with LEFT alignment
341    fn default() -> Cell {
342        Cell {
343            content: vec!["".to_string(); 1],
344            width: 0,
345            align: Alignment::LEFT,
346            style: Vec::new(),
347            hspan: 1,
348        }
349    }
350}
351
352/// This macro simplifies `Cell` creation
353///
354/// Support 2 syntax : With and without style specification.
355/// # Syntax
356/// ```text
357/// cell!(value);
358/// ```
359/// or
360///
361/// ```text
362/// cell!(spec->value);
363/// ```
364/// Value must implement the `std::string::ToString` trait
365///
366/// For details about style specifier syntax, check doc for [`Cell::style_spec`](cell/struct.Cell.html#method.style_spec) method
367/// # Example
368/// ```
369/// # #[macro_use] extern crate prettytable;
370/// # fn main() {
371/// let cell = cell!("value");
372/// // Do something with the cell
373/// # drop(cell);
374/// // Create a cell with style (Red foreground, Bold, aligned to left);
375/// let styled = cell!(Frbl->"value");
376/// # drop(styled);
377/// # }
378/// ```
379#[macro_export]
380macro_rules! cell {
381    () => {
382        $crate::Cell::default()
383    };
384    ($value:expr) => {
385        $crate::Cell::new(&$value.to_string())
386    };
387    ($style:ident -> $value:expr) => {
388        $crate::cell!($value).style_spec(stringify!($style))
389    };
390}
391
392#[cfg(test)]
393mod tests {
394    use super::Cell;
395    use crate::format::Alignment;
396    use crate::utils::StringWriter;
397    use term::{color, Attr};
398
399    #[test]
400    fn get_content() {
401        let cell = Cell::new("test");
402        assert_eq!(cell.get_content(), "test");
403    }
404
405    #[test]
406    fn print_ascii() {
407        let ascii_cell = Cell::new("hello");
408        assert_eq!(ascii_cell.get_width(), 5);
409
410        let mut out = StringWriter::new();
411        let _ = ascii_cell.print(&mut out, 0, 10, false);
412        assert_eq!(out.as_string(), "hello     ");
413    }
414
415    #[test]
416    fn print_unicode() {
417        let unicode_cell = Cell::new("привет");
418        assert_eq!(unicode_cell.get_width(), 6);
419
420        let mut out = StringWriter::new();
421        let _ = unicode_cell.print(&mut out, 0, 10, false);
422        assert_eq!(out.as_string(), "привет    ");
423    }
424
425    #[test]
426    fn print_cjk() {
427        let unicode_cell = Cell::new("由系统自动更新");
428        assert_eq!(unicode_cell.get_width(), 14);
429        let mut out = StringWriter::new();
430        let _ = unicode_cell.print(&mut out, 0, 20, false);
431        assert_eq!(out.as_string(), "由系统自动更新      ");
432    }
433
434    #[test]
435    fn print_ascii_html() {
436        let ascii_cell = Cell::new("hello");
437        assert_eq!(ascii_cell.get_width(), 5);
438
439        let mut out = StringWriter::new();
440        let _ = ascii_cell.print_html(&mut out);
441        assert_eq!(
442            out.as_string(),
443            r#"<td style="text-align: left;">hello</td>"#
444        );
445    }
446
447    #[test]
448    fn print_html_special_chars() {
449        let ascii_cell = Cell::new("<abc\">&'");
450
451        let mut out = StringWriter::new();
452        let _ = ascii_cell.print_html(&mut out);
453        assert_eq!(
454            out.as_string(),
455            r#"<td style="text-align: left;">&lt;abc&quot;&gt;&amp;&#39;</td>"#
456        );
457    }
458
459    #[test]
460    fn align_left() {
461        let cell = Cell::new_align("test", Alignment::LEFT);
462        let mut out = StringWriter::new();
463        let _ = cell.print(&mut out, 0, 10, false);
464        assert_eq!(out.as_string(), "test      ");
465    }
466
467    #[test]
468    fn align_center() {
469        let cell = Cell::new_align("test", Alignment::CENTER);
470        let mut out = StringWriter::new();
471        let _ = cell.print(&mut out, 0, 10, false);
472        assert_eq!(out.as_string(), "   test   ");
473    }
474
475    #[test]
476    fn align_right() {
477        let cell = Cell::new_align("test", Alignment::RIGHT);
478        let mut out = StringWriter::new();
479        let _ = cell.print(&mut out, 0, 10, false);
480        assert_eq!(out.as_string(), "      test");
481    }
482
483    #[test]
484    fn style_spec() {
485        let mut cell = Cell::new("test").style_spec("FrBBbuic");
486        assert_eq!(cell.style.len(), 5);
487        assert!(cell.style.contains(&Attr::Underline(true)));
488        assert!(cell.style.contains(&Attr::Italic(true)));
489        assert!(cell.style.contains(&Attr::Bold));
490        assert!(cell.style.contains(&Attr::ForegroundColor(color::RED)));
491        assert!(cell
492            .style
493            .contains(&Attr::BackgroundColor(color::BRIGHT_BLUE)));
494        assert_eq!(cell.align, Alignment::CENTER);
495
496        cell = cell.style_spec("FDBwr");
497        assert_eq!(cell.style.len(), 2);
498        assert!(cell
499            .style
500            .contains(&Attr::ForegroundColor(color::BRIGHT_BLACK)));
501        assert!(cell.style.contains(&Attr::BackgroundColor(color::WHITE)));
502        assert_eq!(cell.align, Alignment::RIGHT);
503
504        // Test with invalid sepcifier chars
505        cell = cell.clone();
506        cell = cell.style_spec("FzBr");
507        assert!(cell.style.contains(&Attr::BackgroundColor(color::RED)));
508        assert_eq!(cell.style.len(), 1);
509        cell = cell.style_spec("zzz");
510        assert!(cell.style.is_empty());
511        assert_eq!(cell.get_hspan(), 1);
512        cell = cell.style_spec("FDBwH03r");
513        assert_eq!(cell.get_hspan(), 3);
514    }
515
516    #[test]
517    fn reset_style() {
518        let mut cell = Cell::new("test")
519            .with_style(Attr::ForegroundColor(color::BRIGHT_BLACK))
520            .with_style(Attr::BackgroundColor(color::WHITE));
521        cell.align(Alignment::RIGHT);
522
523        //style_spec("FDBwr");
524        assert_eq!(cell.style.len(), 2);
525        assert_eq!(cell.align, Alignment::RIGHT);
526        cell.reset_style();
527        assert_eq!(cell.style.len(), 0);
528        assert_eq!(cell.align, Alignment::LEFT);
529    }
530
531    #[test]
532    fn default_empty_cell() {
533        let cell = Cell::default();
534        assert_eq!(cell.align, Alignment::LEFT);
535        assert!(cell.style.is_empty());
536        assert_eq!(cell.get_content(), "");
537        assert_eq!(cell.to_string(), "");
538        assert_eq!(cell.get_height(), 1);
539        assert_eq!(cell.get_width(), 0);
540    }
541}