use std::fmt;
#[derive(Debug, Clone, Copy)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
Custom(u8),
}
#[derive(Debug, Clone, Copy)]
pub enum Style {
Bold,
Dim,
Italic,
Underline,
Blink,
Reverse,
Hidden,
}
#[derive(Debug)]
pub struct StyledString {
text: String,
color: Option<Color>,
styles: Vec<Style>,
}
impl Color {
fn to_fg_code_string(self) -> String {
match self {
Color::Black => "30".to_string(),
Color::Red => "31".to_string(),
Color::Green => "32".to_string(),
Color::Yellow => "33".to_string(),
Color::Blue => "34".to_string(),
Color::Magenta => "35".to_string(),
Color::Cyan => "36".to_string(),
Color::White => "37".to_string(),
Color::BrightBlack => "90".to_string(),
Color::BrightRed => "91".to_string(),
Color::BrightGreen => "92".to_string(),
Color::BrightYellow => "93".to_string(),
Color::BrightBlue => "94".to_string(),
Color::BrightMagenta => "95".to_string(),
Color::BrightCyan => "96".to_string(),
Color::BrightWhite => "97".to_string(),
Color::Custom(n) => format!("38;5;{}", n),
}
}
pub fn paint<T: Into<String>>(self, text: T) -> StyledString {
StyledString {
text: text.into(),
color: Some(self),
styles: Vec::new(),
}
}
}
impl Style {
fn code(self) -> u8 {
match self {
Style::Bold => 1,
Style::Dim => 2,
Style::Italic => 3,
Style::Underline => 4,
Style::Blink => 5,
Style::Reverse => 7,
Style::Hidden => 8,
}
}
pub fn apply<T: Into<String>>(&self, text: T) -> StyledString {
StyledString {
text: text.into(),
color: None,
styles: vec![*self],
}
}
}
impl StyledString {
pub fn style(mut self, style: Style) -> Self {
self.styles.push(style);
self
}
fn supports_colors() -> bool {
if std::env::var("NO_COLOR").is_ok() {
return false;
}
if cfg!(test) {
return std::env::var("COLORTERM").is_ok();
}
std::env::var("COLORTERM").is_ok() || std::env::var("TERM").is_ok_and(|term| term != "dumb")
}
}
impl fmt::Display for StyledString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !StyledString::supports_colors() {
return write!(f, "{}", self.text);
}
let mut codes = Vec::new();
if let Some(color) = self.color {
codes.push(color.to_fg_code_string());
}
for style in &self.styles {
codes.push(style.code().to_string());
}
if codes.is_empty() {
write!(f, "{}", self.text)
} else {
write!(f, "\x1b[{}m{}\x1b[0m", codes.join(";"), self.text)
}
}
}