wz_fmt/
table.rs

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/// Table representation of wz's output
25#[derive(Debug)]
26pub struct Table {
27    table: tabled::Table,
28}
29
30/// Defines a table row
31#[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}