1use std::{
7 collections::HashMap,
8 fmt::{Display, Write},
9};
10
11pub struct Table {
13 header: Box<[Box<dyn Display>]>,
15 body: HashMap<(usize, usize), Box<dyn Display>>,
16 nrows: usize,
17}
18
19impl Table {
20 pub fn new<I>(header: I, nrows: usize) -> Self
21 where
22 I: IntoIterator<Item: Display + 'static>,
23 {
24 fn as_dyn_display<T: Display + 'static>(x: T) -> Box<dyn Display> {
25 Box::new(x)
26 }
27
28 let header: Box<[_]> = header.into_iter().map(as_dyn_display).collect();
29 Self {
30 header,
31 body: HashMap::new(),
32 nrows,
33 }
34 }
35
36 pub fn nrows(&self) -> usize {
37 self.nrows
38 }
39
40 pub fn ncols(&self) -> usize {
41 self.header.len()
42 }
43
44 pub fn insert<T>(&mut self, item: T, row: usize, col: usize) -> bool
45 where
46 T: Display + 'static,
47 {
48 self.check_bounds(row, col);
49 self.body.insert((row, col), Box::new(item)).is_some()
50 }
51
52 pub fn get(&self, row: usize, col: usize) -> Option<&dyn Display> {
53 self.check_bounds(row, col);
54 self.body.get(&(row, col)).map(|x| &**x)
55 }
56
57 pub fn row(&mut self, row: usize) -> Row<'_> {
58 self.check_bounds(row, 0);
59 Row::new(self, row)
60 }
61
62 #[expect(clippy::panic, reason = "table interfaces are bounds checked")]
63 fn check_bounds(&self, row: usize, col: usize) {
64 if row >= self.nrows() {
65 panic!("row {} is out of bounds (max {})", row, self.nrows());
66 }
67 if col >= self.ncols() {
68 panic!("col {} is out of bounds (max {})", col, self.ncols());
69 }
70 }
71}
72
73pub struct Row<'a> {
74 table: &'a mut Table,
75 row: usize,
76}
77
78impl<'a> Row<'a> {
79 fn new(table: &'a mut Table, row: usize) -> Self {
81 Self { table, row }
82 }
83
84 pub fn insert<T>(&mut self, item: T, col: usize) -> bool
86 where
87 T: Display + 'static,
88 {
89 self.table.insert(item, self.row, col)
90 }
91}
92
93impl Display for Table {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 const SEP: &str = ", ";
96
97 struct Count(usize);
99
100 impl Write for Count {
101 fn write_str(&mut self, s: &str) -> std::fmt::Result {
102 self.0 += s.len();
103 Ok(())
104 }
105 }
106
107 fn formatted_size<T>(x: &T) -> usize
108 where
109 T: Display + ?Sized,
110 {
111 let mut buf = Count(0);
112 match write!(&mut buf, "{}", x) {
113 Ok(()) => buf.0,
115 Err(_) => 0,
116 }
117 }
118
119 let mut widths: Vec<usize> = self.header.iter().map(formatted_size).collect();
120 for row in 0..self.nrows() {
121 for (col, width) in widths.iter_mut().enumerate() {
122 if let Some(v) = self.body.get(&(row, col)) {
123 *width = (*width).max(formatted_size(v))
124 }
125 }
126 }
127
128 let header_width: usize = widths.iter().sum::<usize>() + (widths.len() - 1) * SEP.len();
129
130 let mut buf = String::new();
131 std::iter::zip(widths.iter(), self.header.iter())
133 .enumerate()
134 .try_for_each(|(col, (width, head))| {
135 buf.clear();
136 write!(buf, "{}", head)?;
137 write!(f, "{:>width$}", buf)?;
138 if col + 1 != self.ncols() {
139 write!(f, "{}", SEP)?;
140 }
141 Ok(())
142 })?;
143
144 write!(f, "\n{:=>header_width$}\n", "")?;
146
147 for row in 0..self.nrows() {
149 for (col, width) in widths.iter_mut().enumerate() {
150 match self.body.get(&(row, col)) {
151 Some(v) => {
152 buf.clear();
153 write!(buf, "{}", v)?;
154 write!(f, "{:>width$}", buf)?;
155 }
156 None => write!(f, "{:>width$}", "")?,
157 }
158 if col + 1 != self.ncols() {
159 write!(f, "{}", SEP)?;
160 } else {
161 writeln!(f)?;
162 }
163 }
164 }
165 Ok(())
166 }
167}
168
169pub(crate) struct Banner<'a>(&'a str);
174
175impl<'a> Banner<'a> {
176 pub(crate) fn new(message: &'a str) -> Self {
177 Self(message)
178 }
179}
180
181impl std::fmt::Display for Banner<'_> {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 let st = format!("# {} #", self.0);
184 let len = st.len();
185 writeln!(f, "{:#>len$}", "")?;
186 writeln!(f, "{}", st)?;
187 writeln!(f, "{:#>len$}", "")?;
188 Ok(())
189 }
190}
191
192#[derive(Debug, Clone, Copy)]
209pub struct Indent<'a> {
210 string: &'a str,
211 spaces: usize,
212}
213
214impl<'a> Indent<'a> {
215 pub fn new(string: &'a str, spaces: usize) -> Self {
217 Self { string, spaces }
218 }
219}
220
221impl std::fmt::Display for Indent<'_> {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 let spaces = self.spaces;
224
225 let mut first = true;
229 self.string.lines().try_for_each(|ln| {
230 if first {
231 first = false;
232 write!(f, "{: >spaces$}{}", "", ln)
233 } else {
234 write!(f, "\n{: >spaces$}{}", "", ln)
235 }
236 })
237 }
238}
239
240pub struct Delimit<'a, I> {
273 itr: std::cell::Cell<Option<I>>,
274 delimiter: &'a str,
275 last: &'a str,
276 pair: Option<&'a str>,
277}
278
279impl<'a, I> Delimit<'a, I> {
280 pub fn new(itr: impl IntoIterator<IntoIter = I>, delimiter: &'a str) -> Self {
286 Self {
287 itr: std::cell::Cell::new(Some(itr.into_iter())),
288 delimiter,
289 last: delimiter,
290 pair: None,
291 }
292 }
293
294 pub fn with_last(mut self, last: &'a str) -> Self {
296 self.last = last;
297 self
298 }
299
300 pub fn with_pair(mut self, pair: &'a str) -> Self {
302 self.pair = Some(pair);
303 self
304 }
305}
306
307impl<I> std::fmt::Display for Delimit<'_, I>
308where
309 I: Iterator<Item: std::fmt::Display>,
310{
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 let Some(mut itr) = self.itr.take() else {
313 return write!(f, "<missing>");
314 };
315
316 let mut count = 0;
317 let mut current = if let Some(item) = itr.next() {
318 item
319 } else {
320 return Ok(());
322 };
323
324 loop {
325 match itr.next() {
326 None => {
327 let delimiter = if count == 0 {
334 ""
335 } else if count == 1 {
336 self.pair.unwrap_or(self.last)
337 } else {
338 self.last
339 };
340
341 return write!(f, "{}{}", delimiter, current);
342 }
343 Some(next) => {
344 let delimiter = if count == 0 { "" } else { self.delimiter };
346
347 write!(f, "{}{}", delimiter, current)?;
348 count += 1;
349 current = next;
350 }
351 }
352 }
353 }
354}
355
356#[derive(Debug, Clone, Copy)]
371pub struct Quote<T>(pub T);
372
373impl<T> std::fmt::Display for Quote<T>
374where
375 T: std::fmt::Display,
376{
377 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378 write!(f, "\"{}\"", self.0)
379 }
380}
381
382enum MaybeLazy<'a> {
387 Lazy(&'a dyn std::fmt::Display),
388 Debug(&'a dyn std::fmt::Debug),
389 Eager(String),
390}
391
392impl std::fmt::Display for MaybeLazy<'_> {
393 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394 match self {
395 Self::Lazy(lazy) => write!(f, "{}", lazy),
396 Self::Debug(debug) => write!(f, "{:?}", debug),
397 Self::Eager(s) => f.write_str(s),
398 }
399 }
400}
401
402impl std::fmt::Debug for MaybeLazy<'_> {
403 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
404 struct AsDisplay<'a>(&'a dyn std::fmt::Display);
405 impl std::fmt::Debug for AsDisplay<'_> {
406 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407 self.0.fmt(f)
408 }
409 }
410
411 match self {
412 Self::Lazy(o) => {
413 let as_display = AsDisplay(o);
414 f.debug_tuple("MaybeLazy::Lazy").field(&as_display).finish()
415 }
416 Self::Debug(o) => f.debug_tuple("MaybeLazy::Debug").field(o).finish(),
417 Self::Eager(s) => f.debug_tuple("MaybeLazy::Eager").field(s).finish(),
418 }
419 }
420}
421
422#[derive(Debug, Default)]
464pub struct KeyValue<'a> {
465 kv: Vec<(&'a str, Nesting, MaybeLazy<'a>)>,
466 max_key_length: usize,
467}
468
469#[derive(Debug, Clone, Copy)]
470enum Nesting {
471 Auto,
472 Forced,
473}
474
475impl<'a> KeyValue<'a> {
476 pub fn new() -> Self {
478 Self {
479 kv: Vec::new(),
480 max_key_length: 0,
481 }
482 }
483
484 pub fn push(&mut self, key: &'a str, value: &'a dyn std::fmt::Display) {
486 self.max_key_length = self.max_key_length.max(key.len());
487 self.kv.push((key, Nesting::Auto, MaybeLazy::Lazy(value)))
488 }
489
490 pub fn push_debug(&mut self, key: &'a str, value: &'a dyn std::fmt::Debug) {
492 self.max_key_length = self.max_key_length.max(key.len());
493 self.kv.push((key, Nesting::Auto, MaybeLazy::Debug(value)))
494 }
495
496 pub fn push_eager<D>(&mut self, key: &'a str, value: D)
498 where
499 D: std::fmt::Display,
500 {
501 self.max_key_length = self.max_key_length.max(key.len());
502 self.kv
503 .push((key, Nesting::Auto, MaybeLazy::Eager(value.to_string())))
504 }
505
506 pub fn push_eager_nested<D>(&mut self, key: &'a str, value: D)
508 where
509 D: std::fmt::Display,
510 {
511 self.max_key_length = self.max_key_length.max(key.len());
512 self.kv
513 .push((key, Nesting::Forced, MaybeLazy::Eager(value.to_string())))
514 }
515
516 pub fn len(&self) -> usize {
518 self.kv.len()
519 }
520
521 pub fn is_empty(&self) -> bool {
523 self.kv.is_empty()
524 }
525}
526
527impl std::fmt::Display for KeyValue<'_> {
528 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
529 let width = self.max_key_length;
530 let mut prefix = "";
531 for (k, nesting, v) in self.kv.iter() {
532 let rendered = v.to_string();
533 let nest = match nesting {
534 Nesting::Forced => true,
535 Nesting::Auto => rendered.contains('\n'),
536 };
537
538 if nest {
539 write!(f, "{}{}:\n{}", prefix, k, Indent::new(&rendered, 2))?
540 } else {
541 let pad = (width + 1).saturating_sub(k.len());
544 write!(f, "{}{}:{:pad$}{rendered}", prefix, k, "")?;
545 }
546 prefix = "\n";
547 }
548 Ok(())
549 }
550}
551
552#[cfg(test)]
557mod tests {
558 use super::*;
559
560 #[test]
561 fn test_banner() {
562 let b = Banner::new("hello world");
563 let s = b.to_string();
564
565 let expected = "###############\n\
566 # hello world #\n\
567 ###############\n";
568
569 assert_eq!(s, expected);
570
571 let b = Banner::new("");
572 let s = b.to_string();
573
574 let expected = "####\n\
575 # #\n\
576 ####\n";
577
578 assert_eq!(s, expected);
579
580 let b = Banner::new("foo");
581 let s = b.to_string();
582
583 let expected = "#######\n\
584 # foo #\n\
585 #######\n";
586
587 assert_eq!(s, expected);
588 }
589
590 #[test]
591 fn test_format() {
592 {
594 let headers = ["h 0"];
595 let mut table = Table::new(headers, 3);
596 table.insert("a", 0, 0);
597 table.insert("hello world", 1, 0);
598 table.insert(62, 2, 0);
599
600 let s = table.to_string();
601 let expected = r#"
602 h 0
603===========
604 a
605hello world
606 62
607"#;
608 assert_eq!(s, expected.strip_prefix('\n').unwrap());
609 }
610
611 {
613 let headers = ["a really really long header", "h1"];
614 let mut table = Table::new(headers, 3);
615 table.insert("a", 0, 0);
616 table.insert("b", 0, 1);
617
618 table.insert("hello world", 1, 0);
619 table.insert("hello world version 2", 1, 1);
620
621 table.insert(7, 2, 0);
622 table.insert("bar", 2, 1);
623
624 let s = table.to_string();
625 let expected = r#"
626a really really long header, h1
627====================================================
628 a, b
629 hello world, hello world version 2
630 7, bar
631"#;
632 assert_eq!(s, expected.strip_prefix('\n').unwrap());
633 }
634 }
635
636 #[test]
637 fn test_row_api() {
638 let mut table = Table::new(["a", "b", "c"], 2);
639 let mut row = table.row(0);
640 row.insert(1, 0);
641 row.insert("long", 1);
642 row.insert("s", 2);
643
644 let mut row = table.row(1);
645 row.insert("string", 0);
646 row.insert(2, 1);
647 row.insert(3, 2);
648
649 let s = table.to_string();
650
651 let expected = r#"
652 a, b, c
653===================
654 1, long, s
655string, 2, 3
656"#;
657 assert_eq!(s, expected.strip_prefix('\n').unwrap());
658 }
659
660 #[test]
661 fn missing_values() {
662 let mut table = Table::new(["a", "loong", "c"], 1);
663 let mut row = table.row(0);
664 row.insert("string", 0);
665 row.insert("string", 2);
666
667 let s = table.to_string();
668 let expected = r#"
669 a, loong, c
670=========================
671string, , string
672"#;
673 assert_eq!(s, expected.strip_prefix('\n').unwrap());
674 }
675
676 #[test]
677 #[should_panic(expected = "row 3 is out of bounds (max 2)")]
678 fn test_panic_row() {
679 let mut table = Table::new([1, 2, 3], 2);
680 let _ = table.row(3);
681 }
682
683 #[test]
684 #[should_panic(expected = "col 3 is out of bounds (max 2)")]
685 fn test_panic_col() {
686 let mut table = Table::new([1, 2], 1);
687 let mut row = table.row(0);
688 row.insert(1, 3);
689 }
690
691 #[test]
692 fn test_indent_single_line() {
693 let s = Indent::new("hello", 4).to_string();
694 assert_eq!(s, " hello");
695 }
696
697 #[test]
698 fn test_indent_multi_line() {
699 let s = Indent::new("hello\nworld\nfoo", 2).to_string();
700 assert_eq!(s, " hello\n world\n foo");
701 }
702
703 #[test]
704 fn test_indent_zero_spaces() {
705 let s = Indent::new("hello\nworld", 0).to_string();
706 assert_eq!(s, "hello\nworld");
707 }
708
709 #[test]
710 fn test_indent_empty_string() {
711 let s = Indent::new("", 4).to_string();
712 assert_eq!(s, "");
713 }
714
715 #[test]
716 fn test_delimit_empty() {
717 let d = Delimit::new(std::iter::empty::<&str>(), ", ");
718 assert_eq!(d.to_string(), "");
719 }
720
721 #[test]
722 fn test_delimit_single_item() {
723 let d = Delimit::new(["a"], ", ").with_last(", and ");
724 assert_eq!(d.to_string(), "a");
725 }
726
727 #[test]
728 fn test_delimit_two_items_with_last() {
729 let d = Delimit::new(["a", "b"], ", ").with_last(", and ");
730 assert_eq!(d.to_string(), "a, and b");
731 }
732
733 #[test]
734 fn test_delimit_two_items_with_pair() {
735 let d = Delimit::new(["a", "b"], ", ")
736 .with_last(", and ")
737 .with_pair(" and ");
738 assert_eq!(d.to_string(), "a and b");
739 }
740
741 #[test]
742 fn test_delimit_three_items_with_last() {
743 let d = Delimit::new(["a", "b", "c"], ", ")
744 .with_last(", and ")
745 .with_pair(" and ");
746 assert_eq!(d.to_string(), "a, b, and c");
747 }
748
749 #[test]
750 fn test_delimit_without_last() {
751 let d = Delimit::new(["x", "y", "z"], " | ");
752 assert_eq!(d.to_string(), "x | y | z");
753 }
754
755 #[test]
756 fn test_delimit_second_display_prints_missing() {
757 let d = Delimit::new(["a", "b"], ", ");
758 assert_eq!(d.to_string(), "a, b");
759 assert_eq!(d.to_string(), "<missing>");
760 }
761
762 #[test]
763 fn test_quote() {
764 assert_eq!(Quote("hello").to_string(), "\"hello\"");
765 }
766
767 #[test]
768 fn test_quote_with_integer() {
769 assert_eq!(Quote(42).to_string(), "\"42\"");
770 }
771
772 #[test]
773 fn test_delimit_with_quote() {
774 let d = Delimit::new(["topk", "range"].iter().map(Quote), ", ")
775 .with_last(", and ")
776 .with_pair(" and ");
777 assert_eq!(d.to_string(), "\"topk\" and \"range\"");
778 }
779
780 struct Snoop;
785
786 impl std::fmt::Display for Snoop {
787 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
788 write!(f, "display")
789 }
790 }
791
792 impl std::fmt::Debug for Snoop {
793 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
794 write!(f, "debug")
795 }
796 }
797
798 fn process(x: &str) -> &str {
800 let x = x.strip_prefix('\n').unwrap_or(x);
801 x.strip_suffix('\n').unwrap_or(x)
802 }
803
804 #[test]
805 fn test_key_value_empty() {
806 let kv = KeyValue::new();
807 assert_eq!(kv.to_string(), "");
808 }
809
810 #[test]
811 fn test_key_value_single_pair() {
812 let mut kv = KeyValue::new();
813 kv.push("a", &1);
814 assert_eq!(kv.to_string(), "a: 1");
815 }
816
817 #[test]
818 fn test_key_value_aligns_values() {
819 let mut kv = KeyValue::new();
820 kv.push("a", &1);
821 kv.push("hello", &"world");
822 kv.push("display", &Snoop);
823 kv.push_debug("debug", &Snoop);
824 let expected = process(
825 r#"
826a: 1
827hello: world
828display: display
829debug: debug
830"#,
831 );
832 assert_eq!(kv.to_string(), expected);
833 }
834
835 #[test]
836 fn test_key_value_push_eager() {
837 let mut kv = KeyValue::new();
838 kv.push_eager("a", 1);
839 kv.push_eager("hello", "world");
840
841 let expected = process(
842 r#"
843a: 1
844hello: world
845"#,
846 );
847
848 assert_eq!(kv.to_string(), expected);
849 }
850
851 #[test]
852 fn test_key_value_multiline_value_is_indented() {
853 let mut inner = KeyValue::new();
854 inner.push("x", &1);
855 inner.push("yy", &2);
856 let inner = inner.to_string();
857
858 let mut kv = KeyValue::new();
859 kv.push("name", &"example");
860 kv.push("nested", &inner);
861 kv.push("another line", &1);
862
863 let expected = process(
864 r#"
865name: example
866nested:
867 x: 1
868 yy: 2
869another line: 1
870"#,
871 );
872
873 assert_eq!(kv.to_string(), expected);
874 }
875
876 #[test]
877 fn maybe_lazy_debug() {
878 let x = MaybeLazy::Lazy(&Snoop);
879 let dbg = format!("{:?}", x);
880 assert_eq!(dbg, "MaybeLazy::Lazy(display)");
881
882 let x = MaybeLazy::Debug(&Snoop);
883 let dbg = format!("{:?}", x);
884 assert_eq!(dbg, "MaybeLazy::Debug(debug)");
885
886 let x = MaybeLazy::Eager("hello".into());
887 let dbg = format!("{:?}", x);
888 assert_eq!(dbg, "MaybeLazy::Eager(\"hello\")");
889 }
890}