1mod border;
2pub mod row;
3pub mod cell;
4
5use std::str::FromStr;
6pub use border::Border;
7use super::data_item::DataItem;
8use cell::Cell;
9use row::Row;
10use crate::content::{ContentStyle, CellWidth};
11
12#[allow(unused_macros)]
13#[macro_export]
14macro_rules! table {
15 ( $($style:literal=>$header:literal),*; $($data:literal),* ) => {
17 table!(
18 $($style => $header),*;
19 "{}";
20 $($data),*
21 )
22 };
23
24 ( $($style:literal=>$header:literal),*;
26 $($cell_style:literal),*;
27 $($data:literal),* ) =>
28 {
29 Table::from_vec(
30 crate::row!($($style => $header), *),
32 &[$(crate::content_style!($cell_style)),*],
34 &[$($data),*]
36 )
37 }
38}
39
40#[derive(Debug)]
41pub struct Table {
42 pub border: Border,
43 column_breaks: Vec<CellWidth>,
44 column_headers: Row,
45 row_headers: Vec<Cell>,
46 data_rows: Vec<Row>
47}
48
49impl Table {
50 #[must_use]
52 pub fn empty() -> Table {
53 Table {
54 border: Border::default(),
55 column_breaks: Vec::new(),
56 column_headers: Row::new(),
57 row_headers: Vec::new(),
58 data_rows: Vec::new(),
59 }
60 }
61
62 #[must_use]
72 pub fn new(
73 border: Border,
74 column_breaks: Vec<CellWidth>,
75 column_headers: Row,
76 row_headers: Vec<Cell>,
77 data_rows: Vec<Row>,
78 ) -> Table {
79 Table {
80 border,
81 column_breaks,
82 column_headers,
83 row_headers,
84 data_rows
85 }
86 }
87
88 #[must_use]
100 pub fn from_vec(
101 column_headers: Row,
102 cell_styles: &[ContentStyle],
103 data: &[&str]
104 ) -> Table {
105 let d: Vec<DataItem> =
107 data.iter().map(|i| DataItem::from_str(i).unwrap())
108 .collect::<Vec<DataItem>>();
109
110 Table::from_data_source(
111 column_headers,
112 &cell_styles,
113 Vec::new(),
114 d.iter()
115 )
116 }
117
118 pub fn from_data_source<'a, I>(
127 column_headers: Row,
128 cell_styles: &[ContentStyle],
129 row_headers: Vec<Cell>,
130 data_source: I,
131 ) -> Table
132 where
133 I: Iterator<Item=&'a DataItem>
134 {
135 let mut data_rows = Vec::new();
136
137 let mut column_breaks: Vec<CellWidth> = Vec::new();
139 for cell in column_headers.iter() {
140 column_breaks.push(cell.get_cell_width());
141 }
142
143 let mut row_ix = 0;
145 data_rows.push(Row::new());
146
147 let mut break_ix = 0;
148
149 for item in data_source {
150 if break_ix == column_breaks.len() {
152 break_ix = 0;
153 data_rows.push(Row::new());
154 row_ix += 1;
155 }
156
157 let mut cell_style = &ContentStyle::default();
159 if cell_styles.len() > break_ix {
160 cell_style = &cell_styles[break_ix];
161 }
162
163 data_rows[row_ix].add_cell(
164 Cell::from_data_item(item, cell_style.clone())
165 );
166
167 break_ix += 1;
168 }
169
170 Table::new(
171 Border::default(),
172 column_breaks,
173 column_headers,
174 row_headers,
175 data_rows
176 )
177 }
178
179 #[must_use]
185 pub fn format(self: &Table) -> String {
186 let mut result: String = String::from("");
187
188 let widths = self.measure_column_widths();
190
191 result.push_str(&self.format_header(&widths));
193
194 result.push_str(&self.format_body(&widths));
196
197 result
198 }
199
200 fn format_header(
206 self: &Table,
207 widths: &[usize]
208 ) -> String {
209 let mut result: String = String::from("");
210
211 result.push_str(&self.border.format_top(&widths));
213 result.push('\n');
214
215 result.push_str(
217 &self.column_headers.format(
218 &self.border,
219 &self.column_breaks
220 )
221 );
222
223 result.push_str(&self.border.format_horizontal_split(&widths));
225 result.push('\n');
226
227 result
228 }
229
230 fn format_body(
242 self: &Table,
243 widths: &[usize]
244 ) -> String {
245 let mut result: String = String::from("");
246
247 for row_ix in 0..self.data_rows.len() {
249 let row = &self.data_rows[row_ix];
250 result.push_str(
251 &row.format(
252 &self.border,
253 &self.column_breaks
254 )
255 );
256
257 if row_ix < self.data_rows.len() - 1 {
259 result.push_str(
260 &self.border.format_horizontal_split(&widths));
261 result.push('\n');
262 }
263 }
264
265 result.push_str(&self.border.format_bottom(&widths));
267 result.push('\n');
268
269 result
270 }
271
272 fn measure_column_widths(
281 self: &Table
282 ) -> Vec<usize> {
283 let mut widths = Vec::new();
284
285 let content_break = CellWidth::Content;
287 for (column_break_ix, cell) in self.column_headers.iter().enumerate() {
288 let column_break: &CellWidth =
290 if column_break_ix < self.column_breaks.len() {
291 &self.column_breaks[column_break_ix]
292 } else {
293 &content_break
295 };
296 widths.push(cell.measure_width(column_break));
298 }
299
300 widths
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use std::env;
308
309 #[test]
316 fn table_macro_simple_unstyled_body() {
317 let table = table!(
318 "{B^:12:}" => "Food", "{G^:7:}" => "Count";
319 "Fish", "15", "Pizza", "10", "Tomato", "24"
320 );
321
322 let expected =
323 match env::var("NO_COLOR") {
324 Ok(_) => "+------------+-------+\n| Food | Count |\n+------------+-------+\n|Fish |15 |\n+------------+-------+\n|Pizza |10 |\n+------------+-------+\n|Tomato |24 |\n+------------+-------+\n",
325 Err(_) => "+------------+-------+\n|\u{1b}[94m Food \u{1b}[0m|\u{1b}[92m Count \u{1b}[0m|\n+------------+-------+\n|Fish |15 |\n+------------+-------+\n|Pizza |10 |\n+------------+-------+\n|Tomato |24 |\n+------------+-------+\n",
326 };
327
328 assert_eq!(
329 table.format(),
330 expected
331 );
332 }
333
334 #[test]
335 fn table_macro_simple_styled_body() {
336 let table = table!(
337 "{m>:10:}" => "Item", "{m>:10:}" => "Price";
338 "{c^}", "{g<}";
339 "Basic", "$5,000", "Super", "$12,000", "Ultimate", "$35,000"
340 );
341
342 let expected =
343 match env::var("NO_COLOR") {
344 Ok(_) => "+----------+----------+\n| Item| Price|\n+----------+----------+\n| Basic |$5,000 |\n+----------+----------+\n| Super |$12,000 |\n+----------+----------+\n| Ultimate |$35,000 |\n+----------+----------+\n",
345 Err(_) => "+----------+----------+\n|\u{1b}[35m Item\u{1b}[0m|\u{1b}[35m Price\u{1b}[0m|\n+----------+----------+\n|\u{1b}[36m Basic \u{1b}[0m|\u{1b}[32m$5,000 \u{1b}[0m|\n+----------+----------+\n|\u{1b}[36m Super \u{1b}[0m|\u{1b}[32m$12,000 \u{1b}[0m|\n+----------+----------+\n|\u{1b}[36m Ultimate \u{1b}[0m|\u{1b}[32m$35,000 \u{1b}[0m|\n+----------+----------+\n"
346 };
347
348 assert_eq!(
349 table.format(),
350 expected
351 );
352 }
353}