use std::fmt;
use std::io;
use std::io::Write;
use std::time::Duration;
use indicatif::ProgressBar;
use indicatif::ProgressStyle;
use mz_ore::option::OptionExt;
use serde::{Deserialize, Serialize};
use serde_aux::serde_introspection::serde_introspect;
use tabled::{Style, Table, Tabled};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use crate::error::Error;
#[derive(Debug, Clone, clap::ValueEnum)]
pub enum OutputFormat {
Text,
Json,
Csv,
}
#[derive(Clone)]
pub struct OutputFormatter {
output_format: OutputFormat,
no_color: bool,
}
const TICKS: [&str; 9] = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷", ""];
impl OutputFormatter {
pub fn new(output_format: OutputFormat, no_color: bool) -> OutputFormatter {
OutputFormatter {
output_format,
no_color,
}
}
pub fn print_with_color(&self, message: &str, color: Color, stderr: bool) -> Result<(), Error> {
let mut stdeo = match stderr {
true => StandardStream::stderr(ColorChoice::Always),
false => StandardStream::stdout(ColorChoice::Always),
};
stdeo.set_color(ColorSpec::new().set_fg(Some(color)))?;
write!(&mut stdeo, "{}", message)?;
let _ = stdeo.reset();
Ok(())
}
pub fn output_warning(&self, msg: &str) -> Result<(), Error> {
if self.no_color {
eprintln!("\n* Warning * {}", msg);
} else {
let _ = self.print_with_color("\n* Warning *", Color::Yellow, true)?;
eprintln!(" {}", msg);
}
Ok(())
}
pub fn output_scalar(&self, scalar: Option<&str>) -> Result<(), Error> {
match self.output_format {
OutputFormat::Text => println!("{}", scalar.display_or("<unset>")),
OutputFormat::Json => serde_json::to_writer(io::stdout(), &scalar)?,
OutputFormat::Csv => {
let mut w = csv::Writer::from_writer(io::stdout());
w.write_record([scalar.unwrap_or("<unset>")])?;
w.flush()?;
}
}
Ok(())
}
pub fn output_table<'a, I, R>(&self, rows: I) -> Result<(), Error>
where
I: IntoIterator<Item = R>,
R: Deserialize<'a> + Serialize + Tabled,
{
match self.output_format {
OutputFormat::Text => {
let table = Table::new(rows).with(Style::psql()).to_string();
println!("{table}");
}
OutputFormat::Json => {
let rows = rows.into_iter().collect::<Vec<_>>();
serde_json::to_writer(io::stdout(), &rows)?;
}
OutputFormat::Csv => {
let mut w = csv::WriterBuilder::new()
.has_headers(false)
.from_writer(io::stdout());
w.write_record(serde_introspect::<R>())?;
for row in rows {
w.serialize(row)?;
}
w.flush()?;
}
}
Ok(())
}
pub fn loading_spinner(&self, message: &str) -> ProgressBar {
let progress_bar = ProgressBar::new_spinner();
progress_bar.enable_steady_tick(Duration::from_millis(120));
let tick_strings: Vec<&str> = match self.no_color {
true => TICKS.to_vec(),
false => TICKS.to_vec(),
};
progress_bar.set_style(
ProgressStyle::default_spinner()
.template("{spinner:1.green/green} {msg}")
.expect("template known to be valid")
.tick_strings(&tick_strings),
);
progress_bar.set_message(message.to_string());
progress_bar
}
}
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct OptionalStr<'a>(pub Option<&'a str>);
impl fmt::Display for OptionalStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.0 {
None => f.write_str("<unset>"),
Some(s) => s.fmt(f),
}
}
}