builder_rust/library/scheme/
mod.rs

1// builder-rust is a Tinted Theming template builder which uses color
2// schemes to generate theme files.
3// Copyright (C) 2024  Tinted Theming
4
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
18pub mod color;
19
20use serde::{Deserialize, Deserializer};
21use std::collections::HashMap;
22use unicode_normalization::UnicodeNormalization;
23
24use crate::library::constants::{REQUIRED_BASE16_PALETTE_KEYS, REQUIRED_BASE24_PALETTE_KEYS};
25use crate::library::scheme::color::Color;
26
27#[derive(Deserialize)]
28pub struct SchemeWrapper {
29    pub system: String,
30    pub name: String,
31    pub author: String,
32    pub description: Option<String>,
33    pub variant: Option<String>,
34    pub palette: HashMap<String, String>,
35}
36
37#[derive(Debug)]
38pub struct Scheme {
39    pub system: String,
40    pub name: String,
41    pub slug: String,
42    pub author: String,
43    pub description: Option<String>,
44    pub variant: String,
45    pub palette: HashMap<String, Color>,
46}
47
48fn slugify(input: &str) -> String {
49    input
50        .nfd() // Normalize the string to NFD form
51        .filter(char::is_ascii) // Only keep ASCII characters
52        .collect::<String>()
53        .to_lowercase()
54        .replace(' ', "-")
55        .chars()
56        .filter(|c| c.is_alphanumeric() || *c == '-') // Only keep alphanumeric and hyphens
57        .collect()
58}
59
60impl<'de> Deserialize<'de> for Scheme {
61    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
62    where
63        D: Deserializer<'de>,
64    {
65        let wrapper = SchemeWrapper::deserialize(deserializer)?;
66        let slug = slugify(&wrapper.name);
67        let variant = wrapper.variant.unwrap_or(String::from("dark"));
68
69        match wrapper.system.as_str() {
70            "base16" => {
71                let contains_all_keys = REQUIRED_BASE16_PALETTE_KEYS
72                    .iter()
73                    .all(|&key| wrapper.palette.contains_key(key));
74
75                if !contains_all_keys {
76                    return Err(serde::de::Error::custom(format!(
77                        "{} scheme does not contain the required palette properties",
78                        wrapper.system
79                    )));
80                }
81            }
82            "base24" => {
83                let contains_all_keys = REQUIRED_BASE24_PALETTE_KEYS
84                    .iter()
85                    .all(|&key| wrapper.palette.contains_key(key));
86
87                if !contains_all_keys {
88                    return Err(serde::de::Error::custom(format!(
89                        "{} scheme does not contain the required palette properties",
90                        wrapper.system
91                    )));
92                }
93            }
94            _ => {
95                return Err(serde::de::Error::custom(format!(
96                    "Unknown system: {}",
97                    wrapper.system
98                )))
99            }
100        }
101
102        let palette_result: Result<HashMap<String, Color>, _> = wrapper
103            .palette
104            .into_iter()
105            .map(|(key, value)| {
106                Color::new(value)
107                    .map(|color| (key, color))
108                    .map_err(|e| serde::de::Error::custom(e.to_string()))
109            })
110            .collect();
111
112        Ok(Scheme {
113            name: wrapper.name,
114            slug,
115            system: wrapper.system,
116            author: wrapper.author,
117            description: wrapper.description,
118            variant,
119            palette: palette_result?,
120        })
121    }
122}