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 {
320 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
321 where
322 S: Serializer,
323 {
324 let s = match self {
325 Self::Underlined => "underlined",
326 Self::Atx => "atx",
327 Self::AtxClosed => "atxclosed",
328 };
329 serializer.serialize_str(s)
330 }
331 }
332
333 impl Serialize for ListIndentType {
334 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
335 where
336 S: Serializer,
337 {
338 let s = match self {
339 Self::Spaces => "spaces",
340 Self::Tabs => "tabs",
341 };
342 serializer.serialize_str(s)
343 }
344 }
345
346 impl Serialize for WhitespaceMode {
347 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
348 where
349 S: Serializer,
350 {
351 let s = match self {
352 Self::Normalized => "normalized",
353 Self::Strict => "strict",
354 };
355 serializer.serialize_str(s)
356 }
357 }
358
359 impl Serialize for NewlineStyle {
360 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
361 where
362 S: Serializer,
363 {
364 let s = match self {
365 Self::Spaces => "spaces",
366 Self::Backslash => "backslash",
367 };
368 serializer.serialize_str(s)
369 }
370 }
371
372 impl Serialize for CodeBlockStyle {
373 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
374 where
375 S: Serializer,
376 {
377 let s = match self {
378 Self::Indented => "indented",
379 Self::Backticks => "backticks",
380 Self::Tildes => "tildes",
381 };
382 serializer.serialize_str(s)
383 }
384 }
385
386 impl Serialize for HighlightStyle {
387 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
388 where
389 S: Serializer,
390 {
391 let s = match self {
392 Self::DoubleEqual => "doubleequal",
393 Self::Html => "html",
394 Self::Bold => "bold",
395 Self::None => "none",
396 };
397 serializer.serialize_str(s)
398 }
399 }
400
401 impl Serialize for LinkStyle {
402 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
403 where
404 S: Serializer,
405 {
406 let s = match self {
407 Self::Inline => "inline",
408 Self::Reference => "reference",
409 };
410 serializer.serialize_str(s)
411 }
412 }
413
414 impl Serialize for UrlEscapeStyle {
415 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
416 where
417 S: Serializer,
418 {
419 let s = match self {
420 Self::Angle => "angle",
421 Self::Percent => "percent",
422 };
423 serializer.serialize_str(s)
424 }
425 }
426
427 impl Serialize for OutputFormat {
428 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
429 where
430 S: Serializer,
431 {
432 let s = match self {
433 Self::Markdown => "markdown",
434 Self::Djot => "djot",
435 Self::Plain => "plain",
436 };
437 serializer.serialize_str(s)
438 }
439 }
440}
441
442#[cfg(all(test, any(feature = "serde", feature = "metadata")))]
443mod tests {
444 use super::*;
445
446 #[test]
447 fn test_enum_serialization() {
448 let heading = HeadingStyle::AtxClosed;
450 let json = serde_json::to_string(&heading).expect("Failed to serialize");
451 assert_eq!(json, r#""atxclosed""#);
452
453 let list_indent = ListIndentType::Tabs;
454 let json = serde_json::to_string(&list_indent).expect("Failed to serialize");
455 assert_eq!(json, r#""tabs""#);
456
457 let whitespace = WhitespaceMode::Strict;
458 let json = serde_json::to_string(&whitespace).expect("Failed to serialize");
459 assert_eq!(json, r#""strict""#);
460 }
461
462 #[test]
463 fn test_enum_deserialization() {
464 let heading: HeadingStyle = serde_json::from_str(r#""atxclosed""#).expect("Failed");
466 assert_eq!(heading, HeadingStyle::AtxClosed);
467
468 let heading: HeadingStyle = serde_json::from_str(r#""ATXCLOSED""#).expect("Failed");
469 assert_eq!(heading, HeadingStyle::AtxClosed);
470
471 let list_indent: ListIndentType = serde_json::from_str(r#""tabs""#).expect("Failed");
472 assert_eq!(list_indent, ListIndentType::Tabs);
473 }
474}