quinjet 0.0.71

A fast, live, keyboard-first Git source-control interface for the terminal
use std::fmt;
use std::str::FromStr;

use serde::Deserialize;

use super::Appearance;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct HostTheme {
    light: HostPalette,
    dark: HostPalette,
}

impl HostTheme {
    pub(super) const fn palette(self, appearance: Appearance) -> [u32; 16] {
        match appearance {
            Appearance::Light => self.light.values,
            Appearance::Dark => self.dark.values,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct HostPalette {
    values: [u32; 16],
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct HostThemeInput {
    light: HostPaletteInput,
    dark: HostPaletteInput,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct HostPaletteInput {
    background: String,
    panel: String,
    panel_alt: String,
    border: String,
    muted: String,
    text: String,
    text_strong: String,
    contrast: String,
    removed: String,
    orange: String,
    modified: String,
    added: String,
    cyan: String,
    accent: String,
    purple: String,
    brown: String,
}

impl HostPaletteInput {
    fn parse(self) -> Result<HostPalette, String> {
        Ok(HostPalette {
            values: [
                parse_hex(&self.background)?,
                parse_hex(&self.panel)?,
                parse_hex(&self.panel_alt)?,
                parse_hex(&self.border)?,
                parse_hex(&self.muted)?,
                parse_hex(&self.text)?,
                parse_hex(&self.text_strong)?,
                parse_hex(&self.contrast)?,
                parse_hex(&self.removed)?,
                parse_hex(&self.orange)?,
                parse_hex(&self.modified)?,
                parse_hex(&self.added)?,
                parse_hex(&self.cyan)?,
                parse_hex(&self.accent)?,
                parse_hex(&self.purple)?,
                parse_hex(&self.brown)?,
            ],
        })
    }
}

impl FromStr for HostTheme {
    type Err = String;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        let input: HostThemeInput =
            serde_json::from_str(value).map_err(|error| format!("invalid theme JSON: {error}"))?;
        Ok(Self {
            light: input.light.parse()?,
            dark: input.dark.parse()?,
        })
    }
}

impl fmt::Display for HostTheme {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("host theme")
    }
}

fn parse_hex(value: &str) -> Result<u32, String> {
    let value = value.strip_prefix('#').unwrap_or(value);
    if value.len() != 6 || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
        return Err(format!("{value} is not a six-digit RGB color"));
    }
    u32::from_str_radix(value, 16).map_err(|error| format!("invalid RGB color {value}: {error}"))
}