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}