1use std::{cmp::max, collections::HashMap, fmt::Display};
2
3use anyhow::Result;
4
5#[derive(PartialEq, Eq)]
6enum Value {
7 Seperator,
8 NewLine,
9 Entry(String, String),
10}
11
12#[derive(Default)]
28pub struct List(Vec<Value>);
29
30impl List {
31 pub fn add(&mut self, title: impl ToString, value: impl ToString) {
32 self.0
33 .push(Value::Entry(title.to_string(), value.to_string()));
34 }
35
36 pub fn add_newline(&mut self) {
37 self.0.push(Value::NewLine);
38 }
39
40 pub fn add_seperator(&mut self) {
41 if self.0.last() == Some(&Value::Seperator) {
42 return;
43 }
44 self.0.push(Value::Seperator);
45 }
46
47 pub fn longest_title(&self) -> usize {
48 self.0
49 .iter()
50 .map(|value| match value {
51 Value::Seperator => 0,
52 Value::NewLine => 0,
53 Value::Entry(title, _) => title.len(),
54 })
55 .max()
56 .unwrap_or(0)
57 }
58}
59
60impl Display for List {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 let longest_key = self.longest_title();
63 let entries = self
64 .0
65 .iter()
66 .map(|entry| match entry {
67 Value::Seperator => None,
68 Value::NewLine => Some("".to_owned()),
69 Value::Entry(title, value) => {
70 let padding = " ".repeat(longest_key - title.len());
71 Some(format!("{}{}: {}", title, padding, value))
72 }
73 })
74 .collect::<Vec<_>>();
75
76 let longest_entry = entries
77 .iter()
78 .map(|entry| entry.as_ref().map(|s| s.len()).unwrap_or(0))
79 .max()
80 .unwrap_or(0);
81
82 let seperator = "-".repeat(longest_entry);
83
84 let formatted = entries
85 .into_iter()
86 .map(|entry| entry.map(|s| s.to_string()).unwrap_or(seperator.clone()))
87 .collect::<Vec<_>>()
88 .join("\n");
89
90 write!(f, "{formatted}")
91 }
92}
93
94#[derive(Default)]
95pub struct Table {
96 headers: Vec<String>,
97 rows: Vec<Vec<String>>,
98}
99
100impl Table {
101 pub fn add_header(&mut self, header: impl ToString) {
102 self.headers.push(header.to_string());
103 }
104
105 pub fn add_row(&mut self, row: Vec<impl ToString>) -> Result<()> {
106 if self.headers.len() != row.len() {
107 anyhow::bail!("Row length does not match header length");
108 }
109 self.rows
110 .push(row.into_iter().map(|x| x.to_string()).collect());
111 Ok(())
112 }
113}
114
115impl Display for Table {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 let mut longest_columns = self
118 .headers
119 .iter()
120 .enumerate()
121 .map(|(column_id, x)| (column_id, x.len()))
122 .collect::<HashMap<_, _>>();
123
124 for row in self.rows.iter() {
125 for (column_id, value) in row.iter().enumerate() {
126 longest_columns
127 .entry(column_id)
128 .and_modify(|x| *x = max(*x, value.len()));
129 }
130 }
131 let separator = self
132 .headers
133 .iter()
134 .enumerate()
135 .map(|(column_id, _)| "-".repeat(longest_columns[&column_id]))
136 .collect::<Vec<_>>()
137 .join("-|-");
138
139 let mut table = vec![
140 self.headers
141 .iter()
142 .enumerate()
143 .map(|(column_id, header)| {
144 let padding = " ".repeat(longest_columns[&column_id] - header.len());
145 format!("{}{}", header, padding)
146 })
147 .collect::<Vec<_>>()
148 .join(" | "),
149 separator.clone(),
150 ];
151
152 for row in &self.rows {
153 table.push(
154 row.iter()
155 .enumerate()
156 .map(|(column_id, value)| {
157 let padding = " ".repeat(longest_columns[&column_id] - value.len());
158 format!("{}{}", value, padding)
159 })
160 .collect::<Vec<_>>()
161 .join(" | "),
162 );
163 table.push(separator.clone());
164 }
165
166 let formatted = table.join("\n");
167
168 write!(f, "{formatted}")
169 }
170}