cli_table/
table.rs

1use std::io::Result;
2
3use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec};
4
5use crate::{
6    buffers::Buffers,
7    display::TableDisplay,
8    row::{Dimension as RowDimension, Row, RowStruct},
9    style::{Style, StyleStruct},
10    utils::*,
11};
12
13/// Struct for building a table on command line
14pub struct TableStruct {
15    /// Title row of the table
16    title: Option<RowStruct>,
17    /// Rows in the table
18    rows: Vec<RowStruct>,
19    /// Format of the table
20    format: TableFormat,
21    /// Style of the table
22    style: StyleStruct,
23    /// Color preferences for printing the table
24    color_choice: ColorChoice,
25}
26
27impl TableStruct {
28    /// Used to add a title row of a table
29    pub fn title<T: Row>(mut self, title: T) -> Self {
30        self.title = Some(title.row());
31        self
32    }
33
34    /// Used to set border of a table
35    pub fn border(mut self, border: Border) -> Self {
36        self.format.border = border;
37        self
38    }
39
40    /// Used to set column/row separators of a table
41    pub fn separator(mut self, separator: Separator) -> Self {
42        self.format.separator = separator;
43        self
44    }
45
46    /// Used to set the color preferences for printing the table
47    pub fn color_choice(mut self, color_choice: ColorChoice) -> Self {
48        self.color_choice = color_choice;
49        self
50    }
51
52    /// Returns a struct which implements the `Display` trait
53    pub fn display(&self) -> Result<TableDisplay> {
54        let writer = BufferWriter::stdout(self.color_choice);
55        let buffers = self.buffers(&writer)?;
56
57        let mut output = Vec::new();
58
59        for buffer in buffers {
60            output.append(&mut buffer.into_inner());
61        }
62
63        Ok(TableDisplay::new(output))
64    }
65
66    /// Prints current table to `stdout`
67    pub(crate) fn print_stdout(&self) -> Result<()> {
68        self.print_writer(BufferWriter::stdout(self.color_choice))
69    }
70
71    /// Prints current table to `stderr`
72    pub(crate) fn print_stderr(&self) -> Result<()> {
73        self.print_writer(BufferWriter::stderr(self.color_choice))
74    }
75
76    fn color_spec(&self) -> ColorSpec {
77        self.style.color_spec()
78    }
79
80    fn required_dimension(&self) -> Dimension {
81        let mut heights = Vec::with_capacity(self.rows.len() + 1);
82        let mut widths = Vec::new();
83
84        let title_dimension = self.title.as_ref().map(RowStruct::required_dimension);
85
86        if let Some(title_dimension) = title_dimension {
87            widths = title_dimension.widths;
88            heights.push(title_dimension.height);
89        }
90
91        for row in self.rows.iter() {
92            let row_dimension = row.required_dimension();
93
94            heights.push(row_dimension.height);
95
96            let new_widths = row_dimension.widths;
97
98            if widths.is_empty() {
99                widths = new_widths;
100            } else {
101                for (width, new_width) in widths.iter_mut().zip(new_widths.into_iter()) {
102                    *width = std::cmp::max(new_width, *width);
103                }
104            }
105        }
106
107        Dimension { widths, heights }
108    }
109
110    fn buffers(&self, writer: &BufferWriter) -> Result<Vec<Buffer>> {
111        let table_dimension = self.required_dimension();
112        let row_dimensions: Vec<RowDimension> = table_dimension.clone().into();
113        let mut row_dimensions = row_dimensions.into_iter();
114        let color_spec = self.color_spec();
115
116        let mut buffers = Buffers::new(writer);
117
118        print_horizontal_line(
119            &mut buffers,
120            self.format.border.top.as_ref(),
121            &table_dimension,
122            &self.format,
123            &color_spec,
124        )?;
125
126        if let Some(ref title) = self.title {
127            let title_dimension = row_dimensions.next().unwrap();
128            let mut title_buffers =
129                title.buffers(writer, title_dimension, &self.format, &color_spec)?;
130
131            buffers.append(&mut title_buffers)?;
132
133            if self.format.separator.title.is_some() {
134                print_horizontal_line(
135                    &mut buffers,
136                    self.format.separator.title.as_ref(),
137                    &table_dimension,
138                    &self.format,
139                    &color_spec,
140                )?
141            } else {
142                print_horizontal_line(
143                    &mut buffers,
144                    self.format.separator.row.as_ref(),
145                    &table_dimension,
146                    &self.format,
147                    &color_spec,
148                )?
149            }
150        }
151
152        let mut rows = self.rows.iter().zip(row_dimensions).peekable();
153
154        while let Some((row, row_dimension)) = rows.next() {
155            let mut row_buffers = row.buffers(writer, row_dimension, &self.format, &color_spec)?;
156
157            buffers.append(&mut row_buffers)?;
158
159            match rows.peek() {
160                Some(_) => print_horizontal_line(
161                    &mut buffers,
162                    self.format.separator.row.as_ref(),
163                    &table_dimension,
164                    &self.format,
165                    &color_spec,
166                )?,
167                None => print_horizontal_line(
168                    &mut buffers,
169                    self.format.border.bottom.as_ref(),
170                    &table_dimension,
171                    &self.format,
172                    &color_spec,
173                )?,
174            }
175        }
176
177        buffers.into_vec()
178    }
179
180    fn print_writer(&self, writer: BufferWriter) -> Result<()> {
181        let buffers = self.buffers(&writer)?;
182
183        for buffer in buffers.iter() {
184            writer.print(buffer)?;
185        }
186
187        Ok(())
188    }
189}
190
191/// Trait to convert raw type into table
192pub trait Table {
193    /// Converts raw type to a table
194    fn table(self) -> TableStruct;
195}
196
197impl<T, R> Table for T
198where
199    T: IntoIterator<Item = R>,
200    R: Row,
201{
202    fn table(self) -> TableStruct {
203        let rows = self.into_iter().map(Row::row).collect();
204
205        TableStruct {
206            title: Default::default(),
207            rows,
208            format: Default::default(),
209            style: Default::default(),
210            color_choice: ColorChoice::Always,
211        }
212    }
213}
214
215impl Table for TableStruct {
216    fn table(self) -> TableStruct {
217        self
218    }
219}
220
221impl Style for TableStruct {
222    fn foreground_color(mut self, foreground_color: Option<Color>) -> Self {
223        self.style = self.style.foreground_color(foreground_color);
224        self
225    }
226
227    fn background_color(mut self, background_color: Option<Color>) -> Self {
228        self.style = self.style.background_color(background_color);
229        self
230    }
231
232    fn bold(mut self, bold: bool) -> Self {
233        self.style = self.style.bold(bold);
234        self
235    }
236
237    fn underline(mut self, underline: bool) -> Self {
238        self.style = self.style.underline(underline);
239        self
240    }
241
242    fn italic(mut self, italic: bool) -> Self {
243        self.style = self.style.italic(italic);
244        self
245    }
246
247    fn intense(mut self, intense: bool) -> Self {
248        self.style = self.style.intense(intense);
249        self
250    }
251
252    fn dimmed(mut self, dimmed: bool) -> Self {
253        self.style = self.style.dimmed(dimmed);
254        self
255    }
256}
257
258/// A vertical line in a table (border or column separator)
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub struct VerticalLine {
261    pub(crate) filler: char,
262}
263
264impl Default for VerticalLine {
265    fn default() -> Self {
266        Self { filler: '|' }
267    }
268}
269
270impl VerticalLine {
271    /// Creates a new instance of vertical line
272    pub fn new(filler: char) -> Self {
273        Self { filler }
274    }
275}
276
277/// A horizontal line in a table (border or row separator)
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
279pub struct HorizontalLine {
280    pub(crate) left_end: char,
281    pub(crate) right_end: char,
282    pub(crate) junction: char,
283    pub(crate) filler: char,
284}
285
286impl Default for HorizontalLine {
287    fn default() -> Self {
288        Self {
289            left_end: '+',
290            right_end: '+',
291            junction: '+',
292            filler: '-',
293        }
294    }
295}
296
297impl HorizontalLine {
298    /// Creates a new instance of horizontal line
299    pub fn new(left_end: char, right_end: char, junction: char, filler: char) -> Self {
300        Self {
301            left_end,
302            right_end,
303            junction,
304            filler,
305        }
306    }
307}
308
309/// Borders of a table
310#[derive(Debug, Copy, Clone, PartialEq, Eq)]
311pub struct Border {
312    pub(crate) top: Option<HorizontalLine>,
313    pub(crate) bottom: Option<HorizontalLine>,
314    pub(crate) left: Option<VerticalLine>,
315    pub(crate) right: Option<VerticalLine>,
316}
317
318impl Border {
319    /// Creates a new builder for border
320    pub fn builder() -> BorderBuilder {
321        BorderBuilder(Border {
322            top: None,
323            bottom: None,
324            left: None,
325            right: None,
326        })
327    }
328}
329
330impl Default for Border {
331    fn default() -> Self {
332        Self {
333            top: Some(Default::default()),
334            bottom: Some(Default::default()),
335            left: Some(Default::default()),
336            right: Some(Default::default()),
337        }
338    }
339}
340
341/// Builder for border
342#[derive(Debug, Clone, PartialEq, Eq)]
343pub struct BorderBuilder(Border);
344
345impl BorderBuilder {
346    /// Set top border of a table
347    pub fn top(mut self, top: HorizontalLine) -> Self {
348        self.0.top = Some(top);
349        self
350    }
351
352    /// Set bottom border of a table
353    pub fn bottom(mut self, bottom: HorizontalLine) -> Self {
354        self.0.bottom = Some(bottom);
355        self
356    }
357
358    /// Set left border of a table
359    pub fn left(mut self, left: VerticalLine) -> Self {
360        self.0.left = Some(left);
361        self
362    }
363
364    /// Set right border of a table
365    pub fn right(mut self, right: VerticalLine) -> Self {
366        self.0.right = Some(right);
367        self
368    }
369
370    /// Build border
371    pub fn build(self) -> Border {
372        self.0
373    }
374}
375
376/// Inner (column/row) separators of a table
377#[derive(Debug, Copy, Clone, PartialEq, Eq)]
378pub struct Separator {
379    pub(crate) column: Option<VerticalLine>,
380    pub(crate) row: Option<HorizontalLine>,
381    pub(crate) title: Option<HorizontalLine>,
382}
383
384impl Separator {
385    /// Creates a new builder for separator
386    pub fn builder() -> SeparatorBuilder {
387        SeparatorBuilder(Separator {
388            column: None,
389            row: None,
390            title: None,
391        })
392    }
393}
394
395impl Default for Separator {
396    fn default() -> Self {
397        Self {
398            column: Some(Default::default()),
399            row: Some(Default::default()),
400            title: None,
401        }
402    }
403}
404
405/// Builder for separator
406#[derive(Debug)]
407pub struct SeparatorBuilder(Separator);
408
409impl SeparatorBuilder {
410    /// Set column separators of a table
411    pub fn column(mut self, column: Option<VerticalLine>) -> Self {
412        self.0.column = column;
413        self
414    }
415
416    /// Set column separators of a table
417    pub fn row(mut self, row: Option<HorizontalLine>) -> Self {
418        self.0.row = row;
419        self
420    }
421
422    /// Set title of a table
423    ///
424    /// # None
425    ///
426    /// When title separator is not preset (i.e., it is `None`), row separator is displayed in place of title separator.
427    pub fn title(mut self, title: Option<HorizontalLine>) -> Self {
428        self.0.title = title;
429        self
430    }
431
432    /// Build separator
433    pub fn build(self) -> Separator {
434        self.0
435    }
436}
437
438/// Struct for configuring a table's format
439#[derive(Debug, Default, Copy, Clone)]
440pub(crate) struct TableFormat {
441    pub(crate) border: Border,
442    pub(crate) separator: Separator,
443}
444
445/// Dimensions of a table
446#[derive(Debug, Clone, Eq, PartialEq, Default)]
447pub(crate) struct Dimension {
448    /// Widths of each column of table
449    pub(crate) widths: Vec<usize>,
450    /// Height of each row of table
451    pub(crate) heights: Vec<usize>,
452}
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457
458    #[test]
459    fn test_row_from_str_arr() {
460        let table: TableStruct = vec![&["Hello", "World"], &["Scooby", "Doo"]].table();
461        assert_eq!(2, table.rows.len());
462        assert_eq!(2, table.rows[0].cells.len());
463        assert_eq!(2, table.rows[1].cells.len());
464    }
465}