term_data_table/lib.rs
1//! The purpose of term-table is to make it easy for CLI apps to display data in a table format
2//!# Example
3//! Here is an example of how to create a simple table
4//!```
5//! use term_data_table::{ Table, Cell, TableStyle, Alignment, Row };
6//!
7//! let table = Table::new()
8//! .with_style(TableStyle::EXTENDED)
9//! .with_row(Row::new().with_cell(
10//! Cell::from("This is some centered text")
11//! .with_alignment(Alignment::Center)
12//! .with_col_span(2)
13//! ))
14//! .with_row(Row::new().with_cell(
15//! Cell::from("This is left aligned text")
16//! ).with_cell(
17//! Cell::from("This is right aligned text")
18//! .with_alignment(Alignment::Right)
19//! ))
20//! .with_row(Row::new().with_cell(
21//! Cell::from("This is left aligned text")
22//! ).with_cell(
23//! Cell::from("This is right aligned text")
24//! .with_alignment(Alignment::Right)
25//! ))
26//! .with_row(Row::new().with_cell(
27//! Cell::from("This is some really really really really really really really really really that is going to wrap to the next line")
28//! .with_col_span(2)
29//! ));
30//!println!("{}", table.fixed_width(80));
31//!```
32//!
33//!### This is the result
34//!
35//!<pre>
36//! ╔═════════════════════════════════════════════════════════════════════════════════╗
37//! ║ This is some centered text ║
38//! ╠════════════════════════════════════════╦════════════════════════════════════════╣
39//! ║ This is left aligned text ║ This is right aligned text ║
40//! ╠════════════════════════════════════════╬════════════════════════════════════════╣
41//! ║ This is left aligned text ║ This is right aligned text ║
42//! ╠════════════════════════════════════════╩════════════════════════════════════════╣
43//! ║ This is some really really really really really really really really really tha ║
44//! ║ t is going to wrap to the next line ║
45//! ╚═════════════════════════════════════════════════════════════════════════════════╝
46//!</pre>
47
48#[macro_use]
49extern crate lazy_static;
50
51mod cell;
52mod row;
53mod ser;
54mod style;
55
56pub use crate::{
57 cell::{Alignment, Cell},
58 row::{IntoRow, Row},
59 style::TableStyle,
60};
61// TODO just use a serde deserializer.
62#[doc(inline)]
63pub use term_data_table_derive::IntoRow;
64
65use itertools::Itertools;
66use serde::Serialize;
67use std::{cell::RefCell, collections::HashMap, fmt};
68use terminal_size::terminal_size;
69
70thread_local! {
71 /// Used to calculate the maximum width of table cells.
72 static MAX_WIDTHS_CELL_WIDTHS: RefCell<(ColumnWidths, HashMap<usize, usize>)>
73 = RefCell::new((ColumnWidths::new(), HashMap::new()));
74}
75
76/// Represents the vertical position of a row
77#[derive(Eq, PartialEq, Copy, Clone)]
78pub enum RowPosition {
79 First,
80 Mid,
81 Last,
82}
83
84/// A set of rows containing data
85#[derive(Clone, Debug)]
86pub struct Table<'data> {
87 rows: Vec<Row<'data>>,
88 style: TableStyle,
89 /// Whether or not to vertically separate rows in the table.
90 ///
91 /// Defaults to `true`.
92 pub has_separate_rows: bool,
93 /// Whether the table should have a top border.
94 ///
95 /// Setting `has_separator` to false on the first row will have the same effect as setting this
96 /// to false
97 ///
98 /// Defaults to `true`.
99 pub has_top_border: bool,
100 /// Whether the table should have a bottom border
101 ///
102 /// Defaults to `true`.
103 pub has_bottom_border: bool,
104
105 /// Calculated column widths.
106 column_widths: RefCell<ColumnWidths>,
107 /// Calculated row lines
108 row_lines: RefCell<Vec<usize>>,
109}
110
111impl<'data> Default for Table<'data> {
112 fn default() -> Self {
113 Self {
114 rows: Vec::new(),
115 style: TableStyle::EXTENDED,
116 has_separate_rows: true,
117 has_top_border: true,
118 has_bottom_border: true,
119
120 column_widths: RefCell::new(ColumnWidths::new()),
121 row_lines: RefCell::new(vec![]),
122 }
123 }
124}
125
126impl<'data> Table<'data> {
127 pub fn new() -> Table<'data> {
128 Default::default()
129 }
130
131 pub fn from_rows(rows: Vec<Row<'data>>) -> Table<'data> {
132 Self {
133 rows,
134 ..Default::default()
135 }
136 }
137
138 pub fn from_serde(data: impl IntoIterator<Item = impl Serialize>) -> anyhow::Result<Self> {
139 let mut table = Table::new();
140 for row in data {
141 table.add_row(ser::serialize_row(row)?);
142 }
143 Ok(table)
144 }
145
146 /// Add a row
147 pub fn with_row(mut self, row: Row<'data>) -> Self {
148 self.add_row(row);
149 self
150 }
151
152 /// Add a row
153 pub fn add_row(&mut self, row: Row<'data>) -> &mut Self {
154 self.rows.push(row);
155 self
156 }
157
158 pub fn with_style(mut self, style: TableStyle) -> Self {
159 self.set_style(style);
160 self
161 }
162
163 pub fn set_style(&mut self, style: TableStyle) -> &mut Self {
164 self.style = style;
165 self
166 }
167
168 /*
169 pub fn max_column_width(&self) -> usize {
170 self.max_column_width
171 }
172
173 pub fn set_max_column_width(&mut self, max_column_width: usize) -> &mut Self {
174 self.max_column_width = max_column_width;
175 self
176 }
177
178 pub fn with_max_column_width(mut self, max_column_width: usize) -> Self {
179 self.set_max_column_width(max_column_width);
180 self
181 }
182
183 /// Set the max width of a particular column
184 ///
185 /// Overrides any value set for `max_column_width`.
186 pub fn set_max_width_for_column(&mut self, column_index: usize, max_width: usize) -> &mut Self {
187 self.max_column_widths.insert(column_index, max_width);
188 self
189 }
190
191 pub fn with_max_width_for_column(mut self, column_index: usize, max_width: usize) -> Self {
192 self.set_max_width_for_column(column_index, max_width);
193 self
194 }
195 */
196
197 pub fn has_separate_rows(&self) -> bool {
198 self.has_separate_rows
199 }
200
201 pub fn with_separate_rows(mut self, has_separate_rows: bool) -> Self {
202 self.set_separate_rows(has_separate_rows);
203 self
204 }
205
206 pub fn set_separate_rows(&mut self, has_separate_rows: bool) -> &mut Self {
207 self.has_separate_rows = has_separate_rows;
208 self
209 }
210
211 /// Decide how much space to give each cell and layout the rows.
212 ///
213 /// If no width is given, all cells will be the largest of their contents.
214 ///
215 fn layout(&self, width: Option<usize>) {
216 // We need to know the maxiumum number of columns in a row.
217 let cols = self.rows.iter().map(|row| row.columns()).max().unwrap_or(0);
218 let border_width = self.style.border_width();
219 let mut col_widths = self.column_widths.borrow_mut();
220 col_widths.reset(cols);
221
222 // short-circuit when there are no columns
223 if cols == 0 {
224 return;
225 }
226
227 if let Some(width) = width {
228 // total space available for drawing text
229 let cell_width_total = width - (border_width + 1) * cols;
230
231 MAX_WIDTHS_CELL_WIDTHS.with(|lk| {
232 let (ref mut max_widths, ref mut cell_widths) = &mut *lk.borrow_mut();
233 // reset
234 max_widths.reset(cols);
235 cell_widths.clear();
236
237 // first stash the max space each column will need.
238 for row in self.rows.iter() {
239 max_widths.fit_row_singleline(row, border_width);
240 }
241
242 // Next, calculate the width we would give each cell if we were splitting space
243 // evenly
244 let cell_width = cell_width_total / cols;
245
246 // Next, find all cells with max width less than the cell width we calculated and
247 // give them their max width
248 for (idx, max_width) in max_widths.iter().enumerate() {
249 if *max_width < cell_width {
250 cell_widths.insert(idx, *max_width);
251 }
252 }
253
254 let remaining_cells = cols - cell_widths.len();
255 let remaining_space =
256 cell_width_total - cell_widths.values().copied().sum::<usize>();
257 if remaining_cells > 0 {
258 let cell_width = remaining_space / remaining_cells;
259 for idx in 0..cols {
260 cell_widths.entry(idx).or_insert(cell_width);
261 }
262 }
263
264 col_widths.from_map(cell_widths);
265 });
266 } else {
267 // Give all cells all the space they need.
268 for row in self.rows.iter() {
269 col_widths.fit_row_singleline(row, border_width);
270 }
271 }
272 for row in self.rows.iter() {
273 self.row_lines
274 .borrow_mut()
275 .push(row.layout(&*col_widths, border_width));
276 }
277 }
278
279 /// Write the table out to a formatter.
280 ///
281 /// This method calculates stale state that it needs.
282 ///
283 /// # Params
284 /// - `view_width` - the width of the viewport we are rendering to, if any. If unspecified,
285 /// we will assume infinite width.
286 fn render(&self, view_width: Option<usize>, f: &mut fmt::Formatter) -> fmt::Result {
287 self.layout(view_width);
288 if self.rows.is_empty() {
289 return writeln!(f, "<empty table>");
290 }
291 let row_lines = self.row_lines.borrow();
292
293 if self.has_top_border {
294 self.rows[0].render_top_separator(&*self.column_widths.borrow(), &self.style, f)?;
295 }
296 self.rows[0].render_content(&*self.column_widths.borrow(), row_lines[0], &self.style, f)?;
297
298 for (idx, (prev_row, row)) in self.rows.iter().tuple_windows().enumerate() {
299 if self.has_separate_rows {
300 row.render_separator(prev_row, &*self.column_widths.borrow(), &self.style, f)?;
301 }
302
303 let row_lines = self.row_lines.borrow();
304 row.render_content(
305 &*self.column_widths.borrow(),
306 row_lines[idx + 1],
307 &self.style,
308 f,
309 )?;
310 }
311 if self.has_bottom_border {
312 self.rows[self.rows.len() - 1].render_bottom_separator(
313 &*self.column_widths.borrow(),
314 &self.style,
315 f,
316 )?;
317 }
318 Ok(())
319 }
320
321 /// Get the terminal width and use this for the table width.
322 ///
323 /// # Panics
324 ///
325 /// Will panic if it cannot get the terminal width (e.g. because we aren't in a terminal).
326 pub fn for_terminal(&self) -> impl fmt::Display + '_ {
327 match terminal_size().and_then(|v| usize::try_from((v.0).0).ok()) {
328 Some(width) => FixedWidth { table: self, width },
329 None => FixedWidth {
330 table: self,
331 width: usize::MAX,
332 },
333 }
334 }
335
336 /// Use a custom value for the table width
337 pub fn fixed_width(&self, width: usize) -> impl fmt::Display + '_ {
338 FixedWidth { table: self, width }
339 }
340}
341
342struct FixedWidth<'a> {
343 table: &'a Table<'a>,
344 width: usize,
345}
346
347impl fmt::Display for FixedWidth<'_> {
348 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
349 self.table.render(Some(self.width), f)
350 }
351}
352
353impl<'data> fmt::Display for Table<'data> {
354 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
355 self.render(None, f)
356 }
357}
358
359pub fn data_table<'a, R: 'a>(input: impl IntoIterator<Item = &'a R>) -> Table<'a>
360where
361 R: IntoRow,
362{
363 let mut table = Table::new();
364 for row in input {
365 table.add_row(row.into_row());
366 }
367 table
368}
369
370#[derive(Debug, Clone)]
371struct ColumnWidths(Vec<usize>);
372
373impl ColumnWidths {
374 fn new() -> Self {
375 ColumnWidths(vec![])
376 }
377
378 /// Reset all columns to 0 and make sure there are the correct number of columns.
379 fn reset(&mut self, num_cols: usize) {
380 self.0.clear();
381 self.0.resize(num_cols, 0);
382 }
383
384 /// Make our widths fit the given row with all text on a single line.
385 ///
386 /// This is for when we are allowed to use as much space as we want.
387 fn fit_row_singleline(&mut self, row: &Row, border_width: usize) {
388 let mut idx = 0;
389 for cell in row.cells.iter() {
390 if cell.col_span == 1 {
391 self.0[idx] = self.0[idx].max(cell.min_width(true));
392 } else {
393 // space required to fit this cell (taking into account we have some borders to
394 // use).
395 let required_width = cell.min_width(true) - border_width * (cell.col_span - 1);
396 let floor_per_cell = required_width / cell.col_span;
397 // space we need to put somewhere
398 let mut to_fit = required_width % cell.col_span;
399 // split space evenly, with remainder in last space.
400 for i in 0..cell.col_span {
401 let extra = if to_fit > 0 { 1 } else { 0 };
402 to_fit = to_fit.saturating_sub(1);
403 self.0[idx + i] = self.0[idx + 1].max(floor_per_cell + extra);
404 }
405 }
406 idx += cell.col_span;
407 }
408 }
409
410 fn from_map(&mut self, map: &HashMap<usize, usize>) {
411 for (idx, slot) in self.0.iter_mut().enumerate() {
412 *slot = *map.get(&idx).unwrap();
413 }
414 }
415}
416
417impl std::ops::Deref for ColumnWidths {
418 type Target = [usize];
419 fn deref(&self) -> &Self::Target {
420 &self.0
421 }
422}
423
424#[cfg(test)]
425mod test {
426
427 use crate::cell::{Alignment, Cell};
428 use crate::row::Row;
429 use crate::Table;
430 use crate::TableStyle;
431 use pretty_assertions::assert_eq;
432
433 #[test]
434 fn correct_default_padding() {
435 let table = Table::new()
436 .with_separate_rows(false)
437 .with_style(TableStyle::SIMPLE)
438 .with_row(
439 Row::new()
440 .with_cell(Cell::from("A").with_alignment(Alignment::Center))
441 .with_cell(Cell::from("B").with_alignment(Alignment::Center)),
442 )
443 .with_row(
444 Row::new()
445 .with_cell(Cell::from(1.to_string()))
446 .with_cell(Cell::from("1")),
447 )
448 .with_row(
449 Row::new()
450 .with_cell(Cell::from(2.to_string()))
451 .with_cell(Cell::from("10")),
452 )
453 .with_row(
454 Row::new()
455 .with_cell(Cell::from(3.to_string()))
456 .with_cell(Cell::from("100")),
457 );
458 let expected = r"+---+-----+
459| A | B |
460| 1 | 1 |
461| 2 | 10 |
462| 3 | 100 |
463+---+-----+
464";
465 println!("{}", table);
466 assert_eq!(expected, table.to_string());
467 }
468
469 #[test]
470 fn uneven_center_alignment() {
471 let table = Table::new()
472 .with_separate_rows(false)
473 .with_style(TableStyle::SIMPLE)
474 .with_row(Row::new().with_cell(Cell::from("A").with_alignment(Alignment::Center)))
475 .with_row(Row::new().with_cell(Cell::from(11.to_string())))
476 .with_row(Row::new().with_cell(Cell::from(2.to_string())))
477 .with_row(Row::new().with_cell(Cell::from(3.to_string())));
478 let expected = r"+----+
479| A |
480| 11 |
481| 2 |
482| 3 |
483+----+
484";
485 println!("{}", table);
486 assert_eq!(expected, table.to_string());
487 }
488
489 #[test]
490 fn uneven_center_alignment_2() {
491 let table = Table::new()
492 .with_separate_rows(false)
493 .with_style(TableStyle::SIMPLE)
494 .with_row(
495 Row::new()
496 .with_cell(Cell::from("A1").with_alignment(Alignment::Center))
497 .with_cell(Cell::from("B").with_alignment(Alignment::Center)),
498 );
499 println!("{}", table);
500 let expected = r"+----+---+
501| A1 | B |
502+----+---+
503";
504 assert_eq!(expected, table.to_string());
505 }
506
507 #[test]
508 fn simple_table_style() {
509 let mut table = Table::new().with_style(TableStyle::SIMPLE);
510
511 add_data_to_test_table(&mut table);
512
513 let expected = r"+-----------------------------------------------------------------------------+
514| This is some centered text |
515+--------------------------------------+--------------------------------------+
516| This is left aligned text | This is right aligned text |
517+--------------------------------------+--------------------------------------+
518| This is left aligned text | This is right aligned text |
519+--------------------------------------+--------------------------------------+
520| This is some really really really really really really really really |
521| really that is going to wrap to the next line |
522+-----------------------------------------------------------------------------+
523| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
524| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
525+-----------------------------------------------------------------------------+
526";
527 let table = table.fixed_width(80);
528 println!("{}", table.to_string());
529 assert_eq!(expected, table.to_string());
530 }
531
532 #[test]
533 #[ignore]
534 fn uneven_with_varying_col_span() {
535 let table = Table::new()
536 .with_separate_rows(true)
537 .with_style(TableStyle::SIMPLE)
538 .with_row(
539 Row::new()
540 .with_cell(Cell::from("A1111111").with_alignment(Alignment::Center))
541 .with_cell(Cell::from("B").with_alignment(Alignment::Center)),
542 )
543 .with_row(
544 Row::new()
545 .with_cell(Cell::from(1.to_string()))
546 .with_cell(Cell::from("1")),
547 )
548 .with_row(
549 Row::new()
550 .with_cell(Cell::from(2.to_string()))
551 .with_cell(Cell::from("10")),
552 )
553 .with_row(
554 Row::new()
555 .with_cell(
556 Cell::from(3.to_string())
557 .with_alignment(Alignment::Left)
558 .with_padding(false),
559 )
560 .with_cell(Cell::from("100")),
561 )
562 .with_row(Row::new().with_cell(Cell::from("S").with_alignment(Alignment::Center)));
563 let expected = "+----------+-----+
564| A1111111 | B |
565+----------+-----+
566| 1 | 1 |
567+----------+-----+
568| 2 | 10 |
569+----------+-----+
570|\03\0 | 100 |
571+----------+-----+
572| S |
573+----------------+
574";
575 println!("{}", table);
576 assert_eq!(expected.trim(), table.to_string().trim());
577 }
578
579 // TODO - The output of this test isn't ideal. There is probably a better way to calculate the
580 // the column/row layout that would improve this
581 #[test]
582 fn uneven_with_varying_col_span_2() {
583 let table = Table::new()
584 .with_separate_rows(false)
585 .with_style(TableStyle::SIMPLE)
586 .with_row(
587 Row::new()
588 .with_cell(Cell::from("A").with_alignment(Alignment::Center))
589 .with_cell(Cell::from("B").with_alignment(Alignment::Center)),
590 )
591 .with_row(
592 Row::new()
593 .with_cell(Cell::from(1.to_string()))
594 .with_cell(Cell::from("1")),
595 )
596 .with_row(
597 Row::new()
598 .with_cell(Cell::from(2.to_string()))
599 .with_cell(Cell::from("10")),
600 )
601 .with_row(
602 Row::new()
603 .with_cell(Cell::from(3.to_string()))
604 .with_cell(Cell::from("100")),
605 )
606 .with_row(
607 Row::new().with_cell(
608 Cell::from("Spanner")
609 .with_col_span(2)
610 .with_alignment(Alignment::Center),
611 ),
612 );
613 let expected = "+-----+-----+
614| A | B |
615| 1 | 1 |
616| 2 | 10 |
617| 3 | 100 |
618| Spanner |
619+-----------+
620";
621 println!("{}", table);
622 assert_eq!(expected.trim(), table.to_string().trim());
623 }
624
625 /*
626 #[test]
627 fn extended_table_style_wrapped() {
628 let table = Table::new()
629 .with_max_column_width(40)
630 .with_max_widths_for_columns([(0, 1), (1, 1)])
631
632 .with_style ( TableStyle::EXTENDED)
633
634 .with_row(Row::new(vec![Cell::new_with_alignment(
635 "This is some centered text",
636 2,
637 Alignment::Center,
638 )]))
639
640 .with_row(Row::new(vec![
641 Cell::new("This is left aligned text"),
642 Cell::new_with_alignment("This is right aligned text", 1, Alignment::Right),
643 ]))
644
645 .with_row(Row::new(vec![
646 Cell::new("This is left aligned text"),
647 Cell::new_with_alignment("This is right aligned text", 1, Alignment::Right),
648 ]))
649
650 .with_row(Row::new(vec![
651 Cell::new_with_col_span("This is some really really really really really really really really really that is going to wrap to the next line\n1\n2", 2),
652 ]));
653
654 let expected = r"╔═══════╗
655 ║ This ║
656 ║ is so ║
657 ║ me ce ║
658 ║ ntere ║
659 ║ d tex ║
660 ║ t ║
661 ╠═══╦═══╣
662 ║ T ║ T ║
663 ║ h ║ h ║
664 ║ i ║ i ║
665 ║ s ║ s ║
666 ║ ║ ║
667 ║ i ║ i ║
668 ║ s ║ s ║
669 ║ ║ ║
670 ║ l ║ r ║
671 ║ e ║ i ║
672 ║ f ║ g ║
673 ║ t ║ h ║
674 ║ ║ t ║
675 ║ a ║ ║
676 ║ l ║ a ║
677 ║ i ║ l ║
678 ║ g ║ i ║
679 ║ n ║ g ║
680 ║ e ║ n ║
681 ║ d ║ e ║
682 ║ ║ d ║
683 ║ t ║ ║
684 ║ e ║ t ║
685 ║ x ║ e ║
686 ║ t ║ x ║
687 ║ ║ t ║
688 ╠═══╬═══╣
689 ║ T ║ T ║
690 ║ h ║ h ║
691 ║ i ║ i ║
692 ║ s ║ s ║
693 ║ ║ ║
694 ║ i ║ i ║
695 ║ s ║ s ║
696 ║ ║ ║
697 ║ l ║ r ║
698 ║ e ║ i ║
699 ║ f ║ g ║
700 ║ t ║ h ║
701 ║ ║ t ║
702 ║ a ║ ║
703 ║ l ║ a ║
704 ║ i ║ l ║
705 ║ g ║ i ║
706 ║ n ║ g ║
707 ║ e ║ n ║
708 ║ d ║ e ║
709 ║ ║ d ║
710 ║ t ║ ║
711 ║ e ║ t ║
712 ║ x ║ e ║
713 ║ t ║ x ║
714 ║ ║ t ║
715 ╠═══╩═══╣
716 ║ This ║
717 ║ is so ║
718 ║ me re ║
719 ║ ally ║
720 ║ reall ║
721 ║ y rea ║
722 ║ lly r ║
723 ║ eally ║
724 ║ real ║
725 ║ ly re ║
726 ║ ally ║
727 ║ reall ║
728 ║ y rea ║
729 ║ lly r ║
730 ║ eally ║
731 ║ that ║
732 ║ is g ║
733 ║ oing ║
734 ║ to wr ║
735 ║ ap to ║
736 ║ the ║
737 ║ next ║
738 ║ line ║
739 ║ 1 ║
740 ║ 2 ║
741 ╚═══════╝
742 ";
743 println!("{}", table.render());
744 assert_eq!(expected, table.render());
745 }
746
747 #[test]
748 fn elegant_table_style() {
749 let mut table = Table::new();
750 table.style = TableStyle::elegant();
751
752 add_data_to_test_table(&mut table);
753
754 let expected = r"╔─────────────────────────────────────────────────────────────────────────────────╗
755 │ This is some centered text │
756 ╠────────────────────────────────────────╦────────────────────────────────────────╣
757 │ This is left aligned text │ This is right aligned text │
758 ╠────────────────────────────────────────┼────────────────────────────────────────╣
759 │ This is left aligned text │ This is right aligned text │
760 ╠────────────────────────────────────────╩────────────────────────────────────────╣
761 │ This is some really really really really really really really really really tha │
762 │ t is going to wrap to the next line │
763 ╚─────────────────────────────────────────────────────────────────────────────────╝
764 ";
765 println!("{}", table.render());
766 assert_eq!(expected, table.render());
767 }
768
769 #[test]
770 fn thin_table_style() {
771 let mut table = Table::new();
772 table.style = TableStyle::thin();
773
774 add_data_to_test_table(&mut table);
775
776 let expected = r"┌─────────────────────────────────────────────────────────────────────────────────┐
777 │ This is some centered text │
778 ├────────────────────────────────────────┬────────────────────────────────────────┤
779 │ This is left aligned text │ This is right aligned text │
780 ├────────────────────────────────────────┼────────────────────────────────────────┤
781 │ This is left aligned text │ This is right aligned text │
782 ├────────────────────────────────────────┴────────────────────────────────────────┤
783 │ This is some really really really really really really really really really tha │
784 │ t is going to wrap to the next line │
785 └─────────────────────────────────────────────────────────────────────────────────┘
786 ";
787 println!("{}", table.render());
788 assert_eq!(expected, table.render());
789 }
790
791 #[test]
792 fn rounded_table_style() {
793 let mut table = Table::new();
794
795 table.style = TableStyle::rounded();
796
797 add_data_to_test_table(&mut table);
798
799 let expected = r"╭─────────────────────────────────────────────────────────────────────────────────╮
800 │ This is some centered text │
801 ├────────────────────────────────────────┬────────────────────────────────────────┤
802 │ This is left aligned text │ This is right aligned text │
803 ├────────────────────────────────────────┼────────────────────────────────────────┤
804 │ This is left aligned text │ This is right aligned text │
805 ├────────────────────────────────────────┴────────────────────────────────────────┤
806 │ This is some really really really really really really really really really tha │
807 │ t is going to wrap to the next line │
808 ╰─────────────────────────────────────────────────────────────────────────────────╯
809 ";
810 println!("{}", table.render());
811 assert_eq!(expected, table.render());
812 }
813
814 #[test]
815 fn complex_table() {
816 let mut table = Table::new();
817
818 table.add_row(Row::new(vec![
819 Cell::new_with_col_span("Col*1*Span*2", 2),
820 Cell::new("Col 2 Span 1"),
821 Cell::new_with_col_span("Col 3 Span 2", 2),
822 Cell::new("Col 4 Span 1"),
823 ]));
824 table.add_row(Row::new(vec![
825 Cell::new("Col 1 Span 1"),
826 Cell::new("Col 2 Span 1"),
827 Cell::new("Col 3 Span 1"),
828 Cell::new_with_col_span("Col 4 Span 1", 2),
829 ]));
830 table.add_row(Row::new(vec![
831 Cell::new("fasdaff"),
832 Cell::new("fff"),
833 Cell::new("fff"),
834 ]));
835 table.add_row(Row::new(vec![
836 Cell::new_with_alignment("fasdff", 3, Alignment::Right),
837 Cell::new_with_col_span("fffdff", 4),
838 ]));
839 table.add_row(Row::new(vec![
840 Cell::new("fasdsaff"),
841 Cell::new("fff"),
842 Cell::new("f\nf\nf\nfff\nrrr\n\n\n"),
843 ]));
844 table.add_row(Row::new(vec![Cell::new("fasdsaff")]));
845
846 let s = table.render().clone();
847
848 table.add_row(Row::new(vec![Cell::new_with_alignment(
849 s,
850 3,
851 Alignment::Left,
852 )]));
853
854 let expected = r"╔═════════════════════════════════════════════════════════╦════════════════════════════╦════════════════╦══════════════╦═══╗
855 ║ Col*1*Span*2 ║ Col 2 Span 1 ║ Col 3 Span 2 ║ Col 4 Span 1 ║ ║
856 ╠════════════════════════════╦════════════════════════════╬════════════════════════════╬════════════════╬══════════════╬═══╣
857 ║ Col 1 Span 1 ║ Col 2 Span 1 ║ Col 3 Span 1 ║ Col 4 Span 1 ║ ║ ║
858 ╠════════════════════════════╬════════════════════════════╬════════════════════════════╬═══════╦════════╬══════════════╬═══╣
859 ║ fasdaff ║ fff ║ fff ║ ║ ║ ║ ║
860 ╠════════════════════════════╩════════════════════════════╩════════════════════════════╬═══════╩════════╩══════════════╩═══╣
861 ║ fasdff ║ fffdff ║
862 ╠════════════════════════════╦════════════════════════════╦════════════════════════════╬═══════╦════════╦══════════════╦═══╣
863 ║ fasdsaff ║ fff ║ f ║ ║ ║ ║ ║
864 ║ ║ ║ f ║ ║ ║ ║ ║
865 ║ ║ ║ f ║ ║ ║ ║ ║
866 ║ ║ ║ fff ║ ║ ║ ║ ║
867 ║ ║ ║ rrr ║ ║ ║ ║ ║
868 ║ ║ ║ ║ ║ ║ ║ ║
869 ║ ║ ║ ║ ║ ║ ║ ║
870 ║ ║ ║ ║ ║ ║ ║ ║
871 ╠════════════════════════════╬════════════════════════════╬════════════════════════════╬═══════╬════════╬══════════════╬═══╣
872 ║ fasdsaff ║ ║ ║ ║ ║ ║ ║
873 ╠════════════════════════════╩════════════════════════════╩════════════════════════════╬═══════╬════════╬══════════════╬═══╣
874 ║ ╔═════════════════════════════╦══════════════╦════════════════╦══════════════╦═══╗ ║ ║ ║ ║ ║
875 ║ ║ Col*1*Span*2 ║ Col 2 Span 1 ║ Col 3 Span 2 ║ Col 4 Span 1 ║ ║ ║ ║ ║ ║ ║
876 ║ ╠══════════════╦══════════════╬══════════════╬════════════════╬══════════════╬═══╣ ║ ║ ║ ║ ║
877 ║ ║ Col 1 Span 1 ║ Col 2 Span 1 ║ Col 3 Span 1 ║ Col 4 Span 1 ║ ║ ║ ║ ║ ║ ║ ║
878 ║ ╠══════════════╬══════════════╬══════════════╬═══════╦════════╬══════════════╬═══╣ ║ ║ ║ ║ ║
879 ║ ║ fasdaff ║ fff ║ fff ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
880 ║ ╠══════════════╩══════════════╩══════════════╬═══════╩════════╩══════════════╩═══╣ ║ ║ ║ ║ ║
881 ║ ║ fasdff ║ fffdff ║ ║ ║ ║ ║ ║
882 ║ ╠══════════════╦══════════════╦══════════════╬═══════╦════════╦══════════════╦═══╣ ║ ║ ║ ║ ║
883 ║ ║ fasdsaff ║ fff ║ f ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
884 ║ ║ ║ ║ f ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
885 ║ ║ ║ ║ f ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
886 ║ ║ ║ ║ fff ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
887 ║ ║ ║ ║ rrr ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
888 ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
889 ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
890 ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
891 ║ ╠══════════════╬══════════════╬══════════════╬═══════╬════════╬══════════════╬═══╣ ║ ║ ║ ║ ║
892 ║ ║ fasdsaff ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
893 ║ ╚══════════════╩══════════════╩══════════════╩═══════╩════════╩══════════════╩═══╝ ║ ║ ║ ║ ║
894 ║ ║ ║ ║ ║ ║
895 ╚══════════════════════════════════════════════════════════════════════════════════════╩═══════╩════════╩══════════════╩═══╝
896 ";
897 println!("{}", table.render());
898 assert_eq!(expected, table.render());
899 }
900
901 #[test]
902 fn no_top_border() {
903 let mut table = Table::new();
904 table.style = TableStyle::simple();
905 table.has_top_border = false;
906
907 add_data_to_test_table(&mut table);
908
909 let expected = r"| This is some centered text |
910 +----------------------------------------+----------------------------------------+
911 | This is left aligned text | This is right aligned text |
912 +----------------------------------------+----------------------------------------+
913 | This is left aligned text | This is right aligned text |
914 +----------------------------------------+----------------------------------------+
915 | This is some really really really really really really really really really tha |
916 | t is going to wrap to the next line |
917 +---------------------------------------------------------------------------------+
918 ";
919 println!("{}", table.render());
920 assert_eq!(expected, table.render());
921 }
922
923 #[test]
924 fn no_bottom_border() {
925 let mut table = Table::new();
926 table.style = TableStyle::simple();
927 table.has_bottom_border = false;
928
929 add_data_to_test_table(&mut table);
930
931 let expected = r"+---------------------------------------------------------------------------------+
932 | This is some centered text |
933 +----------------------------------------+----------------------------------------+
934 | This is left aligned text | This is right aligned text |
935 +----------------------------------------+----------------------------------------+
936 | This is left aligned text | This is right aligned text |
937 +----------------------------------------+----------------------------------------+
938 | This is some really really really really really really really really really tha |
939 | t is going to wrap to the next line |
940 ";
941 println!("{}", table.render());
942 assert_eq!(expected, table.render());
943 }
944
945 #[test]
946 fn no_separators() {
947 let mut table = Table::new();
948 table.style = TableStyle::simple();
949 table.separate_rows = false;
950
951 add_data_to_test_table(&mut table);
952
953 let expected = r"+---------------------------------------------------------------------------------+
954 | This is some centered text |
955 | This is left aligned text | This is right aligned text |
956 | This is left aligned text | This is right aligned text |
957 | This is some really really really really really really really really really tha |
958 | t is going to wrap to the next line |
959 +---------------------------------------------------------------------------------+
960 ";
961 println!("{}", table.render());
962 assert_eq!(expected, table.render());
963 }
964
965 #[test]
966 fn some_rows_no_separators() {
967 let mut table = Table::new();
968 table.style = TableStyle::simple();
969
970 add_data_to_test_table(&mut table);
971
972 table.rows[2].has_separator = false;
973
974 let expected = r"+---------------------------------------------------------------------------------+
975 | This is some centered text |
976 +----------------------------------------+----------------------------------------+
977 | This is left aligned text | This is right aligned text |
978 | This is left aligned text | This is right aligned text |
979 +----------------------------------------+----------------------------------------+
980 | This is some really really really really really really really really really tha |
981 | t is going to wrap to the next line |
982 +---------------------------------------------------------------------------------+
983 ";
984 println!("{}", table.render());
985 assert_eq!(expected, table.render());
986 }
987
988 #[test]
989 fn colored_data_works() {
990 let mut table = Table::new();
991 table.add_row(Row::new(vec![Cell::new("\u{1b}[31ma\u{1b}[0m")]));
992 let expected = "╔═══╗
993 ║ \u{1b}[31ma\u{1b}[0m ║
994 ╚═══╝
995 ";
996 println!("{}", table.render());
997 assert_eq!(expected, table.render());
998 }
999 */
1000
1001 fn add_data_to_test_table(table: &mut Table) {
1002 table.add_row(
1003 Row::new().with_cell(
1004 Cell::from("This is some centered text")
1005 .with_col_span(2)
1006 .with_alignment(Alignment::Center),
1007 ),
1008 );
1009
1010 table.add_row(
1011 Row::new()
1012 .with_cell(Cell::from("This is left aligned text"))
1013 .with_cell(
1014 Cell::from("This is right aligned text").with_alignment(Alignment::Right),
1015 ),
1016 );
1017
1018 table.add_row(
1019 Row::new()
1020 .with_cell(Cell::from("This is left aligned text"))
1021 .with_cell(
1022 Cell::from("This is right aligned text").with_alignment(Alignment::Right),
1023 ),
1024 );
1025
1026 table.add_row(
1027 Row::new().with_cell(
1028 Cell::from(
1029 "This is some really really really really really \
1030 really really really really that is going to wrap to the next line",
1031 )
1032 .with_col_span(2),
1033 ),
1034 );
1035
1036 table.add_row(
1037 Row::new().with_cell(
1038 Cell::from(
1039 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
1040 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1041 )
1042 .with_col_span(2),
1043 ),
1044 );
1045 }
1046}