stybulate/
lib.rs

1//! # Stybulate : Tabulate with Style!
2//! This library creates tables in ASCII with styled borders
3//!
4//! # References
5//! It was inspired by the Python package <https://pypi.org/project/tabulate/>
6//!
7//! # Examples
8//! ```
9//! use stybulate::{Table, Style, Cell, Headers};
10//! let result = Table::new(
11//!     Style::Fancy,
12//!     vec![
13//!         vec![Cell::from("answer"), Cell::Int(42)],
14//!         vec![Cell::from("pi"), Cell::Float(3.1415)],
15//!     ],
16//!     Some(Headers::from(vec!["strings", "numbers"])),
17//! ).tabulate();
18//! let expected = vec![
19//!     "╒═══════════╤═══════════╕",
20//!     "│ strings   │   numbers │",
21//!     "╞═══════════╪═══════════╡",
22//!     "│ answer    │   42      │",
23//!     "├───────────┼───────────┤",
24//!     "│ pi        │    3.1415 │",
25//!     "╘═══════════╧═══════════╛",
26//! ].join("\n");
27//! assert_eq!(expected, result);
28//! ```
29
30#![warn(missing_docs)]
31
32use std::cmp;
33use std::collections::HashMap;
34
35use unicode_width::UnicodeWidthStr;
36
37mod style;
38pub use style::{Align, Style};
39
40mod unstyle;
41pub use unstyle::{AsciiEscapedString, Unstyle};
42
43mod cell;
44pub use cell::Cell;
45
46// constants
47const MIN_PADDING: usize = 2;
48
49/// The Headers structure is a list of headers (per column)
50/// # Example
51/// ```
52/// use stybulate::{AsciiEscapedString, Headers};
53/// // simple example with only strings
54/// let simple = Headers::from(vec!["foo", "bar"]);
55/// // more elaborated example with a mix of a styled string and a simple string
56/// let mut with_style = Headers::with_capacity(2);
57/// with_style
58///     .push(AsciiEscapedString::from("\x1b[1;35mfoo\x1b[0m bar"))
59///     .push(String::from("baz"));
60/// ```
61#[derive(Default)]
62pub struct Headers {
63    headers: Vec<Box<dyn Unstyle>>,
64}
65
66impl Headers {
67    /// Headers constructor: creates an empty header
68    pub fn new() -> Self {
69        Default::default()
70    }
71
72    /// Headers constructor from a vec of `&str`
73    pub fn from(headers: Vec<&str>) -> Self {
74        let mut unstyle_headers: Vec<Box<dyn Unstyle>> = Vec::with_capacity(headers.len());
75        for header in headers.iter() {
76            unstyle_headers.push(Box::new(String::from(*header)));
77        }
78        Self {
79            headers: unstyle_headers,
80        }
81    }
82
83    /// Headers constructor with capacity
84    pub fn with_capacity(capacity: usize) -> Self {
85        Self {
86            headers: Vec::with_capacity(capacity),
87        }
88    }
89
90    /// Add a header to the Headers
91    pub fn push<H: Unstyle + 'static>(&mut self, header: H) -> &mut Self {
92        self.headers.push(Box::new(header));
93        self
94    }
95
96    fn len(&self) -> usize {
97        self.headers.len()
98    }
99
100    #[allow(clippy::borrowed_box)]
101    fn get(&self, i: usize) -> Option<&Box<dyn Unstyle>> {
102        self.headers.get(i)
103    }
104
105    #[allow(clippy::borrowed_box)]
106    fn to_ref_vec(&self) -> Vec<&Box<dyn Unstyle>> {
107        self.headers.iter().collect()
108    }
109}
110
111/// The Table structure
112/// # Example
113/// ```
114/// use::stybulate::*;
115/// let mut table = Table::new(
116///     Style::Fancy,
117///     vec![
118///         vec![Cell::from("answer"), Cell::Int(42)],
119///         vec![Cell::from("pi"), Cell::Float(3.1415)],
120///     ],
121///     Some(Headers::from(vec!["strings", "numbers"]))
122/// );
123/// table.set_align(Align::Center, Align::Right);
124/// ```
125pub struct Table<'a> {
126    style: Style,
127    str_align: Align,
128    num_align: Align,
129    contents: Vec<Vec<Cell<'a>>>,
130    headers: Option<Headers>,
131
132    #[cfg(feature = "ansi_term_style")]
133    border_style: Option<ansi_term::Style>,
134}
135
136impl<'a> Table<'a> {
137    /// Table constructor with default alignments (`Align::Left` for strings and `Align::Decimal` for numbers)
138    pub fn new(style: Style, contents: Vec<Vec<Cell<'a>>>, headers: Option<Headers>) -> Self {
139        Self {
140            style,
141            str_align: Align::Left,
142            num_align: Align::Decimal,
143            #[cfg(feature = "ansi_term_style")]
144            border_style: None,
145            contents,
146            headers,
147        }
148    }
149
150    /// Set the table alignments (defaults are `Align::Left` for strings and `Align::Decimal` for numbers)
151    /// # Panics
152    /// Panics if str_align is equal to `Align::Decimal`
153    pub fn set_align(&mut self, str_align: Align, num_align: Align) {
154        if str_align == Align::Decimal {
155            panic!("str_align should not be set to Decimal, only num_align can");
156        }
157        self.str_align = str_align;
158        self.num_align = num_align;
159    }
160
161    #[cfg(feature = "ansi_term_style")]
162    /// Set the borders style
163    /// # Feature
164    /// Needs feature `ansi_term_style`.
165    pub fn set_border_style(&mut self, style: ansi_term::Style) {
166        self.border_style = Some(style);
167    }
168
169    /// Creates the table as a `String`
170    pub fn tabulate(&self) -> String {
171        let style = &self.style;
172        let headers = &self.headers;
173        let contents = &self.contents;
174        let str_align = &self.str_align;
175        let num_align = &self.num_align;
176        #[allow(unused_mut)]
177        let mut fmt = style.to_format();
178        #[cfg(feature = "ansi_term_style")]
179        {
180            if let Some(style) = self.border_style {
181                fmt.apply_style(style);
182            }
183        }
184        // number of columns
185        let header_len = if let Some(h) = headers { h.len() } else { 0 };
186        let col_nb = cmp::max(
187            header_len,
188            *contents.iter().map(Vec::len).max().get_or_insert(0),
189        );
190        // column specs = [0]: true if only made of numbers & [1]: digits offset
191        let col_spec = get_col_specs(col_nb, contents);
192        // max width of the content of each column
193        let col_width = get_col_width(col_nb, headers, contents, &col_spec, num_align);
194        // Build the lines
195        let mut lines = vec![];
196        // lineabove
197        if !(headers.is_some() && fmt.hidelineaboveifheader) {
198            if let Some(lineabove) = fmt.lineabove {
199                lines.push(create_line(&lineabove, &col_width));
200            }
201        }
202        if let Some(headers) = headers {
203            // headerrow
204            let headers: Vec<&Box<dyn Unstyle>> = headers.to_ref_vec();
205            for data in create_data_lines(&headers, str_align, num_align, &col_width, &col_spec) {
206                lines.push(create_data_line(&fmt.headerrow, col_nb, &data));
207            }
208            // linebelowheader
209            if let Some(linebelowheader) = fmt.linebelowheader {
210                lines.push(create_line(&linebelowheader, &col_width));
211            }
212        }
213        // loop on contents
214        for (i, content) in contents.iter().enumerate() {
215            // linebetweenrows
216            if i != 0 {
217                if let Some(linebetweenrows) = fmt.linebetweenrows.clone() {
218                    lines.push(create_line(&linebetweenrows, &col_width));
219                }
220            }
221            // datarow
222            let mut unstylable_content = Vec::with_capacity(content.len());
223            let mut temp_unstyle_store = HashMap::new();
224            let mut temp_strings_store = HashMap::new();
225            for (col, cell) in content.iter().enumerate() {
226                if let Some(u) = cell.to_unstylable() {
227                    temp_unstyle_store.insert(col, u);
228                } else {
229                    temp_strings_store.insert(
230                        col,
231                        Box::new(cell.to_string_with_precision(col_spec[col].1).unwrap())
232                            as Box<dyn Unstyle>,
233                    );
234                }
235            }
236            for col in 0..col_nb {
237                if let Some(u) = temp_unstyle_store.get(&col) {
238                    unstylable_content.push(*u);
239                } else {
240                    unstylable_content.push(temp_strings_store.get(&col).unwrap());
241                }
242            }
243            for data in create_data_lines(
244                &unstylable_content,
245                str_align,
246                num_align,
247                &col_width,
248                &col_spec,
249            ) {
250                lines.push(create_data_line(&fmt.datarow, col_nb, &data));
251            }
252        }
253        // linebelow
254        if !(headers.is_some() && fmt.hidelinebelowifheader) {
255            if let Some(linebelow) = fmt.linebelow {
256                lines.push(create_line(&linebelow, &col_width));
257            }
258        }
259        // finally join all lines
260        lines.join("\n")
261    }
262}
263
264// --------------------------- Private ---------------------------
265
266fn get_col_width<'a>(
267    col_nb: usize,
268    headers: &Option<Headers>,
269    contents: &[Vec<Cell<'a>>],
270    col_spec: &[(bool, usize)],
271    num_align: &Align,
272) -> Vec<usize> {
273    let mut col_width = vec![0; col_nb];
274    for col in 0..col_nb {
275        let mut max = 0;
276        if let Some(headers) = headers {
277            if let Some(h) = headers.get(col) {
278                max = *h
279                    .unstyle()
280                    .split('\n')
281                    .map(|s| UnicodeWidthStr::width(s as &str))
282                    .max()
283                    .get_or_insert(0)
284                    + MIN_PADDING;
285            }
286        }
287        for row in contents.iter() {
288            if let Some(c) = row.get(col) {
289                let width = if col_spec[col].0 /* a number */ && num_align == &Align::Decimal && col_spec[col].1 > 0
290                {
291                    c.to_string_with_precision(col_spec[col].1).unwrap().len()
292                } else if let Some(u) = c.to_unstylable() {
293                    *u.unstyle()
294                        .split('\n')
295                        .map(|s| UnicodeWidthStr::width(s as &str))
296                        .max()
297                        .get_or_insert(0)
298                } else {
299                    c.to_string().unwrap().len()
300                };
301                max = cmp::max(width, max);
302            }
303        }
304        col_width[col] = max;
305    }
306    col_width
307}
308
309fn get_col_specs(col_nb: usize, contents: &[Vec<Cell>]) -> Vec<(bool, usize)> {
310    let mut col_spec = vec![(false, 0); col_nb];
311    for (col, spec) in col_spec.iter_mut().enumerate().take(col_nb) {
312        let mut max = 0;
313        let mut all = true;
314        for row in contents.iter() {
315            if let Some(cell) = row.get(col) {
316                if cell.is_a_number() {
317                    max = cmp::max(max, cell.digits_len());
318                } else {
319                    all = false;
320                }
321            }
322        }
323        *spec = (all, max);
324    }
325    col_spec
326}
327
328fn create_line(line: &style::Line, col_width: &[usize]) -> String {
329    (line.begin.clone()
330        + &col_width
331            .iter()
332            .map(|w| line.hline.repeat(*w))
333            .collect::<Vec<String>>()
334            .join(&line.sep)
335        + &line.end)
336        .trim_end()
337        .to_string()
338}
339
340fn create_data_line(row: &style::DataRow, col_nb: usize, content: &[String]) -> String {
341    let mut v = Vec::with_capacity(col_nb);
342    for col in 0..col_nb {
343        if let Some(c) = content.get(col) {
344            v.push(String::from(c));
345        } else {
346            v.push(String::from(""));
347        }
348    }
349    (row.begin.clone() + &v.join(&row.sep) + &row.end)
350        .trim_end()
351        .to_string()
352}
353
354#[allow(clippy::borrowed_box)]
355fn format_unstylable<'a>(
356    word: &Box<dyn Unstyle + 'a>,
357    line_idx: usize,
358    align: &Align,
359    width: usize,
360) -> String {
361    if let Some(unstyled_word) = word.unstyle().split('\n').nth(line_idx) {
362        let word = word.to_string();
363        let word = word
364            .split('\n')
365            .nth(line_idx)
366            .expect("unstyled word can't have more \\n than styled one");
367        let width =
368            width - (UnicodeWidthStr::width(unstyled_word as &str) - unstyled_word.chars().count());
369        let formatted = match align {
370            Align::Right => format!("{:>width$}", unstyled_word, width = width),
371            Align::Left => format!("{:<width$}", unstyled_word, width = width),
372            Align::Center => format!("{:^width$}", unstyled_word, width = width),
373            Align::Decimal => {
374                let mut out = format!("{:>width$}", unstyled_word, width = width);
375                if let Some(dot) = out.rfind('.') {
376                    if out[(dot + 1)..].bytes().all(|c| c == b'0') {
377                        out.replace_range(dot.., &" ".repeat(out.len() - dot));
378                    } else {
379                        for i in (dot + 1..out.len()).rev() {
380                            if out.as_bytes()[i] == b'0' {
381                                out.replace_range(i..i + 1, " ");
382                            }
383                        }
384                    }
385                }
386                out
387            }
388        };
389        if unstyled_word != word {
390            formatted.replace(&unstyled_word, word)
391        } else {
392            formatted
393        }
394    } else {
395        " ".repeat(width)
396    }
397}
398
399#[allow(clippy::borrowed_box)]
400fn create_data_lines<'a>(
401    content: &[&Box<dyn Unstyle + 'a>],
402    str_align: &Align,
403    num_align: &Align,
404    col_width: &[usize],
405    col_spec: &[(bool, usize)],
406) -> Vec<Vec<String>> {
407    let lines_nb = content.iter().map(|u| u.nb_of_lines()).max().unwrap();
408    let mut lines = Vec::with_capacity(lines_nb);
409    for i in 0..lines_nb {
410        let formatted: Vec<_> = content
411            .iter()
412            .enumerate()
413            .map(|(col, text)| {
414                let align = if col_spec[col].0 {
415                    // numbers only
416                    &num_align
417                } else {
418                    // strings only
419                    &str_align
420                };
421                format_unstylable(text, i, align, col_width[col])
422            })
423            .collect();
424        lines.push(formatted);
425    }
426    lines
427}
428
429// --------------------------- Tests ---------------------------
430
431#[cfg(test)]
432mod tests {
433
434    use super::*;
435
436    fn headerless(style: Style) -> Table<'static> {
437        Table::new(
438            style,
439            vec![
440                vec![Cell::from("spam"), Cell::Float(41.9999)],
441                vec![Cell::from("eggs"), Cell::Int(451)],
442            ],
443            None,
444        )
445    }
446
447    fn table(style: Style) -> Table<'static> {
448        Table::new(
449            style.clone(),
450            headerless(style).contents,
451            Some(Headers::from(vec!["strings", "numbers"])),
452        )
453    }
454
455    fn multiline_headerless(style: Style) -> Table<'static> {
456        let mut table = Table::new(
457            style,
458            vec![
459                vec![Cell::from("foo bar\nbaz\nbau"), Cell::from("hello")],
460                vec![Cell::from(""), Cell::from("multiline\nworld")],
461            ],
462            None,
463        );
464        table.set_align(Align::Center, Align::Right);
465        table
466    }
467
468    fn multiline(style: Style) -> Table<'static> {
469        Table::new(
470            style,
471            vec![vec![Cell::Int(2), Cell::from("foo\nbar")]],
472            Some(Headers::from(vec!["more\nspam eggs", "more spam\n& eggs"])),
473        )
474    }
475
476    fn multiline_empty_cells(style: Style) -> Table<'static> {
477        Table::new(
478            style.clone(),
479            vec![
480                vec![Cell::Int(1), Cell::from(""), Cell::from("")],
481                vec![
482                    Cell::Int(2),
483                    Cell::from("very long data"),
484                    Cell::from("fold\nthis"),
485                ],
486            ],
487            Some(Headers::from(vec!["hdr", "data", "fold"])),
488        )
489    }
490
491    fn multiline_empty_cells_headerless(style: Style) -> Table<'static> {
492        Table::new(
493            style,
494            vec![
495                vec![Cell::Int(0), Cell::from(""), Cell::from("")],
496                vec![Cell::Int(1), Cell::from(""), Cell::from("")],
497                vec![
498                    Cell::Int(2),
499                    Cell::from("very long data"),
500                    Cell::from("fold\nthis"),
501                ],
502            ],
503            None,
504        )
505    }
506
507    #[test]
508    fn plain() {
509        //Output: plain with headers
510        let result = table(Style::Plain).tabulate();
511        let expected = vec![
512            "strings      numbers",
513            "spam         41.9999",
514            "eggs        451",
515        ]
516        .join("\n");
517        assert_eq!(expected, result);
518    }
519
520    #[test]
521    fn plain_headerless() {
522        //Output: plain without headers
523        let result = headerless(Style::Plain).tabulate();
524        let expected = vec!["spam   41.9999", "eggs  451"].join("\n");
525        assert_eq!(expected, result);
526    }
527
528    #[test]
529    fn plain_multiline_headerless() {
530        //Output: plain with multiline cells without headers
531        let result = multiline_headerless(Style::Plain).tabulate();
532        let expected = vec![
533            "foo bar    hello",
534            "  baz",
535            "  bau",
536            "         multiline",
537            "           world",
538        ]
539        .join("\n");
540        assert_eq!(expected, result);
541    }
542
543    #[test]
544    fn plain_multiline() {
545        //Output: plain with multiline cells with headers
546        let result = multiline(Style::Plain).tabulate();
547        let expected = vec![
548            "       more  more spam",
549            "  spam eggs  & eggs",
550            "          2  foo",
551            "             bar",
552        ]
553        .join("\n");
554        assert_eq!(expected, result);
555    }
556
557    #[test]
558    fn plain_multiline_with_empty_cells() {
559        //Output: plain with multiline cells and empty cells with headers
560        let result = multiline_empty_cells(Style::Plain).tabulate();
561        let expected = vec![
562            "  hdr  data            fold",
563            "    1",
564            "    2  very long data  fold",
565            "                       this",
566        ]
567        .join("\n");
568        assert_eq!(expected, result);
569    }
570
571    #[test]
572    fn plain_multiline_with_empty_cells_headerless() {
573        //Output: plain with multiline cells and empty cells without headers
574        let result = multiline_empty_cells_headerless(Style::Plain).tabulate();
575        let expected = vec![
576            "0",
577            "1",
578            "2  very long data  fold",
579            "                   this",
580        ]
581        .join("\n");
582        assert_eq!(expected, result);
583    }
584
585    #[test]
586    fn simple() {
587        //Output: simple with headers
588        let result = table(Style::Simple).tabulate();
589        let expected = vec![
590            "strings      numbers",
591            "---------  ---------",
592            "spam         41.9999",
593            "eggs        451",
594        ]
595        .join("\n");
596        assert_eq!(expected, result);
597    }
598
599    #[test]
600    fn simple_multiline_2() {
601        //Output: simple with multiline cells
602        let mut table = Table::new(
603            Style::Simple,
604            vec![
605                vec![Cell::from("foo"), Cell::from("bar")],
606                vec![Cell::from("spam"), Cell::from("multiline\nworld")],
607            ],
608            Some(Headers::from(vec!["key", "value"])),
609        );
610        table.set_align(Align::Center, Align::Right);
611        let result = table.tabulate();
612        let expected = vec![
613            " key     value",
614            "-----  ---------",
615            " foo      bar",
616            "spam   multiline",
617            "         world",
618        ]
619        .join("\n");
620        assert_eq!(expected, result);
621    }
622
623    #[test]
624    fn simple_headerless() {
625        //Output: simple without headers
626        let result = headerless(Style::Simple).tabulate();
627        let expected = vec![
628            "----  --------",
629            "spam   41.9999",
630            "eggs  451",
631            "----  --------",
632        ]
633        .join("\n");
634        assert_eq!(expected, result);
635    }
636
637    #[test]
638    fn simple_multiline_headerless() {
639        //Output: simple with multiline cells without headers
640        let result = multiline_headerless(Style::Simple).tabulate();
641        let expected = vec![
642            "-------  ---------",
643            "foo bar    hello",
644            "  baz",
645            "  bau",
646            "         multiline",
647            "           world",
648            "-------  ---------",
649        ]
650        .join("\n");
651        assert_eq!(expected, result);
652    }
653
654    #[test]
655    fn simple_multiline() {
656        //Output: simple with multiline cells with headers
657        let result = multiline(Style::Simple).tabulate();
658        let expected = vec![
659            "       more  more spam",
660            "  spam eggs  & eggs",
661            "-----------  -----------",
662            "          2  foo",
663            "             bar",
664        ]
665        .join("\n");
666        assert_eq!(expected, result);
667    }
668
669    #[test]
670    fn simple_multiline_ascii_escaped() {
671        //Output: simple with multiline headers and colors (ascii escape)
672        let mut headers = Headers::with_capacity(2);
673        headers
674            .push(AsciiEscapedString::from("more\nspam \x1b[31meggs\x1b[0m"))
675            .push(String::from("more spam\n& eggs"));
676        let result = Table::new(
677            Style::Simple,
678            vec![vec![Cell::Int(2), Cell::from("foo\nbar")]],
679            Some(headers),
680        )
681        .tabulate();
682        let expected = vec![
683            "       more  more spam",
684            "  spam \x1b[31meggs\x1b[0m  & eggs",
685            "-----------  -----------",
686            "          2  foo",
687            "             bar",
688        ]
689        .join("\n");
690        assert_eq!(expected, result);
691    }
692
693    #[test]
694    fn simple_multiline_with_empty_cells() {
695        //Output: simple with multiline cells and empty cells with headers
696        let result = multiline_empty_cells(Style::Simple).tabulate();
697        let expected = vec![
698            "  hdr  data            fold",
699            "-----  --------------  ------",
700            "    1",
701            "    2  very long data  fold",
702            "                       this",
703        ]
704        .join("\n");
705        assert_eq!(expected, result);
706    }
707
708    #[test]
709    fn simple_multiline_with_empty_cells_headerless() {
710        //Output: simple with multiline cells and empty cells without headers
711        let result = multiline_empty_cells_headerless(Style::Simple).tabulate();
712        let expected = vec![
713            "-  --------------  ----",
714            "0",
715            "1",
716            "2  very long data  fold",
717            "                   this",
718            "-  --------------  ----",
719        ]
720        .join("\n");
721        assert_eq!(expected, result);
722    }
723
724    #[test]
725    fn github() {
726        //Output: github with headers
727        let result = table(Style::Github).tabulate();
728        let expected = vec![
729            "| strings   |   numbers |",
730            "|-----------|-----------|",
731            "| spam      |   41.9999 |",
732            "| eggs      |  451      |",
733        ]
734        .join("\n");
735        assert_eq!(expected, result);
736    }
737
738    #[test]
739    fn grid() {
740        //Output: grid with headers
741        let result = table(Style::Grid).tabulate();
742        let expected = vec![
743            "+-----------+-----------+",
744            "| strings   |   numbers |",
745            "+===========+===========+",
746            "| spam      |   41.9999 |",
747            "+-----------+-----------+",
748            "| eggs      |  451      |",
749            "+-----------+-----------+",
750        ]
751        .join("\n");
752        assert_eq!(expected, result);
753    }
754
755    #[test]
756    fn grid_wide_characters() {
757        //Output: grid with wide characters in headers
758        let headers = Headers::from(vec!["strings", "配列"]);
759        let contents = vec![
760            vec![
761                Cell::from("Ответ на главный вопрос жизни, вселенной и всего такого"),
762                Cell::Int(42),
763            ],
764            vec![Cell::from("pi"), Cell::Float(3.1415)],
765        ];
766        let result = Table::new(Style::Grid, contents, Some(headers)).tabulate();
767        let expected = vec![
768            "+---------------------------------------------------------+---------+",
769            "| strings                                                 |    配列 |",
770            "+=========================================================+=========+",
771            "| Ответ на главный вопрос жизни, вселенной и всего такого | 42      |",
772            "+---------------------------------------------------------+---------+",
773            "| pi                                                      |  3.1415 |",
774            "+---------------------------------------------------------+---------+",
775        ]
776        .join("\n");
777        assert_eq!(expected, result);
778    }
779
780    #[test]
781    fn issue_14() {
782        let headers = Headers::from(vec!["Hello", "World"]);
783        let contents = vec![vec![Cell::from("✔"), Cell::from("foo")]];
784
785        let result = Table::new(Style::Grid, contents, Some(headers)).tabulate();
786        let expected = vec![
787            "+---------+---------+",
788            "| Hello   | World   |",
789            "+=========+=========+",
790            "| ✔       | foo     |",
791            "+---------+---------+",
792        ]
793        .join("\n");
794        assert_eq!(expected, result);
795    }
796
797    #[test]
798    fn grid_headerless() {
799        //Output: grid without headers
800        let result = headerless(Style::Grid).tabulate();
801        let expected = vec![
802            "+------+----------+",
803            "| spam |  41.9999 |",
804            "+------+----------+",
805            "| eggs | 451      |",
806            "+------+----------+",
807        ]
808        .join("\n");
809        assert_eq!(expected, result);
810    }
811
812    #[test]
813    fn grid_multiline_headerless() {
814        //Output: grid with multiline cells without headers
815        let result = multiline_headerless(Style::Grid).tabulate();
816        let expected = vec![
817            "+---------+-----------+",
818            "| foo bar |   hello   |",
819            "|   baz   |           |",
820            "|   bau   |           |",
821            "+---------+-----------+",
822            "|         | multiline |",
823            "|         |   world   |",
824            "+---------+-----------+",
825        ]
826        .join("\n");
827        assert_eq!(expected, result);
828    }
829
830    #[test]
831    fn grid_multiline() {
832        //Output: grid with multiline cells with headers
833        let result = multiline(Style::Grid).tabulate();
834        let expected = vec![
835            "+-------------+-------------+",
836            "|        more | more spam   |",
837            "|   spam eggs | & eggs      |",
838            "+=============+=============+",
839            "|           2 | foo         |",
840            "|             | bar         |",
841            "+-------------+-------------+",
842        ]
843        .join("\n");
844        assert_eq!(expected, result);
845    }
846
847    #[test]
848    fn grid_multiline_with_empty_cells() {
849        //Output: grid with multiline cells and empty cells with headers
850        let result = multiline_empty_cells(Style::Grid).tabulate();
851        let expected = vec![
852            "+-------+----------------+--------+",
853            "|   hdr | data           | fold   |",
854            "+=======+================+========+",
855            "|     1 |                |        |",
856            "+-------+----------------+--------+",
857            "|     2 | very long data | fold   |",
858            "|       |                | this   |",
859            "+-------+----------------+--------+",
860        ]
861        .join("\n");
862        assert_eq!(expected, result);
863    }
864
865    #[test]
866    fn grid_multiline_with_empty_cells_headerless() {
867        //Output: grid with multiline cells and empty cells without headers
868        let result = multiline_empty_cells_headerless(Style::Grid).tabulate();
869        let expected = vec![
870            "+---+----------------+------+",
871            "| 0 |                |      |",
872            "+---+----------------+------+",
873            "| 1 |                |      |",
874            "+---+----------------+------+",
875            "| 2 | very long data | fold |",
876            "|   |                | this |",
877            "+---+----------------+------+",
878        ]
879        .join("\n");
880        assert_eq!(expected, result);
881    }
882
883    #[test]
884    fn fancy_grid() {
885        //Output: fancy_grid with headers
886        let result = table(Style::Fancy).tabulate();
887        let expected = vec![
888            "╒═══════════╤═══════════╕",
889            "│ strings   │   numbers │",
890            "╞═══════════╪═══════════╡",
891            "│ spam      │   41.9999 │",
892            "├───────────┼───────────┤",
893            "│ eggs      │  451      │",
894            "╘═══════════╧═══════════╛",
895        ]
896        .join("\n");
897        assert_eq!(expected, result);
898    }
899
900    #[test]
901    fn fancy_grid_headerless() {
902        //Output: fancy_grid without headers
903        let result = headerless(Style::Fancy).tabulate();
904        let expected = vec![
905            "╒══════╤══════════╕",
906            "│ spam │  41.9999 │",
907            "├──────┼──────────┤",
908            "│ eggs │ 451      │",
909            "╘══════╧══════════╛",
910        ]
911        .join("\n");
912        assert_eq!(expected, result);
913    }
914
915    #[test]
916    fn fancy_grid_multiline_headerless() {
917        //Output: fancy_grid with multiline cells without headers
918        let result = multiline_headerless(Style::Fancy).tabulate();
919        let expected = vec![
920            "╒═════════╤═══════════╕",
921            "│ foo bar │   hello   │",
922            "│   baz   │           │",
923            "│   bau   │           │",
924            "├─────────┼───────────┤",
925            "│         │ multiline │",
926            "│         │   world   │",
927            "╘═════════╧═══════════╛",
928        ]
929        .join("\n");
930        assert_eq!(expected, result);
931    }
932
933    #[test]
934    fn fancy_grid_multiline() {
935        //Output: fancy_grid with multiline cells with headers
936        let result = multiline(Style::Fancy).tabulate();
937        let expected = vec![
938            "╒═════════════╤═════════════╕",
939            "│        more │ more spam   │",
940            "│   spam eggs │ & eggs      │",
941            "╞═════════════╪═════════════╡",
942            "│           2 │ foo         │",
943            "│             │ bar         │",
944            "╘═════════════╧═════════════╛",
945        ]
946        .join("\n");
947        assert_eq!(expected, result);
948    }
949
950    #[test]
951    fn fancy_grid_multiline_with_empty_cells() {
952        //Output: fancy_grid with multiline cells and empty cells with headers
953        let result = multiline_empty_cells(Style::Fancy).tabulate();
954        let expected = vec![
955            "╒═══════╤════════════════╤════════╕",
956            "│   hdr │ data           │ fold   │",
957            "╞═══════╪════════════════╪════════╡",
958            "│     1 │                │        │",
959            "├───────┼────────────────┼────────┤",
960            "│     2 │ very long data │ fold   │",
961            "│       │                │ this   │",
962            "╘═══════╧════════════════╧════════╛",
963        ]
964        .join("\n");
965        assert_eq!(expected, result);
966    }
967
968    #[test]
969    fn fancy_grid_multiline_with_empty_cells_headerless() {
970        //Output: fancy_grid with multiline cells and empty cells without headers
971        let result = multiline_empty_cells_headerless(Style::Fancy).tabulate();
972        let expected = vec![
973            "╒═══╤════════════════╤══════╕",
974            "│ 0 │                │      │",
975            "├───┼────────────────┼──────┤",
976            "│ 1 │                │      │",
977            "├───┼────────────────┼──────┤",
978            "│ 2 │ very long data │ fold │",
979            "│   │                │ this │",
980            "╘═══╧════════════════╧══════╛",
981        ]
982        .join("\n");
983        assert_eq!(expected, result);
984    }
985
986    #[test]
987    fn presto() {
988        //Output: presto with headers
989        let result = table(Style::Presto).tabulate();
990        let expected = vec![
991            " strings   |   numbers",
992            "-----------+-----------",
993            " spam      |   41.9999",
994            " eggs      |  451",
995        ]
996        .join("\n");
997        assert_eq!(expected, result);
998    }
999
1000    #[test]
1001    fn presto_headerless() {
1002        //Output: presto without headers
1003        let result = headerless(Style::Presto).tabulate();
1004        let expected = vec![" spam |  41.9999", " eggs | 451"].join("\n");
1005        assert_eq!(expected, result);
1006    }
1007
1008    #[test]
1009    fn presto_multiline_headerless() {
1010        //Output: presto with multiline cells without headers
1011        let result = multiline_headerless(Style::Presto).tabulate();
1012        let expected = vec![
1013            " foo bar |   hello",
1014            "   baz   |",
1015            "   bau   |",
1016            "         | multiline",
1017            "         |   world",
1018        ]
1019        .join("\n");
1020        assert_eq!(expected, result);
1021    }
1022
1023    #[test]
1024    fn presto_multiline() {
1025        //Output: presto with multiline cells with headers
1026        let result = multiline(Style::Presto).tabulate();
1027        let expected = vec![
1028            "        more | more spam",
1029            "   spam eggs | & eggs",
1030            "-------------+-------------",
1031            "           2 | foo",
1032            "             | bar",
1033        ]
1034        .join("\n");
1035        assert_eq!(expected, result);
1036    }
1037
1038    #[test]
1039    fn presto_multiline_with_empty_cells() {
1040        //Output: presto with multiline cells and empty cells with headers
1041        let result = multiline_empty_cells(Style::Presto).tabulate();
1042        let expected = vec![
1043            "   hdr | data           | fold",
1044            "-------+----------------+--------",
1045            "     1 |                |",
1046            "     2 | very long data | fold",
1047            "       |                | this",
1048        ]
1049        .join("\n");
1050        assert_eq!(expected, result);
1051    }
1052
1053    #[test]
1054    fn presto_multiline_with_empty_cells_headerless() {
1055        //Output: presto with multiline cells and empty cells without headers
1056        let result = multiline_empty_cells_headerless(Style::Presto).tabulate();
1057        let expected = vec![
1058            " 0 |                |",
1059            " 1 |                |",
1060            " 2 | very long data | fold",
1061            "   |                | this",
1062        ]
1063        .join("\n");
1064        assert_eq!(expected, result);
1065    }
1066
1067    #[test]
1068    fn fancygithub_grid() {
1069        let result = table(Style::FancyGithub).tabulate();
1070        let expected = vec![
1071            "│ strings   │   numbers │",
1072            "├───────────┼───────────┤",
1073            "│ spam      │   41.9999 │",
1074            "│ eggs      │  451      │",
1075        ]
1076        .join("\n");
1077        assert_eq!(expected, result);
1078    }
1079
1080    #[test]
1081    fn fancygithub_grid_headerless() {
1082        let result = headerless(Style::FancyGithub).tabulate();
1083        let expected = vec!["│ spam │  41.9999 │", "│ eggs │ 451      │"].join("\n");
1084        assert_eq!(expected, result);
1085    }
1086
1087    #[test]
1088    fn fancygithub_grid_multiline_headerless() {
1089        let result = multiline_headerless(Style::FancyGithub).tabulate();
1090        let expected = vec![
1091            "│ foo bar │   hello   │",
1092            "│   baz   │           │",
1093            "│   bau   │           │",
1094            "│         │ multiline │",
1095            "│         │   world   │",
1096        ]
1097        .join("\n");
1098        assert_eq!(expected, result);
1099    }
1100
1101    #[test]
1102    fn fancygithub_grid_multiline() {
1103        let result = multiline(Style::FancyGithub).tabulate();
1104        let expected = vec![
1105            "│        more │ more spam   │",
1106            "│   spam eggs │ & eggs      │",
1107            "├─────────────┼─────────────┤",
1108            "│           2 │ foo         │",
1109            "│             │ bar         │",
1110        ]
1111        .join("\n");
1112        assert_eq!(expected, result);
1113    }
1114
1115    #[test]
1116    fn fancygithub_grid_multiline_with_empty_cells() {
1117        let result = multiline_empty_cells(Style::FancyGithub).tabulate();
1118        let expected = vec![
1119            "│   hdr │ data           │ fold   │",
1120            "├───────┼────────────────┼────────┤",
1121            "│     1 │                │        │",
1122            "│     2 │ very long data │ fold   │",
1123            "│       │                │ this   │",
1124        ]
1125        .join("\n");
1126        assert_eq!(expected, result);
1127    }
1128
1129    #[test]
1130    fn fancygithub_grid_multiline_with_empty_cells_headerless() {
1131        let result = multiline_empty_cells_headerless(Style::FancyGithub).tabulate();
1132        let expected = vec![
1133            "│ 0 │                │      │",
1134            "│ 1 │                │      │",
1135            "│ 2 │ very long data │ fold │",
1136            "│   │                │ this │",
1137        ]
1138        .join("\n");
1139        assert_eq!(expected, result);
1140    }
1141
1142    #[test]
1143    fn fancypresto_grid() {
1144        let result = table(Style::FancyPresto).tabulate();
1145        let expected = vec![
1146            "strings   │   numbers",
1147            "──────────┼──────────",
1148            "spam      │   41.9999",
1149            "eggs      │  451",
1150        ]
1151        .join("\n");
1152        assert_eq!(expected, result);
1153    }
1154
1155    #[test]
1156    fn fancypresto_grid_headerless() {
1157        let result = headerless(Style::FancyPresto).tabulate();
1158        let expected = vec!["spam │  41.9999", "eggs │ 451"].join("\n");
1159        assert_eq!(expected, result);
1160    }
1161
1162    #[test]
1163    fn fancypresto_grid_multiline_headerless() {
1164        let result = multiline_headerless(Style::FancyPresto).tabulate();
1165        let expected = vec![
1166            "foo bar │   hello",
1167            "  baz   │",
1168            "  bau   │",
1169            "        │ multiline",
1170            "        │   world",
1171        ]
1172        .join("\n");
1173        assert_eq!(expected, result);
1174    }
1175
1176    #[test]
1177    fn fancypresto_grid_multiline() {
1178        let result = multiline(Style::FancyPresto).tabulate();
1179        let expected = vec![
1180            "       more │ more spam",
1181            "  spam eggs │ & eggs",
1182            "────────────┼────────────",
1183            "          2 │ foo",
1184            "            │ bar",
1185        ]
1186        .join("\n");
1187        assert_eq!(expected, result);
1188    }
1189
1190    #[test]
1191    fn fancypresto_grid_multiline_with_empty_cells() {
1192        let result = multiline_empty_cells(Style::FancyPresto).tabulate();
1193        let expected = vec![
1194            "  hdr │ data           │ fold",
1195            "──────┼────────────────┼───────",
1196            "    1 │                │",
1197            "    2 │ very long data │ fold",
1198            "      │                │ this",
1199        ]
1200        .join("\n");
1201        assert_eq!(expected, result);
1202    }
1203
1204    #[test]
1205    fn fancypresto_grid_multiline_with_empty_cells_headerless() {
1206        let result = multiline_empty_cells_headerless(Style::FancyPresto).tabulate();
1207        let expected = vec![
1208            "0 │                │",
1209            "1 │                │",
1210            "2 │ very long data │ fold",
1211            "  │                │ this",
1212        ]
1213        .join("\n");
1214        assert_eq!(expected, result);
1215    }
1216
1217    #[cfg(feature = "ansi_term_style")]
1218    #[test]
1219    fn ansi_term_colored_content<'a>() {
1220        use ansi_term::Colour::Red;
1221        use ansi_term::{ANSIString, ANSIStrings};
1222
1223        let some_value = format!("{:b}", 42);
1224        let strings: &[ANSIString<'a>] =
1225            &[Red.paint("["), Red.bold().paint(some_value), Red.paint("]")];
1226
1227        let result = Table::new(
1228            Style::Grid,
1229            vec![vec![
1230                Cell::Int(42),
1231                Cell::Text(Box::new(ANSIStrings(&strings))),
1232            ]],
1233            Some(Headers::from(vec!["Int", "Colored binary"])),
1234        )
1235        .tabulate();
1236
1237        let expected = vec![
1238            "+-------+------------------+",
1239            "|   Int | Colored binary   |",
1240            "+=======+==================+",
1241            "|    42 | \u{1b}[31m[\u{1b}[1m101010\u{1b}[0m\u{1b}[31m]\u{1b}[0m         |",
1242            "+-------+------------------+",
1243        ]
1244        .join("\n");
1245        assert_eq!(expected, result);
1246    }
1247
1248    #[cfg(feature = "ansi_term_style")]
1249    #[test]
1250    fn ansi_styled_borders() {
1251        let mut table = table(Style::Fancy);
1252        table.set_border_style(ansi_term::Color::Green.bold());
1253        let result = table.tabulate();
1254        let expected = vec![
1255            "\u{1b}[1;32m╒═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╤═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╕\u{1b}[0m",
1256            "\u{1b}[1;32m│ \u{1b}[0mstrings  \u{1b}[1;32m │ \u{1b}[0m  numbers\u{1b}[1;32m │\u{1b}[0m",
1257            "\u{1b}[1;32m╞═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╪═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╡\u{1b}[0m",
1258            "\u{1b}[1;32m│ \u{1b}[0mspam     \u{1b}[1;32m │ \u{1b}[0m  41.9999\u{1b}[1;32m │\u{1b}[0m",
1259            "\u{1b}[1;32m├─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─┼─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─┤\u{1b}[0m",
1260            "\u{1b}[1;32m│ \u{1b}[0meggs     \u{1b}[1;32m │ \u{1b}[0m 451     \u{1b}[1;32m │\u{1b}[0m",
1261            "\u{1b}[1;32m╘═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╧═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╛\u{1b}[0m"
1262        ].join("\n");
1263        assert_eq!(expected, result);
1264    }
1265}