1use std::{io::Write, iter::FromIterator};
2
3use rayon::prelude::{FromParallelIterator, IntoParallelRefIterator, ParallelIterator};
4use tabled::{peaker::PriorityMax, Style, TableIteratorExt, Tabled, Width};
5
6use crate::Output;
7
8use super::{Message, Stats};
9
10#[derive(Debug)]
11pub struct TableOptions {
12 pub style: TableStyle,
13}
14
15#[derive(Debug)]
16pub enum TableStyle {
17 Ascii,
18 Psql,
19 Markdown,
20 Rounded,
21 Extended,
22}
23
24#[derive(Debug)]
26pub struct Table {
27 table: tabled::Table,
28}
29
30#[derive(Tabled, Debug)]
32struct Inner {
33 name: String,
34 #[tabled(inline)]
35 result: Either,
36}
37
38#[derive(Tabled, Debug)]
39enum Either {
40 #[tabled(inline)]
41 Stats {
42 #[tabled(inline)]
43 stats: Stats,
44 },
45 #[tabled(inline)]
46 Error { error: String },
47}
48
49impl From<Result<Stats, String>> for Either {
50 fn from(result: Result<Stats, String>) -> Self {
51 match result {
52 Ok(stats) => Self::Stats { stats },
53 Err(error) => Self::Error { error },
54 }
55 }
56}
57
58impl Output for Table {
59 type Options = TableOptions;
60 type Error = std::io::Error;
61 fn to_writer<W: Write>(
62 mut self,
63 options: Self::Options,
64 mut writter: W,
65 ) -> std::io::Result<()> {
66 let width = None
67 .or_else(|| Some(terminal_size::terminal_size()?.0 .0 as _))
68 .unwrap_or(80);
69
70 let table = self.table.with(
71 Width::truncate(width)
72 .suffix("...")
73 .priority::<PriorityMax>(),
74 );
75
76 let string = match options.style {
77 TableStyle::Ascii => table.with(Style::ascii()).to_string(),
78 TableStyle::Psql => table.with(Style::psql()).to_string(),
79 TableStyle::Markdown => table.with(Style::markdown()).to_string(),
80 TableStyle::Rounded => table.with(Style::rounded()).to_string(),
81 TableStyle::Extended => table.with(Style::extended()).to_string(),
82 };
83
84 writter.write_all(string.as_bytes())
85 }
86}
87
88impl FromIterator<Message> for Table {
89 fn from_iter<T: IntoIterator<Item = Message>>(iter: T) -> Self {
90 let mut rows: Vec<_> = iter
91 .into_iter()
92 .map(|(name, result)| Inner {
93 name,
94 result: result.into(),
95 })
96 .collect();
97
98 let total = rows
99 .iter()
100 .flat_map(|x| {
101 if let Either::Stats { stats } = x.result {
102 Some(stats)
103 } else {
104 None
105 }
106 })
107 .fold(Stats::identity(), |x, y| x + y);
108 rows.push(Inner {
109 name: "Total".to_owned(),
110 result: Either::Stats { stats: total },
111 });
112
113 Table {
114 table: rows.table(),
115 }
116 }
117}
118
119impl FromParallelIterator<Message> for Table {
120 fn from_par_iter<I>(par_iter: I) -> Self
121 where
122 I: rayon::prelude::IntoParallelIterator<Item = Message>,
123 {
124 let mut rows: Vec<_> = par_iter
125 .into_par_iter()
126 .map(|(name, result)| Inner {
127 name,
128 result: result.into(),
129 })
130 .collect();
131
132 let total = rows
133 .par_iter()
134 .flat_map(|x| {
135 if let Either::Stats { stats } = x.result {
136 Some(stats)
137 } else {
138 None
139 }
140 })
141 .reduce(Stats::identity, |x, y| x + y);
142 rows.push(Inner {
143 name: "Total".to_owned(),
144 result: Either::Stats { stats: total },
145 });
146
147 Table {
148 table: rows.table(),
149 }
150 }
151}