term_table/
lib.rs

1//! The purpose of term-table is to make it easy for CLI apps to display data in a table format
2//!# Example
3//! Here is an example of how to create a simple table
4//!```
5//! use term_table::table_cell::TableCell;
6//! use term_table::row::Row;
7//! use term_table::{Table, TableStyle, rows, row};
8//!  
9//! let mut table = Table::builder()
10//!     .max_column_width(40)
11//!     .style(TableStyle::extended())
12//!     .rows(rows![
13//!        row![
14//!             TableCell::builder("This is some centered text")
15//!                 .col_span(2)
16//!                 .alignment(term_table::table_cell::Alignment::Center)
17//!                 .build(),
18//!        ],
19//!        row![
20//!             "This is left aligned text",        
21//!             TableCell::builder("This is right aligned text")
22//!                 .alignment(term_table::table_cell::Alignment::Right)
23//!                 .build(),
24//!         ],
25//!         row![
26//!            "This is left aligned text",
27//!           TableCell::builder("This is right aligned text")
28//!                 .alignment(term_table::table_cell::Alignment::Right)
29//!                 .build(),
30//!        ],
31//!        row![
32//!           TableCell::builder("This is some really really really really really really really really really that is going to wrap to the next line")
33//!                 .col_span(2)
34//!                 .build(),
35//!        ],
36//!         
37//!     ])
38//!     .build();
39//!
40//! println!("{}", table.render());
41//! ```
42//!
43//!### This is the result
44//!
45//!<pre>
46//! ╔═════════════════════════════════════════════════════════════════════════════════╗
47//! ║                            This is some centered text                           ║
48//! ╠════════════════════════════════════════╦════════════════════════════════════════╣
49//! ║ This is left aligned text              ║             This is right aligned text ║
50//! ╠════════════════════════════════════════╬════════════════════════════════════════╣
51//! ║ This is left aligned text              ║             This is right aligned text ║
52//! ╠════════════════════════════════════════╩════════════════════════════════════════╣
53//! ║ This is some really really really really really really really really really tha ║
54//! ║ t is going to wrap to the next line                                             ║
55//! ╚═════════════════════════════════════════════════════════════════════════════════╝
56//!</pre>
57
58#[macro_use]
59extern crate lazy_static;
60
61pub mod row;
62pub mod table_cell;
63
64use crate::row::Row;
65use crate::table_cell::Alignment;
66
67use std::cmp::{max, min};
68use std::collections::HashMap;
69
70#[macro_export]
71macro_rules! row {
72    [ $($x:expr),* ] => {
73        Row::new(vec![$(Into::<TableCell>::into($x)),*])
74    };
75    [ $($x:expr,)* ] => (row![$($x),*])
76}
77
78#[macro_export]
79macro_rules! row_no_separator {
80    [ $($x:expr),* ] => {
81        Row::without_separator(vec![$(Into::<TableCell>::into($x)),*])
82    };
83    [ $($x:expr,)* ] => (row![$($x),*])
84}
85
86#[macro_export]
87macro_rules! rows {
88    [ $($x:expr),* ] => {
89        vec![$($x),*]
90    };
91    [ $($x:expr,)* ] => (rows![$($x),*])
92}
93
94/// Represents the vertical position of a row
95#[derive(Eq, PartialEq, Copy, Clone)]
96pub enum RowPosition {
97    First,
98    Mid,
99    Last,
100}
101
102/// A set of characters which make up a table style
103///
104///# Example
105///
106///```
107/// term_table::TableStyle {
108///     top_left_corner: '╔',
109///     top_right_corner: '╗',
110///     bottom_left_corner: '╚',
111///     bottom_right_corner: '╝',
112///     outer_left_vertical: '╠',
113///     outer_right_vertical: '╣',
114///     outer_bottom_horizontal: '╩',
115///     outer_top_horizontal: '╦',
116///     intersection: '╬',
117///     vertical: '║',
118///     horizontal: '═',
119/// };
120/// ```
121#[derive(Debug, Clone, Copy)]
122pub struct TableStyle {
123    pub top_left_corner: char,
124    pub top_right_corner: char,
125    pub bottom_left_corner: char,
126    pub bottom_right_corner: char,
127    pub outer_left_vertical: char,
128    pub outer_right_vertical: char,
129    pub outer_bottom_horizontal: char,
130    pub outer_top_horizontal: char,
131    pub intersection: char,
132    pub vertical: char,
133    pub horizontal: char,
134}
135
136impl TableStyle {
137    /// Basic terminal table style
138    ///
139    ///# Example
140    ///
141    ///<pre>
142    ///   +---------------------------------------------------------------------------------+
143    ///   |                            This is some centered text                           |
144    ///   +----------------------------------------+----------------------------------------+
145    ///   | This is left aligned text              |             This is right aligned text |
146    ///   +----------------------------------------+----------------------------------------+
147    ///   | This is left aligned text              |             This is right aligned text |
148    ///   +----------------------------------------+----------------------------------------+
149    ///   | This is some really really really really really really really really really tha |
150    ///   | t is going to wrap to the next line                                             |
151    ///   +---------------------------------------------------------------------------------+
152    ///</pre>
153    pub fn simple() -> TableStyle {
154        TableStyle {
155            top_left_corner: '+',
156            top_right_corner: '+',
157            bottom_left_corner: '+',
158            bottom_right_corner: '+',
159            outer_left_vertical: '+',
160            outer_right_vertical: '+',
161            outer_bottom_horizontal: '+',
162            outer_top_horizontal: '+',
163            intersection: '+',
164            vertical: '|',
165            horizontal: '-',
166        }
167    }
168
169    /// Table style using extended character set
170    ///
171    ///# Example
172    ///
173    ///<pre>
174    /// ╔═════════════════════════════════════════════════════════════════════════════════╗
175    /// ║                            This is some centered text                           ║
176    /// ╠════════════════════════════════════════╦════════════════════════════════════════╣
177    /// ║ This is left aligned text              ║             This is right aligned text ║
178    /// ╠════════════════════════════════════════╬════════════════════════════════════════╣
179    /// ║ This is left aligned text              ║             This is right aligned text ║
180    /// ╠════════════════════════════════════════╩════════════════════════════════════════╣
181    /// ║ This is some really really really really really really really really really tha ║
182    /// ║ t is going to wrap to the next line                                             ║
183    /// ╚═════════════════════════════════════════════════════════════════════════════════╝
184    ///</pre>
185    pub fn extended() -> TableStyle {
186        TableStyle {
187            top_left_corner: '╔',
188            top_right_corner: '╗',
189            bottom_left_corner: '╚',
190            bottom_right_corner: '╝',
191            outer_left_vertical: '╠',
192            outer_right_vertical: '╣',
193            outer_bottom_horizontal: '╩',
194            outer_top_horizontal: '╦',
195            intersection: '╬',
196            vertical: '║',
197            horizontal: '═',
198        }
199    }
200
201    /// <pre>
202    /// ┌─────────────────────────────────────────────────────────────────────────────────┐
203    /// │                            This is some centered text                           │
204    /// ├────────────────────────────────────────┬────────────────────────────────────────┤
205    /// │ This is left aligned text              │             This is right aligned text │
206    /// ├────────────────────────────────────────┼────────────────────────────────────────┤
207    /// │ This is left aligned text              │             This is right aligned text │
208    /// ├────────────────────────────────────────┴────────────────────────────────────────┤
209    /// │ This is some really really really really really really really really really tha │
210    /// │ t is going to wrap to the next line                                             │
211    /// └─────────────────────────────────────────────────────────────────────────────────┘
212    /// </pre>
213    pub fn thin() -> TableStyle {
214        TableStyle {
215            top_left_corner: '┌',
216            top_right_corner: '┐',
217            bottom_left_corner: '└',
218            bottom_right_corner: '┘',
219            outer_left_vertical: '├',
220            outer_right_vertical: '┤',
221            outer_bottom_horizontal: '┴',
222            outer_top_horizontal: '┬',
223            intersection: '┼',
224            vertical: '│',
225            horizontal: '─',
226        }
227    }
228
229    ///  <pre>
230    /// ╭─────────────────────────────────────────────────────────────────────────────────╮
231    /// │                            This is some centered text                           │
232    /// ├────────────────────────────────────────┬────────────────────────────────────────┤
233    /// │ This is left aligned text              │             This is right aligned text │
234    /// ├────────────────────────────────────────┼────────────────────────────────────────┤
235    /// │ This is left aligned text              │             This is right aligned text │
236    /// ├────────────────────────────────────────┴────────────────────────────────────────┤
237    /// │ This is some really really really really really really really really really tha │
238    /// │ t is going to wrap to the next line                                             │
239    /// ╰─────────────────────────────────────────────────────────────────────────────────╯
240    /// </pre>
241    pub fn rounded() -> TableStyle {
242        TableStyle {
243            top_left_corner: '╭',
244            top_right_corner: '╮',
245            bottom_left_corner: '╰',
246            bottom_right_corner: '╯',
247            outer_left_vertical: '├',
248            outer_right_vertical: '┤',
249            outer_bottom_horizontal: '┴',
250            outer_top_horizontal: '┬',
251            intersection: '┼',
252            vertical: '│',
253            horizontal: '─',
254        }
255    }
256
257    /// <pre>
258    /// ╔─────────────────────────────────────────────────────────────────────────────────╗
259    /// │                            This is some centered text                           │
260    /// ╠────────────────────────────────────────╦────────────────────────────────────────╣
261    /// │ This is left aligned text              │             This is right aligned text │
262    /// ╠────────────────────────────────────────┼────────────────────────────────────────╣
263    /// │ This is left aligned text              │             This is right aligned text │
264    /// ╠────────────────────────────────────────╩────────────────────────────────────────╣
265    /// │ This is some really really really really really really really really really tha │
266    /// │ t is going to wrap to the next line                                             │
267    /// ╚─────────────────────────────────────────────────────────────────────────────────╝
268    /// </pre>
269
270    pub fn elegant() -> TableStyle {
271        TableStyle {
272            top_left_corner: '╔',
273            top_right_corner: '╗',
274            bottom_left_corner: '╚',
275            bottom_right_corner: '╝',
276            outer_left_vertical: '╠',
277            outer_right_vertical: '╣',
278            outer_bottom_horizontal: '╩',
279            outer_top_horizontal: '╦',
280            intersection: '┼',
281            vertical: '│',
282            horizontal: '─',
283        }
284    }
285
286    /// Table style comprised of null characters
287    ///
288    ///# Example
289    ///
290    ///<pre>
291    ///                           This is some centered text
292    ///
293    /// This is left aligned text                           This is right aligned text
294    ///
295    /// This is left aligned text                           This is right aligned text
296    ///
297    /// This is some really really really really really really really really really tha
298    /// t is going to wrap to the next line
299    ///</pre>
300    pub fn blank() -> TableStyle {
301        TableStyle {
302            top_left_corner: '\0',
303            top_right_corner: '\0',
304            bottom_left_corner: '\0',
305            bottom_right_corner: '\0',
306            outer_left_vertical: '\0',
307            outer_right_vertical: '\0',
308            outer_bottom_horizontal: '\0',
309            outer_top_horizontal: '\0',
310            intersection: '\0',
311            vertical: '\0',
312            horizontal: '\0',
313        }
314    }
315
316    /// Table style comprised of empty characters for compatibility with terminals
317    /// that don't handle null characters appropriately
318    ///
319    ///# Example
320    ///
321    ///<pre>
322    ///                           This is some centered text
323    ///
324    /// This is left aligned text                           This is right aligned text
325    ///
326    /// This is left aligned text                           This is right aligned text
327    ///
328    /// This is some really really really really really really really really really tha
329    /// t is going to wrap to the next line
330    ///</pre>
331    pub fn empty() -> TableStyle {
332        TableStyle {
333            top_left_corner: ' ',
334            top_right_corner: ' ',
335            bottom_left_corner: ' ',
336            bottom_right_corner: ' ',
337            outer_left_vertical: ' ',
338            outer_right_vertical: ' ',
339            outer_bottom_horizontal: ' ',
340            outer_top_horizontal: ' ',
341            intersection: ' ',
342            vertical: ' ',
343            horizontal: ' ',
344        }
345    }
346
347    /// Returns the start character of a table style based on the
348    /// vertical position of the row
349    fn start_for_position(&self, pos: RowPosition) -> char {
350        match pos {
351            RowPosition::First => self.top_left_corner,
352            RowPosition::Mid => self.outer_left_vertical,
353            RowPosition::Last => self.bottom_left_corner,
354        }
355    }
356
357    /// Returns the end character of a table style based on the
358    /// vertical position of the row
359    fn end_for_position(&self, pos: RowPosition) -> char {
360        match pos {
361            RowPosition::First => self.top_right_corner,
362            RowPosition::Mid => self.outer_right_vertical,
363            RowPosition::Last => self.bottom_right_corner,
364        }
365    }
366
367    /// Returns the intersect character of a table style based on the
368    /// vertical position of the row
369    fn intersect_for_position(&self, pos: RowPosition) -> char {
370        match pos {
371            RowPosition::First => self.outer_top_horizontal,
372            RowPosition::Mid => self.intersection,
373            RowPosition::Last => self.outer_bottom_horizontal,
374        }
375    }
376
377    /// Merges two intersecting characters based on the vertical position of a row.
378    /// This is used to handle cases where one cell has a larger `col_span` value than the other
379    fn merge_intersection_for_position(&self, top: char, bottom: char, pos: RowPosition) -> char {
380        if (top == self.horizontal || top == self.outer_bottom_horizontal)
381            && bottom == self.intersection
382        {
383            return self.outer_top_horizontal;
384        } else if (top == self.intersection || top == self.outer_top_horizontal)
385            && bottom == self.horizontal
386        {
387            return self.outer_bottom_horizontal;
388        } else if top == self.outer_bottom_horizontal && bottom == self.horizontal {
389            return self.horizontal;
390        } else {
391            return self.intersect_for_position(pos);
392        }
393    }
394}
395
396/// A set of rows containing data
397#[derive(Clone, Debug)]
398pub struct Table {
399    pub rows: Vec<Row>,
400    pub style: TableStyle,
401    /// The maximum width of all columns. Overridden by values in column_widths. Defaults to `std::usize::max`
402    pub max_column_width: usize,
403    /// The maximum widths of specific columns. Override max_column
404    pub max_column_widths: HashMap<usize, usize>,
405    /// Whether or not to vertically separate rows in the table
406    pub separate_rows: bool,
407    /// Whether the table should have a top boarder.
408    /// Setting `has_separator` to false on the first row will have the same effect as setting this to false
409    pub has_top_boarder: bool,
410    /// Whether the table should have a bottom boarder
411    pub has_bottom_boarder: bool,
412}
413
414impl Table {
415    pub fn new() -> Table {
416        Self {
417            rows: Vec::new(),
418            style: TableStyle::extended(),
419            max_column_width: std::usize::MAX,
420            max_column_widths: HashMap::new(),
421            separate_rows: true,
422            has_top_boarder: true,
423            has_bottom_boarder: true,
424        }
425    }
426
427    pub fn builder() -> TableBuilder {
428        TableBuilder::new()
429    }
430
431    #[deprecated(since = "1.4.0", note = "Use builder instead")]
432    pub fn with_rows(rows: Vec<Row>) -> Table {
433        Self {
434            rows,
435            style: TableStyle::extended(),
436            max_column_width: std::usize::MAX,
437            max_column_widths: HashMap::new(),
438            separate_rows: true,
439            has_top_boarder: true,
440            has_bottom_boarder: true,
441        }
442    }
443
444    pub fn max_column_width(&mut self, max_column_width: usize) -> &mut Self {
445        self.max_column_width = max_column_width;
446        self
447    }
448
449    /// Set the max width of a particular column
450    pub fn set_max_width_for_column(&mut self, column_index: usize, width: usize) {
451        self.max_column_widths.insert(column_index, width);
452    }
453
454    /// Set the max widths of specific columns
455    pub fn set_max_column_widths(&mut self, index_width_pairs: Vec<(usize, usize)>) {
456        for pair in index_width_pairs {
457            self.max_column_widths.insert(pair.0, pair.1);
458        }
459    }
460
461    /// Simply adds a row to the rows Vec
462    pub fn add_row(&mut self, row: Row) {
463        self.rows.push(row);
464    }
465
466    /// Does all of the calculations to reformat the row based on it's current
467    /// state and returns the result as a `String`
468    pub fn render(&self) -> String {
469        let mut print_buffer = String::new();
470        let max_widths = self.calculate_max_column_widths();
471        let mut previous_separator = None;
472        if !self.rows.is_empty() {
473            for i in 0..self.rows.len() {
474                let row_pos = if i == 0 {
475                    RowPosition::First
476                } else {
477                    RowPosition::Mid
478                };
479
480                let separator = self.rows[i].gen_separator(
481                    &max_widths,
482                    &self.style,
483                    row_pos,
484                    previous_separator.clone(),
485                );
486
487                previous_separator = Some(separator.clone());
488
489                if self.rows[i].has_separator
490                    && ((i == 0 && self.has_top_boarder) || i != 0 && self.separate_rows)
491                {
492                    Table::buffer_line(&mut print_buffer, &separator);
493                }
494
495                Table::buffer_line(
496                    &mut print_buffer,
497                    &self.rows[i].format(&max_widths, &self.style),
498                );
499            }
500            if self.has_bottom_boarder {
501                let separator = self.rows.last().unwrap().gen_separator(
502                    &max_widths,
503                    &self.style,
504                    RowPosition::Last,
505                    None,
506                );
507                Table::buffer_line(&mut print_buffer, &separator);
508            }
509        }
510        return print_buffer;
511    }
512
513    /// Calculates the maximum width for each column.
514    /// If a cell has a column span greater than 1, then the width
515    /// of it's contents are divided by the column span, otherwise the cell
516    /// would use more space than it needed.
517    fn calculate_max_column_widths(&self) -> Vec<usize> {
518        let mut num_columns = 0;
519
520        for row in &self.rows {
521            num_columns = max(row.num_columns(), num_columns);
522        }
523        let mut max_widths: Vec<usize> = vec![0; num_columns];
524        let mut min_widths: Vec<usize> = vec![0; num_columns];
525        for row in &self.rows {
526            let column_widths = row.split_column_widths();
527            for i in 0..column_widths.len() {
528                min_widths[i] = max(min_widths[i], column_widths[i].1);
529                let mut max_width = *self
530                    .max_column_widths
531                    .get(&i)
532                    .unwrap_or(&self.max_column_width);
533                max_width = max(min_widths[i] as usize, max_width);
534                max_widths[i] = min(max_width, max(max_widths[i], column_widths[i].0 as usize));
535            }
536        }
537
538        // Here we are dealing with the case where we have a cell that is center
539        // aligned but the max_width doesn't allow for even padding on either side
540        for row in &self.rows {
541            let mut col_index = 0;
542            for cell in row.cells.iter() {
543                let mut total_col_width = 0;
544                for i in col_index..col_index + cell.col_span {
545                    total_col_width += max_widths[i];
546                }
547                if cell.width() != total_col_width
548                    && cell.alignment == Alignment::Center
549                    && total_col_width as f32 % 2.0 <= 0.001
550                {
551                    let mut max_col_width = self.max_column_width;
552                    if let Some(specific_width) = self.max_column_widths.get(&col_index) {
553                        max_col_width = *specific_width;
554                    }
555
556                    if max_widths[col_index] < max_col_width {
557                        max_widths[col_index] += 1;
558                    }
559                }
560                if cell.col_span > 1 {
561                    col_index += cell.col_span - 1;
562                } else {
563                    col_index += 1;
564                }
565            }
566        }
567
568        return max_widths;
569    }
570
571    /// Helper method for adding a line to a string buffer
572    fn buffer_line(buffer: &mut String, line: &str) {
573        buffer.push_str(format!("{}\n", line).as_str());
574    }
575}
576
577impl Default for Table {
578    fn default() -> Self {
579        return Table::new();
580    }
581}
582
583impl ToString for Table {
584    fn to_string(&self) -> String {
585        return self.render();
586    }
587}
588
589/// Used to create non-mutable tables
590#[derive(Clone, Debug)]
591pub struct TableBuilder {
592    rows: Vec<Row>,
593    style: TableStyle,
594    max_column_width: usize,
595    max_column_widths: HashMap<usize, usize>,
596    separate_rows: bool,
597    has_top_boarder: bool,
598    has_bottom_boarder: bool,
599}
600
601impl TableBuilder {
602    pub fn new() -> TableBuilder {
603        TableBuilder {
604            rows: Vec::new(),
605            style: TableStyle::extended(),
606            max_column_width: std::usize::MAX,
607            max_column_widths: HashMap::new(),
608            separate_rows: true,
609            has_top_boarder: true,
610            has_bottom_boarder: true,
611        }
612    }
613
614    pub fn rows(&mut self, rows: Vec<Row>) -> &mut Self {
615        self.rows = rows;
616        self
617    }
618
619    pub fn style(&mut self, style: TableStyle) -> &mut Self {
620        self.style = style;
621        self
622    }
623
624    /// The maximum width of all columns. Overridden by values in column_widths. Defaults to `std::usize::max`
625    pub fn max_column_width(&mut self, max_column_width: usize) -> &mut Self {
626        self.max_column_width = max_column_width;
627        self
628    }
629
630    /// The maximum widths of specific columns. Override max_column
631    pub fn max_column_widths(&mut self, max_column_widths: HashMap<usize, usize>) -> &mut Self {
632        self.max_column_widths = max_column_widths;
633        self
634    }
635
636    /// Whether or not to vertically separate rows in the table
637    pub fn separate_rows(&mut self, separate_rows: bool) -> &mut Self {
638        self.separate_rows = separate_rows;
639        self
640    }
641
642    /// Whether the table should have a top boarder.
643    /// Setting `has_separator` to false on the first row will have the same effect as setting this to false
644    pub fn has_top_boarder(&mut self, has_top_boarder: bool) -> &mut Self {
645        self.has_top_boarder = has_top_boarder;
646        self
647    }
648
649    /// Whether the table should have a bottom boarder
650    pub fn has_bottom_boarder(&mut self, has_bottom_boarder: bool) -> &mut Self {
651        self.has_bottom_boarder = has_bottom_boarder;
652        self
653    }
654
655    /// Build a Table using the current configuration
656    pub fn build(&self) -> Table {
657        Table {
658            rows: self.rows.clone(),
659            style: self.style,
660            max_column_width: self.max_column_width,
661            max_column_widths: self.max_column_widths.clone(),
662            separate_rows: self.separate_rows,
663            has_top_boarder: self.has_top_boarder,
664            has_bottom_boarder: self.has_bottom_boarder,
665        }
666    }
667}
668
669impl Default for TableBuilder {
670    fn default() -> Self {
671        Self::new()
672    }
673}
674
675#[cfg(test)]
676mod test {
677    use crate::row::Row;
678    use crate::table_cell::{Alignment, TableCell};
679    use crate::Table;
680    use crate::TableBuilder;
681    use crate::TableStyle;
682    use pretty_assertions::assert_eq;
683
684    #[test]
685    fn correct_default_padding() {
686        let table = Table::builder()
687            .separate_rows(false)
688            .style(TableStyle::simple())
689            .rows(rows![
690                row![
691                    TableCell::builder("A").alignment(Alignment::Center),
692                    TableCell::builder("B").alignment(Alignment::Center),
693                ],
694                row![TableCell::builder("1"), TableCell::builder(1),],
695                row![TableCell::builder("2"), TableCell::builder(10),],
696                row![TableCell::builder("3"), TableCell::builder(100),],
697            ])
698            .build();
699
700        let expected = r"+---+-----+
701| A |  B  |
702| 1 | 1   |
703| 2 | 10  |
704| 3 | 100 |
705+---+-----+
706";
707        println!("{}", table.render());
708        assert_eq!(expected, table.render());
709    }
710
711    #[test]
712    fn uneven_center_alignment() {
713        let table = Table::builder()
714            .separate_rows(false)
715            .style(TableStyle::simple())
716            .rows(rows![
717                row![TableCell::builder("A").alignment(Alignment::Center),],
718                row![TableCell::builder(11),],
719                row![TableCell::builder(2),],
720                row![TableCell::builder(3),],
721            ])
722            .build();
723
724        let expected = r"+-----+
725|  A  |
726| 11  |
727| 2   |
728| 3   |
729+-----+
730";
731        println!("{}", table.render());
732        assert_eq!(expected, table.render());
733    }
734
735    #[test]
736    fn uneven_center_alignment_2() {
737        let table = Table::builder()
738            .separate_rows(false)
739            .style(TableStyle::simple())
740            .rows(rows![row![
741                TableCell::builder("A1").alignment(Alignment::Center),
742                TableCell::builder("B").alignment(Alignment::Center),
743            ],])
744            .build();
745        println!("{}", table.render());
746        let expected = r"+----+---+
747| A1 | B |
748+----+---+
749";
750        println!("{}", table.render());
751        assert_eq!(expected, table.render());
752    }
753
754    #[test]
755    fn simple_table_style() {
756        let mut builder = TableBuilder::new().style(TableStyle::simple()).to_owned();
757        add_data_to_test_table(&mut builder);
758        let table = builder.build();
759
760        let expected = r"+---------------------------------------------------------------------------------+
761|                            This is some centered text                           |
762+----------------------------------------+----------------------------------------+
763| This is left aligned text              |             This is right aligned text |
764+----------------------------------------+----------------------------------------+
765| This is left aligned text              |             This is right aligned text |
766+----------------------------------------+----------------------------------------+
767| This is some really really really really really really really really really tha |
768| t is going to wrap to the next line                                             |
769+---------------------------------------------------------------------------------+
770";
771        println!("{}", table.render());
772        assert_eq!(expected, table.render());
773    }
774
775    #[test]
776    fn uneven_with_varying_col_span() {
777        let table = Table::builder()
778            .separate_rows(true)
779            .style(TableStyle::simple())
780            .rows(rows![
781                row![
782                    TableCell::builder("A1111111").alignment(Alignment::Center),
783                    TableCell::builder("B").alignment(Alignment::Center),
784                ],
785                row![1, "1"],
786                row![2, "10"],
787                row![
788                    TableCell::builder(3)
789                        .alignment(Alignment::Left)
790                        .pad_content(false),
791                    "100",
792                ],
793                row![TableCell::builder("S")
794                    .alignment(Alignment::Center)
795                    .col_span(2),],
796            ])
797            .build();
798
799        let expected = "+----------+-----+
800| A1111111 |  B  |
801+----------+-----+
802| 1        | 1   |
803+----------+-----+
804| 2        | 10  |
805+----------+-----+
806|\03\0         | 100 |
807+----------+-----+
808|        S       |
809+----------------+
810";
811        println!("{}", table.render());
812        assert_eq!(expected.trim(), table.render().trim());
813    }
814
815    // TODO - The output of this test isn't ideal. There is probably a better way to calculate the
816    // the column/row layout that would improve this
817    #[test]
818    fn uneven_with_varying_col_span_2() {
819        let table = Table::builder()
820            .separate_rows(false)
821            .style(TableStyle::simple())
822            .rows(rows![
823                row![
824                    TableCell::builder("A").alignment(Alignment::Center),
825                    TableCell::builder("B").alignment(Alignment::Center),
826                ],
827                row![TableCell::builder(1), TableCell::builder(1),],
828                row![TableCell::builder(2), TableCell::builder(10),],
829                row![TableCell::builder(3), TableCell::builder(100),],
830                row![TableCell::builder("Spanner")
831                    .col_span(2)
832                    .alignment(Alignment::Center),],
833            ])
834            .build();
835
836        let expected = "+------+-----+
837|   A  |  B  |
838| 1    | 1   |
839| 2    | 10  |
840| 3    | 100 |
841|   Spanner  |
842+------------+
843";
844        println!("{}", table.render());
845        assert_eq!(expected.trim(), table.render().trim());
846    }
847
848    #[test]
849    fn extended_table_style_wrapped() {
850        let table = Table::builder()
851            .max_column_width(40)
852            .style(TableStyle::extended())
853            .max_column_widths(vec![(0, 1), (1, 1)].into_iter().collect())
854            .rows(rows![
855                row![
856                    TableCell::builder("This is some centered text").alignment(Alignment::Center).col_span(2),
857                ],
858                row![
859                    TableCell::builder("This is left aligned text"),
860                    TableCell::builder("This is right aligned text").alignment(Alignment::Right),
861                ],
862                row![
863                    TableCell::builder("This is left aligned text"),
864                    TableCell::builder("This is right aligned text").alignment(Alignment::Right),
865                ],
866                row![
867                    TableCell::builder("This is some really really really really really really really really really that is going to wrap to the next line\n1\n2").col_span(2),
868                ],
869            ])
870            .build();
871
872        let expected = r"╔═══════╗
873║ This  ║
874║ is so ║
875║ me ce ║
876║ ntere ║
877║ d tex ║
878║   t   ║
879╠═══╦═══╣
880║ T ║ T ║
881║ h ║ h ║
882║ i ║ i ║
883║ s ║ s ║
884║   ║   ║
885║ i ║ i ║
886║ s ║ s ║
887║   ║   ║
888║ l ║ r ║
889║ e ║ i ║
890║ f ║ g ║
891║ t ║ h ║
892║   ║ t ║
893║ a ║   ║
894║ l ║ a ║
895║ i ║ l ║
896║ g ║ i ║
897║ n ║ g ║
898║ e ║ n ║
899║ d ║ e ║
900║   ║ d ║
901║ t ║   ║
902║ e ║ t ║
903║ x ║ e ║
904║ t ║ x ║
905║   ║ t ║
906╠═══╬═══╣
907║ T ║ T ║
908║ h ║ h ║
909║ i ║ i ║
910║ s ║ s ║
911║   ║   ║
912║ i ║ i ║
913║ s ║ s ║
914║   ║   ║
915║ l ║ r ║
916║ e ║ i ║
917║ f ║ g ║
918║ t ║ h ║
919║   ║ t ║
920║ a ║   ║
921║ l ║ a ║
922║ i ║ l ║
923║ g ║ i ║
924║ n ║ g ║
925║ e ║ n ║
926║ d ║ e ║
927║   ║ d ║
928║ t ║   ║
929║ e ║ t ║
930║ x ║ e ║
931║ t ║ x ║
932║   ║ t ║
933╠═══╩═══╣
934║ This  ║
935║ is so ║
936║ me re ║
937║ ally  ║
938║ reall ║
939║ y rea ║
940║ lly r ║
941║ eally ║
942║  real ║
943║ ly re ║
944║ ally  ║
945║ reall ║
946║ y rea ║
947║ lly r ║
948║ eally ║
949║  that ║
950║  is g ║
951║ oing  ║
952║ to wr ║
953║ ap to ║
954║  the  ║
955║ next  ║
956║ line  ║
957║ 1     ║
958║ 2     ║
959╚═══════╝
960";
961        println!("{}", table.render());
962        assert_eq!(expected, table.render());
963    }
964
965    #[test]
966    fn elegant_table_style() {
967        let mut builder = Table::builder().style(TableStyle::elegant()).to_owned();
968        add_data_to_test_table(&mut builder);
969        let table = builder.build();
970
971        let expected = r"╔─────────────────────────────────────────────────────────────────────────────────╗
972│                            This is some centered text                           │
973╠────────────────────────────────────────╦────────────────────────────────────────╣
974│ This is left aligned text              │             This is right aligned text │
975╠────────────────────────────────────────┼────────────────────────────────────────╣
976│ This is left aligned text              │             This is right aligned text │
977╠────────────────────────────────────────╩────────────────────────────────────────╣
978│ This is some really really really really really really really really really tha │
979│ t is going to wrap to the next line                                             │
980╚─────────────────────────────────────────────────────────────────────────────────╝
981";
982        println!("{}", table.render());
983        assert_eq!(expected, table.render());
984    }
985
986    #[test]
987    fn thin_table_style() {
988        let mut builder = Table::builder().style(TableStyle::thin()).to_owned();
989        add_data_to_test_table(&mut builder);
990        let table = builder.build();
991
992        let expected = r"┌─────────────────────────────────────────────────────────────────────────────────┐
993│                            This is some centered text                           │
994├────────────────────────────────────────┬────────────────────────────────────────┤
995│ This is left aligned text              │             This is right aligned text │
996├────────────────────────────────────────┼────────────────────────────────────────┤
997│ This is left aligned text              │             This is right aligned text │
998├────────────────────────────────────────┴────────────────────────────────────────┤
999│ This is some really really really really really really really really really tha │
1000│ t is going to wrap to the next line                                             │
1001└─────────────────────────────────────────────────────────────────────────────────┘
1002";
1003        println!("{}", table.render());
1004        assert_eq!(expected, table.render());
1005    }
1006
1007    #[test]
1008    fn rounded_table_style() {
1009        let mut builder = Table::builder().style(TableStyle::rounded()).to_owned();
1010        add_data_to_test_table(&mut builder);
1011        let table = builder.build();
1012
1013        let expected = r"╭─────────────────────────────────────────────────────────────────────────────────╮
1014│                            This is some centered text                           │
1015├────────────────────────────────────────┬────────────────────────────────────────┤
1016│ This is left aligned text              │             This is right aligned text │
1017├────────────────────────────────────────┼────────────────────────────────────────┤
1018│ This is left aligned text              │             This is right aligned text │
1019├────────────────────────────────────────┴────────────────────────────────────────┤
1020│ This is some really really really really really really really really really tha │
1021│ t is going to wrap to the next line                                             │
1022╰─────────────────────────────────────────────────────────────────────────────────╯
1023";
1024        println!("{}", table.render());
1025        assert_eq!(expected, table.render());
1026    }
1027
1028    #[test]
1029    fn complex_table() {
1030        let mut table = Table::builder()
1031            .rows(rows![
1032                row![
1033                    TableCell::builder("Col*1*Span*2").col_span(2),
1034                    "Col 2 Span 1",
1035                    TableCell::builder("Col 3 Span 2").col_span(2),
1036                    "Col 4 Span 1"
1037                ],
1038                row![
1039                    "Col 1 Span 1",
1040                    "Col 2 Span 1",
1041                    "Col 3 Span 1",
1042                    TableCell::builder("Col 4 Span 2").col_span(2)
1043                ],
1044                row!["fasdaff", "fff", "fff",],
1045                row![
1046                    TableCell::builder("fasdff")
1047                        .col_span(3)
1048                        .alignment(Alignment::Right),
1049                    TableCell::builder("fffdff").col_span(4),
1050                ],
1051                row!["fasdsaff", "fff", "f\nf\nf\nfff\nrrr\n\n\n",],
1052                row!["fasdsaff",],
1053            ])
1054            .build();
1055
1056        let s = table.render();
1057
1058        table.add_row(row![TableCell::builder(s)
1059            .col_span(3)
1060            .alignment(Alignment::Left)]);
1061
1062        let expected = r"╔═════════════════════════════════════════════════════════╦════════════════════════════╦════════════════╦══════════════╦═══╗
1063║ Col*1*Span*2                                            ║ Col 2 Span 1               ║ Col 3 Span 2   ║ Col 4 Span 1 ║   ║
1064╠════════════════════════════╦════════════════════════════╬════════════════════════════╬════════════════╬══════════════╬═══╣
1065║ Col 1 Span 1               ║ Col 2 Span 1               ║ Col 3 Span 1               ║ Col 4 Span 2   ║              ║   ║
1066╠════════════════════════════╬════════════════════════════╬════════════════════════════╬═══════╦════════╬══════════════╬═══╣
1067║ fasdaff                    ║ fff                        ║ fff                        ║       ║        ║              ║   ║
1068╠════════════════════════════╩════════════════════════════╩════════════════════════════╬═══════╩════════╩══════════════╩═══╣
1069║                                                                               fasdff ║ fffdff                            ║
1070╠════════════════════════════╦════════════════════════════╦════════════════════════════╬═══════╦════════╦══════════════╦═══╣
1071║ fasdsaff                   ║ fff                        ║ f                          ║       ║        ║              ║   ║
1072║                            ║                            ║ f                          ║       ║        ║              ║   ║
1073║                            ║                            ║ f                          ║       ║        ║              ║   ║
1074║                            ║                            ║ fff                        ║       ║        ║              ║   ║
1075║                            ║                            ║ rrr                        ║       ║        ║              ║   ║
1076║                            ║                            ║                            ║       ║        ║              ║   ║
1077║                            ║                            ║                            ║       ║        ║              ║   ║
1078║                            ║                            ║                            ║       ║        ║              ║   ║
1079╠════════════════════════════╬════════════════════════════╬════════════════════════════╬═══════╬════════╬══════════════╬═══╣
1080║ fasdsaff                   ║                            ║                            ║       ║        ║              ║   ║
1081╠════════════════════════════╩════════════════════════════╩════════════════════════════╬═══════╬════════╬══════════════╬═══╣
1082║ ╔═════════════════════════════╦══════════════╦════════════════╦══════════════╦═══╗   ║       ║        ║              ║   ║
1083║ ║ Col*1*Span*2                ║ Col 2 Span 1 ║ Col 3 Span 2   ║ Col 4 Span 1 ║   ║   ║       ║        ║              ║   ║
1084║ ╠══════════════╦══════════════╬══════════════╬════════════════╬══════════════╬═══╣   ║       ║        ║              ║   ║
1085║ ║ Col 1 Span 1 ║ Col 2 Span 1 ║ Col 3 Span 1 ║ Col 4 Span 2   ║              ║   ║   ║       ║        ║              ║   ║
1086║ ╠══════════════╬══════════════╬══════════════╬═══════╦════════╬══════════════╬═══╣   ║       ║        ║              ║   ║
1087║ ║ fasdaff      ║ fff          ║ fff          ║       ║        ║              ║   ║   ║       ║        ║              ║   ║
1088║ ╠══════════════╩══════════════╩══════════════╬═══════╩════════╩══════════════╩═══╣   ║       ║        ║              ║   ║
1089║ ║                                     fasdff ║ fffdff                            ║   ║       ║        ║              ║   ║
1090║ ╠══════════════╦══════════════╦══════════════╬═══════╦════════╦══════════════╦═══╣   ║       ║        ║              ║   ║
1091║ ║ fasdsaff     ║ fff          ║ f            ║       ║        ║              ║   ║   ║       ║        ║              ║   ║
1092║ ║              ║              ║ f            ║       ║        ║              ║   ║   ║       ║        ║              ║   ║
1093║ ║              ║              ║ f            ║       ║        ║              ║   ║   ║       ║        ║              ║   ║
1094║ ║              ║              ║ fff          ║       ║        ║              ║   ║   ║       ║        ║              ║   ║
1095║ ║              ║              ║ rrr          ║       ║        ║              ║   ║   ║       ║        ║              ║   ║
1096║ ║              ║              ║              ║       ║        ║              ║   ║   ║       ║        ║              ║   ║
1097║ ║              ║              ║              ║       ║        ║              ║   ║   ║       ║        ║              ║   ║
1098║ ║              ║              ║              ║       ║        ║              ║   ║   ║       ║        ║              ║   ║
1099║ ╠══════════════╬══════════════╬══════════════╬═══════╬════════╬══════════════╬═══╣   ║       ║        ║              ║   ║
1100║ ║ fasdsaff     ║              ║              ║       ║        ║              ║   ║   ║       ║        ║              ║   ║
1101║ ╚══════════════╩══════════════╩══════════════╩═══════╩════════╩══════════════╩═══╝   ║       ║        ║              ║   ║
1102║                                                                                      ║       ║        ║              ║   ║
1103╚══════════════════════════════════════════════════════════════════════════════════════╩═══════╩════════╩══════════════╩═══╝
1104";
1105        println!("{}", table.render());
1106        assert_eq!(expected, table.render());
1107    }
1108
1109    #[test]
1110    fn no_top_boarder() {
1111        let mut builder = Table::builder()
1112            .style(TableStyle::simple())
1113            .has_top_boarder(false)
1114            .to_owned();
1115        add_data_to_test_table(&mut builder);
1116        let table = builder.build();
1117
1118        let expected = r"|                            This is some centered text                           |
1119+----------------------------------------+----------------------------------------+
1120| This is left aligned text              |             This is right aligned text |
1121+----------------------------------------+----------------------------------------+
1122| This is left aligned text              |             This is right aligned text |
1123+----------------------------------------+----------------------------------------+
1124| This is some really really really really really really really really really tha |
1125| t is going to wrap to the next line                                             |
1126+---------------------------------------------------------------------------------+
1127";
1128        println!("{}", table.render());
1129        assert_eq!(expected, table.render());
1130    }
1131
1132    #[test]
1133    fn no_bottom_boarder() {
1134        let mut builder = Table::builder()
1135            .style(TableStyle::simple())
1136            .has_bottom_boarder(false)
1137            .to_owned();
1138        add_data_to_test_table(&mut builder);
1139        let table = builder.build();
1140
1141        let expected = r"+---------------------------------------------------------------------------------+
1142|                            This is some centered text                           |
1143+----------------------------------------+----------------------------------------+
1144| This is left aligned text              |             This is right aligned text |
1145+----------------------------------------+----------------------------------------+
1146| This is left aligned text              |             This is right aligned text |
1147+----------------------------------------+----------------------------------------+
1148| This is some really really really really really really really really really tha |
1149| t is going to wrap to the next line                                             |
1150";
1151        println!("{}", table.render());
1152        assert_eq!(expected, table.render());
1153    }
1154
1155    #[test]
1156    fn no_separators() {
1157        let mut builder = Table::builder()
1158            .style(TableStyle::simple())
1159            .separate_rows(false)
1160            .to_owned();
1161
1162        add_data_to_test_table(&mut builder);
1163        let table = builder.build();
1164
1165        let expected = r"+---------------------------------------------------------------------------------+
1166|                            This is some centered text                           |
1167| This is left aligned text              |             This is right aligned text |
1168| This is left aligned text              |             This is right aligned text |
1169| This is some really really really really really really really really really tha |
1170| t is going to wrap to the next line                                             |
1171+---------------------------------------------------------------------------------+
1172";
1173        println!("{}", table.render());
1174        assert_eq!(expected, table.render());
1175    }
1176
1177    #[test]
1178    fn some_rows_no_separators() {
1179        let mut builder = Table::builder().style(TableStyle::simple()).to_owned();
1180        add_data_to_test_table(&mut builder);
1181        let mut table = builder.build();
1182
1183        table.rows[2].has_separator = false;
1184
1185        let expected = r"+---------------------------------------------------------------------------------+
1186|                            This is some centered text                           |
1187+----------------------------------------+----------------------------------------+
1188| This is left aligned text              |             This is right aligned text |
1189| This is left aligned text              |             This is right aligned text |
1190+----------------------------------------+----------------------------------------+
1191| This is some really really really really really really really really really tha |
1192| t is going to wrap to the next line                                             |
1193+---------------------------------------------------------------------------------+
1194";
1195        println!("{}", table.render());
1196        assert_eq!(expected, table.render());
1197    }
1198
1199    #[test]
1200    fn colored_data_works() {
1201        let table = Table::builder()
1202            .rows(rows![row![TableCell::builder("\u{1b}[31ma\u{1b}[0m")]])
1203            .build();
1204        let expected = "╔═══╗
1205║ \u{1b}[31ma\u{1b}[0m ║
1206╚═══╝
1207";
1208        println!("{}", table.render());
1209        assert_eq!(expected, table.render());
1210    }
1211
1212    fn add_data_to_test_table(builder: &mut TableBuilder) {
1213        builder
1214        .max_column_width(40)
1215        .rows(
1216            rows![
1217                row![
1218                    TableCell::builder("This is some centered text")
1219                        .col_span(2)
1220                        .alignment(Alignment::Center)
1221                ],
1222                row![
1223                    "This is left aligned text",
1224                    TableCell::builder("This is right aligned text")
1225                        .alignment(Alignment::Right)
1226                ],
1227                row![
1228                    "This is left aligned text",
1229                    TableCell::builder("This is right aligned text")
1230                        .alignment(Alignment::Right)
1231                ],
1232                row![
1233                    TableCell::builder("This is some really really really really really really really really really that is going to wrap to the next line")
1234                        .col_span(2)
1235                ]
1236            ]
1237        );
1238    }
1239}