tv_cli/table/
mod.rs

1use self::cell::{Align, Cell};
2use self::style::{Frame, Style};
3
4pub mod cell;
5pub mod style;
6
7type Row<T> = Vec<Cell<T>>;
8
9fn display_row<T>(row: &Row<T>, width_list: &Vec<usize>, align: Align, style: Style) -> String
10where
11    T: std::fmt::Display + Clone,
12{
13    let frame: Frame = style.into();
14
15    row.iter()
16        .zip(width_list.clone())
17        .map(|(cell, width)| {
18            let mut cell = cell.clone();
19            format!("{}", cell.set_width(width).set_align(align))
20        })
21        .collect::<Vec<_>>()
22        .join(&frame.separator)
23}
24
25pub struct Table<T>
26where
27    T: std::fmt::Display + Clone,
28{
29    cols: Vec<Row<T>>,
30    header: Option<Row<T>>,
31    style: Style,
32    align: Align,
33    no_headers: bool,
34    // cache
35    row_len: usize,
36}
37
38impl<T> Table<T>
39where
40    T: std::fmt::Display + Clone,
41{
42    pub fn new() -> Self {
43        Self {
44            cols: Vec::new(),
45            header: None,
46            style: Style::Markdown,
47            align: Align::None,
48            no_headers: false,
49            row_len: 0,
50        }
51    }
52
53    pub fn push_row(&mut self, row: Row<T>) {
54        self.cols.push(row.clone());
55        self.row_len = self.row_len.max(row.len());
56    }
57
58    pub fn set_header(&mut self, header: Option<Row<T>>) -> &mut Self {
59        self.header = header;
60        self
61    }
62
63    pub fn set_align(&mut self, align: Align) -> &mut Self {
64        self.align = align;
65        self
66    }
67
68    pub fn set_style(&mut self, style: Style) -> &mut Self {
69        self.style = style;
70        self
71    }
72
73    pub fn set_no_headers(&mut self, no_headers: bool) -> &mut Self {
74        self.no_headers = no_headers;
75        self
76    }
77
78    fn cell_width_list(&self) -> Vec<usize> {
79        let widths = self
80            .cols
81            .iter()
82            .map(|xs| xs.iter().map(|x| x.width()).collect::<Vec<_>>());
83
84        let widths = (0..self.row_len).map(|idx| {
85            widths
86                .clone()
87                .map(move |x| x.get(idx).map(Clone::clone).unwrap_or_default())
88                .max()
89                .unwrap_or_default()
90        });
91
92        widths
93            .enumerate()
94            .map(|(idx, w)| {
95                self.header
96                    .clone()
97                    .and_then(|x| x.get(idx).map(|x| x.width()).map(|x| x.max(w)))
98                    .unwrap_or(w)
99            })
100            .map(|x| {
101                if let Style::Markdown = self.style {
102                    match self.align {
103                        Align::Center => x.max(3),
104                        Align::Left | Align::Right => x.max(2),
105                        Align::None => x,
106                    }
107                } else {
108                    x
109                }
110            })
111            .collect()
112    }
113}
114
115impl<T> std::fmt::Display for Table<T>
116where
117    T: std::fmt::Display + Clone,
118{
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        let width_list = self.cell_width_list();
121        let frame: Frame = self.style.into();
122
123        if frame.has_cover {
124            write!(
125                f,
126                "{}{}{}\n",
127                frame.top_left,
128                width_list
129                    .clone()
130                    .into_iter()
131                    .map(|x| frame.border.repeat(x))
132                    .collect::<Vec<_>>()
133                    .join(&frame.top),
134                frame.top_right
135            )?;
136        }
137
138        if !self.no_headers {
139            if let Some(header) = &self.header {
140                write!(
141                    f,
142                    "{}{}{}\n",
143                    frame.separator,
144                    display_row(header, &width_list, self.align, self.style),
145                    frame.separator,
146                )?;
147                let border = width_list
148                    .clone()
149                    .into_iter()
150                    .map(|x| {
151                        if let Style::Markdown = self.style {
152                            frame.align_border(&self.align, x)
153                        } else {
154                            frame.border.repeat(x)
155                        }
156                    })
157                    .collect::<Vec<_>>()
158                    .join(&frame.center);
159                write!(f, "{}{}{}\n", frame.left, border, frame.right)?;
160            }
161        }
162
163        let table = self
164            .cols
165            .iter()
166            .map(|row| display_row(row, &width_list, self.align, self.style))
167            .map(|x| format!("{}{}{}", frame.separator, x, frame.separator))
168            .collect::<Vec<_>>()
169            .join("\n");
170        write!(f, "{}", table)?;
171
172        if frame.has_cover {
173            write!(
174                f,
175                "\n{}{}{}",
176                frame.bottom_left,
177                width_list
178                    .clone()
179                    .into_iter()
180                    .map(|x| frame.border.repeat(x))
181                    .collect::<Vec<_>>()
182                    .join(&frame.bottom),
183                frame.bottom_right
184            )?;
185        }
186
187        Ok(())
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use crate::table::{cell::Cell, Table};
194
195    #[test]
196    fn create_table() {
197        let mut table = Table::new();
198        table.push_row(vec![Cell::new("0"), Cell::new("1"), Cell::new("2")]);
199
200        assert_eq!(table.cols.len(), 1);
201        let row = table.cols.get(0).unwrap();
202        assert_eq!(row.len(), 3);
203    }
204
205    #[test]
206    fn display_table() {
207        let mut table = Table::new();
208        table.push_row(vec![Cell::new("0"), Cell::new("1"), Cell::new("2")]);
209
210        let expected = "|0|1|2|";
211        let actual = format!("{}", table);
212        assert_eq!(expected, actual);
213    }
214
215    #[test]
216    fn display_table_multiline() {
217        let mut table = Table::new();
218        table.push_row(vec![Cell::new("00"), Cell::new("01"), Cell::new("02")]);
219        table.push_row(vec![Cell::new("10"), Cell::new("11"), Cell::new("12")]);
220
221        let expected = "|00|01|02|\n|10|11|12|";
222        let actual = format!("{}", table);
223        assert_eq!(expected, actual);
224    }
225
226    #[test]
227    fn cell_width() {
228        let mut table = Table::new();
229        table.push_row(vec![Cell::new("00000"), Cell::new("0001"), Cell::new("02")]);
230        table.push_row(vec![Cell::new("10"), Cell::new("11"), Cell::new("12")]);
231
232        let expected = vec![5, 4, 2];
233        assert_eq!(expected, table.cell_width_list());
234    }
235
236    #[test]
237    fn cell_width_with_header() {
238        let mut table = Table::new();
239        table.push_row(vec![Cell::new("00000"), Cell::new("0001"), Cell::new("02")]);
240        table.push_row(vec![Cell::new("10"), Cell::new("11"), Cell::new("12")]);
241        table.set_header(Some(vec![
242            Cell::new("hogehogehoge"),
243            Cell::new("abcdefg"),
244            Cell::new("x"),
245        ]));
246
247        let expected = vec![12, 7, 2];
248        assert_eq!(expected, table.cell_width_list());
249    }
250
251    #[test]
252    fn display_table_other_width() {
253        let mut table = Table::new();
254        table.push_row(vec![Cell::new("00000"), Cell::new("0001"), Cell::new("02")]);
255        table.push_row(vec![Cell::new("10"), Cell::new("11"), Cell::new("12")]);
256
257        let expected = "|00000|0001|02|\n|10   |11  |12|";
258        let actual = format!("{}", table);
259        assert_eq!(expected, actual);
260    }
261
262    #[test]
263    fn display_table_width_header() {
264        let mut table = Table::new();
265        table.push_row(vec![Cell::new("00000"), Cell::new("0001"), Cell::new("02")]);
266        table.push_row(vec![Cell::new("10"), Cell::new("11"), Cell::new("12")]);
267        table.set_header(Some(vec![
268            Cell::new("hogehogehoge"),
269            Cell::new("abcdefg"),
270            Cell::new("x"),
271        ]));
272
273        let expected =
274            "|hogehogehoge|abcdefg|x |\n|------------|-------|--|\n|00000       |0001   |02|\n|10          |11     |12|";
275        let actual = format!("{}", table);
276        assert_eq!(expected, actual);
277    }
278}