kwik/table/mod.rs
1/*
2 * Copyright (c) Kia Shakiba
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8mod row;
9mod cell;
10
11use std::{
12 path::Path,
13 io::{self, Write},
14 collections::HashSet,
15};
16
17use crate::file::{
18 FileWriter,
19 csv::CsvWriter,
20};
21
22pub use crate::table::{
23 row::{Row, ColumnJoinType},
24 cell::{Align, Style},
25};
26
27#[derive(Default)]
28pub struct Table {
29 header: Option<Row>,
30 rows: Vec<Row>,
31 spacers: HashSet<usize>,
32
33 row_len: usize,
34}
35
36/// Prints a table to a stream.
37impl Table {
38 /// Sets the table's header row. The header row is followed by a spacer
39 /// row by default.
40 ///
41 /// # Examples
42 /// ```
43 /// use kwik::table::{Table, Row, Align, Style};
44 ///
45 /// let mut table = Table::default();
46 ///
47 /// let header = Row::default()
48 /// .push("Header 1", Align::Center, Style::Bold);
49 ///
50 /// table.set_header(header);
51 ///
52 /// let mut stdout = Vec::new();
53 /// table.print(&mut stdout);
54 ///
55 /// assert_eq!(stdout, b"| \x1B[1mHeader 1\x1B[0m |\n|----------|\n");
56 /// ```
57 ///
58 /// # Panics
59 ///
60 /// Panics if the header length does not match the existing row length.
61 #[inline]
62 pub fn set_header(&mut self, header: Row) {
63 assert!(
64 self.rows.is_empty() || header.len() == self.row_len,
65 "Invalid number of columns in row.",
66 );
67
68 self.row_len = header.len();
69 self.header = Some(header);
70 self.spacers.insert(1);
71 }
72
73 /// Adds a row to the table;
74 ///
75 /// # Examples
76 /// ```
77 /// use kwik::table::{Table, Row, Align, Style};
78 ///
79 /// let mut table = Table::default();
80 ///
81 /// let row = Row::default()
82 /// .push("Row 1", Align::Left, Style::Normal);
83 ///
84 /// table.add_row(row);
85 ///
86 /// let mut stdout = Vec::new();
87 /// table.print(&mut stdout);
88 ///
89 /// assert_eq!(stdout, b"| Row 1 |\n");
90 /// ```
91 ///
92 /// # Panics
93 ///
94 /// Panics if the row length does not match the existing row length.
95 #[inline]
96 pub fn add_row(&mut self, row: Row) {
97 assert!(
98 self.rows.is_empty() || row.len() == self.row_len,
99 "Invalid number of columns in row.",
100 );
101
102 self.row_len = row.len();
103 self.rows.push(row);
104 }
105
106 /// Adds a spacer row to the table.
107 ///
108 /// # Examples
109 /// ```
110 /// use kwik::table::{Table, Row, Align, Style};
111 ///
112 /// let mut table = Table::default();
113 ///
114 /// let row1 = Row::default()
115 /// .push("Row 1", Align::Left, Style::Normal);
116 ///
117 /// let row2 = Row::default()
118 /// .push("Row 2", Align::Left, Style::Normal);
119 ///
120 /// table.add_row(row1);
121 /// table.add_spacer();
122 /// table.add_row(row2);
123 ///
124 /// let mut stdout = Vec::new();
125 /// table.print(&mut stdout);
126 ///
127 /// assert_eq!(stdout, b"| Row 1 |\n|-------|\n| Row 2 |\n");
128 /// ```
129 #[inline]
130 pub fn add_spacer(&mut self) {
131 let mut index = self.rows.len();
132
133 if self.header.is_some() {
134 index += 1;
135 }
136
137 self.spacers.insert(index);
138 }
139
140 /// Prints the table to the supplied stream.
141 ///
142 /// # Examples
143 /// ```
144 /// use kwik::table::{Table, Row, Align, Style};
145 ///
146 /// let mut table = Table::default();
147 ///
148 /// let header = Row::default()
149 /// .push("Header 1", Align::Center, Style::Bold);
150 ///
151 /// let row = Row::default()
152 /// .push("Longer row 1", Align::Left, Style::Normal);
153 ///
154 /// table.set_header(header);
155 /// table.add_row(row);
156 ///
157 /// let mut stdout = Vec::new();
158 /// table.print(&mut stdout);
159 ///
160 /// assert_eq!(stdout, b"| \x1B[1m Header 1 \x1B[0m |\n|--------------|\n| Longer row 1 |\n");
161 /// ```
162 pub fn print(&self, stdout: &mut impl Write) {
163 let mut index: usize = 0;
164 let column_lens = self.max_column_lens();
165
166 if self.spacers.contains(&index) {
167 print_spacer_row(stdout, &column_lens);
168 }
169
170 if let Some(header) = &self.header {
171 index += 1;
172
173 header.print(stdout, &column_lens, ColumnJoinType::Spaced);
174
175 if self.spacers.contains(&index) {
176 print_spacer_row(stdout, &column_lens);
177 }
178 }
179
180 for row in &self.rows {
181 index += 1;
182
183 row.print(stdout, &column_lens, ColumnJoinType::Spaced);
184
185 if self.spacers.contains(&index) {
186 print_spacer_row(stdout, &column_lens);
187 }
188 }
189 }
190
191 /// Writes the table to the file at the supplied path.
192 ///
193 /// # Examples
194 /// ```no_run
195 /// use kwik::table::{Table, Row, Align, Style};
196 ///
197 /// let mut table = Table::default();
198 ///
199 /// let header = Row::default()
200 /// .push("Header 1", Align::Center, Style::Bold);
201 ///
202 /// let row = Row::default()
203 /// .push("Longer row 1", Align::Left, Style::Normal);
204 ///
205 /// table.set_header(header);
206 /// table.add_row(row);
207 ///
208 /// table.to_file("/path/to/file").unwrap();
209 /// ```
210 ///
211 /// # Errors
212 ///
213 /// This function will return an error if the file at the supplied path
214 /// could not be opened.
215 pub fn to_file<P>(&self, path: P) -> io::Result<()>
216 where
217 P: AsRef<Path>,
218 {
219 let mut writer = CsvWriter::<Row>::from_path(path)?;
220
221 if let Some(header) = &self.header {
222 writer.write_row(header).unwrap();
223 }
224
225 for row in &self.rows {
226 writer.write_row(row).unwrap();
227 }
228
229 Ok(())
230 }
231
232 fn max_column_lens(&self) -> Vec<usize> {
233 let mut sizes: Vec<usize> = vec![0; self.row_len];
234
235 if let Some(header) = &self.header {
236 for (index, size) in sizes.iter_mut().enumerate() {
237 let row_column_size = header.get_column_size(index);
238
239 if row_column_size > *size {
240 *size = row_column_size;
241 }
242 }
243 }
244
245 for row in &self.rows {
246 for (index, size) in sizes.iter_mut().enumerate() {
247 let row_column_size = row.get_column_size(index);
248
249 if row_column_size > *size {
250 *size = row_column_size;
251 }
252 }
253 }
254
255 sizes
256 }
257}
258
259fn print_spacer_row(
260 stdout: &mut impl Write,
261 sizes: &Vec<usize>
262) {
263 let mut row = Row::default();
264
265 for size in sizes {
266 let value = vec!["-"; *size + 2].join("");
267 row = row.push(value, Align::Left, Style::Normal);
268 }
269
270 row.print(stdout, sizes, ColumnJoinType::Plus);
271}