use std::str::FromStr;
use serde::{de, Deserialize, Deserializer};
#[macro_export]
macro_rules! esc {
($code:tt) => {
concat!("\x1B[", $code, "m")
};
}
#[derive(Clone, Default)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
#[default]
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
Rgb {
r: u8,
g: u8,
b: u8,
},
}
impl Color {
pub const RESET: &'static str = esc!(0);
pub const BOLD: &'static str = esc!(1);
pub const POSSIBLE_VALUES: &[&'static str] = &[
"black",
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
"bright-black",
"bright-red",
"bright-green",
"bright-yellow",
"bright-blue",
"bright-magenta",
"bright-cyan",
"bright-white",
"'#rrggbb'",
];
pub fn foreground(&self) -> String {
match self {
Self::Black => esc!(30),
Self::Red => esc!(31),
Self::Green => esc!(32),
Self::Yellow => esc!(33),
Self::Blue => esc!(34),
Self::Magenta => esc!(35),
Self::Cyan => esc!(36),
Self::White => esc!(37),
Self::BrightBlack => esc!(90),
Self::BrightRed => esc!(91),
Self::BrightGreen => esc!(92),
Self::BrightYellow => esc!(93),
Self::BrightBlue => esc!(94),
Self::BrightMagenta => esc!(95),
Self::BrightCyan => esc!(96),
Self::BrightWhite => esc!(97),
Self::Rgb { r, g, b } => return format!("\x1B[38;2;{r};{g};{b}m"),
}
.to_string()
}
pub fn background(&self) -> String {
match self {
Self::Black => esc!(40),
Self::Red => esc!(41),
Self::Green => esc!(42),
Self::Yellow => esc!(43),
Self::Blue => esc!(44),
Self::Magenta => esc!(45),
Self::Cyan => esc!(46),
Self::White => esc!(47),
Self::BrightBlack => esc!(100),
Self::BrightRed => esc!(101),
Self::BrightGreen => esc!(102),
Self::BrightYellow => esc!(103),
Self::BrightBlue => esc!(104),
Self::BrightMagenta => esc!(105),
Self::BrightCyan => esc!(106),
Self::BrightWhite => esc!(107),
Self::Rgb { r, g, b } => return format!("\x1B[48;2;{r};{g};{b}m"),
}
.to_string()
}
fn parse_rgb_component(hex: &str) -> Result<u8, String> {
u8::from_str_radix(hex, 16).map_err(|err| err.to_string())
}
}
impl FromStr for Color {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"black" => Self::Black,
"red" => Self::Red,
"green" => Self::Green,
"yellow" => Self::Yellow,
"blue" => Self::Blue,
"magenta" => Self::Magenta,
"cyan" => Self::Cyan,
"white" => Self::White,
"bright-black" => Self::BrightBlack,
"bright-red" => Self::BrightRed,
"bright-green" => Self::BrightGreen,
"bright-yellow" => Self::BrightYellow,
"bright-blue" => Self::BrightBlue,
"bright-magenta" => Self::BrightMagenta,
"bright-cyan" => Self::BrightCyan,
"bright-white" => Self::BrightWhite,
_ if s.starts_with('#') => {
if s.len() != 7 {
return Err(format!("expected format `#rrggbb`, found `{s}`"));
}
let r = Self::parse_rgb_component(&s[1..3])?;
let g = Self::parse_rgb_component(&s[3..5])?;
let b = Self::parse_rgb_component(&s[5..7])?;
Self::Rgb { r, g, b }
}
_ => {
let green_fg = Color::Green.foreground();
return Err(format!(
"color `{s}` is neither a recognized color nor a valid hex color code.\n [possible values: {}{}{}]",
green_fg,
Self::POSSIBLE_VALUES.join(&format!("{},{} ", Color::RESET, green_fg)),
Color::RESET
));
}
})
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
Color::from_str(&string).map_err(de::Error::custom)
}
}