Skip to main content

tinted_builder/
scheme.rs

1pub mod base16;
2pub mod base24;
3mod color;
4pub mod tinted8;
5
6use serde::{Deserialize, Serialize};
7use std::{fmt, str::FromStr};
8
9pub use crate::scheme::color::Color;
10pub use crate::scheme::color::{ColorName, ColorType, ColorVariant};
11use crate::TintedBuilderError;
12
13/// Enum representing schemes for different scheme systems. This enum is non-exhaustive, meaning
14/// additional variants may be added in future versions without it being considered a breaking
15/// change.
16#[non_exhaustive]
17#[derive(Debug, Clone)]
18pub enum Scheme {
19    /// Base16 variant with `base16::Scheme` deserialized content.
20    Base16(base16::Scheme),
21    /// Base24 variant with `base24::Scheme` deserialized content.
22    Base24(base24::Scheme),
23    /// Tinted8 scheme system with `tinted8::Scheme` deserialized content.
24    Tinted8(Box<tinted8::Scheme>),
25}
26
27impl Scheme {
28    /// Parse a YAML string into a `Scheme`, auto-detecting the system.
29    ///
30    /// Inspects the `system` field (top-level or nested under `scheme`) to
31    /// determine which variant to deserialize. Defaults to Base16 if no
32    /// `system` field is found.
33    ///
34    /// # Errors
35    ///
36    /// Returns a `TintedBuilderError` if the YAML is malformed, the system
37    /// is unrecognised, or the scheme fails validation.
38    pub fn from_yaml(yaml: &str) -> Result<Self, TintedBuilderError> {
39        let raw: serde_yaml::Value =
40            serde_yaml::from_str(yaml).map_err(TintedBuilderError::YamlDeserialize)?;
41
42        let system = raw
43            .get("system")
44            .or_else(|| raw.get("scheme").and_then(|s| s.get("system")))
45            .and_then(serde_yaml::Value::as_str)
46            .ok_or_else(|| TintedBuilderError::SchemeMissingProperty("system".to_string()))?
47            .parse::<SchemeSystem>()?;
48
49        match system {
50            SchemeSystem::Base16 => {
51                let scheme: base16::Scheme =
52                    serde_yaml::from_str(yaml).map_err(TintedBuilderError::YamlDeserialize)?;
53                Ok(Self::Base16(scheme))
54            }
55            SchemeSystem::Base24 => {
56                let scheme: base24::Scheme =
57                    serde_yaml::from_str(yaml).map_err(TintedBuilderError::YamlDeserialize)?;
58                Ok(Self::Base24(scheme))
59            }
60            SchemeSystem::Tinted8 => {
61                let scheme: tinted8::Scheme =
62                    serde_yaml::from_str(yaml).map_err(TintedBuilderError::YamlDeserialize)?;
63                Ok(Self::Tinted8(Box::new(scheme)))
64            }
65        }
66    }
67
68    /// Returns the author of the scheme.
69    #[must_use]
70    pub fn get_scheme_author(&self) -> String {
71        match self {
72            Self::Base16(scheme) => scheme.author.clone(),
73            Self::Base24(scheme) => scheme.author.clone(),
74            Self::Tinted8(scheme) => scheme.scheme.author.clone(),
75        }
76    }
77    /// Returns the optional description (empty string when missing).
78    #[must_use]
79    pub fn get_scheme_description(&self) -> String {
80        match self {
81            Self::Base16(scheme) => scheme.description.clone().unwrap_or_default(),
82            Self::Base24(scheme) => scheme.description.clone().unwrap_or_default(),
83            Self::Tinted8(scheme) => scheme.scheme.description.clone().unwrap_or_default(),
84        }
85    }
86    /// Returns the human-readable name of the scheme.
87    #[must_use]
88    pub fn get_scheme_name(&self) -> String {
89        match self {
90            Self::Base16(scheme) => scheme.name.clone(),
91            Self::Base24(scheme) => scheme.name.clone(),
92            Self::Tinted8(scheme) => scheme.scheme.name.clone(),
93        }
94    }
95    /// Returns the scheme slug.
96    #[must_use]
97    pub fn get_scheme_slug(&self) -> String {
98        match self {
99            Self::Base16(scheme) => scheme.slug.clone(),
100            Self::Base24(scheme) => scheme.slug.clone(),
101            Self::Tinted8(scheme) => scheme.scheme.slug.clone(),
102        }
103    }
104    /// Returns the scheme system for this variant.
105    #[must_use]
106    pub const fn get_scheme_system(&self) -> SchemeSystem {
107        match self {
108            Self::Base16(_) => SchemeSystem::Base16,
109            Self::Base24(_) => SchemeSystem::Base24,
110            Self::Tinted8(_) => SchemeSystem::Tinted8,
111        }
112    }
113    /// Returns the scheme variant (light or dark).
114    #[must_use]
115    pub fn get_scheme_variant(&self) -> SchemeVariant {
116        match self {
117            Self::Base16(scheme) => scheme.variant.clone(),
118            Self::Base24(scheme) => scheme.variant.clone(),
119            Self::Tinted8(scheme) => scheme.variant.clone(),
120        }
121    }
122}
123
124/// Enum representing the scheme system. This enum is non-exhaustive, meaning additional variants
125/// may be added in future versions without it being considered a breaking change.
126#[non_exhaustive]
127#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)]
128#[serde(rename_all = "lowercase")]
129pub enum SchemeSystem {
130    /// Base16 scheme system, the default.
131    #[default]
132    Base16,
133    /// Base24 scheme system.
134    Base24,
135    /// Tinted8 scheme system.
136    Tinted8,
137}
138#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)]
139#[serde(rename_all = "lowercase")]
140pub struct SchemeSupports {
141    #[serde(rename = "styling-spec")]
142    pub styling_spec: String,
143}
144
145impl SchemeSystem {
146    /// Returns the string representation of the `SchemeSystem`.
147    #[must_use]
148    pub const fn as_str(&self) -> &str {
149        match self {
150            Self::Base16 => "base16",
151            Self::Base24 => "base24",
152            Self::Tinted8 => "tinted8",
153        }
154    }
155    #[must_use]
156    pub const fn variants() -> &'static [Self] {
157        &[Self::Base16, Self::Base24, Self::Tinted8]
158    }
159}
160
161impl fmt::Display for SchemeSystem {
162    /// Formats the `SchemeSystem` for display purposes.
163    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164        write!(f, "{}", self.as_str())?;
165        Ok(())
166    }
167}
168
169impl FromStr for SchemeSystem {
170    type Err = TintedBuilderError;
171
172    /// Parses a string to create a `SchemeSystem`.
173    ///
174    /// # Errors
175    ///
176    /// Returns a `TintedBuilderError` if the input string does not match
177    /// any valid scheme variant.
178    fn from_str(system_str: &str) -> Result<Self, Self::Err> {
179        match system_str {
180            "base16" => Ok(Self::Base16),
181            "base24" => Ok(Self::Base24),
182            "tinted8" => Ok(Self::Tinted8),
183            _ => Err(TintedBuilderError::InvalidSchemeSystem(
184                system_str.to_string(),
185            )),
186        }
187    }
188}
189
190/// Enum representing variants of a color scheme (Dark or Light). This enum is non-exhaustive,
191/// meaning additional variants may be added in future versions without it being considered a
192/// breaking change.
193#[non_exhaustive]
194#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
195#[serde(rename_all = "lowercase")]
196pub enum SchemeVariant {
197    /// Dark variant of the color scheme, the default.
198    #[default]
199    Dark,
200    /// Light variant of the color scheme.
201    Light,
202}
203
204impl FromStr for SchemeVariant {
205    type Err = TintedBuilderError;
206
207    /// Parses a string to create a `SchemeVariant`.
208    ///
209    /// # Errors
210    ///
211    /// Returns a `TintedBuilderError` if the input string does not match
212    /// any valid scheme variant.
213    fn from_str(variant_str: &str) -> Result<Self, Self::Err> {
214        match variant_str {
215            "light" => Ok(Self::Light),
216            "dark" => Ok(Self::Dark),
217            _ => Err(TintedBuilderError::InvalidSchemeVariant(
218                variant_str.to_string(),
219            )),
220        }
221    }
222}
223
224impl SchemeVariant {
225    /// Returns the string representation of the `SchemeVariant`.
226    #[must_use]
227    pub const fn as_str(&self) -> &str {
228        match self {
229            Self::Dark => "dark",
230            Self::Light => "light",
231        }
232    }
233}
234
235impl fmt::Display for SchemeVariant {
236    /// Formats the `SchemeVariant` for display purposes.
237    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
238        write!(f, "{}", self.as_str())?;
239        Ok(())
240    }
241}