prettytable/
cell.rs

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