use anyhow::{anyhow, Result};
use crossterm::style::Color;
use lazy_static::lazy_static;
use regex::Regex;
use crate::config::AppColorsFromToml;
lazy_static! {
static ref RE_COLOR_HEX: Regex = Regex::new(r"(?i)#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})").expect("Regex error");
static ref RE_COLOR_RGB: Regex = Regex::new(r"(?i)rgb\(([0-9]+), ?([0-9]+), ?([0-9]+)\)").expect("Regex error");
}
#[derive(Debug, Clone)]
pub struct AppColors {
pub normal: (Color, Color),
pub bold: (Color, Color),
pub highlighted_active: (Color, Color),
pub highlighted: (Color, Color),
pub error: (Color, Color),
}
impl AppColors {
pub fn default() -> Self {
return Self {
normal: (Color::Grey, Color::Black),
bold: (Color::White, Color::Black),
highlighted_active: (Color::Black, Color::DarkYellow),
highlighted: (Color::Black, Color::Grey),
error: (Color::Red, Color::Black),
};
}
pub fn add_from_config(&mut self, config: AppColorsFromToml) {
if let Some(val) = config.normal_foreground {
if let Ok(v) = Self::color_from_str(&val) {
self.normal.0 = v;
}
}
if let Some(val) = config.normal_background {
if let Ok(v) = Self::color_from_str(&val) {
self.normal.1 = v;
}
}
if let Some(val) = config.bold_foreground {
if let Ok(v) = Self::color_from_str(&val) {
self.bold.0 = v;
}
}
if let Some(val) = config.bold_background {
if let Ok(v) = Self::color_from_str(&val) {
self.bold.1 = v;
}
}
if let Some(val) = config.highlighted_active_foreground {
if let Ok(v) = Self::color_from_str(&val) {
self.highlighted_active.0 = v;
}
}
if let Some(val) = config.highlighted_active_background {
if let Ok(v) = Self::color_from_str(&val) {
self.highlighted_active.1 = v;
}
}
if let Some(val) = config.highlighted_foreground {
if let Ok(v) = Self::color_from_str(&val) {
self.highlighted.0 = v;
}
}
if let Some(val) = config.highlighted_background {
if let Ok(v) = Self::color_from_str(&val) {
self.highlighted.1 = v;
}
}
if let Some(val) = config.error_foreground {
if let Ok(v) = Self::color_from_str(&val) {
self.error.0 = v;
}
}
if let Some(val) = config.error_background {
if let Ok(v) = Self::color_from_str(&val) {
self.error.1 = v;
}
}
}
pub fn color_from_str(text: &str) -> Result<Color> {
if text.starts_with('#') {
if let Some(cap) = RE_COLOR_HEX.captures(text) {
return Ok(Color::Rgb {
r: u8::from_str_radix(&cap[1], 16)?,
g: u8::from_str_radix(&cap[2], 16)?,
b: u8::from_str_radix(&cap[3], 16)?,
});
}
return Err(anyhow!("Invalid color hex code"));
} else if text.starts_with("rgb") || text.starts_with("RGB") {
#[allow(clippy::from_str_radix_10)]
if let Some(cap) = RE_COLOR_RGB.captures(text) {
return Ok(Color::Rgb {
r: u8::from_str_radix(&cap[1], 10)?,
g: u8::from_str_radix(&cap[2], 10)?,
b: u8::from_str_radix(&cap[3], 10)?,
});
}
return Err(anyhow!("Invalid color RGB code"));
} else {
let text_lower = text.to_lowercase();
return match &text_lower[..] {
"black" => Ok(Color::Black),
"darkgrey" | "darkgray" => Ok(Color::DarkGrey),
"red" => Ok(Color::Red),
"darkred" => Ok(Color::DarkRed),
"green" => Ok(Color::Green),
"darkgreen" => Ok(Color::DarkGreen),
"yellow" => Ok(Color::Yellow),
"darkyellow" => Ok(Color::DarkYellow),
"blue" => Ok(Color::Blue),
"darkblue" => Ok(Color::DarkBlue),
"magenta" => Ok(Color::Magenta),
"darkmagenta" => Ok(Color::DarkMagenta),
"cyan" => Ok(Color::Cyan),
"darkcyan" => Ok(Color::DarkCyan),
"white" => Ok(Color::White),
"grey" | "gray" => Ok(Color::Grey),
"terminal" => Ok(Color::Reset),
_ => Err(anyhow!("Invalid color code")),
};
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn color_hex() {
let color = String::from("#ff0000");
let parsed = AppColors::color_from_str(&color);
assert!(parsed.is_ok());
assert_eq!(parsed.unwrap(), Color::Rgb {
r: 255,
g: 0,
b: 0
});
}
#[test]
fn color_invalid_hex() {
let color = String::from("#gg0000");
assert!(AppColors::color_from_str(&color).is_err());
}
#[test]
fn color_invalid_hex2() {
let color = String::from("#ff000");
assert!(AppColors::color_from_str(&color).is_err());
}
#[test]
fn color_rgb() {
let color = String::from("rgb(255, 0, 0)");
let parsed = AppColors::color_from_str(&color);
assert!(parsed.is_ok());
assert_eq!(parsed.unwrap(), Color::Rgb {
r: 255,
g: 0,
b: 0
});
}
#[test]
fn color_rgb_upper() {
let color = String::from("RGB(255, 0, 0)");
let parsed = AppColors::color_from_str(&color);
assert!(parsed.is_ok());
assert_eq!(parsed.unwrap(), Color::Rgb {
r: 255,
g: 0,
b: 0
});
}
#[test]
fn color_rgb_no_space() {
let color = String::from("rgb(255,0,0)");
let parsed = AppColors::color_from_str(&color);
assert!(parsed.is_ok());
assert_eq!(parsed.unwrap(), Color::Rgb {
r: 255,
g: 0,
b: 0
});
}
}