//! Manage conversion of a user provided string //! defining foreground and background colors into //! a string with TTY colors use { super::*, crate::errors::InvalidSkinError, crokey::crossterm::style::{ Attribute::{self, *}, Attributes, }, lazy_regex::regex, serde::{de::Error, Deserialize, Deserializer}, termimad::CompoundStyle, }; /// Parsed content of a [skin] line of the conf.toml file #[derive(Clone, Debug)] pub struct SkinEntry { focused: CompoundStyle, unfocused: Option, } impl SkinEntry { pub fn new(focused: CompoundStyle, unfocused: Option) -> Self { Self { focused, unfocused } } pub fn get_focused(&self) -> &CompoundStyle { &self.focused } pub fn get_unfocused(&self) -> &CompoundStyle { self.unfocused.as_ref().unwrap_or(&self.focused) } /// Parse a string representation of a skin entry. /// /// The general form is either "" or " / ": /// It may be just the focused compound_style, or both /// the focused and the unfocused ones, in which case there's /// a '/' as separator. /// /// Each part is " " /// where the attributes list may be empty. pub fn parse(s: &str) -> Result { let mut parts = s.split('/'); let focused = parse_compound_style(parts.next().unwrap())?; let unfocused = parts.next() .map(parse_compound_style) .transpose()?; Ok(Self { focused, unfocused }) } } impl<'de> Deserialize<'de> for SkinEntry { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { let s = String::deserialize(deserializer)?; SkinEntry::parse(&s) .map_err(|e| D::Error::custom(e.to_string())) } } fn parse_attribute(s: &str) -> Result { match s { "bold" => Ok(Bold), "crossedout" => Ok(CrossedOut), "dim" => Ok(Dim), // does it do anything ? "italic" => Ok(Italic), "reverse" => Ok(Reverse), "underlined" => Ok(Underlined), "overlined" => Ok(OverLined), // following ones aren't supported by crossterm yet // "defaultforegroundcolor" => Ok(DefaultForegroundColor), // "defaultbackgroundcolor" => Ok(DefaultBackgroundColor), "slowblink" => Ok(SlowBlink), "rapidblink" => Ok(RapidBlink), _ => Err(InvalidSkinError::InvalidAttribute { raw: s.to_owned() }), } } /// parse a sequence of space separated style attributes fn parse_attributes(s: &str) -> Result, InvalidSkinError> { s.split_whitespace().map(parse_attribute).collect() } fn parse_compound_style(s: &str) -> Result { let s = s.to_ascii_lowercase(); let parts_rex = regex!( r"(?x) ^ \s* (?P\w+(\([\d,\s]+\))?) \s+ (?P\w+(\([\d,\s]+\))?) (?P.*) \s* $ " ); if let Some(c) = parts_rex.captures(&s) { let fg_color = colors::parse(c.name("fg").unwrap().as_str())?; let bg_color = colors::parse(c.name("bg").unwrap().as_str())?; let attrs = parse_attributes(c.name("attributes").unwrap().as_str())?; Ok(CompoundStyle::new( fg_color, bg_color, Attributes::from(attrs.as_slice()), )) } else { Err(InvalidSkinError::InvalidStyle { style: s.to_owned(), }) } }