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}