1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum HeadingStyle {
11 Underlined,
13 #[default]
15 Atx,
16 AtxClosed,
18}
19
20impl HeadingStyle {
21 #[must_use]
26 #[cfg_attr(alef, alef(skip))]
27 pub fn parse(value: &str) -> Self {
28 match normalize_token(value).as_str() {
29 "atx" => Self::Atx,
30 "atxclosed" => Self::AtxClosed,
31 _ => Self::Underlined,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum ListIndentType {
41 #[default]
43 Spaces,
44 Tabs,
46}
47
48impl ListIndentType {
49 #[must_use]
54 #[cfg_attr(alef, alef(skip))]
55 pub fn parse(value: &str) -> Self {
56 match normalize_token(value).as_str() {
57 "tabs" => Self::Tabs,
58 _ => Self::Spaces,
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
67pub enum WhitespaceMode {
68 #[default]
70 Normalized,
71 Strict,
73}
74
75impl WhitespaceMode {
76 #[must_use]
81 #[cfg_attr(alef, alef(skip))]
82 pub fn parse(value: &str) -> Self {
83 match normalize_token(value).as_str() {
84 "strict" => Self::Strict,
85 _ => Self::Normalized,
86 }
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
94pub enum NewlineStyle {
95 #[default]
97 Spaces,
98 Backslash,
100}
101
102impl NewlineStyle {
103 #[must_use]
108 #[cfg_attr(alef, alef(skip))]
109 pub fn parse(value: &str) -> Self {
110 match normalize_token(value).as_str() {
111 "backslash" => Self::Backslash,
112 _ => Self::Spaces,
113 }
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
121pub enum CodeBlockStyle {
122 Indented,
124 #[default]
126 Backticks,
127 Tildes,
129}
130
131impl CodeBlockStyle {
132 #[must_use]
137 #[cfg_attr(alef, alef(skip))]
138 pub fn parse(value: &str) -> Self {
139 match normalize_token(value).as_str() {
140 "backticks" => Self::Backticks,
141 "tildes" => Self::Tildes,
142 _ => Self::Indented,
143 }
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
151pub enum HighlightStyle {
152 #[default]
154 DoubleEqual,
155 Html,
157 Bold,
159 None,
161}
162
163impl HighlightStyle {
164 #[must_use]
169 #[cfg_attr(alef, alef(skip))]
170 pub fn parse(value: &str) -> Self {
171 match normalize_token(value).as_str() {
172 "doubleequal" => Self::DoubleEqual,
173 "html" => Self::Html,
174 "bold" => Self::Bold,
175 "none" => Self::None,
176 _ => Self::None,
177 }
178 }
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
186pub enum LinkStyle {
187 #[default]
189 Inline,
190 Reference,
192}
193
194impl LinkStyle {
195 #[must_use]
200 #[cfg_attr(alef, alef(skip))]
201 pub fn parse(value: &str) -> Self {
202 match normalize_token(value).as_str() {
203 "reference" => Self::Reference,
204 _ => Self::Inline,
205 }
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
222pub enum UrlEscapeStyle {
223 #[default]
225 Angle,
226 Percent,
228}
229
230impl UrlEscapeStyle {
231 #[must_use]
236 #[cfg_attr(alef, alef(skip))]
237 pub fn parse(value: &str) -> Self {
238 match normalize_token(value).as_str() {
239 "percent" => Self::Percent,
240 _ => Self::Angle,
241 }
242 }
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
249pub enum OutputFormat {
250 #[default]
252 Markdown,
253 Djot,
255 Plain,
257}
258
259impl OutputFormat {
260 #[must_use]
265 #[cfg_attr(alef, alef(skip))]
266 pub fn parse(value: &str) -> Self {
267 match normalize_token(value).as_str() {
268 "djot" => Self::Djot,
269 "plain" | "plaintext" | "text" => Self::Plain,
270 _ => Self::Markdown,
271 }
272 }
273}
274
275pub(crate) fn normalize_token(value: &str) -> String {
277 let mut out = String::with_capacity(value.len());
278 for ch in value.chars() {
279 if ch.is_ascii_alphanumeric() {
280 out.push(ch.to_ascii_lowercase());
281 }
282 }
283 out
284}
285
286#[cfg(any(feature = "serde", feature = "metadata"))]
287mod serde_impls {
288 use super::{
289 CodeBlockStyle, HeadingStyle, HighlightStyle, LinkStyle, ListIndentType, NewlineStyle, OutputFormat,
290 UrlEscapeStyle, WhitespaceMode,
291 };
292 use serde::{Deserialize, Serialize, Serializer};
293
294 macro_rules! impl_deserialize_from_parse {
295 ($ty:ty, $parser:expr) => {
296 impl<'de> Deserialize<'de> for $ty {
297 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
298 where
299 D: serde::Deserializer<'de>,
300 {
301 let value = String::deserialize(deserializer)?;
302 Ok($parser(&value))
303 }
304 }
305 };
306 }
307
308 impl_deserialize_from_parse!(HeadingStyle, HeadingStyle::parse);
309 impl_deserialize_from_parse!(ListIndentType, ListIndentType::parse);
310 impl_deserialize_from_parse!(WhitespaceMode, WhitespaceMode::parse);
311 impl_deserialize_from_parse!(NewlineStyle, NewlineStyle::parse);
312 impl_deserialize_from_parse!(CodeBlockStyle, CodeBlockStyle::parse);
313 impl_deserialize_from_parse!(HighlightStyle, HighlightStyle::parse);
314 impl_deserialize_from_parse!(LinkStyle, LinkStyle::parse);
315 impl_deserialize_from_parse!(UrlEscapeStyle, UrlEscapeStyle::parse);
316 impl_deserialize_from_parse!(OutputFormat, OutputFormat::parse);
317
318 impl Serialize for HeadingStyle {
319 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
320 where
321 S: Serializer,
322 {
323 let s = match self {
324 Self::Underlined => "underlined",
325 Self::Atx => "atx",
326 Self::AtxClosed => "atxclosed",
327 };
328 serializer.serialize_str(s)
329 }
330 }
331
332 impl Serialize for ListIndentType {
333 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
334 where
335 S: Serializer,
336 {
337 let s = match self {
338 Self::Spaces => "spaces",
339 Self::Tabs => "tabs",
340 };
341 serializer.serialize_str(s)
342 }
343 }
344
345 impl Serialize for WhitespaceMode {
346 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
347 where
348 S: Serializer,
349 {
350 let s = match self {
351 Self::Normalized => "normalized",
352 Self::Strict => "strict",
353 };
354 serializer.serialize_str(s)
355 }
356 }
357
358 impl Serialize for NewlineStyle {
359 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
360 where
361 S: Serializer,
362 {
363 let s = match self {
364 Self::Spaces => "spaces",
365 Self::Backslash => "backslash",
366 };
367 serializer.serialize_str(s)
368 }
369 }
370
371 impl Serialize for CodeBlockStyle {
372 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
373 where
374 S: Serializer,
375 {
376 let s = match self {
377 Self::Indented => "indented",
378 Self::Backticks => "backticks",
379 Self::Tildes => "tildes",
380 };
381 serializer.serialize_str(s)
382 }
383 }
384
385 impl Serialize for HighlightStyle {
386 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
387 where
388 S: Serializer,
389 {
390 let s = match self {
391 Self::DoubleEqual => "doubleequal",
392 Self::Html => "html",
393 Self::Bold => "bold",
394 Self::None => "none",
395 };
396 serializer.serialize_str(s)
397 }
398 }
399
400 impl Serialize for LinkStyle {
401 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
402 where
403 S: Serializer,
404 {
405 let s = match self {
406 Self::Inline => "inline",
407 Self::Reference => "reference",
408 };
409 serializer.serialize_str(s)
410 }
411 }
412
413 impl Serialize for UrlEscapeStyle {
414 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
415 where
416 S: Serializer,
417 {
418 let s = match self {
419 Self::Angle => "angle",
420 Self::Percent => "percent",
421 };
422 serializer.serialize_str(s)
423 }
424 }
425
426 impl Serialize for OutputFormat {
427 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
428 where
429 S: Serializer,
430 {
431 let s = match self {
432 Self::Markdown => "markdown",
433 Self::Djot => "djot",
434 Self::Plain => "plain",
435 };
436 serializer.serialize_str(s)
437 }
438 }
439}
440
441#[cfg(all(test, any(feature = "serde", feature = "metadata")))]
442mod tests {
443 use super::*;
444
445 #[test]
446 fn test_enum_serialization() {
447 let heading = HeadingStyle::AtxClosed;
448 let json = serde_json::to_string(&heading).expect("Failed to serialize");
449 assert_eq!(json, r#""atxclosed""#);
450
451 let list_indent = ListIndentType::Tabs;
452 let json = serde_json::to_string(&list_indent).expect("Failed to serialize");
453 assert_eq!(json, r#""tabs""#);
454
455 let whitespace = WhitespaceMode::Strict;
456 let json = serde_json::to_string(&whitespace).expect("Failed to serialize");
457 assert_eq!(json, r#""strict""#);
458 }
459
460 #[test]
461 fn test_enum_deserialization() {
462 let heading: HeadingStyle = serde_json::from_str(r#""atxclosed""#).expect("Failed");
463 assert_eq!(heading, HeadingStyle::AtxClosed);
464
465 let heading: HeadingStyle = serde_json::from_str(r#""ATXCLOSED""#).expect("Failed");
466 assert_eq!(heading, HeadingStyle::AtxClosed);
467
468 let list_indent: ListIndentType = serde_json::from_str(r#""tabs""#).expect("Failed");
469 assert_eq!(list_indent, ListIndentType::Tabs);
470 }
471}