1#![warn(missing_docs)]
31
32use std::cmp;
33use std::collections::HashMap;
34
35use unicode_width::UnicodeWidthStr;
36
37mod style;
38pub use style::{Align, Style};
39
40mod unstyle;
41pub use unstyle::{AsciiEscapedString, Unstyle};
42
43mod cell;
44pub use cell::Cell;
45
46const MIN_PADDING: usize = 2;
48
49#[derive(Default)]
62pub struct Headers {
63 headers: Vec<Box<dyn Unstyle>>,
64}
65
66impl Headers {
67 pub fn new() -> Self {
69 Default::default()
70 }
71
72 pub fn from(headers: Vec<&str>) -> Self {
74 let mut unstyle_headers: Vec<Box<dyn Unstyle>> = Vec::with_capacity(headers.len());
75 for header in headers.iter() {
76 unstyle_headers.push(Box::new(String::from(*header)));
77 }
78 Self {
79 headers: unstyle_headers,
80 }
81 }
82
83 pub fn with_capacity(capacity: usize) -> Self {
85 Self {
86 headers: Vec::with_capacity(capacity),
87 }
88 }
89
90 pub fn push<H: Unstyle + 'static>(&mut self, header: H) -> &mut Self {
92 self.headers.push(Box::new(header));
93 self
94 }
95
96 fn len(&self) -> usize {
97 self.headers.len()
98 }
99
100 #[allow(clippy::borrowed_box)]
101 fn get(&self, i: usize) -> Option<&Box<dyn Unstyle>> {
102 self.headers.get(i)
103 }
104
105 #[allow(clippy::borrowed_box)]
106 fn to_ref_vec(&self) -> Vec<&Box<dyn Unstyle>> {
107 self.headers.iter().collect()
108 }
109}
110
111pub struct Table<'a> {
126 style: Style,
127 str_align: Align,
128 num_align: Align,
129 contents: Vec<Vec<Cell<'a>>>,
130 headers: Option<Headers>,
131
132 #[cfg(feature = "ansi_term_style")]
133 border_style: Option<ansi_term::Style>,
134}
135
136impl<'a> Table<'a> {
137 pub fn new(style: Style, contents: Vec<Vec<Cell<'a>>>, headers: Option<Headers>) -> Self {
139 Self {
140 style,
141 str_align: Align::Left,
142 num_align: Align::Decimal,
143 #[cfg(feature = "ansi_term_style")]
144 border_style: None,
145 contents,
146 headers,
147 }
148 }
149
150 pub fn set_align(&mut self, str_align: Align, num_align: Align) {
154 if str_align == Align::Decimal {
155 panic!("str_align should not be set to Decimal, only num_align can");
156 }
157 self.str_align = str_align;
158 self.num_align = num_align;
159 }
160
161 #[cfg(feature = "ansi_term_style")]
162 pub fn set_border_style(&mut self, style: ansi_term::Style) {
166 self.border_style = Some(style);
167 }
168
169 pub fn tabulate(&self) -> String {
171 let style = &self.style;
172 let headers = &self.headers;
173 let contents = &self.contents;
174 let str_align = &self.str_align;
175 let num_align = &self.num_align;
176 #[allow(unused_mut)]
177 let mut fmt = style.to_format();
178 #[cfg(feature = "ansi_term_style")]
179 {
180 if let Some(style) = self.border_style {
181 fmt.apply_style(style);
182 }
183 }
184 let header_len = if let Some(h) = headers { h.len() } else { 0 };
186 let col_nb = cmp::max(
187 header_len,
188 *contents.iter().map(Vec::len).max().get_or_insert(0),
189 );
190 let col_spec = get_col_specs(col_nb, contents);
192 let col_width = get_col_width(col_nb, headers, contents, &col_spec, num_align);
194 let mut lines = vec![];
196 if !(headers.is_some() && fmt.hidelineaboveifheader) {
198 if let Some(lineabove) = fmt.lineabove {
199 lines.push(create_line(&lineabove, &col_width));
200 }
201 }
202 if let Some(headers) = headers {
203 let headers: Vec<&Box<dyn Unstyle>> = headers.to_ref_vec();
205 for data in create_data_lines(&headers, str_align, num_align, &col_width, &col_spec) {
206 lines.push(create_data_line(&fmt.headerrow, col_nb, &data));
207 }
208 if let Some(linebelowheader) = fmt.linebelowheader {
210 lines.push(create_line(&linebelowheader, &col_width));
211 }
212 }
213 for (i, content) in contents.iter().enumerate() {
215 if i != 0 {
217 if let Some(linebetweenrows) = fmt.linebetweenrows.clone() {
218 lines.push(create_line(&linebetweenrows, &col_width));
219 }
220 }
221 let mut unstylable_content = Vec::with_capacity(content.len());
223 let mut temp_unstyle_store = HashMap::new();
224 let mut temp_strings_store = HashMap::new();
225 for (col, cell) in content.iter().enumerate() {
226 if let Some(u) = cell.to_unstylable() {
227 temp_unstyle_store.insert(col, u);
228 } else {
229 temp_strings_store.insert(
230 col,
231 Box::new(cell.to_string_with_precision(col_spec[col].1).unwrap())
232 as Box<dyn Unstyle>,
233 );
234 }
235 }
236 for col in 0..col_nb {
237 if let Some(u) = temp_unstyle_store.get(&col) {
238 unstylable_content.push(*u);
239 } else {
240 unstylable_content.push(temp_strings_store.get(&col).unwrap());
241 }
242 }
243 for data in create_data_lines(
244 &unstylable_content,
245 str_align,
246 num_align,
247 &col_width,
248 &col_spec,
249 ) {
250 lines.push(create_data_line(&fmt.datarow, col_nb, &data));
251 }
252 }
253 if !(headers.is_some() && fmt.hidelinebelowifheader) {
255 if let Some(linebelow) = fmt.linebelow {
256 lines.push(create_line(&linebelow, &col_width));
257 }
258 }
259 lines.join("\n")
261 }
262}
263
264fn get_col_width<'a>(
267 col_nb: usize,
268 headers: &Option<Headers>,
269 contents: &[Vec<Cell<'a>>],
270 col_spec: &[(bool, usize)],
271 num_align: &Align,
272) -> Vec<usize> {
273 let mut col_width = vec![0; col_nb];
274 for col in 0..col_nb {
275 let mut max = 0;
276 if let Some(headers) = headers {
277 if let Some(h) = headers.get(col) {
278 max = *h
279 .unstyle()
280 .split('\n')
281 .map(|s| UnicodeWidthStr::width(s as &str))
282 .max()
283 .get_or_insert(0)
284 + MIN_PADDING;
285 }
286 }
287 for row in contents.iter() {
288 if let Some(c) = row.get(col) {
289 let width = if col_spec[col].0 && num_align == &Align::Decimal && col_spec[col].1 > 0
290 {
291 c.to_string_with_precision(col_spec[col].1).unwrap().len()
292 } else if let Some(u) = c.to_unstylable() {
293 *u.unstyle()
294 .split('\n')
295 .map(|s| UnicodeWidthStr::width(s as &str))
296 .max()
297 .get_or_insert(0)
298 } else {
299 c.to_string().unwrap().len()
300 };
301 max = cmp::max(width, max);
302 }
303 }
304 col_width[col] = max;
305 }
306 col_width
307}
308
309fn get_col_specs(col_nb: usize, contents: &[Vec<Cell>]) -> Vec<(bool, usize)> {
310 let mut col_spec = vec![(false, 0); col_nb];
311 for (col, spec) in col_spec.iter_mut().enumerate().take(col_nb) {
312 let mut max = 0;
313 let mut all = true;
314 for row in contents.iter() {
315 if let Some(cell) = row.get(col) {
316 if cell.is_a_number() {
317 max = cmp::max(max, cell.digits_len());
318 } else {
319 all = false;
320 }
321 }
322 }
323 *spec = (all, max);
324 }
325 col_spec
326}
327
328fn create_line(line: &style::Line, col_width: &[usize]) -> String {
329 (line.begin.clone()
330 + &col_width
331 .iter()
332 .map(|w| line.hline.repeat(*w))
333 .collect::<Vec<String>>()
334 .join(&line.sep)
335 + &line.end)
336 .trim_end()
337 .to_string()
338}
339
340fn create_data_line(row: &style::DataRow, col_nb: usize, content: &[String]) -> String {
341 let mut v = Vec::with_capacity(col_nb);
342 for col in 0..col_nb {
343 if let Some(c) = content.get(col) {
344 v.push(String::from(c));
345 } else {
346 v.push(String::from(""));
347 }
348 }
349 (row.begin.clone() + &v.join(&row.sep) + &row.end)
350 .trim_end()
351 .to_string()
352}
353
354#[allow(clippy::borrowed_box)]
355fn format_unstylable<'a>(
356 word: &Box<dyn Unstyle + 'a>,
357 line_idx: usize,
358 align: &Align,
359 width: usize,
360) -> String {
361 if let Some(unstyled_word) = word.unstyle().split('\n').nth(line_idx) {
362 let word = word.to_string();
363 let word = word
364 .split('\n')
365 .nth(line_idx)
366 .expect("unstyled word can't have more \\n than styled one");
367 let width =
368 width - (UnicodeWidthStr::width(unstyled_word as &str) - unstyled_word.chars().count());
369 let formatted = match align {
370 Align::Right => format!("{:>width$}", unstyled_word, width = width),
371 Align::Left => format!("{:<width$}", unstyled_word, width = width),
372 Align::Center => format!("{:^width$}", unstyled_word, width = width),
373 Align::Decimal => {
374 let mut out = format!("{:>width$}", unstyled_word, width = width);
375 if let Some(dot) = out.rfind('.') {
376 if out[(dot + 1)..].bytes().all(|c| c == b'0') {
377 out.replace_range(dot.., &" ".repeat(out.len() - dot));
378 } else {
379 for i in (dot + 1..out.len()).rev() {
380 if out.as_bytes()[i] == b'0' {
381 out.replace_range(i..i + 1, " ");
382 }
383 }
384 }
385 }
386 out
387 }
388 };
389 if unstyled_word != word {
390 formatted.replace(&unstyled_word, word)
391 } else {
392 formatted
393 }
394 } else {
395 " ".repeat(width)
396 }
397}
398
399#[allow(clippy::borrowed_box)]
400fn create_data_lines<'a>(
401 content: &[&Box<dyn Unstyle + 'a>],
402 str_align: &Align,
403 num_align: &Align,
404 col_width: &[usize],
405 col_spec: &[(bool, usize)],
406) -> Vec<Vec<String>> {
407 let lines_nb = content.iter().map(|u| u.nb_of_lines()).max().unwrap();
408 let mut lines = Vec::with_capacity(lines_nb);
409 for i in 0..lines_nb {
410 let formatted: Vec<_> = content
411 .iter()
412 .enumerate()
413 .map(|(col, text)| {
414 let align = if col_spec[col].0 {
415 &num_align
417 } else {
418 &str_align
420 };
421 format_unstylable(text, i, align, col_width[col])
422 })
423 .collect();
424 lines.push(formatted);
425 }
426 lines
427}
428
429#[cfg(test)]
432mod tests {
433
434 use super::*;
435
436 fn headerless(style: Style) -> Table<'static> {
437 Table::new(
438 style,
439 vec![
440 vec![Cell::from("spam"), Cell::Float(41.9999)],
441 vec![Cell::from("eggs"), Cell::Int(451)],
442 ],
443 None,
444 )
445 }
446
447 fn table(style: Style) -> Table<'static> {
448 Table::new(
449 style.clone(),
450 headerless(style).contents,
451 Some(Headers::from(vec!["strings", "numbers"])),
452 )
453 }
454
455 fn multiline_headerless(style: Style) -> Table<'static> {
456 let mut table = Table::new(
457 style,
458 vec![
459 vec![Cell::from("foo bar\nbaz\nbau"), Cell::from("hello")],
460 vec![Cell::from(""), Cell::from("multiline\nworld")],
461 ],
462 None,
463 );
464 table.set_align(Align::Center, Align::Right);
465 table
466 }
467
468 fn multiline(style: Style) -> Table<'static> {
469 Table::new(
470 style,
471 vec![vec![Cell::Int(2), Cell::from("foo\nbar")]],
472 Some(Headers::from(vec!["more\nspam eggs", "more spam\n& eggs"])),
473 )
474 }
475
476 fn multiline_empty_cells(style: Style) -> Table<'static> {
477 Table::new(
478 style.clone(),
479 vec![
480 vec![Cell::Int(1), Cell::from(""), Cell::from("")],
481 vec![
482 Cell::Int(2),
483 Cell::from("very long data"),
484 Cell::from("fold\nthis"),
485 ],
486 ],
487 Some(Headers::from(vec!["hdr", "data", "fold"])),
488 )
489 }
490
491 fn multiline_empty_cells_headerless(style: Style) -> Table<'static> {
492 Table::new(
493 style,
494 vec![
495 vec![Cell::Int(0), Cell::from(""), Cell::from("")],
496 vec![Cell::Int(1), Cell::from(""), Cell::from("")],
497 vec![
498 Cell::Int(2),
499 Cell::from("very long data"),
500 Cell::from("fold\nthis"),
501 ],
502 ],
503 None,
504 )
505 }
506
507 #[test]
508 fn plain() {
509 let result = table(Style::Plain).tabulate();
511 let expected = vec![
512 "strings numbers",
513 "spam 41.9999",
514 "eggs 451",
515 ]
516 .join("\n");
517 assert_eq!(expected, result);
518 }
519
520 #[test]
521 fn plain_headerless() {
522 let result = headerless(Style::Plain).tabulate();
524 let expected = vec!["spam 41.9999", "eggs 451"].join("\n");
525 assert_eq!(expected, result);
526 }
527
528 #[test]
529 fn plain_multiline_headerless() {
530 let result = multiline_headerless(Style::Plain).tabulate();
532 let expected = vec![
533 "foo bar hello",
534 " baz",
535 " bau",
536 " multiline",
537 " world",
538 ]
539 .join("\n");
540 assert_eq!(expected, result);
541 }
542
543 #[test]
544 fn plain_multiline() {
545 let result = multiline(Style::Plain).tabulate();
547 let expected = vec![
548 " more more spam",
549 " spam eggs & eggs",
550 " 2 foo",
551 " bar",
552 ]
553 .join("\n");
554 assert_eq!(expected, result);
555 }
556
557 #[test]
558 fn plain_multiline_with_empty_cells() {
559 let result = multiline_empty_cells(Style::Plain).tabulate();
561 let expected = vec![
562 " hdr data fold",
563 " 1",
564 " 2 very long data fold",
565 " this",
566 ]
567 .join("\n");
568 assert_eq!(expected, result);
569 }
570
571 #[test]
572 fn plain_multiline_with_empty_cells_headerless() {
573 let result = multiline_empty_cells_headerless(Style::Plain).tabulate();
575 let expected = vec![
576 "0",
577 "1",
578 "2 very long data fold",
579 " this",
580 ]
581 .join("\n");
582 assert_eq!(expected, result);
583 }
584
585 #[test]
586 fn simple() {
587 let result = table(Style::Simple).tabulate();
589 let expected = vec![
590 "strings numbers",
591 "--------- ---------",
592 "spam 41.9999",
593 "eggs 451",
594 ]
595 .join("\n");
596 assert_eq!(expected, result);
597 }
598
599 #[test]
600 fn simple_multiline_2() {
601 let mut table = Table::new(
603 Style::Simple,
604 vec![
605 vec![Cell::from("foo"), Cell::from("bar")],
606 vec![Cell::from("spam"), Cell::from("multiline\nworld")],
607 ],
608 Some(Headers::from(vec!["key", "value"])),
609 );
610 table.set_align(Align::Center, Align::Right);
611 let result = table.tabulate();
612 let expected = vec![
613 " key value",
614 "----- ---------",
615 " foo bar",
616 "spam multiline",
617 " world",
618 ]
619 .join("\n");
620 assert_eq!(expected, result);
621 }
622
623 #[test]
624 fn simple_headerless() {
625 let result = headerless(Style::Simple).tabulate();
627 let expected = vec![
628 "---- --------",
629 "spam 41.9999",
630 "eggs 451",
631 "---- --------",
632 ]
633 .join("\n");
634 assert_eq!(expected, result);
635 }
636
637 #[test]
638 fn simple_multiline_headerless() {
639 let result = multiline_headerless(Style::Simple).tabulate();
641 let expected = vec![
642 "------- ---------",
643 "foo bar hello",
644 " baz",
645 " bau",
646 " multiline",
647 " world",
648 "------- ---------",
649 ]
650 .join("\n");
651 assert_eq!(expected, result);
652 }
653
654 #[test]
655 fn simple_multiline() {
656 let result = multiline(Style::Simple).tabulate();
658 let expected = vec![
659 " more more spam",
660 " spam eggs & eggs",
661 "----------- -----------",
662 " 2 foo",
663 " bar",
664 ]
665 .join("\n");
666 assert_eq!(expected, result);
667 }
668
669 #[test]
670 fn simple_multiline_ascii_escaped() {
671 let mut headers = Headers::with_capacity(2);
673 headers
674 .push(AsciiEscapedString::from("more\nspam \x1b[31meggs\x1b[0m"))
675 .push(String::from("more spam\n& eggs"));
676 let result = Table::new(
677 Style::Simple,
678 vec![vec![Cell::Int(2), Cell::from("foo\nbar")]],
679 Some(headers),
680 )
681 .tabulate();
682 let expected = vec![
683 " more more spam",
684 " spam \x1b[31meggs\x1b[0m & eggs",
685 "----------- -----------",
686 " 2 foo",
687 " bar",
688 ]
689 .join("\n");
690 assert_eq!(expected, result);
691 }
692
693 #[test]
694 fn simple_multiline_with_empty_cells() {
695 let result = multiline_empty_cells(Style::Simple).tabulate();
697 let expected = vec![
698 " hdr data fold",
699 "----- -------------- ------",
700 " 1",
701 " 2 very long data fold",
702 " this",
703 ]
704 .join("\n");
705 assert_eq!(expected, result);
706 }
707
708 #[test]
709 fn simple_multiline_with_empty_cells_headerless() {
710 let result = multiline_empty_cells_headerless(Style::Simple).tabulate();
712 let expected = vec![
713 "- -------------- ----",
714 "0",
715 "1",
716 "2 very long data fold",
717 " this",
718 "- -------------- ----",
719 ]
720 .join("\n");
721 assert_eq!(expected, result);
722 }
723
724 #[test]
725 fn github() {
726 let result = table(Style::Github).tabulate();
728 let expected = vec![
729 "| strings | numbers |",
730 "|-----------|-----------|",
731 "| spam | 41.9999 |",
732 "| eggs | 451 |",
733 ]
734 .join("\n");
735 assert_eq!(expected, result);
736 }
737
738 #[test]
739 fn grid() {
740 let result = table(Style::Grid).tabulate();
742 let expected = vec![
743 "+-----------+-----------+",
744 "| strings | numbers |",
745 "+===========+===========+",
746 "| spam | 41.9999 |",
747 "+-----------+-----------+",
748 "| eggs | 451 |",
749 "+-----------+-----------+",
750 ]
751 .join("\n");
752 assert_eq!(expected, result);
753 }
754
755 #[test]
756 fn grid_wide_characters() {
757 let headers = Headers::from(vec!["strings", "配列"]);
759 let contents = vec![
760 vec![
761 Cell::from("Ответ на главный вопрос жизни, вселенной и всего такого"),
762 Cell::Int(42),
763 ],
764 vec![Cell::from("pi"), Cell::Float(3.1415)],
765 ];
766 let result = Table::new(Style::Grid, contents, Some(headers)).tabulate();
767 let expected = vec![
768 "+---------------------------------------------------------+---------+",
769 "| strings | 配列 |",
770 "+=========================================================+=========+",
771 "| Ответ на главный вопрос жизни, вселенной и всего такого | 42 |",
772 "+---------------------------------------------------------+---------+",
773 "| pi | 3.1415 |",
774 "+---------------------------------------------------------+---------+",
775 ]
776 .join("\n");
777 assert_eq!(expected, result);
778 }
779
780 #[test]
781 fn issue_14() {
782 let headers = Headers::from(vec!["Hello", "World"]);
783 let contents = vec![vec![Cell::from("✔"), Cell::from("foo")]];
784
785 let result = Table::new(Style::Grid, contents, Some(headers)).tabulate();
786 let expected = vec![
787 "+---------+---------+",
788 "| Hello | World |",
789 "+=========+=========+",
790 "| ✔ | foo |",
791 "+---------+---------+",
792 ]
793 .join("\n");
794 assert_eq!(expected, result);
795 }
796
797 #[test]
798 fn grid_headerless() {
799 let result = headerless(Style::Grid).tabulate();
801 let expected = vec![
802 "+------+----------+",
803 "| spam | 41.9999 |",
804 "+------+----------+",
805 "| eggs | 451 |",
806 "+------+----------+",
807 ]
808 .join("\n");
809 assert_eq!(expected, result);
810 }
811
812 #[test]
813 fn grid_multiline_headerless() {
814 let result = multiline_headerless(Style::Grid).tabulate();
816 let expected = vec![
817 "+---------+-----------+",
818 "| foo bar | hello |",
819 "| baz | |",
820 "| bau | |",
821 "+---------+-----------+",
822 "| | multiline |",
823 "| | world |",
824 "+---------+-----------+",
825 ]
826 .join("\n");
827 assert_eq!(expected, result);
828 }
829
830 #[test]
831 fn grid_multiline() {
832 let result = multiline(Style::Grid).tabulate();
834 let expected = vec![
835 "+-------------+-------------+",
836 "| more | more spam |",
837 "| spam eggs | & eggs |",
838 "+=============+=============+",
839 "| 2 | foo |",
840 "| | bar |",
841 "+-------------+-------------+",
842 ]
843 .join("\n");
844 assert_eq!(expected, result);
845 }
846
847 #[test]
848 fn grid_multiline_with_empty_cells() {
849 let result = multiline_empty_cells(Style::Grid).tabulate();
851 let expected = vec![
852 "+-------+----------------+--------+",
853 "| hdr | data | fold |",
854 "+=======+================+========+",
855 "| 1 | | |",
856 "+-------+----------------+--------+",
857 "| 2 | very long data | fold |",
858 "| | | this |",
859 "+-------+----------------+--------+",
860 ]
861 .join("\n");
862 assert_eq!(expected, result);
863 }
864
865 #[test]
866 fn grid_multiline_with_empty_cells_headerless() {
867 let result = multiline_empty_cells_headerless(Style::Grid).tabulate();
869 let expected = vec![
870 "+---+----------------+------+",
871 "| 0 | | |",
872 "+---+----------------+------+",
873 "| 1 | | |",
874 "+---+----------------+------+",
875 "| 2 | very long data | fold |",
876 "| | | this |",
877 "+---+----------------+------+",
878 ]
879 .join("\n");
880 assert_eq!(expected, result);
881 }
882
883 #[test]
884 fn fancy_grid() {
885 let result = table(Style::Fancy).tabulate();
887 let expected = vec![
888 "╒═══════════╤═══════════╕",
889 "│ strings │ numbers │",
890 "╞═══════════╪═══════════╡",
891 "│ spam │ 41.9999 │",
892 "├───────────┼───────────┤",
893 "│ eggs │ 451 │",
894 "╘═══════════╧═══════════╛",
895 ]
896 .join("\n");
897 assert_eq!(expected, result);
898 }
899
900 #[test]
901 fn fancy_grid_headerless() {
902 let result = headerless(Style::Fancy).tabulate();
904 let expected = vec![
905 "╒══════╤══════════╕",
906 "│ spam │ 41.9999 │",
907 "├──────┼──────────┤",
908 "│ eggs │ 451 │",
909 "╘══════╧══════════╛",
910 ]
911 .join("\n");
912 assert_eq!(expected, result);
913 }
914
915 #[test]
916 fn fancy_grid_multiline_headerless() {
917 let result = multiline_headerless(Style::Fancy).tabulate();
919 let expected = vec![
920 "╒═════════╤═══════════╕",
921 "│ foo bar │ hello │",
922 "│ baz │ │",
923 "│ bau │ │",
924 "├─────────┼───────────┤",
925 "│ │ multiline │",
926 "│ │ world │",
927 "╘═════════╧═══════════╛",
928 ]
929 .join("\n");
930 assert_eq!(expected, result);
931 }
932
933 #[test]
934 fn fancy_grid_multiline() {
935 let result = multiline(Style::Fancy).tabulate();
937 let expected = vec![
938 "╒═════════════╤═════════════╕",
939 "│ more │ more spam │",
940 "│ spam eggs │ & eggs │",
941 "╞═════════════╪═════════════╡",
942 "│ 2 │ foo │",
943 "│ │ bar │",
944 "╘═════════════╧═════════════╛",
945 ]
946 .join("\n");
947 assert_eq!(expected, result);
948 }
949
950 #[test]
951 fn fancy_grid_multiline_with_empty_cells() {
952 let result = multiline_empty_cells(Style::Fancy).tabulate();
954 let expected = vec![
955 "╒═══════╤════════════════╤════════╕",
956 "│ hdr │ data │ fold │",
957 "╞═══════╪════════════════╪════════╡",
958 "│ 1 │ │ │",
959 "├───────┼────────────────┼────────┤",
960 "│ 2 │ very long data │ fold │",
961 "│ │ │ this │",
962 "╘═══════╧════════════════╧════════╛",
963 ]
964 .join("\n");
965 assert_eq!(expected, result);
966 }
967
968 #[test]
969 fn fancy_grid_multiline_with_empty_cells_headerless() {
970 let result = multiline_empty_cells_headerless(Style::Fancy).tabulate();
972 let expected = vec![
973 "╒═══╤════════════════╤══════╕",
974 "│ 0 │ │ │",
975 "├───┼────────────────┼──────┤",
976 "│ 1 │ │ │",
977 "├───┼────────────────┼──────┤",
978 "│ 2 │ very long data │ fold │",
979 "│ │ │ this │",
980 "╘═══╧════════════════╧══════╛",
981 ]
982 .join("\n");
983 assert_eq!(expected, result);
984 }
985
986 #[test]
987 fn presto() {
988 let result = table(Style::Presto).tabulate();
990 let expected = vec![
991 " strings | numbers",
992 "-----------+-----------",
993 " spam | 41.9999",
994 " eggs | 451",
995 ]
996 .join("\n");
997 assert_eq!(expected, result);
998 }
999
1000 #[test]
1001 fn presto_headerless() {
1002 let result = headerless(Style::Presto).tabulate();
1004 let expected = vec![" spam | 41.9999", " eggs | 451"].join("\n");
1005 assert_eq!(expected, result);
1006 }
1007
1008 #[test]
1009 fn presto_multiline_headerless() {
1010 let result = multiline_headerless(Style::Presto).tabulate();
1012 let expected = vec![
1013 " foo bar | hello",
1014 " baz |",
1015 " bau |",
1016 " | multiline",
1017 " | world",
1018 ]
1019 .join("\n");
1020 assert_eq!(expected, result);
1021 }
1022
1023 #[test]
1024 fn presto_multiline() {
1025 let result = multiline(Style::Presto).tabulate();
1027 let expected = vec![
1028 " more | more spam",
1029 " spam eggs | & eggs",
1030 "-------------+-------------",
1031 " 2 | foo",
1032 " | bar",
1033 ]
1034 .join("\n");
1035 assert_eq!(expected, result);
1036 }
1037
1038 #[test]
1039 fn presto_multiline_with_empty_cells() {
1040 let result = multiline_empty_cells(Style::Presto).tabulate();
1042 let expected = vec![
1043 " hdr | data | fold",
1044 "-------+----------------+--------",
1045 " 1 | |",
1046 " 2 | very long data | fold",
1047 " | | this",
1048 ]
1049 .join("\n");
1050 assert_eq!(expected, result);
1051 }
1052
1053 #[test]
1054 fn presto_multiline_with_empty_cells_headerless() {
1055 let result = multiline_empty_cells_headerless(Style::Presto).tabulate();
1057 let expected = vec![
1058 " 0 | |",
1059 " 1 | |",
1060 " 2 | very long data | fold",
1061 " | | this",
1062 ]
1063 .join("\n");
1064 assert_eq!(expected, result);
1065 }
1066
1067 #[test]
1068 fn fancygithub_grid() {
1069 let result = table(Style::FancyGithub).tabulate();
1070 let expected = vec![
1071 "│ strings │ numbers │",
1072 "├───────────┼───────────┤",
1073 "│ spam │ 41.9999 │",
1074 "│ eggs │ 451 │",
1075 ]
1076 .join("\n");
1077 assert_eq!(expected, result);
1078 }
1079
1080 #[test]
1081 fn fancygithub_grid_headerless() {
1082 let result = headerless(Style::FancyGithub).tabulate();
1083 let expected = vec!["│ spam │ 41.9999 │", "│ eggs │ 451 │"].join("\n");
1084 assert_eq!(expected, result);
1085 }
1086
1087 #[test]
1088 fn fancygithub_grid_multiline_headerless() {
1089 let result = multiline_headerless(Style::FancyGithub).tabulate();
1090 let expected = vec![
1091 "│ foo bar │ hello │",
1092 "│ baz │ │",
1093 "│ bau │ │",
1094 "│ │ multiline │",
1095 "│ │ world │",
1096 ]
1097 .join("\n");
1098 assert_eq!(expected, result);
1099 }
1100
1101 #[test]
1102 fn fancygithub_grid_multiline() {
1103 let result = multiline(Style::FancyGithub).tabulate();
1104 let expected = vec![
1105 "│ more │ more spam │",
1106 "│ spam eggs │ & eggs │",
1107 "├─────────────┼─────────────┤",
1108 "│ 2 │ foo │",
1109 "│ │ bar │",
1110 ]
1111 .join("\n");
1112 assert_eq!(expected, result);
1113 }
1114
1115 #[test]
1116 fn fancygithub_grid_multiline_with_empty_cells() {
1117 let result = multiline_empty_cells(Style::FancyGithub).tabulate();
1118 let expected = vec![
1119 "│ hdr │ data │ fold │",
1120 "├───────┼────────────────┼────────┤",
1121 "│ 1 │ │ │",
1122 "│ 2 │ very long data │ fold │",
1123 "│ │ │ this │",
1124 ]
1125 .join("\n");
1126 assert_eq!(expected, result);
1127 }
1128
1129 #[test]
1130 fn fancygithub_grid_multiline_with_empty_cells_headerless() {
1131 let result = multiline_empty_cells_headerless(Style::FancyGithub).tabulate();
1132 let expected = vec![
1133 "│ 0 │ │ │",
1134 "│ 1 │ │ │",
1135 "│ 2 │ very long data │ fold │",
1136 "│ │ │ this │",
1137 ]
1138 .join("\n");
1139 assert_eq!(expected, result);
1140 }
1141
1142 #[test]
1143 fn fancypresto_grid() {
1144 let result = table(Style::FancyPresto).tabulate();
1145 let expected = vec![
1146 "strings │ numbers",
1147 "──────────┼──────────",
1148 "spam │ 41.9999",
1149 "eggs │ 451",
1150 ]
1151 .join("\n");
1152 assert_eq!(expected, result);
1153 }
1154
1155 #[test]
1156 fn fancypresto_grid_headerless() {
1157 let result = headerless(Style::FancyPresto).tabulate();
1158 let expected = vec!["spam │ 41.9999", "eggs │ 451"].join("\n");
1159 assert_eq!(expected, result);
1160 }
1161
1162 #[test]
1163 fn fancypresto_grid_multiline_headerless() {
1164 let result = multiline_headerless(Style::FancyPresto).tabulate();
1165 let expected = vec![
1166 "foo bar │ hello",
1167 " baz │",
1168 " bau │",
1169 " │ multiline",
1170 " │ world",
1171 ]
1172 .join("\n");
1173 assert_eq!(expected, result);
1174 }
1175
1176 #[test]
1177 fn fancypresto_grid_multiline() {
1178 let result = multiline(Style::FancyPresto).tabulate();
1179 let expected = vec![
1180 " more │ more spam",
1181 " spam eggs │ & eggs",
1182 "────────────┼────────────",
1183 " 2 │ foo",
1184 " │ bar",
1185 ]
1186 .join("\n");
1187 assert_eq!(expected, result);
1188 }
1189
1190 #[test]
1191 fn fancypresto_grid_multiline_with_empty_cells() {
1192 let result = multiline_empty_cells(Style::FancyPresto).tabulate();
1193 let expected = vec![
1194 " hdr │ data │ fold",
1195 "──────┼────────────────┼───────",
1196 " 1 │ │",
1197 " 2 │ very long data │ fold",
1198 " │ │ this",
1199 ]
1200 .join("\n");
1201 assert_eq!(expected, result);
1202 }
1203
1204 #[test]
1205 fn fancypresto_grid_multiline_with_empty_cells_headerless() {
1206 let result = multiline_empty_cells_headerless(Style::FancyPresto).tabulate();
1207 let expected = vec![
1208 "0 │ │",
1209 "1 │ │",
1210 "2 │ very long data │ fold",
1211 " │ │ this",
1212 ]
1213 .join("\n");
1214 assert_eq!(expected, result);
1215 }
1216
1217 #[cfg(feature = "ansi_term_style")]
1218 #[test]
1219 fn ansi_term_colored_content<'a>() {
1220 use ansi_term::Colour::Red;
1221 use ansi_term::{ANSIString, ANSIStrings};
1222
1223 let some_value = format!("{:b}", 42);
1224 let strings: &[ANSIString<'a>] =
1225 &[Red.paint("["), Red.bold().paint(some_value), Red.paint("]")];
1226
1227 let result = Table::new(
1228 Style::Grid,
1229 vec![vec![
1230 Cell::Int(42),
1231 Cell::Text(Box::new(ANSIStrings(&strings))),
1232 ]],
1233 Some(Headers::from(vec!["Int", "Colored binary"])),
1234 )
1235 .tabulate();
1236
1237 let expected = vec![
1238 "+-------+------------------+",
1239 "| Int | Colored binary |",
1240 "+=======+==================+",
1241 "| 42 | \u{1b}[31m[\u{1b}[1m101010\u{1b}[0m\u{1b}[31m]\u{1b}[0m |",
1242 "+-------+------------------+",
1243 ]
1244 .join("\n");
1245 assert_eq!(expected, result);
1246 }
1247
1248 #[cfg(feature = "ansi_term_style")]
1249 #[test]
1250 fn ansi_styled_borders() {
1251 let mut table = table(Style::Fancy);
1252 table.set_border_style(ansi_term::Color::Green.bold());
1253 let result = table.tabulate();
1254 let expected = vec![
1255 "\u{1b}[1;32m╒═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╤═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╕\u{1b}[0m",
1256 "\u{1b}[1;32m│ \u{1b}[0mstrings \u{1b}[1;32m │ \u{1b}[0m numbers\u{1b}[1;32m │\u{1b}[0m",
1257 "\u{1b}[1;32m╞═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╪═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╡\u{1b}[0m",
1258 "\u{1b}[1;32m│ \u{1b}[0mspam \u{1b}[1;32m │ \u{1b}[0m 41.9999\u{1b}[1;32m │\u{1b}[0m",
1259 "\u{1b}[1;32m├─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─┼─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─\u{1b}[0m\u{1b}[1;32m─┤\u{1b}[0m",
1260 "\u{1b}[1;32m│ \u{1b}[0meggs \u{1b}[1;32m │ \u{1b}[0m 451 \u{1b}[1;32m │\u{1b}[0m",
1261 "\u{1b}[1;32m╘═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╧═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═\u{1b}[0m\u{1b}[1;32m═╛\u{1b}[0m"
1262 ].join("\n");
1263 assert_eq!(expected, result);
1264 }
1265}