use serde::{Deserialize, Serialize};
use tatara_lisp::DeriveTataraDomain;
pub const DEFAULT_PRESET: &str = "nord";
#[derive(DeriveTataraDomain, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[tatara(keyword = "deftheme")]
pub struct ThemeSpec {
#[serde(default = "default_preset")]
pub preset: String,
}
fn default_preset() -> String {
DEFAULT_PRESET.to_string()
}
impl Default for ThemeSpec {
fn default() -> Self {
Self { preset: DEFAULT_PRESET.to_string() }
}
}
impl ThemeSpec {
#[must_use]
pub fn effective_preset(&self) -> &str {
if self.preset.is_empty() {
DEFAULT_PRESET
} else {
&self.preset
}
}
}
pub const KNOWN_PRESETS: &[&str] =
&["nord", "vellum", "polar-veil", "gruvbox-dark", "tokyo-night", "catppuccin-mocha"];
impl ThemeSpec {
#[must_use]
pub fn resolve(&self) -> ishou_tokens::FleetTheme {
use ishou_tokens::FleetTheme;
match self.effective_preset() {
"nord" | "pleme_dark" | "pleme-dark" => FleetTheme::PlemeDark,
"vellum" => FleetTheme::Vellum,
"polar-veil" | "polar_veil" => FleetTheme::PolarVeil,
"bare" => FleetTheme::Bare,
_ => FleetTheme::prescribed_default(),
}
}
}
#[must_use]
pub fn is_known_preset(name: &str) -> bool {
KNOWN_PRESETS.iter().any(|p| *p == name)
}
#[cfg(test)]
mod tests {
use super::*;
use ishou_tokens::FleetTheme;
#[test]
fn nord_and_vellum_are_both_known_presets() {
assert!(is_known_preset("nord"), "the fleet default must be authorable");
assert!(is_known_preset("vellum"), "vellum stays selectable (retired, not removed)");
}
#[test]
fn default_theme_is_nord() {
assert_eq!(ThemeSpec::default().preset, "nord");
assert_eq!(ThemeSpec::default().effective_preset(), "nord");
}
#[test]
fn empty_preset_resolves_to_nord() {
let t = ThemeSpec { preset: String::new() };
assert_eq!(t.effective_preset(), "nord");
}
#[test]
fn default_preset_tracks_the_fleet_prescribed_theme() {
assert_eq!(
DEFAULT_PRESET,
FleetTheme::prescribed_default().preset_name(),
"escriba's default preset must BE the fleet's prescribed theme"
);
}
#[test]
fn deftheme_resolves_to_a_real_fleet_theme() {
let of = |p: &str| ThemeSpec { preset: p.to_string() }.resolve();
assert_eq!(of("nord"), FleetTheme::PlemeDark);
assert_eq!(of("vellum"), FleetTheme::Vellum);
assert_eq!(of("polar-veil"), FleetTheme::PolarVeil);
assert_eq!(of("bare"), FleetTheme::Bare);
assert_eq!(of("pleme_dark"), of("nord"), "wire and preset spellings agree");
assert_eq!(of("polar_veil"), of("polar-veil"), "underscore and dash agree");
}
#[test]
fn the_default_resolves_to_the_same_theme_the_fleet_prescribes() {
assert_eq!(ThemeSpec::default().resolve(), FleetTheme::prescribed_default());
assert_eq!(ThemeSpec::default().resolve(), FleetTheme::PlemeDark);
assert_eq!(ThemeSpec::default().resolve().resolve().background, "#2E3440");
}
#[test]
fn an_unknown_preset_falls_back_to_the_fleet_default() {
let t = ThemeSpec { preset: "no-such-theme-42".to_string() };
assert_eq!(t.resolve(), FleetTheme::prescribed_default());
for p in ["gruvbox-dark", "tokyo-night", "catppuccin-mocha"] {
assert!(is_known_preset(p), "{p} stays authorable");
assert_eq!(
ThemeSpec { preset: p.to_string() }.resolve(),
FleetTheme::prescribed_default(),
"{p} has no ishou palette yet — falls back, never renders arbitrary colour"
);
}
}
}