use crate::error::ColorFromStrErr;
use std::str::FromStr;
use owo_colors::{AnsiColors, DynColors};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
Rgb(u8, u8, u8),
}
impl Color {
pub fn into_owo(self) -> DynColors {
if let Self::Rgb(r, g, b) = self {
DynColors::Rgb(r, g, b)
} else {
DynColors::Ansi(self.into_owo_ansi())
}
}
#[inline]
fn into_owo_ansi(self) -> AnsiColors {
use AnsiColors::*;
match self {
Self::Black => Black,
Self::Red => Red,
Self::Green => Green,
Self::Yellow => Yellow,
Self::Blue => Blue,
Self::Magenta => Magenta,
Self::Cyan => Cyan,
Self::White => White,
Self::Rgb(_, _, _) => unreachable!("not an ANSI color"),
}
}
fn from_hex(hex: &str) -> Result<Self, ColorFromStrErr> {
const RADIX: u32 = 16;
const HEX_CODE_LEN: usize = 6 + 1;
debug_assert!(hex.starts_with('#'));
if hex.len() != HEX_CODE_LEN {
return Err(ColorFromStrErr::invalid_hex(hex));
}
let (r, g, b) = u32::from_str_radix(&hex[1..], RADIX)
.map(Self::split_bytes)
.map_err(|_| ColorFromStrErr::invalid_hex(hex))?;
Ok(Self::Rgb(r, g, b))
}
#[inline]
fn split_bytes(bytes: u32) -> (u8, u8, u8) {
debug_assert!(bytes <= 0xFFFFFF);
const POSITIONS: [u8; 3] = [2, 1, 0];
POSITIONS
.map(|position| Self::byte_at_shift(bytes, 8 * position))
.into()
}
const fn byte_at_shift(bytes: u32, shift: u8) -> u8 {
const BYTE_MASK: u32 = 0xFF;
((bytes >> shift) & BYTE_MASK) as u8
}
}
impl FromStr for Color {
type Err = ColorFromStrErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with('#') {
return Self::from_hex(s);
}
const NAMED_COLORS: [(&str, Color); 8] = [
("black", Color::Black),
("red", Color::Red),
("green", Color::Green),
("yellow", Color::Yellow),
("blue", Color::Blue),
("magenta", Color::Magenta),
("cyan", Color::Cyan),
("white", Color::White),
];
NAMED_COLORS
.into_iter()
.find_map(|(name, color)| name.eq_ignore_ascii_case(s).then_some(color))
.ok_or_else(|| ColorFromStrErr::UnknownColor(s.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_hex() {
assert_eq!(
"#FF8800".parse::<Color>().unwrap(),
Color::Rgb(0xFF, 0x88, 0x00)
);
}
#[test]
fn test_hex_too_long() {
assert!("#AABBCCDD".parse::<Color>().is_err());
}
#[test]
fn test_hex_too_short() {
assert!("#AABB".parse::<Color>().is_err());
}
#[test]
fn test_hex_not_valid() {
assert!("#EEFFGG".parse::<Color>().is_err());
}
#[test]
fn test_hex_no_hash() {
assert!("EEFFGG".parse::<Color>().is_err());
}
}