Skip to main content

comfy_table/
table.rs

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