pub const TERM_COLOR: &str = "TERM_COLOR";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Color {
Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, }
impl Color {
pub fn enabled() -> bool {
*private::TERM_COLOR_FLAG
}
pub fn force(val: Option<bool>) {
*private::FORCE_COLOR.lock().unwrap() = val;
}
pub(crate) fn force_on() -> bool {
match *private::FORCE_COLOR.lock().unwrap() {
Some(val) => val,
None => false,
}
}
pub(crate) fn force_off() -> bool {
match *private::FORCE_COLOR.lock().unwrap() {
Some(val) => !val,
None => false,
}
}
}
impl std::fmt::Display for Color {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str(match *self {
Color::Black => "90",
Color::Red => "91",
Color::Green => "92",
Color::Yellow => "93",
Color::Blue => "94",
Color::Magenta => "95",
Color::Cyan => "96",
Color::White => "97",
})
}
}
pub trait Colorable {
fn set_fg_style(self, color: Color) -> ColorString
where
Self: Sized;
fn black(self) -> ColorString
where
Self: Sized,
{
self.set_fg_style(Color::Black)
}
fn red(self) -> ColorString
where
Self: Sized,
{
self.set_fg_style(Color::Red)
}
fn green(self) -> ColorString
where
Self: Sized,
{
self.set_fg_style(Color::Green)
}
fn yellow(self) -> ColorString
where
Self: Sized,
{
self.set_fg_style(Color::Yellow)
}
fn blue(self) -> ColorString
where
Self: Sized,
{
self.set_fg_style(Color::Blue)
}
fn magenta(self) -> ColorString
where
Self: Sized,
{
self.set_fg_style(Color::Magenta)
}
fn cyan(self) -> ColorString
where
Self: Sized,
{
self.set_fg_style(Color::Cyan)
}
fn white(self) -> ColorString
where
Self: Sized,
{
self.set_fg_style(Color::White)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ColorString {
inner: String,
fg_color: Color,
}
impl core::ops::Deref for ColorString {
type Target = str;
fn deref(&self) -> &str {
&self.inner
}
}
impl Colorable for ColorString {
fn set_fg_style(mut self, color: Color) -> ColorString
where
Self: Sized,
{
self.fg_color = color;
self
}
}
impl std::fmt::Display for ColorString {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if Color::force_off() || (!Color::enabled() && !Color::force_on()) {
return <String as std::fmt::Display>::fmt(&self.inner, f);
}
let d = std::cell::RefCell::new(f);
let _ensure = private::ensure(|| d.borrow_mut().write_str("\x1B[0m"));
d.borrow_mut().write_str("\x1B[")?;
d.borrow_mut().write_str("1;")?;
d.borrow_mut().write_str(&self.fg_color.to_string())?;
d.borrow_mut().write_str("m")?;
d.borrow_mut().write_str(&self.inner)?;
Ok(())
}
}
impl<'a> Colorable for &'a str {
fn set_fg_style(self, color: Color) -> ColorString
where
Self: Sized,
{
ColorString { inner: String::from(self), fg_color: color }
}
}
pub(crate) mod private {
use lazy_static::*;
use std::{env, ffi::OsStr, fmt, sync::Mutex};
lazy_static! {
pub static ref TERM_COLOR_FLAG: bool = hastty() && flag_default(super::TERM_COLOR, true);
pub static ref FORCE_COLOR: Mutex<Option<bool>> = Mutex::new(None);
}
pub fn flag_default<K: AsRef<OsStr>>(key: K, default: bool) -> bool {
!matches!(env::var(key).unwrap_or_else(|_| default.to_string()).to_lowercase().as_str(), "false" | "0")
}
pub fn hastty() -> bool {
unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
}
pub fn ensure<T: FnOnce() -> fmt::Result>(f: T) -> impl Drop {
Ensure(Some(f))
}
struct Ensure<T: FnOnce() -> fmt::Result>(Option<T>);
impl<T: FnOnce() -> fmt::Result> Drop for Ensure<T> {
fn drop(&mut self) {
self.0.take().map(|f| f());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color_enabled() {
assert!(Color::enabled() || !Color::enabled());
}
#[test]
fn test_colors() {
assert!(!Color::force_on());
assert!(!Color::force_off());
Color::force(Some(true));
assert!(Color::force_on());
assert!(!Color::force_off());
let foo = String::from("foo").red();
assert_eq!(String::from("foo"), *foo);
assert_eq!("\u{1b}[1;91m\u{1b}[0m", "".black().red().to_string());
assert_eq!("\u{1b}[1;91m\u{1b}[0m", "".red().to_string());
assert_eq!("\u{1b}[1;90m\u{1b}[0m", "".black().to_string());
assert_eq!("\u{1b}[1;92m\u{1b}[0m", "".green().to_string());
assert_eq!("\u{1b}[1;93m\u{1b}[0m", "".yellow().to_string());
assert_eq!("\u{1b}[1;94m\u{1b}[0m", "".blue().to_string());
assert_eq!("\u{1b}[1;95m\u{1b}[0m", "".magenta().to_string());
assert_eq!("\u{1b}[1;96m\u{1b}[0m", "".cyan().to_string());
assert_eq!("\u{1b}[1;97m\u{1b}[0m", "".white().to_string());
Color::force(Some(false));
assert!(Color::force_off());
assert!(!Color::force_on());
assert_eq!("", "".black().to_string());
assert_eq!("", "".red().to_string());
assert_eq!("", "".green().to_string());
assert_eq!("", "".yellow().to_string());
assert_eq!("", "".blue().to_string());
assert_eq!("", "".magenta().to_string());
assert_eq!("", "".cyan().to_string());
assert_eq!("", "".white().to_string());
Color::force(None);
}
}