comfy_table/
table.rs

1#[cfg(feature = "tty")]
2use std::sync::OnceLock;
3use std::{
4    collections::HashMap,
5    fmt,
6    iter::IntoIterator,
7    slice::{Iter, IterMut},
8};
9
10use crate::{
11    cell::Cell,
12    column::Column,
13    row::Row,
14    style::{ColumnConstraint, ContentArrangement, TableComponent, presets::ASCII_FULL},
15    utils::build_table,
16};
17
18/// This is the main interface for building a table.
19/// Each table consists of [Rows](Row), which in turn contain [Cells](crate::cell::Cell).
20///
21/// There also exists a representation of a [Column].
22/// Columns are automatically created when adding rows to a table.
23#[derive(Debug, Clone)]
24pub struct Table {
25    pub(crate) columns: Vec<Column>,
26    style: HashMap<TableComponent, char>,
27    pub(crate) header: Option<Row>,
28    pub(crate) rows: Vec<Row>,
29    pub(crate) arrangement: ContentArrangement,
30    pub(crate) delimiter: Option<char>,
31    pub(crate) truncation_indicator: String,
32    #[cfg(feature = "tty")]
33    no_tty: bool,
34    #[cfg(feature = "tty")]
35    is_tty_cache: OnceLock<bool>,
36    #[cfg(feature = "tty")]
37    use_stderr: bool,
38    width: Option<u16>,
39    #[cfg(feature = "tty")]
40    enforce_styling: bool,
41    /// Define whether everything in a cells should be styled, including whitespaces
42    /// or whether only the text should be styled.
43    #[cfg(feature = "tty")]
44    pub(crate) style_text_only: bool,
45}
46
47impl fmt::Display for Table {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        write!(f, "{}", self.lines().collect::<Vec<_>>().join("\n"))
50    }
51}
52
53impl Default for Table {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl Table {
60    /// Create a new table with default ASCII styling.
61    pub fn new() -> Self {
62        let mut table = Self {
63            columns: Vec::new(),
64            header: None,
65            rows: Vec::new(),
66            arrangement: ContentArrangement::Disabled,
67            delimiter: None,
68            truncation_indicator: "...".to_string(),
69            #[cfg(feature = "tty")]
70            no_tty: false,
71            #[cfg(feature = "tty")]
72            is_tty_cache: OnceLock::new(),
73            #[cfg(feature = "tty")]
74            use_stderr: false,
75            width: None,
76            style: HashMap::new(),
77            #[cfg(feature = "tty")]
78            enforce_styling: false,
79            #[cfg(feature = "tty")]
80            style_text_only: false,
81        };
82
83        table.load_preset(ASCII_FULL);
84
85        table
86    }
87
88    /// This is an alternative `fmt` function, which simply removes any trailing whitespaces.
89    /// Trailing whitespaces often occur, when using tables without a right border.
90    pub fn trim_fmt(&self) -> String {
91        self.lines()
92            .map(|line| line.trim_end().to_string())
93            .collect::<Vec<_>>()
94            .join("\n")
95    }
96
97    /// This is an alternative to `fmt`, but rather returns an iterator to each line, rather than
98    /// one String separated by newlines.
99    pub fn lines(&self) -> impl Iterator<Item = String> {
100        build_table(self)
101    }
102
103    /// Set the header row of the table. This is usually the title of each column.\
104    /// There'll be no header unless you explicitly set it with this function.
105    ///
106    /// ```
107    /// use comfy_table::{Row, Table};
108    ///
109    /// let mut table = Table::new();
110    /// let header = Row::from(vec!["Header One", "Header Two"]);
111    /// table.set_header(header);
112    /// ```
113    pub fn set_header<T: Into<Row>>(&mut self, row: T) -> &mut Self {
114        let row = row.into();
115        self.autogenerate_columns(&row);
116        self.header = Some(row);
117
118        self
119    }
120
121    pub fn header(&self) -> Option<&Row> {
122        self.header.as_ref()
123    }
124
125    /// Returns the number of currently present columns.
126    ///
127    /// ```
128    /// use comfy_table::Table;
129    ///
130    /// let mut table = Table::new();
131    /// table.set_header(vec!["Col 1", "Col 2", "Col 3"]);
132    ///
133    /// assert_eq!(table.column_count(), 3);
134    /// ```
135    pub fn column_count(&mut self) -> usize {
136        self.discover_columns();
137        self.columns.len()
138    }
139
140    /// Add a new row to the table.
141    ///
142    /// ```
143    /// use comfy_table::{Row, Table};
144    ///
145    /// let mut table = Table::new();
146    /// table.add_row(vec!["One", "Two"]);
147    /// ```
148    pub fn add_row<T: Into<Row>>(&mut self, row: T) -> &mut Self {
149        let mut row = row.into();
150        self.autogenerate_columns(&row);
151        row.index = Some(self.rows.len());
152        self.rows.push(row);
153
154        self
155    }
156
157    /// Add a new row to the table if the predicate evaluates to `true`.
158    ///
159    /// ```
160    /// use comfy_table::{Row, Table};
161    ///
162    /// let mut table = Table::new();
163    /// table.add_row_if(|index, row| true, vec!["One", "Two"]);
164    /// ```
165    pub fn add_row_if<P, T>(&mut self, predicate: P, row: T) -> &mut Self
166    where
167        P: Fn(usize, &T) -> bool,
168        T: Into<Row>,
169    {
170        if predicate(self.rows.len(), &row) {
171            return self.add_row(row);
172        }
173
174        self
175    }
176
177    /// Add multiple rows to the table.
178    ///
179    /// ```
180    /// use comfy_table::{Row, Table};
181    ///
182    /// let mut table = Table::new();
183    /// let rows = vec![vec!["One", "Two"], vec!["Three", "Four"]];
184    /// table.add_rows(rows);
185    /// ```
186    pub fn add_rows<I>(&mut self, rows: I) -> &mut Self
187    where
188        I: IntoIterator,
189        I::Item: Into<Row>,
190    {
191        for row in rows.into_iter() {
192            let mut row = row.into();
193            self.autogenerate_columns(&row);
194            row.index = Some(self.rows.len());
195            self.rows.push(row);
196        }
197
198        self
199    }
200
201    /// Add multiple rows to the table if the predicate evaluates to `true`.
202    ///
203    /// ```
204    /// use comfy_table::{Row, Table};
205    ///
206    /// let mut table = Table::new();
207    /// let rows = vec![vec!["One", "Two"], vec!["Three", "Four"]];
208    /// table.add_rows_if(|index, rows| true, rows);
209    /// ```
210    pub fn add_rows_if<P, I>(&mut self, predicate: P, rows: I) -> &mut Self
211    where
212        P: Fn(usize, &I) -> bool,
213        I: IntoIterator,
214        I::Item: Into<Row>,
215    {
216        if predicate(self.rows.len(), &rows) {
217            return self.add_rows(rows);
218        }
219
220        self
221    }
222
223    /// Returns the number of currently present rows.
224    ///
225    /// ```
226    /// use comfy_table::Table;
227    ///
228    /// let mut table = Table::new();
229    /// table.add_row(vec!["One", "Two"]);
230    ///
231    /// assert_eq!(table.row_count(), 1);
232    /// ```
233    pub fn row_count(&self) -> usize {
234        self.rows.len()
235    }
236
237    /// Returns if the table is empty (contains no data rows).
238    ///
239    /// ```
240    /// use comfy_table::Table;
241    ///
242    /// let mut table = Table::new();
243    /// assert!(table.is_empty());
244    ///
245    /// table.add_row(vec!["One", "Two"]);
246    /// assert!(!table.is_empty());
247    /// ```
248    pub fn is_empty(&self) -> bool {
249        self.rows.is_empty()
250    }
251
252    /// Enforce a max width that should be used in combination with [dynamic content
253    /// arrangement](ContentArrangement::Dynamic).\ This is usually not necessary, if you plan
254    /// to output your table to a tty, since the terminal width can be automatically determined.
255    pub fn set_width(&mut self, width: u16) -> &mut Self {
256        self.width = Some(width);
257
258        self
259    }
260
261    /// Get the expected width of the table.
262    ///
263    /// This will be `Some(width)`, if the terminal width can be detected or if the table width is
264    /// set via [set_width](Table::set_width).
265    ///
266    /// If neither is not possible, `None` will be returned.\
267    /// This implies that both the [Dynamic](ContentArrangement::Dynamic) mode and the
268    /// [Percentage](crate::style::Width::Percentage) constraint won't work.
269    #[cfg(feature = "tty")]
270    pub fn width(&self) -> Option<u16> {
271        if let Some(width) = self.width {
272            Some(width)
273        } else if self.is_tty() {
274            if let Ok((width, _)) = crossterm::terminal::size() {
275                Some(width)
276            } else {
277                None
278            }
279        } else {
280            None
281        }
282    }
283
284    #[cfg(not(feature = "tty"))]
285    pub fn width(&self) -> Option<u16> {
286        self.width
287    }
288
289    /// Specify how Comfy Table should arrange the content in your table.
290    ///
291    /// ```
292    /// use comfy_table::{ContentArrangement, Table};
293    ///
294    /// let mut table = Table::new();
295    /// table.set_content_arrangement(ContentArrangement::Dynamic);
296    /// ```
297    pub fn set_content_arrangement(&mut self, arrangement: ContentArrangement) -> &mut Self {
298        self.arrangement = arrangement;
299
300        self
301    }
302
303    /// Get the current content arrangement of the table.
304    pub fn content_arrangement(&self) -> ContentArrangement {
305        self.arrangement.clone()
306    }
307
308    /// Set the delimiter used to split text in all cells.
309    ///
310    /// A custom delimiter on a cell in will overwrite the column's delimiter.\
311    /// Normal text uses spaces (` `) as delimiters. This is necessary to help comfy-table
312    /// understand the concept of _words_.
313    pub fn set_delimiter(&mut self, delimiter: char) -> &mut Self {
314        self.delimiter = Some(delimiter);
315
316        self
317    }
318
319    /// Set the truncation indicator for cells that are too long to be displayed.
320    ///
321    /// Set it to "…" for example to use an ellipsis that only takes up one character.
322    pub fn set_truncation_indicator(&mut self, indicator: &str) -> &mut Self {
323        self.truncation_indicator = indicator.to_string();
324
325        self
326    }
327
328    /// In case you are sure you don't want export tables to a tty or you experience
329    /// problems with tty specific code, you can enforce a non_tty mode.
330    ///
331    /// This disables:
332    ///
333    /// - width lookup from the current tty
334    /// - Styling and attributes on cells (unless you use [Table::enforce_styling])
335    ///
336    /// If you use the [dynamic content arrangement](ContentArrangement::Dynamic),
337    /// you need to set the width of your desired table manually with [set_width](Table::set_width).
338    #[cfg(feature = "tty")]
339    pub fn force_no_tty(&mut self) -> &mut Self {
340        self.no_tty = true;
341
342        self
343    }
344
345    /// Use this function to check whether `stderr` is a tty.
346    ///
347    /// The default is `stdout`.
348    #[cfg(feature = "tty")]
349    pub fn use_stderr(&mut self) -> &mut Self {
350        self.use_stderr = true;
351
352        self
353    }
354
355    /// Returns whether the table will be handled as if it's printed to a tty.
356    ///
357    /// By default, comfy-table looks at `stdout` and checks whether it's a tty.
358    /// This behavior can be changed via [Table::force_no_tty] and [Table::use_stderr].
359    #[cfg(feature = "tty")]
360    pub fn is_tty(&self) -> bool {
361        use std::io::IsTerminal;
362
363        if self.no_tty {
364            return false;
365        }
366
367        *self.is_tty_cache.get_or_init(|| {
368            if self.use_stderr {
369                std::io::stderr().is_terminal()
370            } else {
371                std::io::stdout().is_terminal()
372            }
373        })
374    }
375
376    /// Enforce terminal styling.
377    ///
378    /// Only useful if you forcefully disabled tty, but still want those fancy terminal styles.
379    ///
380    /// ```
381    /// use comfy_table::Table;
382    ///
383    /// let mut table = Table::new();
384    /// table.force_no_tty().enforce_styling();
385    /// ```
386    #[cfg(feature = "tty")]
387    pub fn enforce_styling(&mut self) -> &mut Self {
388        self.enforce_styling = true;
389
390        self
391    }
392
393    /// Returns whether the content of this table should be styled with the current settings and
394    /// environment.
395    #[cfg(feature = "tty")]
396    pub fn should_style(&self) -> bool {
397        if self.enforce_styling {
398            return true;
399        }
400        self.is_tty()
401    }
402
403    /// By default, the whole content of a cells will be styled.
404    /// Calling this function disables this behavior for all cells, resulting in
405    /// only the text of cells being styled.
406    #[cfg(feature = "tty")]
407    pub fn style_text_only(&mut self) {
408        self.style_text_only = true;
409    }
410
411    /// Convenience method to set a [ColumnConstraint] for all columns at once.
412    /// Constraints are used to influence the way the columns will be arranged.
413    /// Check out their docs for more information.
414    ///
415    /// **Attention:**
416    /// This function should be called after at least one row (or the headers) has been added to the
417    /// table. Before that, the columns won't initialized.
418    ///
419    /// If more constraints are passed than there are columns, any superfluous constraints will be
420    /// ignored. ```
421    /// use comfy_table::{CellAlignment, ColumnConstraint::*, ContentArrangement, Table, Width::*};
422    ///
423    /// let mut table = Table::new();
424    /// table
425    ///     .add_row(&vec!["one", "two", "three"])
426    ///     .set_content_arrangement(ContentArrangement::Dynamic)
427    ///     .set_constraints(vec![UpperBoundary(Fixed(15)), LowerBoundary(Fixed(20))]);
428    /// ```
429    pub fn set_constraints<T: IntoIterator<Item = ColumnConstraint>>(
430        &mut self,
431        constraints: T,
432    ) -> &mut Self {
433        let mut constraints = constraints.into_iter();
434        for column in self.column_iter_mut() {
435            if let Some(constraint) = constraints.next() {
436                column.set_constraint(constraint);
437            } else {
438                break;
439            }
440        }
441
442        self
443    }
444
445    /// This function creates a TableStyle from a given preset string.\
446    /// Preset strings can be found in `styling::presets::*`.
447    ///
448    /// You can also write your own preset strings and use them with this function.
449    /// There's the convenience method [Table::current_style_as_preset], which prints you a preset
450    /// string from your current style configuration. \
451    /// The function expects the to-be-drawn characters to be in the same order as in the
452    /// [TableComponent] enum.
453    ///
454    /// If the string isn't long enough, the default [ASCII_FULL] style will be used for all
455    /// remaining components.
456    ///
457    /// If the string is too long, remaining charaacters will be simply ignored.
458    pub fn load_preset(&mut self, preset: &str) -> &mut Self {
459        let mut components = TableComponent::iter();
460
461        for character in preset.chars() {
462            if let Some(component) = components.next() {
463                // White spaces mean "don't draw this" in presets
464                // If we want to override the default preset, we need to remove
465                // this component from the HashMap in case we find a whitespace.
466                if character == ' ' {
467                    self.remove_style(component);
468                    continue;
469                }
470
471                self.set_style(component, character);
472            } else {
473                break;
474            }
475        }
476
477        self
478    }
479
480    /// Returns the current style as a preset string.
481    ///
482    /// A pure convenience method, so you're not force to fiddle with those preset strings yourself.
483    ///
484    /// ```
485    /// use comfy_table::{Table, presets::UTF8_FULL};
486    ///
487    /// let mut table = Table::new();
488    /// table.load_preset(UTF8_FULL);
489    ///
490    /// assert_eq!(UTF8_FULL, table.current_style_as_preset())
491    /// ```
492    pub fn current_style_as_preset(&mut self) -> String {
493        let components = TableComponent::iter();
494        let mut preset_string = String::new();
495
496        for component in components {
497            match self.style(component) {
498                None => preset_string.push(' '),
499                Some(character) => preset_string.push(character),
500            }
501        }
502
503        preset_string
504    }
505
506    /// Modify a preset with a modifier string from [modifiers](crate::style::modifiers).
507    ///
508    /// For instance, the [UTF8_ROUND_CORNERS](crate::style::modifiers::UTF8_ROUND_CORNERS) modifies
509    /// all corners to be round UTF8 box corners.
510    ///
511    /// ```
512    /// use comfy_table::{Table, modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL};
513    ///
514    /// let mut table = Table::new();
515    /// table.load_preset(UTF8_FULL);
516    /// table.apply_modifier(UTF8_ROUND_CORNERS);
517    /// ```
518    pub fn apply_modifier(&mut self, modifier: &str) -> &mut Self {
519        let mut components = TableComponent::iter();
520
521        for character in modifier.chars() {
522            // Skip spaces while applying modifiers.
523            if character == ' ' {
524                components.next();
525                continue;
526            }
527            if let Some(component) = components.next() {
528                self.set_style(component, character);
529            } else {
530                break;
531            }
532        }
533
534        self
535    }
536
537    /// Define the char that will be used to draw a specific component.\
538    /// Look at [TableComponent] to see all stylable components
539    ///
540    /// If `None` is supplied, the element won't be displayed.\
541    /// In case of a e.g. *BorderIntersection a whitespace will be used as placeholder,
542    /// unless related borders and and corners are set to `None` as well.
543    ///
544    /// For example, if `TopBorderIntersections` is `None` the first row would look like this:
545    ///
546    /// ```text
547    /// +------ ------+
548    /// | this | test |
549    /// ```
550    ///
551    /// If in addition `TopLeftCorner`,`TopBorder` and `TopRightCorner` would be `None` as well,
552    /// the first line wouldn't be displayed at all.
553    ///
554    /// ```
555    /// use comfy_table::{Table, TableComponent::*, presets::UTF8_FULL};
556    ///
557    /// let mut table = Table::new();
558    /// // Load the UTF8_FULL preset
559    /// table.load_preset(UTF8_FULL);
560    /// // Set all outer corners to round UTF8 corners
561    /// // This is basically the same as the UTF8_ROUND_CORNERS modifier
562    /// table.set_style(TopLeftCorner, '╭');
563    /// table.set_style(TopRightCorner, '╮');
564    /// table.set_style(BottomLeftCorner, '╰');
565    /// table.set_style(BottomRightCorner, '╯');
566    /// ```
567    pub fn set_style(&mut self, component: TableComponent, character: char) -> &mut Self {
568        self.style.insert(component, character);
569
570        self
571    }
572
573    /// Get a copy of the char that's currently used for drawing this component.
574    /// ```
575    /// use comfy_table::{Table, TableComponent::*};
576    ///
577    /// let mut table = Table::new();
578    /// assert_eq!(table.style(TopLeftCorner), Some('+'));
579    /// ```
580    pub fn style(&mut self, component: TableComponent) -> Option<char> {
581        self.style.get(&component).copied()
582    }
583
584    /// Remove the style for a specific component of the table.\
585    /// By default, a space will be used as a placeholder instead.\
586    /// Though, if for instance all components of the left border are removed, the left border won't
587    /// be displayed.
588    pub fn remove_style(&mut self, component: TableComponent) -> &mut Self {
589        self.style.remove(&component);
590
591        self
592    }
593
594    /// Get a reference to a specific column.
595    pub fn column(&self, index: usize) -> Option<&Column> {
596        self.columns.get(index)
597    }
598
599    /// Get a mutable reference to a specific column.
600    pub fn column_mut(&mut self, index: usize) -> Option<&mut Column> {
601        self.columns.get_mut(index)
602    }
603
604    /// Iterator over all columns
605    pub fn column_iter(&self) -> Iter<'_, Column> {
606        self.columns.iter()
607    }
608
609    /// Get a mutable iterator over all columns.
610    ///
611    /// ```
612    /// use comfy_table::{ColumnConstraint::*, Table, Width::*};
613    ///
614    /// let mut table = Table::new();
615    /// table.add_row(&vec!["First", "Second", "Third"]);
616    ///
617    /// // Add a ColumnConstraint to each column (left->right)
618    /// // first -> min width of 10
619    /// // second -> max width of 8
620    /// // third -> fixed width of 10
621    /// let constraints = vec![
622    ///     LowerBoundary(Fixed(10)),
623    ///     UpperBoundary(Fixed(8)),
624    ///     Absolute(Fixed(10)),
625    /// ];
626    ///
627    /// // Add the constraints to their respective column
628    /// for (column_index, column) in table.column_iter_mut().enumerate() {
629    ///     let constraint = constraints.get(column_index).unwrap();
630    ///     column.set_constraint(*constraint);
631    /// }
632    /// ```
633    pub fn column_iter_mut(&mut self) -> IterMut<'_, Column> {
634        self.columns.iter_mut()
635    }
636
637    /// Get a mutable iterator over cells of a column.
638    /// The iterator returns a nested `Option<Option<Cell>>`, since there might be
639    /// rows that are missing this specific Cell.
640    ///
641    /// ```
642    /// use comfy_table::Table;
643    /// let mut table = Table::new();
644    /// table.add_row(&vec!["First", "Second"]);
645    /// table.add_row(&vec!["Third"]);
646    /// table.add_row(&vec!["Fourth", "Fifth"]);
647    ///
648    /// // Create an iterator over the second column
649    /// let mut cell_iter = table.column_cells_iter(1);
650    /// assert_eq!(cell_iter.next().unwrap().unwrap().content(), "Second");
651    /// assert!(cell_iter.next().unwrap().is_none());
652    /// assert_eq!(cell_iter.next().unwrap().unwrap().content(), "Fifth");
653    /// assert!(cell_iter.next().is_none());
654    /// ```
655    pub fn column_cells_iter(&self, column_index: usize) -> ColumnCellIter<'_> {
656        ColumnCellIter {
657            rows: &self.rows,
658            column_index,
659            row_index: 0,
660        }
661    }
662
663    /// Get a mutable iterator over cells of a column, including the header cell.
664    /// The header cell will be the very first cell returned.
665    /// The iterator returns a nested `Option<Option<Cell>>`, since there might be
666    /// rows that are missing this specific Cell.
667    ///
668    /// ```
669    /// use comfy_table::Table;
670    /// let mut table = Table::new();
671    /// table.set_header(&vec!["A", "B"]);
672    /// table.add_row(&vec!["First", "Second"]);
673    /// table.add_row(&vec!["Third"]);
674    /// table.add_row(&vec!["Fourth", "Fifth"]);
675    ///
676    /// // Create an iterator over the second column
677    /// let mut cell_iter = table.column_cells_with_header_iter(1);
678    /// assert_eq!(cell_iter.next().unwrap().unwrap().content(), "B");
679    /// assert_eq!(cell_iter.next().unwrap().unwrap().content(), "Second");
680    /// assert!(cell_iter.next().unwrap().is_none());
681    /// assert_eq!(cell_iter.next().unwrap().unwrap().content(), "Fifth");
682    /// assert!(cell_iter.next().is_none());
683    /// ```
684    pub fn column_cells_with_header_iter(
685        &self,
686        column_index: usize,
687    ) -> ColumnCellsWithHeaderIter<'_> {
688        ColumnCellsWithHeaderIter {
689            header_checked: false,
690            header: &self.header,
691            rows: &self.rows,
692            column_index,
693            row_index: 0,
694        }
695    }
696
697    /// Reference to a specific row
698    pub fn row(&self, index: usize) -> Option<&Row> {
699        self.rows.get(index)
700    }
701
702    /// Mutable reference to a specific row
703    pub fn row_mut(&mut self, index: usize) -> Option<&mut Row> {
704        self.rows.get_mut(index)
705    }
706
707    /// Iterator over all rows
708    pub fn row_iter(&self) -> Iter<'_, Row> {
709        self.rows.iter()
710    }
711
712    /// Get a mutable iterator over all rows.
713    ///
714    /// ```
715    /// use comfy_table::Table;
716    /// let mut table = Table::new();
717    /// table.add_row(&vec!["First", "Second", "Third"]);
718    ///
719    /// // Add the constraints to their respective row
720    /// for row in table.row_iter_mut() {
721    ///     row.max_height(5);
722    /// }
723    /// assert!(table.row_iter_mut().len() == 1);
724    /// ```
725    pub fn row_iter_mut(&mut self) -> IterMut<'_, Row> {
726        self.rows.iter_mut()
727    }
728
729    /// Return a vector representing the maximum amount of characters in any line of this column.\
730    ///
731    /// **Attention** This scans the whole current content of the table.
732    pub fn column_max_content_widths(&self) -> Vec<u16> {
733        fn set_max_content_widths(max_widths: &mut [u16], row: &Row) {
734            // Get the max width for each cell of the row
735            let row_max_widths = row.max_content_widths();
736            for (index, width) in row_max_widths.iter().enumerate() {
737                let mut width = (*width).try_into().unwrap_or(u16::MAX);
738                // A column's content is at least 1 char wide.
739                width = std::cmp::max(1, width);
740
741                // Set a new max, if the current cell is the longest for that column.
742                let current_max = max_widths[index];
743                if current_max < width {
744                    max_widths[index] = width;
745                }
746            }
747        }
748        // The vector that'll contain the max widths per column.
749        let mut max_widths = vec![0; self.columns.len()];
750
751        if let Some(header) = &self.header {
752            set_max_content_widths(&mut max_widths, header);
753        }
754        // Iterate through all rows of the table.
755        for row in self.rows.iter() {
756            set_max_content_widths(&mut max_widths, row);
757        }
758
759        max_widths
760    }
761
762    pub(crate) fn style_or_default(&self, component: TableComponent) -> String {
763        match self.style.get(&component) {
764            None => " ".to_string(),
765            Some(character) => character.to_string(),
766        }
767    }
768
769    pub(crate) fn style_exists(&self, component: TableComponent) -> bool {
770        self.style.contains_key(&component)
771    }
772
773    /// Autogenerate new columns, if a row is added with more cells than existing columns.
774    fn autogenerate_columns(&mut self, row: &Row) {
775        if row.cell_count() > self.columns.len() {
776            for index in self.columns.len()..row.cell_count() {
777                self.columns.push(Column::new(index));
778            }
779        }
780    }
781
782    /// Calling this might be necessary if you add new cells to rows that're already added to the
783    /// table.
784    ///
785    /// If more cells than're currently know to the table are added to that row,
786    /// the table cannot know about these, since new [Column]s are only
787    /// automatically detected when a new row is added.
788    ///
789    /// To make sure everything works as expected, just call this function if you're adding cells
790    /// to rows that're already added to the table.
791    pub fn discover_columns(&mut self) {
792        for row in self.rows.iter() {
793            if row.cell_count() > self.columns.len() {
794                for index in self.columns.len()..row.cell_count() {
795                    self.columns.push(Column::new(index));
796                }
797            }
798        }
799    }
800}
801
802/// An iterator over cells of a specific column.
803/// A dedicated struct is necessary, as data is usually handled by rows and thereby stored in
804/// `Table::rows`. This type is returned by [Table::column_cells_iter].
805pub struct ColumnCellIter<'a> {
806    rows: &'a [Row],
807    column_index: usize,
808    row_index: usize,
809}
810
811impl<'a> Iterator for ColumnCellIter<'a> {
812    type Item = Option<&'a Cell>;
813    fn next(&mut self) -> Option<Option<&'a Cell>> {
814        // Check if there's a next row
815        if let Some(row) = self.rows.get(self.row_index) {
816            self.row_index += 1;
817
818            // Return the cell (if it exists).
819            return Some(row.cells.get(self.column_index));
820        }
821
822        None
823    }
824}
825
826/// An iterator over cells of a specific column.
827/// A dedicated struct is necessary, as data is usually handled by rows and thereby stored in
828/// `Table::rows`. This type is returned by [Table::column_cells_iter].
829pub struct ColumnCellsWithHeaderIter<'a> {
830    header_checked: bool,
831    header: &'a Option<Row>,
832    rows: &'a [Row],
833    column_index: usize,
834    row_index: usize,
835}
836
837impl<'a> Iterator for ColumnCellsWithHeaderIter<'a> {
838    type Item = Option<&'a Cell>;
839    fn next(&mut self) -> Option<Option<&'a Cell>> {
840        // Get the header as the first cell
841        if !self.header_checked {
842            self.header_checked = true;
843
844            return match self.header {
845                Some(header) => {
846                    // Return the cell (if it exists).
847                    Some(header.cells.get(self.column_index))
848                }
849                None => Some(None),
850            };
851        }
852
853        // Check if there's a next row
854        if let Some(row) = self.rows.get(self.row_index) {
855            self.row_index += 1;
856
857            // Return the cell (if it exists).
858            return Some(row.cells.get(self.column_index));
859        }
860
861        None
862    }
863}
864
865#[cfg(test)]
866mod tests {
867    use super::*;
868
869    #[test]
870    fn test_column_generation() {
871        let mut table = Table::new();
872        table.set_header(vec!["thr", "four", "fivef"]);
873
874        // When adding a new row, columns are automatically generated
875        assert_eq!(table.columns.len(), 3);
876        // The max content width is also correctly set for each column
877        assert_eq!(table.column_max_content_widths(), vec![3, 4, 5]);
878
879        // When adding a new row, the max content width is updated accordingly
880        table.add_row(vec!["four", "fivef", "very long text with 23"]);
881        assert_eq!(table.column_max_content_widths(), vec![4, 5, 22]);
882
883        // Now add a row that has column lines. The max content width shouldn't change
884        table.add_row(vec!["", "", "shorter"]);
885        assert_eq!(table.column_max_content_widths(), vec![4, 5, 22]);
886
887        println!("{table}");
888    }
889}