use std::collections::HashMap;
use ratatui::style::Color;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Theme {
tags: HashMap<String, Color>,
issue_types: HashMap<String, Color>,
milestones: HashMap<String, Color>,
}
impl Theme {
pub fn new(
tags: HashMap<String, Color>,
issue_types: HashMap<String, Color>,
milestones: HashMap<String, Color>,
) -> Self {
Self {
tags,
issue_types,
milestones,
}
}
pub fn tag(&self, label: &str) -> Option<Color> {
self.tags.get(&label.trim().to_ascii_lowercase()).copied()
}
pub fn issue_type(&self, type_name: &str) -> Option<Color> {
self.issue_types
.get(&type_name.trim().to_ascii_lowercase())
.copied()
}
pub fn milestone(&self, name: &str) -> Option<Color> {
self.milestones
.get(&name.trim().to_ascii_lowercase())
.copied()
}
pub fn is_empty(&self) -> bool {
self.tags.is_empty() && self.issue_types.is_empty() && self.milestones.is_empty()
}
}
pub fn parse(name: &str) -> Option<Color> {
let key: String = name
.trim()
.to_ascii_lowercase()
.chars()
.filter(|c| !matches!(c, '_' | '-' | ' '))
.collect();
Some(match key.as_str() {
"black" => Color::Black,
"red" => Color::Red,
"green" => Color::Green,
"yellow" => Color::Yellow,
"blue" => Color::Blue,
"magenta" | "purple" => Color::Magenta,
"cyan" => Color::Cyan,
"gray" | "grey" => Color::Gray,
"white" => Color::White,
"darkgray" | "darkgrey" => Color::DarkGray,
"lightred" | "brightred" => Color::LightRed,
"lightgreen" | "brightgreen" => Color::LightGreen,
"lightyellow" | "brightyellow" => Color::LightYellow,
"lightblue" | "brightblue" => Color::LightBlue,
"lightmagenta" | "brightmagenta" => Color::LightMagenta,
"lightcyan" | "brightcyan" => Color::LightCyan,
_ => return None,
})
}
fn is_light(color: Color) -> bool {
matches!(
color,
Color::White
| Color::Yellow
| Color::Cyan
| Color::LightRed
| Color::LightGreen
| Color::LightYellow
| Color::LightBlue
| Color::LightMagenta
| Color::LightCyan
)
}
pub fn contrast_fg(bg: Color) -> Color {
if is_light(bg) {
Color::Black
} else {
Color::White
}
}
#[cfg(test)]
mod tests {
#[allow(clippy::wildcard_imports)]
use super::*;
#[test]
fn parses_names_case_and_separator_insensitively() {
assert_eq!(parse("Red"), Some(Color::Red));
assert_eq!(parse("dark_gray"), Some(Color::DarkGray));
assert_eq!(parse("Dark Gray"), Some(Color::DarkGray));
assert_eq!(parse("brightblue"), Some(Color::LightBlue));
assert_eq!(parse("purple"), Some(Color::Magenta));
assert_eq!(parse("nope"), None);
}
#[test]
fn contrast_is_black_on_light_and_white_on_dark() {
assert_eq!(contrast_fg(Color::Yellow), Color::Black);
assert_eq!(contrast_fg(Color::White), Color::Black);
assert_eq!(contrast_fg(Color::Blue), Color::White);
assert_eq!(contrast_fg(Color::DarkGray), Color::White);
}
}