termimad/serde/
serde_skin.rs1use {
2 super::ScrollBarStyleDef,
3 crate::{
4 minimad::Alignment,
5 parse_compound_style,
6 parse_line_style,
7 parse_styled_char,
8 LineStyle,
9 MadSkin,
10 TableBorderChars,
11 ATTRIBUTES,
12 },
13 serde::{
14 de,
15 ser::SerializeMap,
16 Deserialize,
17 Serialize,
18 Serializer,
19 },
20 std::fmt,
21};
22
23impl<'de> de::Deserialize<'de> for MadSkin {
24 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
25 where
26 D: de::Deserializer<'de>,
27 {
28 struct SkinVisitor;
29
30 impl<'de> de::Visitor<'de> for SkinVisitor {
31 type Value = MadSkin;
32
33 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
34 formatter.write_str("MadSkin")
35 }
36
37 fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
38 where
39 V: de::MapAccess<'de>,
40 {
41 let mut skin = MadSkin::default();
42 while let Some(key) = map.next_key::<String>()? {
43 match key.as_str() {
44 "bold" => {
46 let value = map.next_value::<String>()?;
47 let cs = parse_compound_style(&value).map_err(de::Error::custom)?;
48 skin.bold = cs;
49 }
50 "italic" => {
51 let value = map.next_value::<String>()?;
52 let cs = parse_compound_style(&value).map_err(de::Error::custom)?;
53 skin.italic = cs;
54 }
55 "strikeout" => {
56 let value = map.next_value::<String>()?;
57 let cs = parse_compound_style(&value).map_err(de::Error::custom)?;
58 skin.strikeout = cs;
59 }
60 "inline_code" | "inline-code" => {
61 let value = map.next_value::<String>()?;
62 let cs = parse_compound_style(&value).map_err(de::Error::custom)?;
63 skin.inline_code = cs;
64 }
65 "ellipsis" => {
66 let value = map.next_value::<String>()?;
67 let cs = parse_compound_style(&value).map_err(de::Error::custom)?;
68 skin.ellipsis = cs;
69 }
70
71 "bullet" => {
73 let value = map.next_value::<String>()?;
74 let sc = parse_styled_char(&value, '*').map_err(de::Error::custom)?;
75 skin.bullet = sc;
76 }
77 "quote_mark" | "quote" | "quote-mark" => {
78 let value = map.next_value::<String>()?;
79 let sc = parse_styled_char(&value, '*').map_err(de::Error::custom)?;
80 skin.quote_mark = sc;
81 }
82 "horizontal_rule" | "horizontal-rule" | "rule" => {
83 let value = map.next_value::<String>()?;
84 let sc = parse_styled_char(&value, '*').map_err(de::Error::custom)?;
85 skin.horizontal_rule = sc;
86 }
87
88 "scrollbar" => {
90 let def: ScrollBarStyleDef = map.next_value()?;
91 skin.scrollbar = def.into_scrollbar_style();
92 }
93
94 "paragraph" => {
96 let value = map.next_value::<String>()?;
97 let ls = parse_line_style(&value).map_err(de::Error::custom)?;
98 skin.paragraph = ls;
99 }
100 "code_block" | "code-block" => {
101 let value = map.next_value::<String>()?;
102 let ls = parse_line_style(&value).map_err(de::Error::custom)?;
103 skin.code_block = ls;
104 }
105 "table" => {
106 let value = map.next_value::<String>()?;
107 let ls = parse_line_style(&value).map_err(de::Error::custom)?;
108 skin.table = ls;
109 }
110
111 "headers" => match map.next_value::<HeadersStyleInfo>()? {
113 HeadersStyleInfo::Add(ls) => {
114 for h in &mut skin.headers {
115 if let Some(fg) = ls.compound_style.get_fg() {
116 h.compound_style.set_fg(fg);
117 }
118 if let Some(bg) = ls.compound_style.get_bg() {
119 h.compound_style.set_bg(bg);
120 }
121 for &attr in ATTRIBUTES {
122 if ls.compound_style.has_attr(attr) {
123 h.compound_style.add_attr(attr);
124 }
125 }
126 if ls.align != Alignment::Unspecified {
127 h.align = ls.align;
128 }
129 }
130 }
131 HeadersStyleInfo::Levels(mut vls) => {
132 for (lvl, h) in vls.drain(..).enumerate() {
133 if lvl < skin.headers.len() {
134 skin.headers[lvl] = h;
135 }
136 }
137 }
138 },
139
140 "table_border_chars" | "table-border-chars" => {
146 let key = map.next_value::<String>()?;
147 if let Some(chars) = TableBorderChars::by_key(&key) {
148 skin.table_border_chars = chars;
149 }
150 }
151
152 _ => {
153 let _ = map.next_value::<String>()?;
154 println!("unknown key: {key}");
155 }
156 }
157 }
158 Ok(skin)
159 }
160 }
161
162 deserializer.deserialize_map(SkinVisitor {})
163 }
164}
165
166impl Serialize for MadSkin {
167 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
168 where
169 S: Serializer,
170 {
171 let mut skin = serializer.serialize_map(None)?;
172
173 skin.serialize_entry("bold", &self.bold)?;
175 skin.serialize_entry("italic", &self.italic)?;
176 skin.serialize_entry("strikeout", &self.strikeout)?;
177 skin.serialize_entry("inline_code", &self.inline_code)?;
178 skin.serialize_entry("ellipsis", &self.ellipsis)?;
179
180 skin.serialize_entry("bullet", &self.bullet)?;
182 skin.serialize_entry("quote", &self.quote_mark)?;
183 skin.serialize_entry("horizontal_rule", &self.horizontal_rule)?;
184
185 let def: ScrollBarStyleDef = (&self.scrollbar).into();
187 skin.serialize_entry("scrollbar", &def)?;
188
189 skin.serialize_entry("paragraph", &self.paragraph)?;
191 skin.serialize_entry("code_block", &self.code_block)?;
192 skin.serialize_entry("table", &self.table)?;
193
194 skin.serialize_entry("headers", &self.headers)?;
196
197 if let Some(key) = self.table_border_chars.key() {
200 skin.serialize_entry("table_border_chars", key)?;
201 }
202
203 skin.end()
204 }
205}
206
207#[derive(Deserialize)]
208#[serde(untagged)]
209enum HeadersStyleInfo {
210 Add(LineStyle),
211 Levels(Vec<LineStyle>),
212}
213
214#[test]
217fn skin_json_roundtrip() {
218 use {
219 crate::{
220 crossterm::style::{
221 Attribute,
222 Color::*,
223 },
224 gray,
225 rgb,
226 StyledChar,
227 ROUNDED_TABLE_BORDER_CHARS,
228 },
229 pretty_assertions::assert_eq,
230 };
231
232 let skin = MadSkin::default();
233 let serialized = serde_json::to_string_pretty(&skin).unwrap();
234 let deserialized = serde_json::from_str(&serialized).unwrap();
235 assert_eq!(skin, deserialized);
236
237 let mut skin = MadSkin::no_style();
238 skin.limit_to_ascii();
239 let serialized = serde_json::to_string_pretty(&skin).unwrap();
240 let deserialized = serde_json::from_str(&serialized).unwrap();
241 assert_eq!(skin, deserialized);
242
243 let skin = MadSkin::default_dark();
244 let serialized = serde_json::to_string_pretty(&skin).unwrap();
245 let deserialized = serde_json::from_str(&serialized).unwrap();
246 assert_eq!(skin, deserialized);
247
248 let skin = MadSkin::default_light();
249 let serialized = serde_json::to_string_pretty(&skin).unwrap();
250 let deserialized = serde_json::from_str(&serialized).unwrap();
251 assert_eq!(skin, deserialized);
252
253 let mut skin = MadSkin::default();
254 skin.set_headers_fg(AnsiValue(178));
255 skin.headers[2].set_fg(gray(22));
256 skin.bold.set_fg(Yellow);
257 skin.italic.set_fgbg(Magenta, rgb(30, 30, 40));
258 skin.bullet = StyledChar::from_fg_char(Yellow, '⟡');
259 skin.quote_mark.set_fg(Yellow);
260 skin.italic.set_fg(Magenta);
261 skin.scrollbar.thumb.set_fg(AnsiValue(178));
262 skin.table_border_chars = ROUNDED_TABLE_BORDER_CHARS;
263 skin.paragraph.align = Alignment::Center;
264 skin.table.align = Alignment::Center;
265 skin.inline_code.add_attr(Attribute::Reverse);
266 skin.paragraph.set_fgbg(Magenta, rgb(30, 30, 40));
267 skin.italic.add_attr(Attribute::Underlined);
268 skin.italic.add_attr(Attribute::OverLined);
269 let serialized = serde_json::to_string_pretty(&skin).unwrap();
270 let deserialized = serde_json::from_str(&serialized).unwrap();
271 assert_eq!(skin, deserialized);
272}