#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Color {
Reset,
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
DarkGray,
LightRed,
LightGreen,
LightYellow,
LightBlue,
LightMagenta,
LightCyan,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub bold: bool,
pub underline: bool,
pub reversed: bool,
}
impl Style {
pub const fn new() -> Style {
Style {
fg: None,
bg: None,
bold: false,
underline: false,
reversed: false,
}
}
pub const fn fg(mut self, color: Color) -> Style {
self.fg = Some(color);
self
}
pub const fn bg(mut self, color: Color) -> Style {
self.bg = Some(color);
self
}
pub const fn bold(mut self) -> Style {
self.bold = true;
self
}
pub const fn underline(mut self) -> Style {
self.underline = true;
self
}
pub const fn reversed(mut self) -> Style {
self.reversed = true;
self
}
pub fn patch(self, other: Style) -> Style {
Style {
fg: other.fg.or(self.fg),
bg: other.bg.or(self.bg),
bold: self.bold || other.bold,
underline: self.underline || other.underline,
reversed: self.reversed || other.reversed,
}
}
}
pub fn parse_color(name: &str) -> Option<Color> {
Some(match name.to_lowercase().as_str() {
"default" => Color::Reset,
"black" => Color::Black,
"red" => Color::Red,
"green" => Color::Green,
"yellow" => Color::Yellow,
"blue" => Color::Blue,
"magenta" => Color::Magenta,
"cyan" => Color::Cyan,
"white" => Color::White,
"gray" | "grey" | "darkgray" | "darkgrey" => Color::DarkGray,
"lightred" => Color::LightRed,
"lightgreen" => Color::LightGreen,
"lightyellow" => Color::LightYellow,
"lightblue" => Color::LightBlue,
"lightmagenta" => Color::LightMagenta,
"lightcyan" => Color::LightCyan,
_ => return None,
})
}
pub fn rule_style(
rule: &rmut_core::config::ColorRule,
what: &str,
warnings: &mut Vec<String>,
) -> Style {
let mut style = Style::new();
for (name, is_fg) in [(&rule.fg, true), (&rule.bg, false)] {
let Some(name) = name else { continue };
match name.as_str() {
"bold" => style = style.bold(),
"underline" => style = style.underline(),
"reverse" | "standout" => style = style.reversed(),
"none" => {}
_ => match parse_color(name) {
Some(color) if is_fg => style = style.fg(color),
Some(color) => style = style.bg(color),
None => warnings.push(format!("unknown {what} color {name:?}")),
},
}
}
style
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn patch_overlays_colors_and_adds_attributes() {
let base = Style::new().fg(Color::Red).bold();
let over = Style::new().bg(Color::Blue).reversed();
let got = base.patch(over);
assert_eq!(
got,
Style::new()
.fg(Color::Red)
.bg(Color::Blue)
.bold()
.reversed()
);
let got = got.patch(Style::new().fg(Color::Green));
assert_eq!(got.fg, Some(Color::Green));
assert!(got.bold && got.reversed);
}
#[test]
fn rules_read_attributes_and_colors_in_either_slot() {
let rule = rmut_core::config::ColorRule {
pattern: String::new(),
fg: Some("bold".into()),
bg: Some("blue".into()),
};
let mut warnings = Vec::new();
let style = rule_style(&rule, "color_index", &mut warnings);
assert_eq!(style, Style::new().bold().bg(Color::Blue));
assert!(warnings.is_empty());
let rule = rmut_core::config::ColorRule {
pattern: String::new(),
fg: Some("chartreuse".into()),
bg: None,
};
assert_eq!(rule_style(&rule, "color_body", &mut warnings), Style::new());
assert_eq!(warnings.len(), 1);
}
}