1use std::cmp::Ordering;
7use std::collections::HashMap;
8use std::fmt;
9use std::ops::Index as IndexTrait;
10use std::sync::Arc;
11
12use crate::types::Value;
13
14fn natural_cmp(a: &str, b: &str) -> Ordering {
17 match (a.parse::<i64>(), b.parse::<i64>()) {
19 (Ok(na), Ok(nb)) => na.cmp(&nb),
20 _ => a.cmp(b),
21 }
22}
23
24#[derive(Debug, Clone)]
26pub struct TextTable {
27 header: Arc<Vec<String>>,
29
30 rows: Vec<Row>,
32
33 superkey: Vec<String>,
35
36 column_index: HashMap<String, usize>,
38}
39
40impl TextTable {
41 pub fn new(header: Vec<String>) -> Self {
43 let column_index: HashMap<String, usize> = header
44 .iter()
45 .enumerate()
46 .map(|(i, name)| (name.to_lowercase(), i))
47 .collect();
48
49 Self {
50 header: Arc::new(header),
51 rows: Vec::new(),
52 superkey: Vec::new(),
53 column_index,
54 }
55 }
56
57 pub fn from_values(header: Vec<String>, values: Vec<Vec<Value>>) -> Self {
59 let column_index: HashMap<String, usize> = header
60 .iter()
61 .enumerate()
62 .map(|(i, name)| (name.to_lowercase(), i))
63 .collect();
64
65 let header = Arc::new(header);
66 let rows: Vec<Row> = values
67 .into_iter()
68 .map(|v| Row::new(v, Arc::clone(&header)))
69 .collect();
70
71 Self {
72 header,
73 rows,
74 superkey: Vec::new(),
75 column_index,
76 }
77 }
78
79 pub fn header(&self) -> &[String] {
81 &self.header
82 }
83
84 pub fn len(&self) -> usize {
86 self.rows.len()
87 }
88
89 pub fn is_empty(&self) -> bool {
91 self.rows.is_empty()
92 }
93
94 pub fn superkey(&self) -> &[String] {
96 &self.superkey
97 }
98
99 pub fn set_superkey(&mut self, keys: Vec<String>) {
101 self.superkey = keys;
102 }
103
104 pub fn add_keys(&mut self, keys: &[String]) {
106 for key in keys {
107 if !self.superkey.contains(key) {
108 self.superkey.push(key.clone());
109 }
110 }
111 }
112
113 pub fn append(&mut self, values: Vec<Value>) {
115 self.rows.push(Row::new(values, Arc::clone(&self.header)));
116 }
117
118 pub fn append_row(&mut self, row: Row) {
120 self.rows.push(row);
121 }
122
123 pub fn remove(&mut self, index: usize) -> Option<Row> {
125 if index < self.rows.len() {
126 Some(self.rows.remove(index))
127 } else {
128 None
129 }
130 }
131
132 pub fn get(&self, index: usize) -> Option<&Row> {
134 self.rows.get(index)
135 }
136
137 pub fn get_mut(&mut self, index: usize) -> Option<&mut Row> {
139 self.rows.get_mut(index)
140 }
141
142 pub fn iter(&self) -> impl Iterator<Item = &Row> {
144 self.rows.iter()
145 }
146
147 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Row> {
149 self.rows.iter_mut()
150 }
151
152 pub fn filter<F>(&self, predicate: F) -> TextTable
154 where
155 F: Fn(&Row) -> bool,
156 {
157 let rows: Vec<Row> = self.rows.iter().filter(|r| predicate(r)).cloned().collect();
158
159 TextTable {
160 header: Arc::clone(&self.header),
161 rows,
162 superkey: self.superkey.clone(),
163 column_index: self.column_index.clone(),
164 }
165 }
166
167 pub fn sort(&mut self) {
170 if self.superkey.is_empty() {
171 self.rows.sort_by(|a, b| {
173 let va = a.values.first().map(|v| v.as_string()).unwrap_or_default();
174 let vb = b.values.first().map(|v| v.as_string()).unwrap_or_default();
175 natural_cmp(&va, &vb)
176 });
177 } else {
178 let key_indices: Vec<usize> = self
180 .superkey
181 .iter()
182 .filter_map(|k| self.column_index.get(&k.to_lowercase()).copied())
183 .collect();
184
185 self.rows.sort_by(|a, b| {
186 for &idx in &key_indices {
187 let va = a.values.get(idx).map(|v| v.as_string()).unwrap_or_default();
188 let vb = b.values.get(idx).map(|v| v.as_string()).unwrap_or_default();
189 match natural_cmp(&va, &vb) {
190 std::cmp::Ordering::Equal => continue,
191 other => return other,
192 }
193 }
194 std::cmp::Ordering::Equal
195 });
196 }
197 }
198
199 pub fn sort_by_key<K, F>(&mut self, f: F)
201 where
202 K: Ord,
203 F: Fn(&Row) -> K,
204 {
205 self.rows.sort_by_key(f);
206 }
207
208 pub fn sort_by<F>(&mut self, compare: F)
210 where
211 F: Fn(&Row, &Row) -> std::cmp::Ordering,
212 {
213 self.rows.sort_by(compare);
214 }
215
216 pub fn row_with(&self, column: &str, value: &str) -> Option<&Row> {
218 let idx = self.column_index.get(&column.to_lowercase())?;
219 self.rows.iter().find(|r| {
220 r.values
221 .get(*idx)
222 .map(|v| v.as_string() == value)
223 .unwrap_or(false)
224 })
225 }
226
227 pub fn formatted(&self) -> String {
229 if self.rows.is_empty() {
230 return String::new();
231 }
232
233 let mut widths: Vec<usize> = self.header.iter().map(|h| h.len()).collect();
235 for row in &self.rows {
236 for (i, value) in row.values.iter().enumerate() {
237 if i < widths.len() {
238 widths[i] = widths[i].max(value.as_string().len());
239 }
240 }
241 }
242
243 let mut output = String::new();
244
245 for (i, name) in self.header.iter().enumerate() {
247 if i > 0 {
248 output.push_str(" ");
249 }
250 output.push_str(&format!("{:width$}", name, width = widths[i]));
251 }
252 output.push('\n');
253
254 for (i, &width) in widths.iter().enumerate() {
256 if i > 0 {
257 output.push_str(" ");
258 }
259 output.push_str(&"-".repeat(width));
260 }
261 output.push('\n');
262
263 for row in &self.rows {
265 for (i, value) in row.values.iter().enumerate() {
266 if i > 0 {
267 output.push_str(" ");
268 }
269 if i < widths.len() {
270 output.push_str(&format!("{:width$}", value.as_string(), width = widths[i]));
271 }
272 }
273 output.push('\n');
274 }
275
276 output
277 }
278
279 pub fn to_csv(&self) -> String {
281 let mut output = String::new();
282
283 output.push_str(&self.header.join(", "));
285 output.push('\n');
286
287 for row in &self.rows {
289 let values: Vec<String> = row.values.iter().map(|v| v.as_string()).collect();
290 output.push_str(&values.join(", "));
291 output.push('\n');
292 }
293
294 output
295 }
296
297 pub fn into_values(self) -> Vec<Vec<Value>> {
299 self.rows.into_iter().map(|r| r.values).collect()
300 }
301
302 pub fn values(&self) -> impl Iterator<Item = &Vec<Value>> {
304 self.rows.iter().map(|r| &r.values)
305 }
306
307 pub fn clear(&mut self) {
309 self.rows.clear();
310 }
311}
312
313impl fmt::Display for TextTable {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 write!(f, "{}", self.formatted())
316 }
317}
318
319impl<'a> IntoIterator for &'a TextTable {
320 type Item = &'a Row;
321 type IntoIter = std::slice::Iter<'a, Row>;
322
323 fn into_iter(self) -> Self::IntoIter {
324 self.rows.iter()
325 }
326}
327
328impl IntoIterator for TextTable {
329 type Item = Row;
330 type IntoIter = std::vec::IntoIter<Row>;
331
332 fn into_iter(self) -> Self::IntoIter {
333 self.rows.into_iter()
334 }
335}
336
337#[cfg(feature = "serde")]
338impl TextTable {
339 pub fn into_deserialize<T>(self) -> Result<Vec<T>, super::CliTableError>
341 where
342 T: serde::de::DeserializeOwned,
343 {
344 let header: Vec<String> = self.header.iter().map(|s| s.to_lowercase()).collect();
346
347 self.rows
348 .into_iter()
349 .map(|row| {
350 crate::de::from_record_borrowed(&header, row.values)
351 .map_err(|e| super::CliTableError::Parse(crate::ParseError::DeserializeError(e.to_string())))
352 })
353 .collect()
354 }
355}
356
357#[derive(Debug, Clone)]
359pub struct Row {
360 values: Vec<Value>,
362
363 header: Arc<Vec<String>>,
365}
366
367impl Row {
368 pub fn new(values: Vec<Value>, header: Arc<Vec<String>>) -> Self {
370 Self { values, header }
371 }
372
373 pub fn get(&self, column: &str) -> Option<&Value> {
375 let column_lower = column.to_lowercase();
376 self.header
377 .iter()
378 .position(|h| h.to_lowercase() == column_lower)
379 .and_then(|i| self.values.get(i))
380 }
381
382 pub fn get_mut(&mut self, column: &str) -> Option<&mut Value> {
384 let column_lower = column.to_lowercase();
385 self.header
386 .iter()
387 .position(|h| h.to_lowercase() == column_lower)
388 .and_then(|i| self.values.get_mut(i))
389 }
390
391 pub fn values(&self) -> &[Value] {
393 &self.values
394 }
395
396 pub fn header(&self) -> &[String] {
398 &self.header
399 }
400
401 pub fn to_map(&self) -> HashMap<String, Value> {
403 self.header
404 .iter()
405 .zip(self.values.iter())
406 .map(|(k, v)| (k.clone(), v.clone()))
407 .collect()
408 }
409}
410
411impl IndexTrait<&str> for Row {
412 type Output = Value;
413
414 fn index(&self, column: &str) -> &Self::Output {
415 self.get(column)
416 .unwrap_or_else(|| panic!("column '{}' not found", column))
417 }
418}
419
420impl IndexTrait<usize> for Row {
421 type Output = Value;
422
423 fn index(&self, index: usize) -> &Self::Output {
424 &self.values[index]
425 }
426}
427
428impl fmt::Display for Row {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 let values: Vec<String> = self.values.iter().map(|v| v.as_string()).collect();
431 write!(f, "{}", values.join(", "))
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438
439 fn sample_table() -> TextTable {
440 let header = vec![
441 "Interface".into(),
442 "Status".into(),
443 "IP_Address".into(),
444 ];
445 let values = vec![
446 vec![
447 Value::Single("eth0".into()),
448 Value::Single("up".into()),
449 Value::Single("192.168.1.1".into()),
450 ],
451 vec![
452 Value::Single("eth1".into()),
453 Value::Single("down".into()),
454 Value::Single("10.0.0.1".into()),
455 ],
456 vec![
457 Value::Single("lo".into()),
458 Value::Single("up".into()),
459 Value::Single("127.0.0.1".into()),
460 ],
461 ];
462 TextTable::from_values(header, values)
463 }
464
465 #[test]
466 fn test_table_creation() {
467 let table = sample_table();
468 assert_eq!(table.len(), 3);
469 assert_eq!(table.header().len(), 3);
470 }
471
472 #[test]
473 fn test_row_indexing_by_name() {
474 let table = sample_table();
475 let row = table.get(0).unwrap();
476 assert_eq!(row["Interface"].as_string(), "eth0");
477 assert_eq!(row["Status"].as_string(), "up");
478 assert_eq!(row["IP_Address"].as_string(), "192.168.1.1");
479 }
480
481 #[test]
482 fn test_row_indexing_case_insensitive() {
483 let table = sample_table();
484 let row = table.get(0).unwrap();
485 assert_eq!(row["interface"].as_string(), "eth0");
486 assert_eq!(row["INTERFACE"].as_string(), "eth0");
487 }
488
489 #[test]
490 fn test_filter() {
491 let table = sample_table();
492 let up_only = table.filter(|row| row["Status"].as_string() == "up");
493 assert_eq!(up_only.len(), 2);
494 }
495
496 #[test]
497 fn test_sort() {
498 let mut table = sample_table();
499 table.sort();
500 assert_eq!(table.get(0).unwrap()["Interface"].as_string(), "eth0");
501 assert_eq!(table.get(1).unwrap()["Interface"].as_string(), "eth1");
502 assert_eq!(table.get(2).unwrap()["Interface"].as_string(), "lo");
503 }
504
505 #[test]
506 fn test_sort_by_superkey() {
507 let mut table = sample_table();
508 table.set_superkey(vec!["Status".into(), "Interface".into()]);
509 table.sort();
510 assert_eq!(table.get(0).unwrap()["Status"].as_string(), "down");
512 }
513
514 #[test]
515 fn test_row_with() {
516 let table = sample_table();
517 let row = table.row_with("Interface", "eth1");
518 assert!(row.is_some());
519 assert_eq!(row.unwrap()["Status"].as_string(), "down");
520 }
521
522 #[test]
523 fn test_formatted_output() {
524 let table = sample_table();
525 let output = table.formatted();
526 assert!(output.contains("Interface"));
527 assert!(output.contains("eth0"));
528 assert!(output.contains("192.168.1.1"));
529 }
530
531 #[test]
532 fn test_iteration() {
533 let table = sample_table();
534 let count = table.iter().count();
535 assert_eq!(count, 3);
536 }
537
538 #[test]
539 fn test_append() {
540 let mut table = sample_table();
541 table.append(vec![
542 Value::Single("eth2".into()),
543 Value::Single("up".into()),
544 Value::Single("172.16.0.1".into()),
545 ]);
546 assert_eq!(table.len(), 4);
547 }
548
549 #[test]
550 fn test_remove() {
551 let mut table = sample_table();
552 let removed = table.remove(1);
553 assert!(removed.is_some());
554 assert_eq!(table.len(), 2);
555 }
556
557 #[test]
558 fn test_to_map() {
559 let table = sample_table();
560 let row = table.get(0).unwrap();
561 let map = row.to_map();
562 assert_eq!(map.get("Interface").unwrap().as_string(), "eth0");
563 }
564}