tabled/tables/
extended.rs1use crate::grid::util::string::get_line_width;
5use crate::Tabled;
6use std::cell::RefCell;
7use std::fmt::{self, Debug, Display};
8use std::rc::Rc;
9
10#[cfg_attr(feature = "derive", doc = "```")]
19#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
20#[derive(Clone)]
53pub struct ExtendedTable {
54 fields: Vec<String>,
55 records: Vec<Vec<String>>,
56 template: Rc<RefCell<dyn Fn(usize) -> String>>,
57}
58
59impl ExtendedTable {
60 pub fn new<T>(iter: impl IntoIterator<Item = T>) -> Self
62 where
63 T: Tabled,
64 {
65 let data = iter
66 .into_iter()
67 .map(|i| {
68 i.fields()
69 .into_iter()
70 .map(|s| s.escape_debug().to_string())
71 .collect()
72 })
73 .collect();
74 let header = T::headers()
75 .into_iter()
76 .map(|s| s.escape_debug().to_string())
77 .collect();
78
79 Self {
80 records: data,
81 fields: header,
82 template: Rc::new(RefCell::new(record_template)),
83 }
84 }
85
86 pub fn truncate(&mut self, max: usize, suffix: &str) -> bool {
94 let teplate_width = self.records.len().to_string().len() + 13;
96 let min_width = teplate_width;
97 if max < min_width {
98 return false;
99 }
100
101 let suffix_width = get_line_width(suffix);
102 if max < suffix_width {
103 return false;
104 }
105
106 let max = max - suffix_width;
107
108 let fields_max_width = self
109 .fields
110 .iter()
111 .map(|s| get_line_width(s))
112 .max()
113 .unwrap_or_default();
114
115 let fields_affected = max < fields_max_width + 3;
117 if fields_affected {
118 if max < 3 {
119 return false;
120 }
121
122 let max = max - 3;
123
124 if max < suffix_width {
125 return false;
126 }
127
128 let max = max - suffix_width;
129
130 truncate_fields(&mut self.fields, max, suffix);
131 truncate_records(&mut self.records, 0, suffix);
132 } else {
133 let max = max - fields_max_width - 3 - suffix_width;
134 truncate_records(&mut self.records, max, suffix);
135 }
136
137 true
138 }
139
140 pub fn template<F>(mut self, template: F) -> Self
142 where
143 F: Fn(usize) -> String + 'static,
144 {
145 self.template = Rc::new(RefCell::new(template));
146 self
147 }
148}
149
150impl From<Vec<Vec<String>>> for ExtendedTable {
151 fn from(mut data: Vec<Vec<String>>) -> Self {
152 if data.is_empty() {
153 return Self {
154 fields: vec![],
155 records: vec![],
156 template: Rc::new(RefCell::new(record_template)),
157 };
158 }
159
160 let fields = data.remove(0);
161
162 Self {
163 fields,
164 records: data,
165 template: Rc::new(RefCell::new(record_template)),
166 }
167 }
168}
169
170impl Display for ExtendedTable {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 if self.records.is_empty() {
173 return Ok(());
174 }
175
176 let fields = self.fields.iter().collect::<Vec<_>>();
179
180 let max_field_width = fields
181 .iter()
182 .map(|s| get_line_width(s))
183 .max()
184 .unwrap_or_default();
185
186 let max_values_length = self
187 .records
188 .iter()
189 .map(|record| record.iter().map(|s| get_line_width(s)).max())
190 .max()
191 .unwrap_or_default()
192 .unwrap_or_default();
193
194 for (i, records) in self.records.iter().enumerate() {
195 write_header_template(f, &self.template, i, max_field_width, max_values_length)?;
196
197 for (value, field) in records.iter().zip(fields.iter()) {
198 writeln!(f)?;
199 write_record(f, field, value, max_field_width)?;
200 }
201
202 let is_last_record = i + 1 == self.records.len();
203 if !is_last_record {
204 writeln!(f)?;
205 }
206 }
207
208 Ok(())
209 }
210}
211
212impl Debug for ExtendedTable {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 f.debug_struct("ExtendedTable")
215 .field("fields", &self.fields)
216 .field("records", &self.records)
217 .finish_non_exhaustive()
218 }
219}
220
221fn truncate_records(records: &mut Vec<Vec<String>>, max_width: usize, suffix: &str) {
222 for fields in records {
223 truncate_fields(fields, max_width, suffix);
224 }
225}
226
227fn truncate_fields(records: &mut Vec<String>, max_width: usize, suffix: &str) {
228 for text in records {
229 truncate(text, max_width, suffix);
230 }
231}
232
233fn write_header_template(
234 f: &mut fmt::Formatter<'_>,
235 template: &Rc<RefCell<dyn Fn(usize) -> String>>,
236 index: usize,
237 max_field_width: usize,
238 max_values_length: usize,
239) -> fmt::Result {
240 let record_template = template.borrow()(index);
241 let mut template = format!("-{record_template}-");
242 let default_template_length = template.len();
243
244 let max_line_width = std::cmp::max(
246 max_field_width + 3 + max_values_length,
247 default_template_length,
248 );
249 let rest_to_print = max_line_width - default_template_length;
250 if rest_to_print > 0 {
251 if max_field_width + 2 > default_template_length {
253 let part1 = (max_field_width + 1) - default_template_length;
254 let part2 = rest_to_print - part1 - 1;
255
256 template.extend(
257 std::iter::repeat_n('-', part1)
258 .chain(std::iter::once('+'))
259 .chain(std::iter::repeat_n('-', part2)),
260 );
261 } else {
262 template.extend(std::iter::repeat_n('-', rest_to_print));
263 }
264 }
265
266 write!(f, "{template}")?;
267
268 Ok(())
269}
270
271fn write_record(
272 f: &mut fmt::Formatter<'_>,
273 field: &str,
274 value: &str,
275 max_field_width: usize,
276) -> fmt::Result {
277 write!(f, "{field:max_field_width$} | {value}")
278}
279
280fn truncate(text: &mut String, max: usize, suffix: &str) {
281 let original_len = text.len();
282
283 if max == 0 || text.is_empty() {
284 *text = String::new();
285 } else {
286 *text = crate::util::string::cut_str(text, max).into_owned();
287 }
288
289 let cut_was_done = text.len() < original_len;
290 if !suffix.is_empty() && cut_was_done {
291 text.push_str(suffix);
292 }
293}
294
295fn record_template(index: usize) -> String {
296 format!("[ RECORD {index} ]")
297}