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}