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}