cliform/
table.rs

1#![allow(dead_code)]
2
3pub enum TableHeaderStyle {
4    LeftAlign,
5    CenterAlign,
6    RightAlign,
7}
8
9#[derive(PartialEq)]
10pub enum TableLineStyle {
11    NoLines,
12    Lines,
13}
14
15pub struct TableStyle {
16    header: TableHeaderStyle,
17    lines: TableLineStyle,
18}
19
20pub struct Table<T> {
21    header: Vec<T>,
22    content: Vec<Vec<T>>,
23    max_cols: usize,
24    max_col_size: usize,
25    style: TableStyle,
26}
27
28impl<T: std::fmt::Display> Table<T> {
29    pub fn new() -> Table<T> {
30        let header = vec![];
31        let content = vec![vec![]];
32        let max_cols = header.len();
33        let max_col_size = 0;
34        let style = TableStyle { 
35            header: TableHeaderStyle::LeftAlign, 
36            lines: TableLineStyle::Lines
37        };
38
39        return Table { header, content, max_cols, max_col_size, style }
40    }
41
42    pub fn header(&mut self, header: Vec<T>) {
43        self.max_cols = usize::max(self.max_cols, header.len());
44        let max_size = header.iter()
45            .map(|item| { item.to_string().len() })
46            .max()
47            .unwrap();
48        self.max_col_size = usize::max(self.max_col_size, max_size);
49        self.header = header;
50    }
51
52    pub fn style(&mut self, style: TableStyle) {
53        self.style = style;
54    }
55
56    pub fn push(&mut self, row: Vec<T>) {
57        self.max_cols = usize::max(self.max_cols, row.len());
58        let max_size = row.iter()
59            .map(|item| { item.to_string().len() })
60            .max()
61            .unwrap();
62        self.max_col_size = usize::max(self.max_col_size, max_size);
63        self.content.push(row);
64    }
65
66    pub fn to_string(&self, padding: usize) -> String {
67        if self.max_cols == 0 {
68            return String::new();
69        }
70
71        let col_width = self.max_col_size + padding;
72        let mut result = String::new();
73
74        for item in &self.header {
75            result.push_str(&format!("{item: <col_width$}"));
76        }
77        result = result.trim_end_matches(' ').to_string();
78
79        result.push('\n');
80
81        if self.style.lines == TableLineStyle::Lines {
82            result.push_str(&"─".repeat(self.max_cols * (self.max_col_size + padding) - padding));
83        }
84
85        for row in &self.content {
86            for item in row {
87                result.push_str(&format!("{item: <col_width$}"));
88            }
89            result = result.trim_end_matches(' ').to_string();
90            result.push('\n');
91        }
92        result = result.trim_end_matches('\n').to_string();
93
94        result
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn empty() {
104        let table: Table<&str> = Table::new();
105        assert_eq!(
106            table.to_string(2),
107            ""
108        )
109    }
110
111    #[test]
112    fn emtpy_with_header() {
113        let mut table = Table::new();
114        table.header(vec!["First", "Second", "Third"]);
115
116        assert_eq!(
117            table.to_string(2),
118"First   Second  Third
119──────────────────────"
120        )
121    }
122
123    #[test]
124    fn table() {
125        let mut table = Table::new();
126        table.header(vec!["first", "second", "third"]);
127        table.push(vec!["Hello", "World", "!"]);
128        table.push(vec!["How", "are", "you?"]);
129        table.push(vec!["Great", "weather", "right?"]);
130
131        assert_eq!(
132            table.to_string(2),
133"first    second   third
134─────────────────────────
135Hello    World    !
136How      are      you?
137Great    weather  right?"
138        )
139    }
140}