use lazy_static::lazy_static;
use std::error::Error;
use std::fmt::{self, Display};
use std::io::Write;
use std::sync::Mutex;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
pub enum Level {
Silent,
Error,
Warn,
Success,
Status,
Info,
Debug,
Trace,
}
impl Level {
fn get_color(&self) -> ColorSpec {
let mut spec = ColorSpec::new();
match self {
Level::Silent => {} Level::Error => {
spec.set_fg(Some(Color::Red)).set_bold(true);
}
Level::Warn => {
spec.set_fg(Some(Color::Yellow)).set_bold(true);
}
Level::Status => {}
Level::Success => {
spec.set_fg(Some(Color::Green));
}
Level::Info => {
spec.set_fg(Some(Color::White));
}
Level::Debug => {
spec.set_fg(Some(Color::Cyan));
}
Level::Trace => {
spec.set_fg(Some(Color::Magenta));
}
};
spec
}
}
pub enum UseColor {
Never,
Always,
Auto,
}
impl Into<ColorChoice> for UseColor {
fn into(self) -> ColorChoice {
match self {
UseColor::Never => ColorChoice::Never,
UseColor::Always => ColorChoice::Auto,
UseColor::Auto => {
if atty::is(atty::Stream::Stdout) {
ColorChoice::Auto
} else {
ColorChoice::Never
}
}
}
}
}
#[derive(Debug)]
pub enum CloutError {
AlreadyInit,
AlreadyShutdown,
}
impl Display for CloutError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
CloutError::AlreadyInit => write!(f, "clout already initialised"),
CloutError::AlreadyShutdown => write!(f, "clout already shutdown"),
}
}
}
impl Error for CloutError {}
struct Clout {
level: Level,
write: StandardStream,
}
lazy_static! {
static ref CLOUT: Mutex<Option<Clout>> = Mutex::new(None);
}
pub struct Builder {
level: Level,
use_color: UseColor,
}
impl Builder {
pub fn new() -> Builder {
Self {
level: Level::Status,
use_color: UseColor::Auto,
}
}
pub fn level(&self) -> Level {
self.level
}
pub fn with_level(mut self, level: Level) -> Builder {
self.level = level;
self
}
pub fn with_verbose(mut self, verbose: u8) -> Builder {
self.level = match verbose {
0 => Level::Status,
1 => Level::Info,
2 => Level::Debug,
_ => Level::Trace,
};
self
}
pub fn with_quiet(mut self, quiet: bool) -> Builder {
if quiet {
self.level = Level::Error;
}
self
}
pub fn with_silent(mut self, silent: bool) -> Builder {
if silent {
self.level = Level::Silent;
}
self
}
pub fn with_use_color(mut self, use_color: UseColor) -> Builder {
self.use_color = use_color;
self
}
fn build(self) -> Clout {
Clout {
level: self.level,
write: StandardStream::stdout(self.use_color.into()),
}
}
pub fn done(self) -> Result<(), CloutError> {
let mut clout = CLOUT.lock().unwrap();
if clout.is_some() {
Err(CloutError::AlreadyInit)
} else {
*clout = Some(self.build());
Ok(())
}
}
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
pub fn init() -> Builder {
Builder::new()
}
pub fn shutdown() -> Result<(), CloutError> {
let mut clout = CLOUT.lock().unwrap();
if clout.is_some() {
*clout = None;
Ok(())
} else {
Err(CloutError::AlreadyShutdown)
}
}
fn with_clout<F, R>(f: F) -> R
where
F: FnOnce(&mut Clout) -> R,
{
let mut clout = CLOUT.lock().unwrap();
if let Some(ref mut inner) = *clout {
f(inner)
} else {
panic!("attempt to output with clout before initialising")
}
}
pub fn emit(level: Level, args: fmt::Arguments) {
with_clout(|clout| {
if clout.level < level {
return;
}
let _ = clout.write.set_color(&level.get_color());
let _ = clout.write.write_fmt(args);
let _ = clout.write.reset();
let _ = writeln!(clout.write);
});
}
pub fn level() -> Level {
with_clout(|clout| {
clout.level
})
}
#[macro_export]
macro_rules! error {
($($args:tt)*) => ($crate::emit($crate::Level::Error, format_args!($($args)*)))
}
#[macro_export]
macro_rules! warn {
($($args:tt)*) => ($crate::emit($crate::Level::Warn, format_args!($($args)*)))
}
#[macro_export]
macro_rules! success {
($($args:tt)*) => ($crate::emit($crate::Level::Success, format_args!($($args)*)))
}
#[macro_export]
macro_rules! status {
($($args:tt)*) => ($crate::emit($crate::Level::Status, format_args!($($args)*)))
}
#[macro_export]
macro_rules! info {
($($args:tt)*) => ($crate::emit($crate::Level::Info, format_args!($($args)*)))
}
#[macro_export]
macro_rules! debug {
($($args:tt)*) => ($crate::emit($crate::Level::Debug, format_args!($($args)*)))
}
#[macro_export]
macro_rules! trace {
($($args:tt)*) => ($crate::emit($crate::Level::Trace, format_args!($($args)*)))
}