1use std::fmt::{Formatter, Write};
2
3use serde::{
4 Deserialize, Deserializer,
5 de::{Error, MapAccess, Visitor, value::MapAccessDeserializer},
6};
7
8#[derive(Deserialize)]
9pub struct Theme {
10 palette: hashbrown::HashMap<String, String>,
11 #[serde(flatten)]
12 definitions: hashbrown::HashMap<String, PaletteReference>,
13}
14
15pub enum PaletteReference {
16 Foreground(String),
17 WithModifiers(PaletteReferenceWithModifiers),
18}
19
20impl<'de> Deserialize<'de> for PaletteReference {
21 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
22 where
23 D: Deserializer<'de>,
24 {
25 struct PaletteReferenceVisitor;
26
27 impl<'de> Visitor<'de> for PaletteReferenceVisitor {
28 type Value = PaletteReference;
29
30 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
31 formatter.write_str("palette reference")
32 }
33
34 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
35 where
36 E: Error,
37 {
38 Ok(PaletteReference::Foreground(v.to_string()))
39 }
40
41 fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
42 where
43 A: MapAccess<'de>,
44 {
45 PaletteReferenceWithModifiers::deserialize(MapAccessDeserializer::new(map))
46 .map(PaletteReference::WithModifiers)
47 }
48 }
49
50 deserializer.deserialize_any(PaletteReferenceVisitor)
51 }
52}
53
54#[derive(Deserialize)]
55pub struct PaletteReferenceWithModifiers {
56 bg: Option<String>,
57 fg: Option<String>,
58 #[serde(default)]
59 modifiers: Vec<Modifiers>,
60}
61
62#[derive(Deserialize)]
63#[serde(rename_all = "snake_case")]
64pub enum Modifiers {
65 Underlined,
66 Bold,
67 Italic,
68 CrossedOut,
69 Reversed,
70 Dim,
71}
72
73impl Theme {
74 fn get_color<'a>(&'a self, reference: &'a str) -> &'a str {
75 if reference.starts_with('#') {
76 reference
77 } else {
78 self.palette
79 .get(reference)
80 .unwrap_or_else(|| panic!("bad palette ref {reference}"))
81 }
82 }
83
84 pub fn build_css(&self) -> String {
85 let mut out = String::new();
86
87 for (kind, palette_ref) in &self.definitions {
88 write!(out, ".highlight.{kind} {{").unwrap();
89
90 match palette_ref {
91 PaletteReference::Foreground(color) => {
92 let color = self.get_color(color);
93 write!(out, "color:{color};").unwrap();
94 }
95 PaletteReference::WithModifiers(PaletteReferenceWithModifiers {
96 bg,
97 fg,
98 modifiers,
99 }) => {
100 if let Some(color) = bg {
101 let color = self.get_color(color);
102 write!(out, "background:{color};").unwrap();
103 }
104
105 if let Some(color) = fg {
106 let color = self.get_color(color);
107 write!(out, "color:{color};").unwrap();
108 }
109
110 for modifier in modifiers {
111 match modifier {
112 Modifiers::Underlined => out.push_str("text-decoration:underline;"),
113 Modifiers::Bold => out.push_str("font-weight:bold;"),
114 Modifiers::Italic => out.push_str("font-style:italic;"),
115 Modifiers::CrossedOut => out.push_str("text-decoration:line-through;"),
116 Modifiers::Reversed | Modifiers::Dim => {}
117 }
118 }
119 }
120 }
121
122 out.push('}');
123 }
124
125 out
126 }
127}