1use std::fmt;
13use std::io;
14use std::io::Write;
15use std::time::Duration;
16
17use indicatif::ProgressBar;
18use indicatif::ProgressStyle;
19use mz_ore::option::OptionExt;
20
21use serde::{Deserialize, Serialize};
22use serde_aux::serde_introspection::serde_introspect;
23use tabled::settings::Style;
24use tabled::{Table, Tabled};
25use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
26
27use crate::error::Error;
28
29#[derive(Debug, Clone, clap::ValueEnum)]
31pub enum OutputFormat {
32 Text,
34 Json,
36 Csv,
38}
39
40#[derive(Clone)]
42pub struct OutputFormatter {
43 output_format: OutputFormat,
44 no_color: bool,
45}
46
47const TICKS: [&str; 9] = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷", ""];
49
50impl OutputFormatter {
51 pub fn new(output_format: OutputFormat, no_color: bool) -> OutputFormatter {
53 OutputFormatter {
54 output_format,
55 no_color,
56 }
57 }
58
59 pub fn print_with_color(&self, message: &str, color: Color, stderr: bool) -> Result<(), Error> {
61 let mut stdeo = match stderr {
62 true => StandardStream::stderr(ColorChoice::Always),
63 false => StandardStream::stdout(ColorChoice::Always),
64 };
65 stdeo.set_color(ColorSpec::new().set_fg(Some(color)))?;
66 write!(&mut stdeo, "{}", message)?;
67
68 let _ = stdeo.reset();
70
71 Ok(())
72 }
73
74 pub fn output_warning(&self, msg: &str) -> Result<(), Error> {
76 if self.no_color {
77 eprintln!("\n* Warning * {}", msg);
78 } else {
79 let _ = self.print_with_color("\n* Warning *", Color::Yellow, true)?;
80 eprintln!(" {}", msg);
81 }
82
83 Ok(())
84 }
85
86 pub fn output_scalar(&self, scalar: Option<&str>) -> Result<(), Error> {
88 match self.output_format {
89 OutputFormat::Text => println!("{}", scalar.display_or("<unset>")),
90 OutputFormat::Json => serde_json::to_writer(io::stdout(), &scalar)?,
91 OutputFormat::Csv => {
92 let mut w = csv::Writer::from_writer(io::stdout());
93 w.write_record([scalar.unwrap_or("<unset>")])?;
94 w.flush()?;
95 }
96 }
97 Ok(())
98 }
99
100 pub fn output_table<'a, I, R>(&self, rows: I) -> Result<(), Error>
108 where
109 I: IntoIterator<Item = R>,
110 R: Deserialize<'a> + Serialize + Tabled,
111 {
112 match self.output_format {
113 OutputFormat::Text => {
114 let table = Table::new(rows).with(Style::psql()).to_string();
115 println!("{table}");
116 }
117 OutputFormat::Json => {
118 let rows = rows.into_iter().collect::<Vec<_>>();
119 serde_json::to_writer(io::stdout(), &rows)?;
120 }
121 OutputFormat::Csv => {
122 let mut w = csv::WriterBuilder::new()
123 .has_headers(false)
124 .from_writer(io::stdout());
125 w.write_record(serde_introspect::<R>())?;
126 for row in rows {
127 w.serialize(row)?;
128 }
129 w.flush()?;
130 }
131 }
132 Ok(())
133 }
134
135 pub fn loading_spinner(&self, message: &str) -> ProgressBar {
137 let progress_bar = ProgressBar::new_spinner();
138 progress_bar.enable_steady_tick(Duration::from_millis(120));
139
140 let tick_strings: Vec<&str> = match self.no_color {
141 true => TICKS.to_vec(),
142 false => TICKS.to_vec(),
143 };
144
145 progress_bar.set_style(
146 ProgressStyle::default_spinner()
147 .template("{spinner:1.green/green} {msg}")
148 .expect("template known to be valid")
149 .tick_strings(&tick_strings),
152 );
153
154 progress_bar.set_message(message.to_string());
155
156 progress_bar
157 }
158}
159
160#[derive(Serialize, Deserialize)]
162#[serde(transparent)]
163pub struct OptionalStr<'a>(pub Option<&'a str>);
164
165impl fmt::Display for OptionalStr<'_> {
166 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167 match &self.0 {
168 None => f.write_str("<unset>"),
169 Some(s) => s.fmt(f),
170 }
171 }
172}