comfy_table/
table.rs

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