//! Status messages use lazy_static::lazy_static; use std::io::{self, Write}; use std::sync::Mutex; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; /// Print a success status message (in green if colors are enabled) #[macro_export] macro_rules! status_ok { ($status:expr, $msg:expr) => { $crate::terminal::Status::new() .justified() .bold() .color(termcolor::Color::Green) .status($status) .print_stdout($msg); }; ($status:expr, $fmt:expr, $($arg:tt)+) => { $crate::status_ok!($status, format!($fmt, $($arg)+)); }; } /// Print a warning status message (in yellow if colors are enabled) #[macro_export] macro_rules! status_warn { ($msg:expr) => { $crate::terminal::Status::new() .bold() .color(termcolor::Color::Yellow) .status("warning:") .print_stdout($msg); }; ($fmt:expr, $($arg:tt)+) => { $crate::status_warn!(format!($fmt, $($arg)+)); }; } /// Print an error message (in red if colors are enabled) #[macro_export] macro_rules! status_err { ($msg:expr) => { $crate::terminal::Status::new() .bold() .color(termcolor::Color::Red) .status("error:") .print_stderr($msg); }; ($fmt:expr, $($arg:tt)+) => { $crate::status_err!(format!($fmt, $($arg)+)); }; } lazy_static! { /// Color configuration static ref COLOR_CHOICE: Mutex> = Mutex::new(None); /// Standard output pub static ref STDOUT: StandardStream = StandardStream::stdout(get_color_choice()); /// Standard error pub static ref STDERR: StandardStream = StandardStream::stderr(get_color_choice()); } /// Obtain the color configuration. /// /// Panics if no configuration has been provided. fn get_color_choice() -> ColorChoice { let choice = COLOR_CHOICE.lock().unwrap(); *choice .as_ref() .expect("terminal stream accessed before initialized!") } /// Set the color configuration. /// /// Panics if the terminal has already been configured. pub(super) fn set_color_choice(color_choice: ColorChoice) { let mut choice = COLOR_CHOICE.lock().unwrap(); assert!(choice.is_none(), "terminal colors already configured!"); *choice = Some(color_choice); } /// Status message builder #[derive(Clone, Debug, Default)] pub struct Status { /// Should the status be justified? justified: bool, /// Should colors be bold? bold: bool, /// Color in which status should be displayed color: Option, /// Prefix of the status message (e.g. `Success`) status: Option, } impl Status { /// Create a new status message with default settings pub fn new() -> Self { Self::default() } /// Justify status on display pub fn justified(mut self) -> Self { self.justified = true; self } /// Make colors bold pub fn bold(mut self) -> Self { self.bold = true; self } /// Set the colors used to display this message pub fn color(mut self, c: Color) -> Self { self.color = Some(c); self } /// Set a status message to display pub fn status(mut self, msg: S) -> Self where S: ToString, { self.status = Some(msg.to_string()); self } /// Print the given message to stdout pub fn print_stdout(self, msg: impl AsRef) { self.print(&*STDOUT, msg) .expect("error printing to stdout!") } /// Print the given message to stderr pub fn print_stderr(self, msg: impl AsRef) { self.print(&*STDERR, msg) .expect("error printing to stderr!") } /// Print the given message fn print(self, stream: &StandardStream, msg: impl AsRef) -> io::Result<()> { let mut s = stream.lock(); s.reset()?; s.set_color(ColorSpec::new().set_fg(self.color).set_bold(self.bold))?; if let Some(status) = self.status { if self.justified { write!(s, "{:>12}", status)?; } else { write!(s, "{}", status)?; } } s.reset()?; writeln!(s, " {}", msg.as_ref())?; s.flush()?; Ok(()) } }