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
13pub struct TableStruct {
15 title: Option<RowStruct>,
17 rows: Vec<RowStruct>,
19 format: TableFormat,
21 style: StyleStruct,
23 color_choice: ColorChoice,
25}
26
27impl TableStruct {
28 pub fn title<T: Row>(mut self, title: T) -> Self {
30 self.title = Some(title.row());
31 self
32 }
33
34 pub fn border(mut self, border: Border) -> Self {
36 self.format.border = border;
37 self
38 }
39
40 pub fn separator(mut self, separator: Separator) -> Self {
42 self.format.separator = separator;
43 self
44 }
45
46 pub fn color_choice(mut self, color_choice: ColorChoice) -> Self {
48 self.color_choice = color_choice;
49 self
50 }
51
52 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 pub(crate) fn print_stdout(&self) -> Result<()> {
68 self.print_writer(BufferWriter::stdout(self.color_choice))
69 }
70
71 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
191pub trait Table {
193 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#[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 pub fn new(filler: char) -> Self {
273 Self { filler }
274 }
275}
276
277#[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 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#[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 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#[derive(Debug, Clone, PartialEq, Eq)]
343pub struct BorderBuilder(Border);
344
345impl BorderBuilder {
346 pub fn top(mut self, top: HorizontalLine) -> Self {
348 self.0.top = Some(top);
349 self
350 }
351
352 pub fn bottom(mut self, bottom: HorizontalLine) -> Self {
354 self.0.bottom = Some(bottom);
355 self
356 }
357
358 pub fn left(mut self, left: VerticalLine) -> Self {
360 self.0.left = Some(left);
361 self
362 }
363
364 pub fn right(mut self, right: VerticalLine) -> Self {
366 self.0.right = Some(right);
367 self
368 }
369
370 pub fn build(self) -> Border {
372 self.0
373 }
374}
375
376#[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 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#[derive(Debug)]
407pub struct SeparatorBuilder(Separator);
408
409impl SeparatorBuilder {
410 pub fn column(mut self, column: Option<VerticalLine>) -> Self {
412 self.0.column = column;
413 self
414 }
415
416 pub fn row(mut self, row: Option<HorizontalLine>) -> Self {
418 self.0.row = row;
419 self
420 }
421
422 pub fn title(mut self, title: Option<HorizontalLine>) -> Self {
428 self.0.title = title;
429 self
430 }
431
432 pub fn build(self) -> Separator {
434 self.0
435 }
436}
437
438#[derive(Debug, Default, Copy, Clone)]
440pub(crate) struct TableFormat {
441 pub(crate) border: Border,
442 pub(crate) separator: Separator,
443}
444
445#[derive(Debug, Clone, Eq, PartialEq, Default)]
447pub(crate) struct Dimension {
448 pub(crate) widths: Vec<usize>,
450 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}