lunar-lib 0.10.0

Common utilities for lunar applications
Documentation
use std::{collections::HashMap, str::FromStr};

use ratatui::style::{Color, Modifier, Style};
use serde::Deserialize;

use crate::{iterator_ext::IteratorExtensions, ratatui_theme::ResolvedPalette};

#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum StyleType {
    Normal(Style),
    Raw(RawStyle),
    FgOnlyNormal(Color),
    FgOnlyPalette(String),
}

#[derive(Debug, Deserialize, Clone)]
struct RawStyle {
    /// The foreground color.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fg: Option<String>,

    /// The background color.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bg: Option<String>,

    /// The underline color.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub underline_color: Option<String>,

    /// The modifiers to add.
    #[serde(
        default,
        skip_serializing_if = "Modifier::is_empty",
        deserialize_with = "deserialize_modifier"
    )]
    pub add_modifier: Modifier,

    /// The modifiers to remove.
    #[serde(
        default,
        skip_serializing_if = "Modifier::is_empty",
        deserialize_with = "deserialize_modifier"
    )]
    pub sub_modifier: Modifier,
}

impl RawStyle {
    fn into_style(self, palette: &HashMap<String, Color>) -> Style {
        Style {
            fg: self.fg.and_then(|fg| palette.get(&fg).copied()),
            bg: self.bg.and_then(|bg| palette.get(&bg).copied()),
            underline_color: self
                .underline_color
                .and_then(|uc| palette.get(&uc).copied()),
            add_modifier: self.add_modifier,
            sub_modifier: self.sub_modifier,
        }
    }
}

impl FromStr for RawStyle {
    type Err = toml::de::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        toml::from_str(s)
    }
}

fn deserialize_modifier<'de, D>(deserializer: D) -> Result<Modifier, D::Error>
where
    D: serde::Deserializer<'de>,
{
    use serde::Deserialize;

    Option::<Modifier>::deserialize(deserializer)
        .map(|modifier| modifier.unwrap_or_else(Modifier::empty))
}

#[derive(Deserialize)]
pub(super) struct RawTheme {
    #[serde(default)]
    styles: HashMap<String, StyleType>,

    #[serde(default)]
    palette: HashMap<String, Color>,
}

impl RawTheme {
    pub fn resolve(self) -> ResolvedPalette {
        let mut resolved_styles = HashMap::with_capacity(self.styles.len());

        for key in self.styles.keys() {
            let mut final_style = Style::default();
            let parts = key.split('.').to_vec();

            for i in 1..=parts.len() {
                let parent_key = parts[..i].join(".");

                if let Some(style_type) = self.styles.get(&parent_key) {
                    let style = match style_type {
                        StyleType::Normal(style) => *style,
                        StyleType::Raw(raw_style) => raw_style.clone().into_style(&self.palette),
                        StyleType::FgOnlyNormal(color) => {
                            resolved_styles.insert(key.clone(), Style::default().fg(*color));
                            continue;
                        }
                        StyleType::FgOnlyPalette(key) => {
                            if let Some(copied) = self.palette.get(key) {
                                final_style.fg = Some(*copied);
                            }

                            resolved_styles.insert(key.clone(), final_style);
                            continue;
                        }
                    };

                    if let Some(fg) = style.fg {
                        final_style.fg = Some(fg);
                    }
                    if let Some(bg) = style.bg {
                        final_style.bg = Some(bg);
                    }
                    if let Some(uc) = style.underline_color {
                        final_style.underline_color = Some(uc);
                    }

                    final_style.add_modifier |= style.add_modifier & !Modifier::UNDERLINED;
                    final_style.sub_modifier |= style.sub_modifier & !Modifier::UNDERLINED;
                }
            }

            resolved_styles.insert(key.clone(), final_style);
        }

        ResolvedPalette {
            styles: resolved_styles,
            palette: self.palette,
        }
    }
}