grid_printer/
lib.rs

1//! An API to easily print a two dimensional array to stdout.
2//! # Example
3//! ```rust
4//! use grid_printer::GridPrinter;
5//!
6//! let cars = vec![
7//!     vec!["Make", "Model", "Color", "Year", "Price", ],
8//!     vec!["Ford", "Pinto", "Green", "1978", "$750.00", ],
9//!     vec!["Toyota", "Tacoma", "Red", "2006", "$15,475.23", ],
10//!     vec!["Lamborghini", "Diablo", "Yellow", "2001", "$238,459.99", ],
11//! ];
12//! 
13//! let rows = cars.len();
14//! let cols = cars[0].len();
15//! let printer = GridPrinter::builder(rows, cols)
16//!     .col_spacing(4)
17//!     .build();
18//! printer.print(&cars);
19//! ```
20//!
21//! # Output:
22//! ```bash
23//! Make           Model     Color     Year    Price
24//! Ford           Pinto     Green     1978    $750.00
25//! Toyota         Tacoma    Red       2006    $15,475.23
26//! Lamborghini    Diablo    Yellow    2001    $238,459.99
27//! ```
28
29pub mod style;
30
31use std::io;
32use std::fmt;
33use std::io::Write;
34use std::fmt::Display;
35use std::error::Error;
36use std::cell::RefCell;
37
38use crate::style::StyleOpt;
39use crate::style::stylize;
40
41/// An API to easily print a two dimensional array to stdout.
42///
43/// # Example
44/// ```rust
45/// use grid_printer::GridPrinter;
46///
47/// let cars = vec![
48///     vec!["Make", "Model", "Color", "Year", "Price", ],
49///     vec!["Ford", "Pinto", "Green", "1978", "$750.00", ],
50///     vec!["Toyota", "Tacoma", "Red", "2006", "$15,475.23", ],
51///     vec!["Lamborghini", "Diablo", "Yellow", "2001", "$238,459.99", ],
52/// ];
53/// 
54/// let rows = cars.len();
55/// let cols = cars[0].len();
56/// let printer = GridPrinter::builder(rows, cols)
57///     .col_spacing(4)
58///     .build();
59/// printer.print(&cars);
60/// ```
61///
62/// Output:
63/// ```bash
64/// Make           Model     Color     Year    Price
65/// Ford           Pinto     Green     1978    $750.00
66/// Toyota         Tacoma    Red       2006    $15,475.23
67/// Lamborghini    Diablo    Yellow    2001    $238,459.99
68/// ```
69// #[derive(Debug)]
70pub struct GridPrinter {
71    rows: usize,
72    cols: usize,
73    max_widths: RefCell<Vec<usize>>,
74    col_spacing: usize,
75    col_styles: Option<Vec<Option<StyleOpt>>>,
76}
77
78impl GridPrinter {
79    pub fn new(rows: usize, cols: usize) -> Self {
80        Self {
81            rows,
82            cols,
83            ..GridPrinterBuilder::new(rows, cols).build()
84        }
85    }
86
87    pub fn builder(rows: usize, cols: usize) -> GridPrinterBuilder {
88        GridPrinterBuilder::new(rows, cols)
89    }
90
91    fn pad(n: usize) -> String {
92        vec![' '; n].into_iter().collect()
93    }
94
95    #[allow(clippy::print_with_newline)]
96    pub fn print_cell(&self, cell: &str, col_idx: usize, style_opt: Option<&StyleOpt>) {
97
98        let mut s = cell.to_string(); 
99        if let Some(style_opt) = style_opt {
100            s = stylize(cell, style_opt);
101        }
102        let col_width = self.max_widths.borrow()[col_idx];
103        let pad = GridPrinter::pad(col_width - cell.len() + self.col_spacing);
104        print!("{}{}", s, pad);
105    }
106
107    #[allow(clippy::print_with_newline)]
108    pub fn print<F: Display>(&self, source: &[Vec<F>]) {
109        let mut buff: Vec<String> = Vec::new();
110
111        for i in 0..self.rows {
112            let row = source.get(i);
113            for j in 0..self.cols {
114                let cell = match row {
115                    None => "".to_string(),
116                    Some(row) => match row.get(j) {
117                        None => "".to_string(),
118                        Some(el) => format!("{}", el),
119                    } 
120                };
121                let len = cell.len();
122                if len > self.max_widths.borrow()[j] {
123                    self.max_widths.borrow_mut()[j] = len;
124                }
125                // self.buff.borrow_mut().push(cell);
126                buff.push(cell);
127            }
128        }
129
130
131        for (i, cell) in buff.iter().enumerate() {
132            let col_idx = i % self.cols;
133            let _row_idx = i / self.rows;
134
135            let style_opt = match self.col_styles.as_ref() {
136                None => None,
137                Some(col_styles) => match col_styles.get(col_idx) {
138                    None => None,
139                    Some(style_opt) => style_opt.as_ref(),
140                }
141            };
142
143            self.print_cell(cell, col_idx, style_opt);
144
145            if (i + 1) % self.cols == 0 {
146                print!("\n");
147                io::stdout().flush().unwrap();
148            }
149        }
150
151
152    }
153}
154
155/// A Builder to create/customize a GridPrinter instance
156/// ```rust
157/// use grid_printer::GridPrinter;
158/// use grid_printer::GridPrinterBuilder;
159/// 
160/// let rows = 3;
161/// let cols = 3;
162/// let printer: GridPrinter = GridPrinterBuilder::new(rows, cols)
163///     .col_spacing(4)
164///     .build();
165/// ```
166#[derive(Debug)]
167pub struct GridPrinterBuilder {
168    rows: usize,
169    cols: usize,
170    col_spacing: usize,
171    col_styles: Option<Vec<Option<StyleOpt>>>,
172}
173
174impl Default for GridPrinterBuilder {
175     fn default() -> Self {
176        Self {
177            rows: 1,
178            cols: 1,
179            col_spacing: 2,
180            col_styles: None,
181        }
182    }
183}
184
185impl GridPrinterBuilder {
186
187    pub fn new(rows: usize, cols: usize) -> Self {
188        GridPrinterBuilder {
189            rows,
190            cols,
191            ..Default::default()
192        }
193    }
194
195    pub fn col_spacing(mut self, col_spacing: usize) -> Self {
196        self.col_spacing = col_spacing;
197
198        self
199    }
200
201    pub fn col_styles(mut self, col_styles: Vec<Option<StyleOpt>>) -> Result<Self, GridPrinterErr> {
202        match col_styles.len() == self.cols {
203            false => Err(GridPrinterErr::DimensionErr),
204            true => {
205                self.col_styles = Some(col_styles);
206
207                Ok(self)
208            }
209        }
210    }
211
212    pub fn col_style(mut self, idx: usize, opt: StyleOpt) -> Result<Self, GridPrinterErr> {
213        // Note: The size check here is somewhat redundant given the subsequent logic; however,
214        // performing the check here guarantees we don't mutate the GridPrinterBuilder by adding
215        // a Vec for an index that is outside the column range.
216        if idx >= self.cols {
217            return Err(GridPrinterErr::DimensionErr);
218        }
219
220        let col_styles = self.col_styles.get_or_insert(vec![None; self.cols]);
221        let col_style = col_styles.get_mut(idx)
222            .ok_or(GridPrinterErr::DimensionErr)?;
223        *col_style = Some(opt);
224
225        Ok(self)
226    }
227
228    pub fn build(self) -> GridPrinter {
229        GridPrinter {
230            rows: self.rows,
231            cols: self.cols,
232            max_widths: RefCell::new(vec![0; self.cols]),
233            col_spacing: self.col_spacing,
234            col_styles: self.col_styles,
235        }
236    }
237
238}
239
240#[derive(Debug)]
241pub enum GridPrinterErr {
242    DimensionErr,
243}
244
245impl Display for GridPrinterErr {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        match self {
248            GridPrinterErr::DimensionErr => {
249                write!(f, "DimensionErr. Caused by mismatch in dimension size between method calls.")
250            },
251        }
252    }
253}
254
255impl Error for GridPrinterErr {}
256
257
258#[cfg(test)]
259mod tests {
260
261    use super::*;
262
263    #[test]
264    fn test_2d_arr() {
265        let v = vec![
266            vec![1, 20, 3, ],
267            vec![40, 5, 6, ],
268            vec![7, 800, 9, ],
269        ];
270
271        let rows = v.len();
272        let cols = v[0].len();
273        let printer = GridPrinterBuilder::new(rows, cols)
274            .col_spacing(20)
275            .build();
276        printer.print(&v);
277    }
278
279}