1use std::io::Stdout;
18
19use crate::align::VerticalAlignMethod;
20use crate::r#box::{Box as RichBox, HEAVY_HEAD, RowLevel};
21use crate::console::{ConsoleOptions, JustifyMethod, OverflowMethod};
22use crate::measure::Measurement;
23use crate::padding::PaddingDimensions;
24use crate::rule::AlignMethod;
25use crate::segment::{Segment, Segments};
26use crate::style::Style;
27use crate::text::Text;
28use crate::{Console, Renderable};
29
30pub struct Column {
45 pub header: Option<Box<dyn Renderable + Send + Sync>>,
47 pub footer: Option<Box<dyn Renderable + Send + Sync>>,
49 pub header_style: Style,
51 pub footer_style: Style,
53 pub style: Style,
55 pub justify: JustifyMethod,
57 pub vertical: VerticalAlignMethod,
59 pub overflow: OverflowMethod,
61 pub width: Option<usize>,
63 pub min_width: Option<usize>,
65 pub max_width: Option<usize>,
67 pub ratio: Option<usize>,
69 pub no_wrap: bool,
71 _index: usize,
73}
74
75impl std::fmt::Debug for Column {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.debug_struct("Column")
78 .field("header_style", &self.header_style)
79 .field("footer_style", &self.footer_style)
80 .field("style", &self.style)
81 .field("justify", &self.justify)
82 .field("vertical", &self.vertical)
83 .field("overflow", &self.overflow)
84 .field("width", &self.width)
85 .field("min_width", &self.min_width)
86 .field("max_width", &self.max_width)
87 .field("ratio", &self.ratio)
88 .field("no_wrap", &self.no_wrap)
89 .finish_non_exhaustive()
90 }
91}
92
93impl Default for Column {
94 fn default() -> Self {
95 Column {
96 header: None,
97 footer: None,
98 header_style: Style::default(),
99 footer_style: Style::default(),
100 style: Style::default(),
101 justify: JustifyMethod::Left,
102 vertical: VerticalAlignMethod::Top,
103 overflow: OverflowMethod::Ellipsis,
104 width: None,
105 min_width: None,
106 max_width: None,
107 ratio: None,
108 no_wrap: false,
109 _index: 0,
110 }
111 }
112}
113
114impl Column {
115 pub fn new() -> Self {
117 Self::default()
118 }
119
120 pub fn with_header_str(header: &str) -> Self {
122 Column {
123 header: Some(Box::new(Text::plain(header))),
124 ..Default::default()
125 }
126 }
127
128 pub fn with_header(header: Box<dyn Renderable + Send + Sync>) -> Self {
130 Column {
131 header: Some(header),
132 ..Default::default()
133 }
134 }
135
136 pub fn header_style(mut self, style: Style) -> Self {
138 self.header_style = style;
139 self
140 }
141
142 pub fn footer_style(mut self, style: Style) -> Self {
144 self.footer_style = style;
145 self
146 }
147
148 pub fn style(mut self, style: Style) -> Self {
150 self.style = style;
151 self
152 }
153
154 pub fn justify(mut self, justify: JustifyMethod) -> Self {
156 self.justify = justify;
157 self
158 }
159
160 pub fn vertical(mut self, vertical: VerticalAlignMethod) -> Self {
162 self.vertical = vertical;
163 self
164 }
165
166 pub fn width(mut self, width: usize) -> Self {
168 self.width = Some(width);
169 self
170 }
171
172 pub fn min_width(mut self, width: usize) -> Self {
174 self.min_width = Some(width);
175 self
176 }
177
178 pub fn max_width(mut self, width: usize) -> Self {
180 self.max_width = Some(width);
181 self
182 }
183
184 pub fn ratio(mut self, ratio: usize) -> Self {
186 self.ratio = Some(ratio);
187 self
188 }
189
190 pub fn no_wrap(mut self, no_wrap: bool) -> Self {
192 self.no_wrap = no_wrap;
193 self
194 }
195
196 pub fn with_footer(mut self, footer: Box<dyn Renderable + Send + Sync>) -> Self {
198 self.footer = Some(footer);
199 self
200 }
201
202 pub fn flexible(&self) -> bool {
204 self.ratio.is_some()
205 }
206
207 pub fn set_justify(&mut self, justify: JustifyMethod) {
211 self.justify = justify;
212 }
213
214 pub fn set_style(&mut self, style: Style) {
216 self.style = style;
217 }
218
219 pub fn set_header_style(&mut self, style: Style) {
221 self.header_style = style;
222 }
223
224 pub fn set_footer_style(&mut self, style: Style) {
226 self.footer_style = style;
227 }
228}
229
230pub struct Row {
238 pub cells: Vec<Box<dyn Renderable + Send + Sync>>,
240 pub style: Option<Style>,
242 pub end_section: bool,
244}
245
246impl std::fmt::Debug for Row {
247 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248 f.debug_struct("Row")
249 .field("cells_count", &self.cells.len())
250 .field("style", &self.style)
251 .field("end_section", &self.end_section)
252 .finish()
253 }
254}
255
256impl Row {
257 pub fn new(cells: Vec<Box<dyn Renderable + Send + Sync>>) -> Self {
259 Row {
260 cells,
261 style: None,
262 end_section: false,
263 }
264 }
265
266 pub fn empty() -> Self {
268 Row {
269 cells: Vec::new(),
270 style: None,
271 end_section: false,
272 }
273 }
274
275 pub fn with_style(mut self, style: Style) -> Self {
277 self.style = Some(style);
278 self
279 }
280
281 pub fn with_end_section(mut self, end_section: bool) -> Self {
283 self.end_section = end_section;
284 self
285 }
286}
287
288pub struct Table {
309 columns: Vec<Column>,
311 rows: Vec<Row>,
313 box_type: Option<RichBox>,
315 safe_box: Option<bool>,
317 padding: (usize, usize, usize, usize),
319 collapse_padding: bool,
321 pad_edge: bool,
323 expand: bool,
325 show_header: bool,
327 show_footer: bool,
329 show_edge: bool,
331 show_lines: bool,
333 leading: usize,
335 style: Style,
337 row_styles: Vec<Style>,
339 header_style: Style,
341 footer_style: Style,
343 border_style: Style,
345 title: Option<Text>,
347 caption: Option<Text>,
349 title_style: Option<Style>,
351 caption_style: Option<Style>,
353 title_align: AlignMethod,
355 caption_align: AlignMethod,
357 width: Option<usize>,
359 min_width: Option<usize>,
361 highlight: bool,
363}
364
365impl std::fmt::Debug for Table {
366 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367 f.debug_struct("Table")
368 .field("columns", &self.columns.len())
369 .field("rows", &self.rows.len())
370 .field("box_type", &self.box_type)
371 .field("show_header", &self.show_header)
372 .field("show_footer", &self.show_footer)
373 .field("show_edge", &self.show_edge)
374 .field("expand", &self.expand)
375 .field("width", &self.width)
376 .finish_non_exhaustive()
377 }
378}
379
380impl Default for Table {
381 fn default() -> Self {
382 Table::new()
383 }
384}
385
386impl Table {
387 pub fn new() -> Self {
392 Table {
393 columns: Vec::new(),
394 rows: Vec::new(),
395 box_type: Some(HEAVY_HEAD),
396 safe_box: None,
397 padding: (0, 1, 0, 1),
398 collapse_padding: false,
399 pad_edge: true,
400 expand: false,
401 show_header: true,
402 show_footer: false,
403 show_edge: true,
404 show_lines: false,
405 leading: 0,
406 style: Style::default(),
407 row_styles: Vec::new(),
408 header_style: Style::new().with_bold(true),
409 footer_style: Style::default(),
410 border_style: Style::default(),
411 title: None,
412 caption: None,
413 title_style: None,
414 caption_style: None,
415 title_align: AlignMethod::Center,
416 caption_align: AlignMethod::Center,
417 width: None,
418 min_width: None,
419 highlight: false,
420 }
421 }
422
423 pub fn grid() -> Self {
427 Table {
428 box_type: None,
429 padding: (0, 0, 0, 0),
430 collapse_padding: true,
431 pad_edge: false,
432 show_header: false,
433 show_footer: false,
434 show_edge: false,
435 ..Table::new()
436 }
437 }
438
439 pub fn with_box(mut self, box_type: Option<RichBox>) -> Self {
445 self.box_type = box_type;
446 self
447 }
448
449 pub fn with_safe_box(mut self, safe: bool) -> Self {
451 self.safe_box = Some(safe);
452 self
453 }
454
455 pub fn with_padding(mut self, left: usize, right: usize) -> Self {
459 self.padding = (0, right, 0, left);
460 self
461 }
462
463 pub fn with_padding_dims(mut self, pad: impl Into<PaddingDimensions>) -> Self {
470 self.padding = pad.into().unpack();
471 self
472 }
473
474 pub fn with_title_style(mut self, style: Style) -> Self {
476 self.title_style = Some(style);
477 self
478 }
479
480 pub fn with_caption_style(mut self, style: Style) -> Self {
482 self.caption_style = Some(style);
483 self
484 }
485
486 pub fn with_collapse_padding(mut self, collapse: bool) -> Self {
488 self.collapse_padding = collapse;
489 self
490 }
491
492 pub fn with_pad_edge(mut self, pad: bool) -> Self {
494 self.pad_edge = pad;
495 self
496 }
497
498 pub fn with_expand(mut self, expand: bool) -> Self {
500 self.expand = expand;
501 self
502 }
503
504 pub fn with_show_header(mut self, show: bool) -> Self {
506 self.show_header = show;
507 self
508 }
509
510 pub fn with_show_footer(mut self, show: bool) -> Self {
512 self.show_footer = show;
513 self
514 }
515
516 pub fn with_show_edge(mut self, show: bool) -> Self {
518 self.show_edge = show;
519 self
520 }
521
522 pub fn with_show_lines(mut self, show: bool) -> Self {
524 self.show_lines = show;
525 self
526 }
527
528 pub fn with_leading(mut self, leading: usize) -> Self {
530 self.leading = leading;
531 self
532 }
533
534 pub fn with_style(mut self, style: Style) -> Self {
536 self.style = style;
537 self
538 }
539
540 pub fn with_row_styles(mut self, styles: Vec<Style>) -> Self {
542 self.row_styles = styles;
543 self
544 }
545
546 pub fn with_header_style(mut self, style: Style) -> Self {
548 self.header_style = style;
549 self
550 }
551
552 pub fn with_footer_style(mut self, style: Style) -> Self {
554 self.footer_style = style;
555 self
556 }
557
558 pub fn with_border_style(mut self, style: Style) -> Self {
560 self.border_style = style;
561 self
562 }
563
564 pub fn with_title(mut self, title: &str) -> Self {
566 self.title = Some(Text::plain(title));
567 self
568 }
569
570 pub fn with_title_text(mut self, title: Text) -> Self {
572 self.title = Some(title);
573 self
574 }
575
576 pub fn with_caption(mut self, caption: &str) -> Self {
578 self.caption = Some(Text::plain(caption));
579 self
580 }
581
582 pub fn with_caption_text(mut self, caption: Text) -> Self {
584 self.caption = Some(caption);
585 self
586 }
587
588 pub fn with_title_align(mut self, align: AlignMethod) -> Self {
590 self.title_align = align;
591 self
592 }
593
594 pub fn with_caption_align(mut self, align: AlignMethod) -> Self {
596 self.caption_align = align;
597 self
598 }
599
600 pub fn with_width(mut self, width: usize) -> Self {
602 self.width = Some(width);
603 self
604 }
605
606 pub fn with_min_width(mut self, width: usize) -> Self {
608 self.min_width = Some(width);
609 self
610 }
611
612 pub fn with_highlight(mut self, highlight: bool) -> Self {
614 self.highlight = highlight;
615 self
616 }
617
618 pub fn add_column(&mut self, mut column: Column) {
624 column._index = self.columns.len();
625 self.columns.push(column);
626 }
627
628 pub fn add_column_str(&mut self, header: &str) {
630 self.add_column(Column::with_header_str(header));
631 }
632
633 pub fn add_column_renderable(&mut self, header: Box<dyn Renderable + Send + Sync>) {
635 self.add_column(Column::with_header(header));
636 }
637
638 pub fn add_row(&mut self, row: Row) {
640 while self.columns.len() < row.cells.len() {
642 let mut col = Column::default();
643 col._index = self.columns.len();
644 self.columns.push(col);
645 }
646 self.rows.push(row);
647 }
648
649 pub fn add_row_strs(&mut self, cells: &[&str]) {
651 let cell_boxes: Vec<Box<dyn Renderable + Send + Sync>> = cells
652 .iter()
653 .map(|s| Box::new(Text::plain(*s)) as Box<dyn Renderable + Send + Sync>)
654 .collect();
655 self.add_row(Row::new(cell_boxes));
656 }
657
658 pub fn add_row_renderables(&mut self, cells: Vec<Box<dyn Renderable + Send + Sync>>) {
660 self.add_row(Row::new(cells));
661 }
662
663 pub fn add_section(&mut self) {
665 if let Some(row) = self.rows.last_mut() {
666 row.end_section = true;
667 }
668 }
669
670 pub fn row_count(&self) -> usize {
672 self.rows.len()
673 }
674
675 pub fn column_count(&self) -> usize {
677 self.columns.len()
678 }
679
680 pub fn set_title(&mut self, title: Option<Text>) {
686 self.title = title;
687 }
688
689 pub fn set_caption(&mut self, caption: Option<Text>) {
691 self.caption = caption;
692 }
693
694 pub fn set_show_footer(&mut self, show: bool) {
696 self.show_footer = show;
697 }
698
699 pub fn set_border_style(&mut self, style: Style) {
701 self.border_style = style;
702 }
703
704 pub fn set_box(&mut self, box_type: Option<RichBox>) {
706 self.box_type = box_type;
707 }
708
709 pub fn set_row_styles(&mut self, styles: Vec<Style>) {
711 self.row_styles = styles;
712 }
713
714 pub fn set_pad_edge(&mut self, pad: bool) {
716 self.pad_edge = pad;
717 }
718
719 pub fn set_width(&mut self, width: Option<usize>) {
721 self.width = width;
722 }
723
724 pub fn column_mut(&mut self, idx: usize) -> Option<&mut Column> {
726 self.columns.get_mut(idx)
727 }
728
729 fn extra_width(&self) -> usize {
735 let mut width = 0;
736 if self.box_type.is_some() && self.show_edge {
737 width += 2; }
739 if self.box_type.is_some() && self.columns.len() > 1 {
740 width += self.columns.len() - 1; }
742 width
743 }
744
745 fn get_measure_padding_width(&self, column_index: usize) -> usize {
754 let (_top, pad_right, _bottom, pad_left) = self.padding;
755 let mut left = pad_left;
756 let right = pad_right;
757
758 if self.collapse_padding && column_index > 0 {
759 left = left.saturating_sub(right);
760 }
761
762 left + right
763 }
764
765 fn get_padding_for_column(&self, column_index: usize) -> (usize, usize) {
768 let (_top, pad_right, _bottom, pad_left) = self.padding;
769 let num_columns = self.columns.len();
770 let is_first = column_index == 0;
771 let is_last = column_index == num_columns.saturating_sub(1);
772
773 let mut left = pad_left;
774 let mut right = pad_right;
775
776 if self.collapse_padding && !is_first {
778 left = left.saturating_sub(pad_right);
781 }
782
783 if !self.pad_edge {
785 if is_first {
786 left = 0;
787 }
788 if is_last {
789 right = 0;
790 }
791 }
792
793 (left, right)
794 }
795
796 fn get_row_style(&self, index: usize) -> Style {
798 let mut style = Style::default();
799 if !self.row_styles.is_empty() {
800 style = style.combine(&self.row_styles[index % self.row_styles.len()]);
801 }
802 if let Some(row_style) = self.rows.get(index).and_then(|r| r.style) {
803 style = style.combine(&row_style);
804 }
805 style
806 }
807
808 fn measure_column(
810 &self,
811 console: &Console<Stdout>,
812 options: &ConsoleOptions,
813 column: &Column,
814 ) -> Measurement {
815 let max_width = options.max_width;
816 if max_width < 1 {
817 return Measurement::new(0, 0);
818 }
819
820 let (pad_left, pad_right) = self.get_padding_for_column(column._index);
827 let content_padding_width = pad_left + pad_right;
828 let bounds_padding_width = self.get_measure_padding_width(column._index);
829
830 if let Some(w) = column.width {
832 return Measurement::new(w + bounds_padding_width, w + bounds_padding_width)
833 .with_maximum(max_width);
834 }
835
836 let mut min_widths: Vec<usize> = Vec::new();
838 let mut max_widths: Vec<usize> = Vec::new();
839
840 if self.show_header {
842 if let Some(ref header) = column.header {
843 let m = header.measure(console, options);
844 min_widths.push(m.minimum);
845 max_widths.push(m.maximum);
846 }
847 }
848
849 for row in &self.rows {
851 if let Some(cell) = row.cells.get(column._index) {
852 let m = cell.measure(console, options);
853 min_widths.push(m.minimum);
854 max_widths.push(m.maximum);
855 }
856 }
857
858 if self.show_footer {
860 if let Some(ref footer) = column.footer {
861 let m = footer.measure(console, options);
862 min_widths.push(m.minimum);
863 max_widths.push(m.maximum);
864 }
865 }
866
867 let min_w = min_widths.iter().max().copied().unwrap_or(1) + content_padding_width;
868 let max_w = max_widths.iter().max().copied().unwrap_or(max_width) + content_padding_width;
869
870 Measurement::new(min_w, max_w)
871 .with_maximum(max_width)
872 .clamp_bounds(
873 column.min_width.map(|w| w + bounds_padding_width),
874 column.max_width.map(|w| w + bounds_padding_width),
875 )
876 }
877
878 fn calculate_column_widths(
880 &self,
881 console: &Console<Stdout>,
882 options: &ConsoleOptions,
883 ) -> Vec<usize> {
884 if self.columns.is_empty() {
885 return Vec::new();
886 }
887
888 let max_width = options.max_width;
892 let extra_width = self.extra_width();
893 let effective_expand = self.expand || self.width.is_some();
894
895 let measurements: Vec<Measurement> = self
897 .columns
898 .iter()
899 .map(|col| self.measure_column(console, options, col))
900 .collect();
901
902 let mut widths: Vec<usize> = measurements.iter().map(|m| m.maximum.max(1)).collect();
903
904 if effective_expand {
906 let ratios: Vec<usize> = self
907 .columns
908 .iter()
909 .filter(|c| c.flexible())
910 .map(|c| c.ratio.unwrap_or(0))
911 .collect();
912
913 if ratios.iter().any(|&r| r > 0) {
914 let fixed_widths: Vec<usize> = self
915 .columns
916 .iter()
917 .zip(measurements.iter())
918 .map(|(column, measurement)| {
919 if column.flexible() {
920 0
921 } else {
922 measurement.maximum
923 }
924 })
925 .collect();
926
927 let flex_minimums: Vec<usize> = self
928 .columns
929 .iter()
930 .filter(|column| column.flexible())
931 .map(|column| {
932 (column.width.unwrap_or(1)) + self.get_measure_padding_width(column._index)
933 })
934 .collect();
935
936 let fixed_total: usize = fixed_widths.iter().sum();
937 let flexible_width = max_width.saturating_sub(fixed_total);
938 let flex_widths = ratio_distribute(flexible_width, &ratios, Some(&flex_minimums));
939 let mut flex_iter = flex_widths.into_iter();
940 for (index, column) in self.columns.iter().enumerate() {
941 if column.flexible() {
942 widths[index] = fixed_widths[index] + flex_iter.next().unwrap_or(0);
943 }
944 }
945 }
946 }
947
948 let mut table_width: usize = widths.iter().sum();
949
950 if table_width > max_width {
951 widths = collapse_widths(
952 widths,
953 self.columns
954 .iter()
955 .map(|column| column.width.is_none() && !column.no_wrap)
956 .collect(),
957 max_width,
958 );
959
960 table_width = widths.iter().sum();
961
962 if table_width > max_width {
964 let excess_width = table_width - max_width;
965 let ratios = vec![1; widths.len()];
966 widths = ratio_reduce(excess_width, &ratios, &widths, &widths);
967 table_width = widths.iter().sum();
968 }
969
970 let constrained: Vec<Measurement> = widths
972 .iter()
973 .zip(self.columns.iter())
974 .map(|(width, column)| {
975 self.measure_column(console, &options.update_width(*width), column)
976 })
977 .collect();
978 widths = constrained.iter().map(|m| m.maximum).collect();
979 }
980
981 if (table_width < max_width && effective_expand)
984 || (self.min_width.is_some()
985 && table_width < self.min_width.unwrap_or(0).saturating_sub(extra_width))
986 {
987 let min_target = self.min_width.unwrap_or(0).saturating_sub(extra_width);
988 let target = if self.min_width.is_some() {
989 min_target.min(max_width)
990 } else {
991 max_width
992 };
993
994 if table_width < target {
995 let pad_widths = ratio_distribute(target - table_width, &widths, None);
996 widths = widths
997 .into_iter()
998 .zip(pad_widths.into_iter())
999 .map(|(w, pad)| w + pad)
1000 .collect();
1001 }
1002 }
1003
1004 widths
1005 }
1006
1007 fn render_cell(
1009 &self,
1010 console: &Console<Stdout>,
1011 options: &ConsoleOptions,
1012 cell: &dyn Renderable,
1013 column: &Column,
1014 width: usize,
1015 style: Style,
1016 _is_header: bool,
1017 _is_footer: bool,
1018 ) -> Vec<Vec<Segment>> {
1019 let (pad_left, pad_right) = self.get_padding_for_column(column._index);
1021 let padding = pad_left + pad_right;
1022 let content_width = width.saturating_sub(padding);
1023
1024 let mut cell_options = options.update_width(content_width);
1026 if column.justify != JustifyMethod::Left {
1027 cell_options.justify = Some(column.justify);
1028 }
1029 if column.overflow != OverflowMethod::Ellipsis {
1030 cell_options.overflow = Some(column.overflow);
1031 }
1032 if column.no_wrap {
1033 cell_options.no_wrap = true;
1034 }
1035
1036 let cell_lines = console.render_lines(cell, Some(&cell_options), Some(style), true, false);
1038
1039 let left_pad = Segment::styled(" ".repeat(pad_left), style);
1041 let right_pad = Segment::styled(" ".repeat(pad_right), style);
1042
1043 let mut result: Vec<Vec<Segment>> = Vec::new();
1044 let (pad_top, _, pad_bottom, _) = self.padding;
1045
1046 if pad_top > 0 {
1048 let blank = Segment::adjust_line_length(&[], width, Some(style), true);
1049 for _ in 0..pad_top {
1050 result.push(blank.clone());
1051 }
1052 }
1053
1054 for line in cell_lines {
1056 let mut padded = Vec::new();
1057 if pad_left > 0 {
1058 padded.push(left_pad.clone());
1059 }
1060 for seg in line {
1061 padded.push(seg);
1062 }
1063 if pad_right > 0 {
1064 padded.push(right_pad.clone());
1065 }
1066 result.push(Segment::adjust_line_length(
1067 &padded,
1068 width,
1069 Some(style),
1070 true,
1071 ));
1072 }
1073
1074 if pad_bottom > 0 {
1076 let blank = Segment::adjust_line_length(&[], width, Some(style), true);
1077 for _ in 0..pad_bottom {
1078 result.push(blank.clone());
1079 }
1080 }
1081
1082 result
1083 }
1084}
1085
1086impl Renderable for Table {
1087 fn render(&self, console: &Console<Stdout>, options: &ConsoleOptions) -> Segments {
1088 let mut result = Segments::new();
1089
1090 if self.columns.is_empty() {
1092 result.push(Segment::line());
1093 return result;
1094 }
1095
1096 let safe_box = self.safe_box.unwrap_or(options.legacy_windows);
1098 let box_chars = self
1099 .box_type
1100 .map(|b| b.substitute(safe_box, options.ascii_only()));
1101
1102 let max_width = self.width.unwrap_or(options.max_width);
1104 let extra = self.extra_width();
1105 let col_options = options.update_width(max_width.saturating_sub(extra));
1108 let widths = self.calculate_column_widths(console, &col_options);
1109
1110 if widths.is_empty() {
1111 result.push(Segment::line());
1112 return result;
1113 }
1114
1115 let table_width: usize = widths.iter().sum::<usize>() + extra;
1116 let border_style = self.style.combine(&self.border_style);
1117 let new_line = Segment::line();
1118
1119 if let Some(ref title) = self.title {
1121 let title_lines =
1122 console.render_lines(title, Some(options), self.title_style, false, false);
1123 for line in title_lines {
1124 let line_width = Segment::get_line_length(&line);
1125 let padding = table_width.saturating_sub(line_width);
1126
1127 let (left_pad, right_pad) = match self.title_align {
1128 AlignMethod::Left => (0, padding),
1129 AlignMethod::Center => {
1130 let left = padding / 2;
1131 (left, padding - left)
1132 }
1133 AlignMethod::Right => (padding, 0),
1134 };
1135
1136 if left_pad > 0 {
1137 result.push(Segment::new(" ".repeat(left_pad)));
1138 }
1139 for seg in line {
1140 result.push(seg);
1141 }
1142 if right_pad > 0 {
1143 result.push(Segment::new(" ".repeat(right_pad)));
1144 }
1145 result.push(new_line.clone());
1146 }
1147
1148 if !self.show_edge {
1151 result.push(new_line.clone());
1152 }
1153 }
1154
1155 if let Some(ref bx) = box_chars {
1157 if self.show_edge {
1158 let top = bx.get_top(&widths);
1159 result.push(Segment::styled(top, border_style));
1160 result.push(new_line.clone());
1161 }
1162 }
1163
1164 if self.show_header {
1166 let header_row_style = self.header_style;
1167
1168 let mut header_cells: Vec<Vec<Vec<Segment>>> = Vec::new();
1170 let mut max_height = 1;
1171
1172 let empty_text = Text::plain("");
1173 for (i, column) in self.columns.iter().enumerate() {
1174 let cell: &dyn Renderable = column
1175 .header
1176 .as_ref()
1177 .map(|b| b.as_ref())
1178 .unwrap_or(&empty_text as &dyn Renderable);
1179
1180 let cell_style = header_row_style.combine(&column.header_style);
1181 let cell_lines = self.render_cell(
1182 console, options, cell, column, widths[i], cell_style, true, false,
1183 );
1184 max_height = max_height.max(cell_lines.len());
1185 header_cells.push(cell_lines);
1186 }
1187
1188 for (i, cells) in header_cells.iter_mut().enumerate() {
1190 let col_style = header_row_style.combine(&self.columns[i].header_style);
1191 let hint_style = cells.last().and_then(|line| Segment::get_last_style(line));
1192 while cells.len() < max_height {
1193 let pad_style = hint_style
1197 .and_then(|hint| hint.bgcolor.map(|bg| Style::new().with_bgcolor(bg)))
1198 .map(|bg_style| col_style.combine(&bg_style))
1199 .unwrap_or(col_style);
1200 let blank = Segment::adjust_line_length(&[], widths[i], Some(pad_style), true);
1201 cells.push(blank);
1202 }
1203 }
1204
1205 for line_idx in 0..max_height {
1207 if let Some(ref bx) = box_chars {
1208 if self.show_edge {
1209 result.push(Segment::styled(bx.head_left.to_string(), border_style));
1210 }
1211 }
1212
1213 for (col_idx, cells) in header_cells.iter().enumerate() {
1214 for seg in &cells[line_idx] {
1215 result.push(seg.clone());
1216 }
1217 if col_idx < header_cells.len() - 1 {
1218 if let Some(ref bx) = box_chars {
1219 result
1220 .push(Segment::styled(bx.head_vertical.to_string(), border_style));
1221 }
1222 }
1223 }
1224
1225 if let Some(ref bx) = box_chars {
1226 if self.show_edge {
1227 result.push(Segment::styled(bx.head_right.to_string(), border_style));
1228 }
1229 }
1230 result.push(new_line.clone());
1231 }
1232
1233 if let Some(ref bx) = box_chars {
1235 let row_line = bx.get_row(&widths, RowLevel::Head, self.show_edge);
1236 result.push(Segment::styled(row_line, border_style));
1237 result.push(new_line.clone());
1238 }
1239 }
1240
1241 let empty_cell = Text::plain("");
1243 for (row_idx, row) in self.rows.iter().enumerate() {
1244 let row_style = self.get_row_style(row_idx);
1245
1246 let mut row_cells: Vec<Vec<Vec<Segment>>> = Vec::new();
1248 let mut max_height = 1;
1249
1250 for (col_idx, column) in self.columns.iter().enumerate() {
1251 let cell: &dyn Renderable = row
1252 .cells
1253 .get(col_idx)
1254 .map(|b| b.as_ref())
1255 .unwrap_or(&empty_cell as &dyn Renderable);
1256
1257 let cell_style = self.style.combine(&column.style).combine(&row_style);
1258 let cell_lines = self.render_cell(
1259 console,
1260 options,
1261 cell,
1262 column,
1263 widths[col_idx],
1264 cell_style,
1265 false,
1266 false,
1267 );
1268 max_height = max_height.max(cell_lines.len());
1269 row_cells.push(cell_lines);
1270 }
1271
1272 for (i, cells) in row_cells.iter_mut().enumerate() {
1274 let col_style = self
1275 .style
1276 .combine(&self.columns[i].style)
1277 .combine(&row_style);
1278 let hint_style = cells.last().and_then(|line| Segment::get_last_style(line));
1279 let pad_style = hint_style
1281 .and_then(|hint| hint.bgcolor.map(|bg| Style::new().with_bgcolor(bg)))
1282 .map(|bg_style| col_style.combine(&bg_style))
1283 .unwrap_or(col_style);
1284 let blank = Segment::adjust_line_length(&[], widths[i], Some(pad_style), true);
1285
1286 let lines_needed = max_height.saturating_sub(cells.len());
1287 if lines_needed > 0 {
1288 match self.columns[i].vertical {
1289 VerticalAlignMethod::Top => {
1290 for _ in 0..lines_needed {
1292 cells.push(blank.clone());
1293 }
1294 }
1295 VerticalAlignMethod::Middle => {
1296 let top_pad = lines_needed / 2;
1298 let bottom_pad = lines_needed - top_pad;
1299 let mut new_cells = Vec::with_capacity(max_height);
1300 for _ in 0..top_pad {
1301 new_cells.push(blank.clone());
1302 }
1303 new_cells.append(cells);
1304 for _ in 0..bottom_pad {
1305 new_cells.push(blank.clone());
1306 }
1307 *cells = new_cells;
1308 }
1309 VerticalAlignMethod::Bottom => {
1310 let mut new_cells = Vec::with_capacity(max_height);
1312 for _ in 0..lines_needed {
1313 new_cells.push(blank.clone());
1314 }
1315 new_cells.append(cells);
1316 *cells = new_cells;
1317 }
1318 }
1319 }
1320 }
1321
1322 for line_idx in 0..max_height {
1324 if let Some(ref bx) = box_chars {
1325 if self.show_edge {
1326 result.push(Segment::styled(bx.mid_left.to_string(), border_style));
1327 }
1328 }
1329
1330 for (col_idx, cells) in row_cells.iter().enumerate() {
1331 for seg in &cells[line_idx] {
1332 result.push(seg.clone());
1333 }
1334 if col_idx < row_cells.len() - 1 {
1335 if let Some(ref bx) = box_chars {
1336 result.push(Segment::styled(bx.mid_vertical.to_string(), border_style));
1337 }
1338 }
1339 }
1340
1341 if let Some(ref bx) = box_chars {
1342 if self.show_edge {
1343 result.push(Segment::styled(bx.mid_right.to_string(), border_style));
1344 }
1345 }
1346 result.push(new_line.clone());
1347 }
1348
1349 let is_last_row = row_idx == self.rows.len() - 1;
1351 let needs_separator = !is_last_row && (self.show_lines || row.end_section);
1352
1353 if let Some(ref bx) = box_chars {
1354 if needs_separator {
1355 let row_line = bx.get_row(&widths, RowLevel::Row, self.show_edge);
1356 result.push(Segment::styled(row_line, border_style));
1357 result.push(new_line.clone());
1358 } else if self.leading > 0 && !is_last_row {
1359 for _ in 0..self.leading {
1361 let row_line = bx.get_row(&widths, RowLevel::Mid, self.show_edge);
1362 result.push(Segment::styled(row_line, border_style));
1363 result.push(new_line.clone());
1364 }
1365 }
1366 }
1367 }
1368
1369 if self.show_footer {
1371 if let Some(ref bx) = box_chars {
1373 let row_line = bx.get_row(&widths, RowLevel::Foot, self.show_edge);
1374 result.push(Segment::styled(row_line, border_style));
1375 result.push(new_line.clone());
1376 }
1377
1378 let footer_row_style = self.footer_style;
1379
1380 let mut footer_cells: Vec<Vec<Vec<Segment>>> = Vec::new();
1381 let mut max_height = 1;
1382
1383 let empty_footer = Text::plain("");
1384 for (i, column) in self.columns.iter().enumerate() {
1385 let cell: &dyn Renderable = column
1386 .footer
1387 .as_ref()
1388 .map(|b| b.as_ref())
1389 .unwrap_or(&empty_footer as &dyn Renderable);
1390
1391 let cell_style = footer_row_style.combine(&column.footer_style);
1392 let cell_lines = self.render_cell(
1393 console, options, cell, column, widths[i], cell_style, false, true,
1394 );
1395 max_height = max_height.max(cell_lines.len());
1396 footer_cells.push(cell_lines);
1397 }
1398
1399 for (i, cells) in footer_cells.iter_mut().enumerate() {
1401 let col_style = footer_row_style.combine(&self.columns[i].footer_style);
1402 let hint_style = cells.last().and_then(|line| Segment::get_last_style(line));
1403 while cells.len() < max_height {
1404 let pad_style = hint_style
1406 .and_then(|hint| hint.bgcolor.map(|bg| Style::new().with_bgcolor(bg)))
1407 .map(|bg_style| col_style.combine(&bg_style))
1408 .unwrap_or(col_style);
1409 let blank = Segment::adjust_line_length(&[], widths[i], Some(pad_style), true);
1410 cells.push(blank);
1411 }
1412 }
1413
1414 for line_idx in 0..max_height {
1416 if let Some(ref bx) = box_chars {
1417 if self.show_edge {
1418 result.push(Segment::styled(bx.foot_left.to_string(), border_style));
1419 }
1420 }
1421
1422 for (col_idx, cells) in footer_cells.iter().enumerate() {
1423 for seg in &cells[line_idx] {
1424 result.push(seg.clone());
1425 }
1426 if col_idx < footer_cells.len() - 1 {
1427 if let Some(ref bx) = box_chars {
1428 result
1429 .push(Segment::styled(bx.foot_vertical.to_string(), border_style));
1430 }
1431 }
1432 }
1433
1434 if let Some(ref bx) = box_chars {
1435 if self.show_edge {
1436 result.push(Segment::styled(bx.foot_right.to_string(), border_style));
1437 }
1438 }
1439 result.push(new_line.clone());
1440 }
1441 }
1442
1443 if let Some(ref bx) = box_chars {
1445 if self.show_edge {
1446 let bottom = bx.get_bottom(&widths);
1447 result.push(Segment::styled(bottom, border_style));
1448 result.push(new_line.clone());
1449 }
1450 }
1451
1452 if let Some(ref caption) = self.caption {
1454 let mut caption_options = options.clone();
1455 caption_options.max_width = table_width;
1456 let caption_lines = console.render_lines(
1457 caption,
1458 Some(&caption_options),
1459 self.caption_style,
1460 false,
1461 false,
1462 );
1463 for line in caption_lines {
1464 let line_width = Segment::get_line_length(&line);
1465 let padding = table_width.saturating_sub(line_width);
1466
1467 let (left_pad, right_pad) = match self.caption_align {
1468 AlignMethod::Left => (0, padding),
1469 AlignMethod::Center => {
1470 let left = padding / 2;
1471 (left, padding - left)
1472 }
1473 AlignMethod::Right => (padding, 0),
1474 };
1475
1476 if left_pad > 0 {
1477 result.push(Segment::new(" ".repeat(left_pad)));
1478 }
1479 for seg in line {
1480 result.push(seg);
1481 }
1482 if right_pad > 0 {
1483 result.push(Segment::new(" ".repeat(right_pad)));
1484 }
1485 result.push(new_line.clone());
1486 }
1487 }
1488
1489 result
1490 }
1491
1492 fn measure(&self, console: &Console<Stdout>, options: &ConsoleOptions) -> Measurement {
1493 if self.columns.is_empty() {
1494 return Measurement::new(0, 0);
1495 }
1496
1497 let max_width = self.width.unwrap_or(options.max_width);
1498 if max_width == 0 {
1499 return Measurement::new(0, 0);
1500 }
1501
1502 let extra = self.extra_width();
1506 let col_options = options.update_width(max_width.saturating_sub(extra));
1507 let col_widths = self.calculate_column_widths(console, &col_options);
1508 let content_max_width: usize = col_widths.iter().sum();
1509
1510 let measurements: Vec<Measurement> = self
1511 .columns
1512 .iter()
1513 .map(|col| self.measure_column(console, &options.update_width(content_max_width), col))
1514 .collect();
1515
1516 let min_width: usize = measurements.iter().map(|m| m.minimum).sum::<usize>() + extra;
1517 let max_calc: usize = if self.width.is_some() {
1518 self.width.unwrap()
1519 } else {
1520 measurements.iter().map(|m| m.maximum).sum::<usize>() + extra
1521 };
1522
1523 Measurement::new(min_width, max_calc).clamp_bounds(self.min_width, Some(max_width))
1524 }
1525}
1526
1527fn div_ceil(numer: u128, denom: u128) -> usize {
1532 if denom == 0 {
1533 return 0;
1534 }
1535 ((numer + denom - 1) / denom) as usize
1536}
1537
1538fn round_div_bankers(numer: u128, denom: u128) -> usize {
1542 if denom == 0 {
1543 return 0;
1544 }
1545 let q = numer / denom;
1546 let r = numer % denom;
1547 let twice_r = r.saturating_mul(2);
1548
1549 if twice_r < denom {
1550 q as usize
1551 } else if twice_r > denom {
1552 (q + 1) as usize
1553 } else {
1554 if q % 2 == 0 {
1556 q as usize
1557 } else {
1558 (q + 1) as usize
1559 }
1560 }
1561}
1562
1563fn ratio_reduce(
1568 total: usize,
1569 ratios: &[usize],
1570 maximums: &[usize],
1571 values: &[usize],
1572) -> Vec<usize> {
1573 let mut adjusted_ratios: Vec<usize> = Vec::with_capacity(ratios.len());
1574 for (&ratio, &max) in ratios.iter().zip(maximums.iter()) {
1575 adjusted_ratios.push(if max > 0 { ratio } else { 0 });
1576 }
1577
1578 let mut total_ratio: usize = adjusted_ratios.iter().sum();
1579 if total_ratio == 0 {
1580 return values.to_vec();
1581 }
1582
1583 let mut total_remaining = total;
1584 let mut result: Vec<usize> = Vec::with_capacity(values.len());
1585
1586 for ((&ratio, &maximum), &value) in adjusted_ratios
1587 .iter()
1588 .zip(maximums.iter())
1589 .zip(values.iter())
1590 {
1591 if ratio > 0 && total_ratio > 0 {
1592 let numer = (ratio as u128) * (total_remaining as u128);
1593 let rounded = round_div_bankers(numer, total_ratio as u128);
1594 let distributed = rounded.min(maximum);
1595 result.push(value.saturating_sub(distributed));
1596 total_remaining = total_remaining.saturating_sub(distributed);
1597 total_ratio = total_ratio.saturating_sub(ratio);
1598 } else {
1599 result.push(value);
1600 }
1601 }
1602
1603 result
1604}
1605
1606fn ratio_distribute(total: usize, ratios: &[usize], minimums: Option<&[usize]>) -> Vec<usize> {
1611 let mut adjusted_ratios: Vec<usize> = Vec::with_capacity(ratios.len());
1612 if let Some(minimums) = minimums {
1613 for (&ratio, &min) in ratios.iter().zip(minimums.iter()) {
1614 adjusted_ratios.push(if min > 0 { ratio } else { 0 });
1615 }
1616 } else {
1617 adjusted_ratios.extend_from_slice(ratios);
1618 }
1619
1620 let mut total_ratio: usize = adjusted_ratios.iter().sum();
1621 if total_ratio == 0 {
1622 return vec![0; ratios.len()];
1625 }
1626
1627 let mut total_remaining = total;
1628 let mut distributed_total: Vec<usize> = Vec::with_capacity(adjusted_ratios.len());
1629
1630 let mins: Vec<usize> = if let Some(minimums) = minimums {
1631 minimums.to_vec()
1632 } else {
1633 vec![0; adjusted_ratios.len()]
1634 };
1635
1636 for (&ratio, &minimum) in adjusted_ratios.iter().zip(mins.iter()) {
1637 let distributed = if total_ratio > 0 {
1638 let numer = (ratio as u128) * (total_remaining as u128);
1639 div_ceil(numer, total_ratio as u128).max(minimum)
1640 } else {
1641 total_remaining
1642 };
1643 distributed_total.push(distributed);
1644 total_ratio = total_ratio.saturating_sub(ratio);
1645 total_remaining = total_remaining.saturating_sub(distributed);
1646 }
1647
1648 distributed_total
1649}
1650
1651fn collapse_widths(mut widths: Vec<usize>, wrapable: Vec<bool>, max_width: usize) -> Vec<usize> {
1653 let mut total_width: usize = widths.iter().sum();
1654 let mut excess_width = total_width.saturating_sub(max_width);
1655
1656 if wrapable.iter().any(|&w| w) {
1657 while total_width > 0 && excess_width > 0 {
1658 let max_column = widths
1659 .iter()
1660 .zip(wrapable.iter())
1661 .filter_map(|(width, allow_wrap)| allow_wrap.then_some(*width))
1662 .max()
1663 .unwrap_or(0);
1664
1665 let second_max_column = widths
1666 .iter()
1667 .zip(wrapable.iter())
1668 .filter_map(|(width, allow_wrap)| {
1669 if *allow_wrap && *width != max_column {
1670 Some(*width)
1671 } else {
1672 None
1673 }
1674 })
1675 .max()
1676 .unwrap_or(0);
1677
1678 let column_difference = max_column.saturating_sub(second_max_column);
1679 let ratios: Vec<usize> = widths
1680 .iter()
1681 .zip(wrapable.iter())
1682 .map(|(width, allow_wrap)| {
1683 if *allow_wrap && *width == max_column {
1684 1
1685 } else {
1686 0
1687 }
1688 })
1689 .collect();
1690
1691 if ratios.iter().all(|&r| r == 0) || column_difference == 0 {
1692 break;
1693 }
1694
1695 let max_reduce: Vec<usize> = vec![excess_width.min(column_difference); widths.len()];
1696 widths = ratio_reduce(excess_width, &ratios, &max_reduce, &widths);
1697
1698 total_width = widths.iter().sum();
1699 excess_width = total_width.saturating_sub(max_width);
1700 }
1701 }
1702
1703 widths
1704}
1705
1706#[cfg(test)]
1711mod tests {
1712 use super::*;
1713 use crate::r#box::{ASCII, DOUBLE, ROUNDED, SQUARE};
1714 use crate::cells::cell_len;
1715
1716 #[test]
1719 fn test_column_default() {
1720 let col = Column::new();
1721 assert!(col.header.is_none());
1722 assert_eq!(col.justify, JustifyMethod::Left);
1723 assert!(!col.flexible());
1724 }
1725
1726 #[test]
1727 fn test_column_with_header_str() {
1728 let col = Column::with_header_str("Name");
1729 assert!(col.header.is_some());
1730 }
1731
1732 #[test]
1733 fn test_column_flexible() {
1734 let col = Column::new().ratio(1);
1735 assert!(col.flexible());
1736 }
1737
1738 #[test]
1739 fn test_column_builder() {
1740 let col = Column::new()
1741 .justify(JustifyMethod::Right)
1742 .width(10)
1743 .min_width(5)
1744 .max_width(20)
1745 .no_wrap(true);
1746
1747 assert_eq!(col.justify, JustifyMethod::Right);
1748 assert_eq!(col.width, Some(10));
1749 assert_eq!(col.min_width, Some(5));
1750 assert_eq!(col.max_width, Some(20));
1751 assert!(col.no_wrap);
1752 }
1753
1754 #[test]
1757 fn test_row_empty() {
1758 let row = Row::empty();
1759 assert!(row.cells.is_empty());
1760 assert!(row.style.is_none());
1761 assert!(!row.end_section);
1762 }
1763
1764 #[test]
1765 fn test_row_with_style() {
1766 let style = Style::new().with_bold(true);
1767 let row = Row::empty().with_style(style);
1768 assert_eq!(row.style, Some(style));
1769 }
1770
1771 #[test]
1772 fn test_row_with_end_section() {
1773 let row = Row::empty().with_end_section(true);
1774 assert!(row.end_section);
1775 }
1776
1777 #[test]
1780 fn test_table_new() {
1781 let table = Table::new();
1782 assert_eq!(table.column_count(), 0);
1783 assert_eq!(table.row_count(), 0);
1784 assert!(table.box_type.is_some());
1785 assert!(table.show_header);
1786 }
1787
1788 #[test]
1789 fn test_table_grid() {
1790 let table = Table::grid();
1791 assert!(table.box_type.is_none());
1792 assert!(!table.show_header);
1793 assert!(!table.show_edge);
1794 }
1795
1796 #[test]
1797 fn test_table_add_column() {
1798 let mut table = Table::new();
1799 table.add_column_str("Name");
1800 table.add_column_str("Age");
1801 assert_eq!(table.column_count(), 2);
1802 }
1803
1804 #[test]
1805 fn test_table_add_row() {
1806 let mut table = Table::new();
1807 table.add_column_str("Name");
1808 table.add_column_str("Age");
1809 table.add_row_strs(&["Alice", "30"]);
1810 table.add_row_strs(&["Bob", "25"]);
1811 assert_eq!(table.row_count(), 2);
1812 }
1813
1814 #[test]
1815 fn test_table_auto_add_columns() {
1816 let mut table = Table::new();
1817 table.add_row_strs(&["A", "B", "C"]);
1818 assert_eq!(table.column_count(), 3);
1819 }
1820
1821 #[test]
1822 fn test_table_add_section() {
1823 let mut table = Table::new();
1824 table.add_row_strs(&["A", "B"]);
1825 table.add_section();
1826 assert!(table.rows[0].end_section);
1827 }
1828
1829 #[test]
1832 fn test_table_builder() {
1833 let table = Table::new()
1834 .with_box(Some(DOUBLE))
1835 .with_expand(true)
1836 .with_show_header(false)
1837 .with_width(50);
1838
1839 assert_eq!(table.box_type, Some(DOUBLE));
1840 assert!(table.expand);
1841 assert!(!table.show_header);
1842 assert_eq!(table.width, Some(50));
1843 }
1844
1845 #[test]
1846 fn test_table_with_title() {
1847 let table = Table::new().with_title("My Table");
1848 assert!(table.title.is_some());
1849 }
1850
1851 #[test]
1852 fn test_table_with_caption() {
1853 let table = Table::new().with_caption("Data from 2023");
1854 assert!(table.caption.is_some());
1855 }
1856
1857 #[test]
1858 fn test_table_with_styles() {
1859 let style = Style::new().with_bold(true);
1860 let table = Table::new()
1861 .with_style(style)
1862 .with_header_style(style)
1863 .with_border_style(style)
1864 .with_row_styles(vec![style]);
1865
1866 assert_eq!(table.style, style);
1867 assert_eq!(table.header_style, style);
1868 assert_eq!(table.border_style, style);
1869 assert_eq!(table.row_styles.len(), 1);
1870 }
1871
1872 #[test]
1875 fn test_table_render_empty() {
1876 let table = Table::new();
1877 let console = Console::with_options(ConsoleOptions::default());
1878 let options = console.options().clone();
1879
1880 let segments = table.render(&console, &options);
1881 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
1882
1883 assert!(output.contains('\n'));
1884 }
1885
1886 #[test]
1887 fn test_table_render_basic() {
1888 let mut table = Table::new();
1889 table.add_column_str("Name");
1890 table.add_column_str("Age");
1891 table.add_row_strs(&["Alice", "30"]);
1892
1893 let console = Console::with_options(ConsoleOptions {
1894 max_width: 40,
1895 ..Default::default()
1896 });
1897 let options = console.options().clone();
1898
1899 let segments = table.render(&console, &options);
1900 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
1901
1902 assert!(output.contains("Name"));
1903 assert!(output.contains("Age"));
1904 assert!(output.contains("Alice"));
1905 assert!(output.contains("30"));
1906 }
1907
1908 #[test]
1909 fn test_table_render_grid() {
1910 let mut table = Table::grid();
1911 table.add_row_strs(&["A", "B", "C"]);
1912
1913 let console = Console::with_options(ConsoleOptions {
1914 max_width: 20,
1915 ..Default::default()
1916 });
1917 let options = console.options().clone();
1918
1919 let segments = table.render(&console, &options);
1920 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
1921
1922 assert!(output.contains('A'));
1923 assert!(output.contains('B'));
1924 assert!(output.contains('C'));
1925 assert!(!output.contains('┏'));
1927 }
1928
1929 #[test]
1930 fn test_table_render_with_box_styles() {
1931 let boxes = [ROUNDED, SQUARE, DOUBLE, ASCII];
1932
1933 for box_style in boxes {
1934 let mut table = Table::new().with_box(Some(box_style));
1935 table.add_column_str("X");
1936 table.add_row_strs(&["Y"]);
1937
1938 let console = Console::with_options(ConsoleOptions {
1939 max_width: 20,
1940 ..Default::default()
1941 });
1942 let options = console.options().clone();
1943
1944 let segments = table.render(&console, &options);
1945 assert!(!segments.is_empty());
1946 }
1947 }
1948
1949 #[test]
1950 fn test_table_render_no_edge() {
1951 let mut table = Table::new().with_show_edge(false);
1952 table.add_column_str("Name");
1953 table.add_row_strs(&["Alice"]);
1954
1955 let console = Console::with_options(ConsoleOptions {
1956 max_width: 30,
1957 ..Default::default()
1958 });
1959 let options = console.options().clone();
1960
1961 let segments = table.render(&console, &options);
1962 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
1963
1964 assert!(!output.contains("┃Name"));
1966 }
1967
1968 #[test]
1969 fn test_table_render_show_lines() {
1970 let mut table = Table::new().with_show_lines(true);
1971 table.add_column_str("Name");
1972 table.add_row_strs(&["Alice"]);
1973 table.add_row_strs(&["Bob"]);
1974
1975 let console = Console::with_options(ConsoleOptions {
1976 max_width: 30,
1977 ..Default::default()
1978 });
1979 let options = console.options().clone();
1980
1981 let segments = table.render(&console, &options);
1982 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
1983 let lines: Vec<&str> = output.lines().collect();
1984
1985 assert!(lines.len() > 4); }
1988
1989 #[test]
1990 fn test_table_render_expand() {
1991 let mut table = Table::new().with_expand(true);
1992 table.add_column_str("X");
1993 table.add_row_strs(&["Y"]);
1994
1995 let console = Console::with_options(ConsoleOptions {
1996 max_width: 40,
1997 ..Default::default()
1998 });
1999 let options = console.options().clone();
2000
2001 let segments = table.render(&console, &options);
2002 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
2003 let first_line = output.lines().next().unwrap();
2004
2005 let width = cell_len(first_line);
2008 assert!(
2009 width >= 30,
2010 "Expanded table width {} should be >= 30",
2011 width
2012 );
2013 }
2014
2015 #[test]
2018 fn test_table_measure_empty() {
2019 let table = Table::new();
2020 let console = Console::new();
2021 let options = ConsoleOptions::default();
2022
2023 let m = table.measure(&console, &options);
2024 assert_eq!(m.minimum, 0);
2025 assert_eq!(m.maximum, 0);
2026 }
2027
2028 #[test]
2029 fn test_table_measure_basic() {
2030 let mut table = Table::new();
2031 table.add_column_str("Name");
2032 table.add_row_strs(&["Alice"]);
2033
2034 let console = Console::new();
2035 let options = ConsoleOptions::default();
2036
2037 let m = table.measure(&console, &options);
2038 assert!(m.minimum > 0);
2039 assert!(m.maximum >= m.minimum);
2040 }
2041
2042 #[test]
2043 fn test_table_measure_fixed_width() {
2044 let mut table = Table::new().with_width(50);
2045 table.add_column_str("Name");
2046 table.add_row_strs(&["Alice"]);
2047
2048 let console = Console::new();
2049 let options = ConsoleOptions::default();
2050
2051 let m = table.measure(&console, &options);
2052 assert_eq!(m.maximum, 50);
2053 }
2054
2055 #[test]
2058 fn test_table_with_title_style() {
2059 let style = Style::new().with_bold(true);
2060 let table = Table::new().with_title("My Title").with_title_style(style);
2061 assert_eq!(table.title_style, Some(style));
2062 }
2063
2064 #[test]
2065 fn test_table_with_caption_style() {
2066 let style = Style::new().with_italic(true);
2067 let table = Table::new()
2068 .with_caption("My Caption")
2069 .with_caption_style(style);
2070 assert_eq!(table.caption_style, Some(style));
2071 }
2072
2073 #[test]
2074 fn test_table_four_way_padding() {
2075 use crate::padding::PaddingDimensions;
2076 let table = Table::new().with_padding_dims(PaddingDimensions::FourWay(1, 2, 1, 2));
2077 assert_eq!(table.padding, (1, 2, 1, 2));
2078 }
2079
2080 #[test]
2081 fn test_table_padding_from_usize() {
2082 let table = Table::new().with_padding_dims(3usize);
2083 assert_eq!(table.padding, (3, 3, 3, 3));
2084 }
2085
2086 #[test]
2087 fn test_table_padding_compat() {
2088 let table = Table::new().with_padding(2, 3);
2090 assert_eq!(table.padding, (0, 3, 0, 2));
2091 }
2092
2093 #[test]
2096 fn test_table_is_send_sync() {
2097 fn assert_send<T: Send>() {}
2098 fn assert_sync<T: Sync>() {}
2099 assert_send::<Table>();
2100 assert_sync::<Table>();
2101 }
2102
2103 #[test]
2104 fn test_column_is_send_sync() {
2105 fn assert_send<T: Send>() {}
2106 fn assert_sync<T: Sync>() {}
2107 assert_send::<Column>();
2108 assert_sync::<Column>();
2109 }
2110
2111 #[test]
2112 fn test_row_is_send_sync() {
2113 fn assert_send<T: Send>() {}
2114 fn assert_sync<T: Sync>() {}
2115 assert_send::<Row>();
2116 assert_sync::<Row>();
2117 }
2118
2119 #[test]
2122 fn test_table_debug() {
2123 let mut table = Table::new().with_title("Test");
2124 table.add_column_str("A");
2125 table.add_row_strs(&["B"]);
2126
2127 let debug_str = format!("{:?}", table);
2128 assert!(debug_str.contains("Table"));
2129 assert!(debug_str.contains("columns"));
2130 assert!(debug_str.contains("rows"));
2131 }
2132
2133 #[test]
2134 fn test_column_debug() {
2135 let col = Column::with_header_str("Name");
2136 let debug_str = format!("{:?}", col);
2137 assert!(debug_str.contains("Column"));
2138 }
2139
2140 #[test]
2141 fn test_row_debug() {
2142 let row = Row::empty();
2143 let debug_str = format!("{:?}", row);
2144 assert!(debug_str.contains("Row"));
2145 }
2146}