1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
9pub enum Flavor {
10 #[default]
12 Pandoc,
13 Quarto,
15 #[cfg_attr(feature = "serde", serde(rename = "rmarkdown"))]
17 RMarkdown,
18 Gfm,
20 #[cfg_attr(feature = "serde", serde(alias = "commonmark"))]
22 CommonMark,
23 #[cfg_attr(feature = "serde", serde(rename = "multimarkdown"))]
25 MultiMarkdown,
26}
27
28#[derive(Debug, Clone, PartialEq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33#[cfg_attr(feature = "serde", serde(default))]
34#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
35#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
36pub struct Extensions {
37 #[cfg_attr(feature = "serde", serde(alias = "blank_before_header"))]
42 pub blank_before_header: bool,
43 #[cfg_attr(feature = "serde", serde(alias = "header_attributes"))]
45 pub header_attributes: bool,
46 pub auto_identifiers: bool,
48 pub gfm_auto_identifiers: bool,
50 pub implicit_header_references: bool,
52
53 #[cfg_attr(feature = "serde", serde(alias = "blank_before_blockquote"))]
56 pub blank_before_blockquote: bool,
57
58 #[cfg_attr(feature = "serde", serde(alias = "fancy_lists"))]
61 pub fancy_lists: bool,
62 pub startnum: bool,
64 #[cfg_attr(feature = "serde", serde(alias = "example_lists"))]
66 pub example_lists: bool,
67 #[cfg_attr(feature = "serde", serde(alias = "task_lists"))]
69 pub task_lists: bool,
70 #[cfg_attr(feature = "serde", serde(alias = "definition_lists"))]
72 pub definition_lists: bool,
73 #[cfg_attr(feature = "serde", serde(alias = "lists_without_preceding_blankline"))]
75 pub lists_without_preceding_blankline: bool,
76 #[cfg_attr(feature = "serde", serde(alias = "four_space_rule"))]
79 pub four_space_rule: bool,
80
81 #[cfg_attr(feature = "serde", serde(alias = "backtick_code_blocks"))]
84 pub backtick_code_blocks: bool,
85 #[cfg_attr(feature = "serde", serde(alias = "fenced_code_blocks"))]
87 pub fenced_code_blocks: bool,
88 #[cfg_attr(feature = "serde", serde(alias = "fenced_code_attributes"))]
90 pub fenced_code_attributes: bool,
91 pub executable_code: bool,
93 pub rmarkdown_inline_code: bool,
95 pub quarto_inline_code: bool,
97 #[cfg_attr(feature = "serde", serde(alias = "inline_code_attributes"))]
99 pub inline_code_attributes: bool,
100
101 #[cfg_attr(feature = "serde", serde(alias = "simple_tables"))]
104 pub simple_tables: bool,
105 #[cfg_attr(feature = "serde", serde(alias = "multiline_tables"))]
107 pub multiline_tables: bool,
108 #[cfg_attr(feature = "serde", serde(alias = "grid_tables"))]
110 pub grid_tables: bool,
111 #[cfg_attr(feature = "serde", serde(alias = "pipe_tables"))]
113 pub pipe_tables: bool,
114 #[cfg_attr(feature = "serde", serde(alias = "table_captions"))]
116 pub table_captions: bool,
117
118 #[cfg_attr(feature = "serde", serde(alias = "fenced_divs"))]
121 pub fenced_divs: bool,
122 #[cfg_attr(feature = "serde", serde(alias = "native_divs"))]
124 pub native_divs: bool,
125
126 #[cfg_attr(feature = "serde", serde(alias = "line_blocks"))]
129 pub line_blocks: bool,
130
131 #[cfg_attr(feature = "serde", serde(alias = "intraword_underscores"))]
136 pub intraword_underscores: bool,
137 pub strikeout: bool,
139 pub superscript: bool,
141 pub subscript: bool,
142
143 #[cfg_attr(feature = "serde", serde(alias = "inline_links"))]
146 pub inline_links: bool,
147 #[cfg_attr(feature = "serde", serde(alias = "reference_links"))]
149 pub reference_links: bool,
150 #[cfg_attr(feature = "serde", serde(alias = "shortcut_reference_links"))]
152 pub shortcut_reference_links: bool,
153 #[cfg_attr(feature = "serde", serde(alias = "link_attributes"))]
155 pub link_attributes: bool,
156 pub autolinks: bool,
158
159 #[cfg_attr(feature = "serde", serde(alias = "inline_images"))]
162 pub inline_images: bool,
163 #[cfg_attr(feature = "serde", serde(alias = "implicit_figures"))]
165 pub implicit_figures: bool,
166
167 #[cfg_attr(feature = "serde", serde(alias = "tex_math_dollars"))]
170 pub tex_math_dollars: bool,
171 #[cfg_attr(feature = "serde", serde(alias = "tex_math_gfm"))]
173 pub tex_math_gfm: bool,
174 #[cfg_attr(feature = "serde", serde(alias = "tex_math_single_backslash"))]
176 pub tex_math_single_backslash: bool,
177 #[cfg_attr(feature = "serde", serde(alias = "tex_math_double_backslash"))]
179 pub tex_math_double_backslash: bool,
180
181 #[cfg_attr(feature = "serde", serde(alias = "inline_footnotes"))]
184 pub inline_footnotes: bool,
185 pub footnotes: bool,
187
188 pub citations: bool,
191
192 #[cfg_attr(feature = "serde", serde(alias = "bracketed_spans"))]
195 pub bracketed_spans: bool,
196 #[cfg_attr(feature = "serde", serde(alias = "native_spans"))]
198 pub native_spans: bool,
199
200 #[cfg_attr(feature = "serde", serde(alias = "yaml_metadata_block"))]
203 pub yaml_metadata_block: bool,
204 #[cfg_attr(feature = "serde", serde(alias = "pandoc_title_block"))]
206 pub pandoc_title_block: bool,
207 pub mmd_title_block: bool,
209
210 #[cfg_attr(feature = "serde", serde(alias = "raw_html"))]
213 pub raw_html: bool,
214 #[cfg_attr(feature = "serde", serde(alias = "markdown_in_html_blocks"))]
216 pub markdown_in_html_blocks: bool,
217 #[cfg_attr(feature = "serde", serde(alias = "raw_tex"))]
219 pub raw_tex: bool,
220 #[cfg_attr(feature = "serde", serde(alias = "raw_attribute"))]
222 pub raw_attribute: bool,
223
224 #[cfg_attr(feature = "serde", serde(alias = "all_symbols_escapable"))]
227 pub all_symbols_escapable: bool,
228 #[cfg_attr(feature = "serde", serde(alias = "escaped_line_breaks"))]
230 pub escaped_line_breaks: bool,
231
232 #[cfg_attr(feature = "serde", serde(alias = "autolink_bare_uris"))]
236 pub autolink_bare_uris: bool,
237 #[cfg_attr(feature = "serde", serde(alias = "hard_line_breaks"))]
239 pub hard_line_breaks: bool,
240 #[cfg_attr(feature = "serde", serde(alias = "east_asian_line_breaks"))]
242 pub east_asian_line_breaks: bool,
243 pub mmd_header_identifiers: bool,
245 pub mmd_link_attributes: bool,
247 pub alerts: bool,
249 pub emoji: bool,
251 pub mark: bool,
253
254 #[cfg_attr(feature = "serde", serde(alias = "quarto_callouts"))]
257 pub quarto_callouts: bool,
258 #[cfg_attr(feature = "serde", serde(alias = "quarto_crossrefs"))]
260 pub quarto_crossrefs: bool,
261 #[cfg_attr(feature = "serde", serde(alias = "quarto_shortcodes"))]
263 pub quarto_shortcodes: bool,
264 pub bookdown_references: bool,
266 pub bookdown_equation_references: bool,
268}
269
270impl Default for Extensions {
271 fn default() -> Self {
272 Self::for_flavor(Flavor::default())
273 }
274}
275
276impl Extensions {
277 fn none_defaults() -> Self {
278 Self {
279 alerts: false,
280 all_symbols_escapable: false,
281 auto_identifiers: false,
282 autolink_bare_uris: false,
283 autolinks: false,
284 backtick_code_blocks: false,
285 blank_before_blockquote: false,
286 blank_before_header: false,
287 bookdown_references: false,
288 bookdown_equation_references: false,
289 bracketed_spans: false,
290 citations: false,
291 definition_lists: false,
292 lists_without_preceding_blankline: false,
293 emoji: false,
294 escaped_line_breaks: false,
295 example_lists: false,
296 executable_code: false,
297 rmarkdown_inline_code: false,
298 quarto_inline_code: false,
299 fancy_lists: false,
300 fenced_code_attributes: false,
301 fenced_code_blocks: false,
302 fenced_divs: false,
303 footnotes: false,
304 four_space_rule: false,
305 gfm_auto_identifiers: false,
306 grid_tables: false,
307 east_asian_line_breaks: false,
308 hard_line_breaks: false,
309 header_attributes: false,
310 implicit_figures: false,
311 implicit_header_references: false,
312 inline_code_attributes: false,
313 inline_footnotes: false,
314 inline_images: false,
315 inline_links: false,
316 intraword_underscores: false,
317 line_blocks: false,
318 link_attributes: false,
319 mark: false,
320 markdown_in_html_blocks: false,
321 mmd_header_identifiers: false,
322 mmd_link_attributes: false,
323 mmd_title_block: false,
324 multiline_tables: false,
325 native_divs: false,
326 native_spans: false,
327 pandoc_title_block: false,
328 pipe_tables: false,
329 quarto_callouts: false,
330 quarto_crossrefs: false,
331 quarto_shortcodes: false,
332 raw_attribute: false,
333 raw_html: false,
334 raw_tex: false,
335 reference_links: false,
336 shortcut_reference_links: false,
337 simple_tables: false,
338 startnum: false,
339 strikeout: false,
340 subscript: false,
341 superscript: false,
342 table_captions: false,
343 task_lists: false,
344 tex_math_dollars: false,
345 tex_math_double_backslash: false,
346 tex_math_gfm: false,
347 tex_math_single_backslash: false,
348 yaml_metadata_block: false,
349 }
350 }
351
352 pub fn for_flavor(flavor: Flavor) -> Self {
354 match flavor {
355 Flavor::Pandoc => Self::pandoc_defaults(),
356 Flavor::Quarto => Self::quarto_defaults(),
357 Flavor::RMarkdown => Self::rmarkdown_defaults(),
358 Flavor::Gfm => Self::gfm_defaults(),
359 Flavor::CommonMark => Self::commonmark_defaults(),
360 Flavor::MultiMarkdown => Self::multimarkdown_defaults(),
361 }
362 }
363
364 fn pandoc_defaults() -> Self {
365 Self {
366 auto_identifiers: true,
368 blank_before_blockquote: true,
369 blank_before_header: true,
370 gfm_auto_identifiers: false,
371 header_attributes: true,
372 implicit_header_references: true,
373
374 definition_lists: true,
376 example_lists: true,
377 fancy_lists: true,
378 lists_without_preceding_blankline: false,
379 startnum: true,
380 task_lists: true,
381
382 backtick_code_blocks: true,
384 executable_code: false,
385 rmarkdown_inline_code: false,
386 quarto_inline_code: false,
387 fenced_code_attributes: true,
388 fenced_code_blocks: true,
389 inline_code_attributes: true,
390
391 grid_tables: true,
393 multiline_tables: true,
394 pipe_tables: true,
395 simple_tables: true,
396 table_captions: true,
397
398 fenced_divs: true,
400 native_divs: true,
401
402 line_blocks: true,
404
405 intraword_underscores: true,
407 strikeout: true,
408 subscript: true,
409 superscript: true,
410
411 autolinks: true,
413 inline_links: true,
414 link_attributes: true,
415 reference_links: true,
416 shortcut_reference_links: true,
417
418 implicit_figures: true,
420 inline_images: true,
421
422 tex_math_dollars: true,
424 tex_math_double_backslash: false,
425 tex_math_gfm: false,
426 tex_math_single_backslash: false,
427
428 footnotes: true,
430 inline_footnotes: true,
431
432 citations: true,
434
435 bracketed_spans: true,
437 native_spans: true,
438
439 mmd_title_block: false,
441 pandoc_title_block: true,
442 yaml_metadata_block: true,
443
444 markdown_in_html_blocks: false,
446 raw_attribute: true,
447 raw_html: true,
448 raw_tex: true,
449
450 all_symbols_escapable: true,
452 escaped_line_breaks: true,
453
454 alerts: false,
456 autolink_bare_uris: false,
457 east_asian_line_breaks: false,
458 emoji: false,
459 four_space_rule: false,
460 hard_line_breaks: false,
461 mark: false,
462 mmd_header_identifiers: false,
463 mmd_link_attributes: false,
464
465 bookdown_references: false,
467 bookdown_equation_references: false,
468 quarto_callouts: false,
469 quarto_crossrefs: false,
470 quarto_shortcodes: false,
471 }
472 }
473
474 fn quarto_defaults() -> Self {
475 let mut ext = Self::pandoc_defaults();
476
477 ext.executable_code = true;
478 ext.rmarkdown_inline_code = true;
479 ext.quarto_inline_code = true;
480 ext.quarto_callouts = true;
481 ext.quarto_crossrefs = true;
482 ext.quarto_shortcodes = true;
483
484 ext
485 }
486
487 fn rmarkdown_defaults() -> Self {
488 let mut ext = Self::pandoc_defaults();
489
490 ext.bookdown_references = true;
491 ext.bookdown_equation_references = true;
492 ext.executable_code = true;
493 ext.rmarkdown_inline_code = true;
494 ext.quarto_inline_code = false;
495 ext.tex_math_dollars = true;
496 ext.tex_math_single_backslash = true;
497
498 ext
499 }
500
501 fn gfm_defaults() -> Self {
502 let mut ext = Self::none_defaults();
503
504 ext.alerts = true;
505 ext.auto_identifiers = true;
506 ext.autolink_bare_uris = true;
507 ext.autolinks = true;
508 ext.backtick_code_blocks = true;
509 ext.emoji = true;
510 ext.fenced_code_blocks = true;
511 ext.footnotes = true;
512 ext.gfm_auto_identifiers = true;
513 ext.inline_images = true;
514 ext.inline_links = true;
515 ext.pipe_tables = true;
516 ext.raw_html = true;
517 ext.reference_links = true;
518 ext.shortcut_reference_links = true;
519 ext.strikeout = true;
520 ext.task_lists = true;
521 ext.tex_math_dollars = true;
522 ext.tex_math_gfm = true;
523 ext.yaml_metadata_block = true;
524
525 ext
526 }
527
528 fn commonmark_defaults() -> Self {
529 let mut ext = Self::none_defaults();
530 ext.autolinks = true;
540 ext.backtick_code_blocks = true;
541 ext.escaped_line_breaks = true;
542 ext.fenced_code_blocks = true;
543 ext.inline_images = true;
544 ext.inline_links = true;
545 ext.intraword_underscores = true;
546 ext.raw_html = true;
547 ext.reference_links = true;
548 ext.shortcut_reference_links = true;
549 ext
550 }
551
552 fn multimarkdown_defaults() -> Self {
553 let mut ext = Self::none_defaults();
554
555 ext.all_symbols_escapable = true;
556 ext.auto_identifiers = true;
557 ext.backtick_code_blocks = true;
558 ext.definition_lists = true;
559 ext.footnotes = true;
560 ext.implicit_figures = true;
561 ext.implicit_header_references = true;
562 ext.intraword_underscores = true;
563 ext.mmd_header_identifiers = true;
564 ext.mmd_link_attributes = true;
565 ext.mmd_title_block = true;
566 ext.pipe_tables = true;
567 ext.raw_attribute = true;
568 ext.raw_html = true;
569 ext.reference_links = true;
570 ext.shortcut_reference_links = true;
571 ext.subscript = true;
572 ext.superscript = true;
573 ext.tex_math_dollars = true;
574 ext.tex_math_double_backslash = true;
575
576 ext
577 }
578
579 pub fn merge_with_flavor(user_overrides: HashMap<String, bool>, flavor: Flavor) -> Self {
593 let defaults = Self::for_flavor(flavor);
594 Self::merge_overrides(defaults, user_overrides)
595 }
596
597 pub fn apply_overrides(&mut self, user_overrides: HashMap<String, bool>) {
603 *self = Self::merge_overrides(self.clone(), user_overrides);
604 }
605
606 fn merge_overrides(mut base: Extensions, user_overrides: HashMap<String, bool>) -> Self {
607 for (key, value) in user_overrides {
608 base.set_by_name(&key, value);
609 }
610 base
611 }
612}
613
614macro_rules! known_extensions {
619 ( $( $kebab:literal => $field:ident ),* $(,)? ) => {
620 impl Extensions {
621 pub const KNOWN_NAMES: &'static [&'static str] = &[ $($kebab),* ];
626
627 pub fn is_known_name(name: &str) -> bool {
630 let normalized = name.replace('_', "-");
631 Self::KNOWN_NAMES.iter().any(|k| *k == normalized)
632 }
633
634 fn set_by_name(&mut self, name: &str, value: bool) -> bool {
637 match name.replace('_', "-").as_str() {
638 $( $kebab => { self.$field = value; true } )*
639 _ => false,
640 }
641 }
642 }
643 };
644}
645
646known_extensions! {
647 "blank-before-header" => blank_before_header,
648 "header-attributes" => header_attributes,
649 "auto-identifiers" => auto_identifiers,
650 "gfm-auto-identifiers" => gfm_auto_identifiers,
651 "implicit-header-references" => implicit_header_references,
652 "blank-before-blockquote" => blank_before_blockquote,
653 "fancy-lists" => fancy_lists,
654 "startnum" => startnum,
655 "example-lists" => example_lists,
656 "task-lists" => task_lists,
657 "definition-lists" => definition_lists,
658 "lists-without-preceding-blankline" => lists_without_preceding_blankline,
659 "four-space-rule" => four_space_rule,
660 "backtick-code-blocks" => backtick_code_blocks,
661 "fenced-code-blocks" => fenced_code_blocks,
662 "fenced-code-attributes" => fenced_code_attributes,
663 "executable-code" => executable_code,
664 "rmarkdown-inline-code" => rmarkdown_inline_code,
665 "quarto-inline-code" => quarto_inline_code,
666 "inline-code-attributes" => inline_code_attributes,
667 "simple-tables" => simple_tables,
668 "multiline-tables" => multiline_tables,
669 "grid-tables" => grid_tables,
670 "pipe-tables" => pipe_tables,
671 "table-captions" => table_captions,
672 "fenced-divs" => fenced_divs,
673 "native-divs" => native_divs,
674 "line-blocks" => line_blocks,
675 "intraword-underscores" => intraword_underscores,
676 "strikeout" => strikeout,
677 "superscript" => superscript,
678 "subscript" => subscript,
679 "inline-links" => inline_links,
680 "reference-links" => reference_links,
681 "shortcut-reference-links" => shortcut_reference_links,
682 "link-attributes" => link_attributes,
683 "autolinks" => autolinks,
684 "inline-images" => inline_images,
685 "implicit-figures" => implicit_figures,
686 "tex-math-dollars" => tex_math_dollars,
687 "tex-math-gfm" => tex_math_gfm,
688 "tex-math-single-backslash" => tex_math_single_backslash,
689 "tex-math-double-backslash" => tex_math_double_backslash,
690 "inline-footnotes" => inline_footnotes,
691 "footnotes" => footnotes,
692 "citations" => citations,
693 "bracketed-spans" => bracketed_spans,
694 "native-spans" => native_spans,
695 "yaml-metadata-block" => yaml_metadata_block,
696 "pandoc-title-block" => pandoc_title_block,
697 "mmd-title-block" => mmd_title_block,
698 "raw-html" => raw_html,
699 "markdown-in-html-blocks" => markdown_in_html_blocks,
700 "raw-tex" => raw_tex,
701 "raw-attribute" => raw_attribute,
702 "all-symbols-escapable" => all_symbols_escapable,
703 "escaped-line-breaks" => escaped_line_breaks,
704 "autolink-bare-uris" => autolink_bare_uris,
705 "hard-line-breaks" => hard_line_breaks,
706 "east-asian-line-breaks" => east_asian_line_breaks,
707 "mmd-header-identifiers" => mmd_header_identifiers,
708 "mmd-link-attributes" => mmd_link_attributes,
709 "alerts" => alerts,
710 "emoji" => emoji,
711 "mark" => mark,
712 "quarto-callouts" => quarto_callouts,
713 "quarto-crossrefs" => quarto_crossrefs,
714 "quarto-shortcodes" => quarto_shortcodes,
715 "bookdown-references" => bookdown_references,
716 "bookdown-equation-references" => bookdown_equation_references,
717}
718
719#[cfg(test)]
720mod tests {
721 use super::{Extensions, Flavor};
722 use std::collections::HashMap;
723
724 #[test]
725 fn merge_with_flavor_keeps_known_extension_overrides() {
726 let mut overrides = HashMap::new();
727 overrides.insert("intraword-underscores".to_string(), false);
728 let ext = Extensions::merge_with_flavor(overrides, Flavor::Pandoc);
729 assert!(!ext.intraword_underscores);
730 }
731
732 #[test]
733 fn merge_with_flavor_ignores_unknown_extension_overrides() {
734 let mut overrides = HashMap::new();
735 overrides.insert("smart".to_string(), true);
736 overrides.insert("smart-quotes".to_string(), true);
737 let ext = Extensions::merge_with_flavor(overrides, Flavor::Gfm);
738 assert!(ext.strikeout, "known defaults should remain intact");
739 }
740
741 #[test]
742 fn lists_without_preceding_blankline_defaults_false_for_pandoc_and_gfm() {
743 assert!(!Extensions::for_flavor(Flavor::Pandoc).lists_without_preceding_blankline);
744 assert!(!Extensions::for_flavor(Flavor::Gfm).lists_without_preceding_blankline);
745 }
746
747 #[test]
748 fn merge_with_flavor_accepts_lists_without_preceding_blankline_override() {
749 let mut overrides = HashMap::new();
750 overrides.insert("lists-without-preceding-blankline".to_string(), true);
751 let ext = Extensions::merge_with_flavor(overrides, Flavor::Pandoc);
752 assert!(ext.lists_without_preceding_blankline);
753 }
754
755 #[test]
756 fn four_space_rule_defaults_off_for_every_flavor() {
757 for flavor in [
758 Flavor::Pandoc,
759 Flavor::Quarto,
760 Flavor::RMarkdown,
761 Flavor::Gfm,
762 Flavor::CommonMark,
763 Flavor::MultiMarkdown,
764 ] {
765 assert!(
766 !Extensions::for_flavor(flavor).four_space_rule,
767 "four_space_rule should be off by default for {flavor:?}"
768 );
769 }
770 }
771
772 #[test]
773 fn merge_with_flavor_accepts_four_space_rule_override() {
774 let mut overrides = HashMap::new();
775 overrides.insert("four-space-rule".to_string(), true);
776 let ext = Extensions::merge_with_flavor(overrides, Flavor::Pandoc);
777 assert!(ext.four_space_rule);
778 }
779}
780
781#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
782#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
783pub enum PandocCompat {
784 #[cfg_attr(feature = "serde", serde(rename = "latest"))]
789 Latest,
790 #[cfg_attr(
792 feature = "serde",
793 serde(rename = "3.7", alias = "3-7", alias = "v3.7", alias = "v3-7")
794 )]
795 V3_7,
796 #[default]
798 #[cfg_attr(
799 feature = "serde",
800 serde(rename = "3.9", alias = "3-9", alias = "v3.9", alias = "v3-9")
801 )]
802 V3_9,
803}
804
805impl PandocCompat {
806 pub const PINNED_LATEST: Self = Self::V3_9;
808
809 pub fn effective(self) -> Self {
810 match self {
811 Self::Latest => Self::PINNED_LATEST,
812 other => other,
813 }
814 }
815}
816
817#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
828#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
829#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
830#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
831pub enum Dialect {
832 #[default]
835 Pandoc,
836 CommonMark,
838}
839
840impl Dialect {
841 pub fn for_flavor(flavor: Flavor) -> Self {
843 match flavor {
844 Flavor::CommonMark | Flavor::Gfm => Dialect::CommonMark,
845 Flavor::Pandoc | Flavor::Quarto | Flavor::RMarkdown | Flavor::MultiMarkdown => {
846 Dialect::Pandoc
847 }
848 }
849 }
850}
851
852#[derive(Debug, Clone)]
853#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
854#[cfg_attr(feature = "serde", serde(default, rename_all = "kebab-case"))]
855#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
856pub struct ParserOptions {
857 pub flavor: Flavor,
858 pub dialect: Dialect,
859 pub extensions: Extensions,
860 pub pandoc_compat: PandocCompat,
862 #[cfg_attr(feature = "serde", serde(skip))]
873 pub refdef_labels: Option<Arc<HashSet<String>>>,
874}
875
876impl Default for ParserOptions {
877 fn default() -> Self {
878 let flavor = Flavor::default();
879 Self {
880 flavor,
881 dialect: Dialect::for_flavor(flavor),
882 extensions: Extensions::for_flavor(flavor),
883 pandoc_compat: PandocCompat::default(),
884 refdef_labels: None,
885 }
886 }
887}
888
889impl ParserOptions {
890 pub fn effective_pandoc_compat(&self) -> PandocCompat {
891 self.pandoc_compat.effective()
892 }
893}
894
895#[cfg(feature = "schema")]
896impl schemars::JsonSchema for Flavor {
897 fn schema_name() -> std::borrow::Cow<'static, str> {
898 "Flavor".into()
899 }
900
901 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
902 schemars::json_schema!({
906 "type": "string",
907 "description": "Markdown flavor to parse and format against.",
908 "enum": [
909 "pandoc",
910 "quarto",
911 "rmarkdown",
912 "gfm",
913 "common-mark",
914 "commonmark",
915 "multimarkdown"
916 ]
917 })
918 }
919}
920
921#[cfg(feature = "schema")]
922impl schemars::JsonSchema for PandocCompat {
923 fn schema_name() -> std::borrow::Cow<'static, str> {
924 "PandocCompat".into()
925 }
926
927 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
928 schemars::json_schema!({
929 "type": "string",
930 "description": "Compatibility target for ambiguous Pandoc behavior.",
931 "enum": [
932 "latest",
933 "3.7", "3-7", "v3.7", "v3-7",
934 "3.9", "3-9", "v3.9", "v3-9"
935 ]
936 })
937 }
938}