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 #[cfg_attr(feature = "serde", serde(rename = "mdsvex"))]
28 Mdsvex,
29 #[cfg_attr(feature = "serde", serde(rename = "myst"))]
31 Myst,
32}
33
34#[derive(Debug, Clone, PartialEq)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39#[cfg_attr(feature = "serde", serde(default))]
40#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
41#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
42pub struct Extensions {
43 pub blank_before_header: bool,
48 pub header_attributes: bool,
50 pub auto_identifiers: bool,
52 pub gfm_auto_identifiers: bool,
54 pub implicit_header_references: bool,
56
57 pub blank_before_blockquote: bool,
60
61 pub fancy_lists: bool,
64 pub startnum: bool,
66 pub example_lists: bool,
68 pub task_lists: bool,
70 pub definition_lists: bool,
72 pub lists_without_preceding_blankline: bool,
74 pub four_space_rule: bool,
77
78 pub backtick_code_blocks: bool,
81 pub fenced_code_blocks: bool,
83 pub fenced_code_attributes: bool,
85 pub executable_code: bool,
87 pub rmarkdown_inline_code: bool,
89 pub quarto_inline_code: bool,
91 pub inline_code_attributes: bool,
93
94 pub simple_tables: bool,
97 pub multiline_tables: bool,
99 pub grid_tables: bool,
101 pub pipe_tables: bool,
103 pub table_captions: bool,
105
106 pub fenced_divs: bool,
109 pub native_divs: bool,
111
112 pub line_blocks: bool,
115
116 pub intraword_underscores: bool,
121 pub strikeout: bool,
123 pub superscript: bool,
125 pub subscript: bool,
126
127 pub inline_links: bool,
130 pub reference_links: bool,
132 pub shortcut_reference_links: bool,
134 pub link_attributes: bool,
136 pub autolinks: bool,
138
139 pub inline_images: bool,
142 pub implicit_figures: bool,
144
145 pub tex_math_dollars: bool,
148 pub tex_math_gfm: bool,
150 pub tex_math_single_backslash: bool,
152 pub tex_math_double_backslash: bool,
154
155 pub inline_footnotes: bool,
158 pub footnotes: bool,
160
161 pub citations: bool,
164
165 pub bracketed_spans: bool,
168 pub native_spans: bool,
170
171 pub yaml_metadata_block: bool,
174 pub pandoc_title_block: bool,
176 pub mmd_title_block: bool,
178
179 pub raw_html: bool,
182 pub markdown_in_html_blocks: bool,
184 pub raw_tex: bool,
186 pub raw_attribute: bool,
188
189 pub all_symbols_escapable: bool,
192 pub escaped_line_breaks: bool,
194
195 pub autolink_bare_uris: bool,
199 pub hard_line_breaks: bool,
201 pub east_asian_line_breaks: bool,
203 pub mmd_header_identifiers: bool,
205 pub mmd_link_attributes: bool,
207 pub alerts: bool,
209 pub python_markdown_admonitions: bool,
212 pub pymdownx_details: bool,
214 pub emoji: bool,
216 pub mark: bool,
218 pub wikilinks_title_after_pipe: bool,
220 pub wikilinks_title_before_pipe: bool,
222 pub spaced_reference_links: bool,
224
225 pub quarto_callouts: bool,
228 pub quarto_crossrefs: bool,
230 pub quarto_shortcodes: bool,
232 pub bookdown_references: bool,
234 pub bookdown_equation_references: bool,
236
237 pub svelte_template: bool,
242
243 pub myst_directives: bool,
248 pub myst_roles: bool,
250 pub myst_targets: bool,
252 pub myst_substitutions: bool,
254 pub myst_comments: bool,
256 pub myst_colon_fence: bool,
259 pub myst_block_breaks: bool,
263}
264
265impl Default for Extensions {
266 fn default() -> Self {
267 Self::for_flavor(Flavor::default())
268 }
269}
270
271impl Extensions {
272 fn none_defaults() -> Self {
273 Self {
274 alerts: false,
275 all_symbols_escapable: false,
276 auto_identifiers: false,
277 autolink_bare_uris: false,
278 autolinks: false,
279 backtick_code_blocks: false,
280 blank_before_blockquote: false,
281 blank_before_header: false,
282 bookdown_references: false,
283 bookdown_equation_references: false,
284 bracketed_spans: false,
285 citations: false,
286 definition_lists: false,
287 lists_without_preceding_blankline: false,
288 emoji: false,
289 escaped_line_breaks: false,
290 example_lists: false,
291 executable_code: false,
292 rmarkdown_inline_code: false,
293 quarto_inline_code: false,
294 fancy_lists: false,
295 fenced_code_attributes: false,
296 fenced_code_blocks: false,
297 fenced_divs: false,
298 footnotes: false,
299 four_space_rule: false,
300 gfm_auto_identifiers: false,
301 grid_tables: false,
302 east_asian_line_breaks: false,
303 hard_line_breaks: false,
304 header_attributes: false,
305 implicit_figures: false,
306 implicit_header_references: false,
307 inline_code_attributes: false,
308 inline_footnotes: false,
309 inline_images: false,
310 inline_links: false,
311 intraword_underscores: false,
312 line_blocks: false,
313 link_attributes: false,
314 mark: false,
315 markdown_in_html_blocks: false,
316 mmd_header_identifiers: false,
317 mmd_link_attributes: false,
318 mmd_title_block: false,
319 multiline_tables: false,
320 native_divs: false,
321 native_spans: false,
322 pandoc_title_block: false,
323 pipe_tables: false,
324 python_markdown_admonitions: false,
325 pymdownx_details: false,
326 quarto_callouts: false,
327 quarto_crossrefs: false,
328 quarto_shortcodes: false,
329 raw_attribute: false,
330 raw_html: false,
331 raw_tex: false,
332 reference_links: false,
333 shortcut_reference_links: false,
334 simple_tables: false,
335 startnum: false,
336 strikeout: false,
337 subscript: false,
338 superscript: false,
339 svelte_template: false,
340 myst_directives: false,
341 myst_roles: false,
342 myst_targets: false,
343 myst_substitutions: false,
344 myst_comments: false,
345 myst_colon_fence: false,
346 myst_block_breaks: false,
347 table_captions: false,
348 task_lists: false,
349 tex_math_dollars: false,
350 tex_math_double_backslash: false,
351 tex_math_gfm: false,
352 tex_math_single_backslash: false,
353 wikilinks_title_after_pipe: false,
354 wikilinks_title_before_pipe: false,
355 spaced_reference_links: false,
356 yaml_metadata_block: false,
357 }
358 }
359
360 pub fn for_flavor(flavor: Flavor) -> Self {
362 match flavor {
363 Flavor::Pandoc => Self::pandoc_defaults(),
364 Flavor::Quarto => Self::quarto_defaults(),
365 Flavor::RMarkdown => Self::rmarkdown_defaults(),
366 Flavor::Gfm => Self::gfm_defaults(),
367 Flavor::CommonMark => Self::commonmark_defaults(),
368 Flavor::MultiMarkdown => Self::multimarkdown_defaults(),
369 Flavor::Mdsvex => Self::mdsvex_defaults(),
370 Flavor::Myst => Self::myst_defaults(),
371 }
372 }
373
374 fn pandoc_defaults() -> Self {
375 Self {
376 auto_identifiers: true,
378 blank_before_blockquote: true,
379 blank_before_header: true,
380 gfm_auto_identifiers: false,
381 header_attributes: true,
382 implicit_header_references: true,
383
384 definition_lists: true,
386 example_lists: true,
387 fancy_lists: true,
388 lists_without_preceding_blankline: false,
389 startnum: true,
390 task_lists: true,
391
392 backtick_code_blocks: true,
394 executable_code: false,
395 rmarkdown_inline_code: false,
396 quarto_inline_code: false,
397 fenced_code_attributes: true,
398 fenced_code_blocks: true,
399 inline_code_attributes: true,
400
401 grid_tables: true,
403 multiline_tables: true,
404 pipe_tables: true,
405 simple_tables: true,
406 table_captions: true,
407
408 fenced_divs: true,
410 native_divs: true,
411
412 line_blocks: true,
414
415 intraword_underscores: true,
417 strikeout: true,
418 subscript: true,
419 superscript: true,
420
421 autolinks: true,
423 inline_links: true,
424 link_attributes: true,
425 reference_links: true,
426 shortcut_reference_links: true,
427
428 implicit_figures: true,
430 inline_images: true,
431
432 tex_math_dollars: true,
434 tex_math_double_backslash: false,
435 tex_math_gfm: false,
436 tex_math_single_backslash: false,
437
438 footnotes: true,
440 inline_footnotes: true,
441
442 citations: true,
444
445 bracketed_spans: true,
447 native_spans: true,
448
449 mmd_title_block: false,
451 pandoc_title_block: true,
452 yaml_metadata_block: true,
453
454 markdown_in_html_blocks: false,
456 raw_attribute: true,
457 raw_html: true,
458 raw_tex: true,
459
460 all_symbols_escapable: true,
462 escaped_line_breaks: true,
463
464 alerts: false,
466 python_markdown_admonitions: false,
467 pymdownx_details: false,
468 autolink_bare_uris: false,
469 east_asian_line_breaks: false,
470 emoji: false,
471 four_space_rule: false,
472 hard_line_breaks: false,
473 mark: false,
474 mmd_header_identifiers: false,
475 mmd_link_attributes: false,
476
477 bookdown_references: false,
479 bookdown_equation_references: false,
480 quarto_callouts: false,
481 quarto_crossrefs: false,
482 quarto_shortcodes: false,
483
484 wikilinks_title_after_pipe: false,
486 wikilinks_title_before_pipe: false,
487
488 spaced_reference_links: false,
490
491 svelte_template: false,
493
494 myst_directives: false,
496 myst_roles: false,
497 myst_targets: false,
498 myst_substitutions: false,
499 myst_comments: false,
500 myst_colon_fence: false,
501 myst_block_breaks: false,
502 }
503 }
504
505 fn quarto_defaults() -> Self {
506 let mut ext = Self::pandoc_defaults();
507
508 ext.executable_code = true;
509 ext.rmarkdown_inline_code = true;
510 ext.quarto_inline_code = true;
511 ext.quarto_callouts = true;
512 ext.quarto_crossrefs = true;
513 ext.quarto_shortcodes = true;
514
515 ext
516 }
517
518 fn rmarkdown_defaults() -> Self {
519 let mut ext = Self::pandoc_defaults();
520
521 ext.bookdown_references = true;
522 ext.bookdown_equation_references = true;
523 ext.executable_code = true;
524 ext.rmarkdown_inline_code = true;
525 ext.quarto_inline_code = false;
526 ext.tex_math_dollars = true;
527 ext.tex_math_single_backslash = true;
528
529 ext
530 }
531
532 fn gfm_defaults() -> Self {
533 let mut ext = Self::none_defaults();
534
535 ext.alerts = true;
536 ext.auto_identifiers = true;
537 ext.autolink_bare_uris = true;
538 ext.autolinks = true;
539 ext.backtick_code_blocks = true;
540 ext.emoji = true;
541 ext.fenced_code_blocks = true;
542 ext.footnotes = true;
543 ext.gfm_auto_identifiers = true;
544 ext.inline_images = true;
545 ext.inline_links = true;
546 ext.pipe_tables = true;
547 ext.raw_html = true;
548 ext.reference_links = true;
549 ext.shortcut_reference_links = true;
550 ext.strikeout = true;
551 ext.task_lists = true;
552 ext.tex_math_dollars = true;
553 ext.tex_math_gfm = true;
554 ext.yaml_metadata_block = true;
555
556 ext
557 }
558
559 fn commonmark_defaults() -> Self {
560 let mut ext = Self::none_defaults();
561 ext.autolinks = true;
571 ext.backtick_code_blocks = true;
572 ext.escaped_line_breaks = true;
573 ext.fenced_code_blocks = true;
574 ext.inline_images = true;
575 ext.inline_links = true;
576 ext.intraword_underscores = true;
577 ext.raw_html = true;
578 ext.reference_links = true;
579 ext.shortcut_reference_links = true;
580 ext
581 }
582
583 fn multimarkdown_defaults() -> Self {
584 let mut ext = Self::none_defaults();
585
586 ext.all_symbols_escapable = true;
587 ext.auto_identifiers = true;
588 ext.backtick_code_blocks = true;
589 ext.definition_lists = true;
590 ext.footnotes = true;
591 ext.implicit_figures = true;
592 ext.implicit_header_references = true;
593 ext.intraword_underscores = true;
594 ext.mmd_header_identifiers = true;
595 ext.mmd_link_attributes = true;
596 ext.mmd_title_block = true;
597 ext.pipe_tables = true;
598 ext.raw_attribute = true;
599 ext.raw_html = true;
600 ext.reference_links = true;
601 ext.shortcut_reference_links = true;
602 ext.subscript = true;
603 ext.superscript = true;
604 ext.tex_math_dollars = true;
605 ext.tex_math_double_backslash = true;
606
607 ext
608 }
609
610 fn mdsvex_defaults() -> Self {
611 let mut ext = Self::gfm_defaults();
627
628 ext.footnotes = false;
629 ext.tex_math_dollars = false;
630 ext.tex_math_gfm = false;
631 ext.emoji = false;
632 ext.alerts = false;
633
634 ext.svelte_template = true;
635
636 ext
637 }
638
639 fn myst_defaults() -> Self {
640 let mut ext = Self::commonmark_defaults();
649
650 ext.myst_directives = true;
651 ext.myst_roles = true;
652 ext.myst_targets = true;
653 ext.myst_comments = true;
654 ext.myst_block_breaks = true;
655
656 ext.pipe_tables = true;
663 ext.footnotes = true;
664 ext.inline_footnotes = false;
665
666 ext.yaml_metadata_block = true;
672
673 ext.myst_substitutions = false;
678 ext.myst_colon_fence = false;
679
680 ext
681 }
682
683 pub fn merge_with_flavor(user_overrides: HashMap<String, bool>, flavor: Flavor) -> Self {
697 let defaults = Self::for_flavor(flavor);
698 Self::merge_overrides(defaults, user_overrides)
699 }
700
701 pub fn apply_overrides(&mut self, user_overrides: HashMap<String, bool>) {
707 *self = Self::merge_overrides(self.clone(), user_overrides);
708 }
709
710 fn merge_overrides(mut base: Extensions, user_overrides: HashMap<String, bool>) -> Self {
711 for (key, value) in user_overrides {
712 base.set_by_name(&key, value);
713 }
714 base
715 }
716}
717
718macro_rules! known_extensions {
723 ( $( $kebab:literal => $field:ident ),* $(,)? ) => {
724 impl Extensions {
725 pub const KNOWN_NAMES: &'static [&'static str] = &[ $($kebab),* ];
730
731 pub fn is_known_name(name: &str) -> bool {
733 Self::KNOWN_NAMES.iter().any(|k| *k == name)
734 }
735
736 fn set_by_name(&mut self, name: &str, value: bool) -> bool {
739 match name {
740 $( $kebab => { self.$field = value; true } )*
741 _ => false,
742 }
743 }
744 }
745 };
746}
747
748known_extensions! {
749 "blank-before-header" => blank_before_header,
750 "header-attributes" => header_attributes,
751 "auto-identifiers" => auto_identifiers,
752 "gfm-auto-identifiers" => gfm_auto_identifiers,
753 "implicit-header-references" => implicit_header_references,
754 "blank-before-blockquote" => blank_before_blockquote,
755 "fancy-lists" => fancy_lists,
756 "startnum" => startnum,
757 "example-lists" => example_lists,
758 "task-lists" => task_lists,
759 "definition-lists" => definition_lists,
760 "lists-without-preceding-blankline" => lists_without_preceding_blankline,
761 "four-space-rule" => four_space_rule,
762 "backtick-code-blocks" => backtick_code_blocks,
763 "fenced-code-blocks" => fenced_code_blocks,
764 "fenced-code-attributes" => fenced_code_attributes,
765 "executable-code" => executable_code,
766 "rmarkdown-inline-code" => rmarkdown_inline_code,
767 "quarto-inline-code" => quarto_inline_code,
768 "inline-code-attributes" => inline_code_attributes,
769 "simple-tables" => simple_tables,
770 "multiline-tables" => multiline_tables,
771 "grid-tables" => grid_tables,
772 "pipe-tables" => pipe_tables,
773 "table-captions" => table_captions,
774 "fenced-divs" => fenced_divs,
775 "native-divs" => native_divs,
776 "line-blocks" => line_blocks,
777 "intraword-underscores" => intraword_underscores,
778 "strikeout" => strikeout,
779 "superscript" => superscript,
780 "subscript" => subscript,
781 "inline-links" => inline_links,
782 "reference-links" => reference_links,
783 "shortcut-reference-links" => shortcut_reference_links,
784 "link-attributes" => link_attributes,
785 "autolinks" => autolinks,
786 "inline-images" => inline_images,
787 "implicit-figures" => implicit_figures,
788 "tex-math-dollars" => tex_math_dollars,
789 "tex-math-gfm" => tex_math_gfm,
790 "tex-math-single-backslash" => tex_math_single_backslash,
791 "tex-math-double-backslash" => tex_math_double_backslash,
792 "inline-footnotes" => inline_footnotes,
793 "footnotes" => footnotes,
794 "citations" => citations,
795 "bracketed-spans" => bracketed_spans,
796 "native-spans" => native_spans,
797 "yaml-metadata-block" => yaml_metadata_block,
798 "pandoc-title-block" => pandoc_title_block,
799 "mmd-title-block" => mmd_title_block,
800 "raw-html" => raw_html,
801 "markdown-in-html-blocks" => markdown_in_html_blocks,
802 "raw-tex" => raw_tex,
803 "raw-attribute" => raw_attribute,
804 "all-symbols-escapable" => all_symbols_escapable,
805 "escaped-line-breaks" => escaped_line_breaks,
806 "autolink-bare-uris" => autolink_bare_uris,
807 "hard-line-breaks" => hard_line_breaks,
808 "east-asian-line-breaks" => east_asian_line_breaks,
809 "mmd-header-identifiers" => mmd_header_identifiers,
810 "mmd-link-attributes" => mmd_link_attributes,
811 "alerts" => alerts,
812 "python-markdown-admonitions" => python_markdown_admonitions,
813 "pymdownx-details" => pymdownx_details,
814 "emoji" => emoji,
815 "mark" => mark,
816 "quarto-callouts" => quarto_callouts,
817 "quarto-crossrefs" => quarto_crossrefs,
818 "quarto-shortcodes" => quarto_shortcodes,
819 "bookdown-references" => bookdown_references,
820 "bookdown-equation-references" => bookdown_equation_references,
821 "wikilinks-title-after-pipe" => wikilinks_title_after_pipe,
822 "wikilinks-title-before-pipe" => wikilinks_title_before_pipe,
823 "spaced-reference-links" => spaced_reference_links,
824 "svelte-template" => svelte_template,
825 "myst-directives" => myst_directives,
826 "myst-roles" => myst_roles,
827 "myst-targets" => myst_targets,
828 "myst-substitutions" => myst_substitutions,
829 "myst-comments" => myst_comments,
830 "myst-colon-fence" => myst_colon_fence,
831 "myst-block-breaks" => myst_block_breaks,
832}
833
834#[cfg(test)]
835mod tests {
836 use super::{Dialect, Extensions, Flavor};
837 use std::collections::HashMap;
838
839 #[test]
840 fn merge_with_flavor_keeps_known_extension_overrides() {
841 let mut overrides = HashMap::new();
842 overrides.insert("intraword-underscores".to_string(), false);
843 let ext = Extensions::merge_with_flavor(overrides, Flavor::Pandoc);
844 assert!(!ext.intraword_underscores);
845 }
846
847 #[test]
848 fn merge_with_flavor_ignores_unknown_extension_overrides() {
849 let mut overrides = HashMap::new();
850 overrides.insert("smart".to_string(), true);
851 overrides.insert("smart-quotes".to_string(), true);
852 let ext = Extensions::merge_with_flavor(overrides, Flavor::Gfm);
853 assert!(ext.strikeout, "known defaults should remain intact");
854 }
855
856 #[test]
857 fn lists_without_preceding_blankline_defaults_false_for_pandoc_and_gfm() {
858 assert!(!Extensions::for_flavor(Flavor::Pandoc).lists_without_preceding_blankline);
859 assert!(!Extensions::for_flavor(Flavor::Gfm).lists_without_preceding_blankline);
860 }
861
862 #[test]
863 fn merge_with_flavor_accepts_lists_without_preceding_blankline_override() {
864 let mut overrides = HashMap::new();
865 overrides.insert("lists-without-preceding-blankline".to_string(), true);
866 let ext = Extensions::merge_with_flavor(overrides, Flavor::Pandoc);
867 assert!(ext.lists_without_preceding_blankline);
868 }
869
870 #[test]
871 fn four_space_rule_defaults_off_for_every_flavor() {
872 for flavor in [
873 Flavor::Pandoc,
874 Flavor::Quarto,
875 Flavor::RMarkdown,
876 Flavor::Gfm,
877 Flavor::CommonMark,
878 Flavor::MultiMarkdown,
879 Flavor::Mdsvex,
880 Flavor::Myst,
881 ] {
882 assert!(
883 !Extensions::for_flavor(flavor).four_space_rule,
884 "four_space_rule should be off by default for {flavor:?}"
885 );
886 }
887 }
888
889 #[test]
890 fn svelte_template_defaults_off_for_non_mdsvex_flavors() {
891 for flavor in [
892 Flavor::Pandoc,
893 Flavor::Quarto,
894 Flavor::RMarkdown,
895 Flavor::Gfm,
896 Flavor::CommonMark,
897 Flavor::MultiMarkdown,
898 Flavor::Myst,
899 ] {
900 assert!(
901 !Extensions::for_flavor(flavor).svelte_template,
902 "svelte_template should be off by default for {flavor:?}"
903 );
904 }
905 }
906
907 #[test]
908 fn myst_uses_commonmark_dialect() {
909 assert_eq!(Dialect::for_flavor(Flavor::Myst), Dialect::CommonMark);
910 }
911
912 #[test]
913 fn myst_defaults_enable_core_constructs_only() {
914 let ext = Extensions::for_flavor(Flavor::Myst);
915
916 assert!(ext.myst_directives);
918 assert!(ext.myst_roles);
919 assert!(ext.myst_targets);
920 assert!(ext.myst_comments);
921 assert!(ext.myst_block_breaks);
922
923 assert!(ext.pipe_tables);
928 assert!(ext.footnotes);
929 assert!(!ext.inline_footnotes);
930
931 assert!(ext.yaml_metadata_block);
934
935 assert!(!ext.myst_colon_fence);
938 assert!(!ext.myst_substitutions);
939 assert!(!ext.tex_math_dollars);
940 assert!(!ext.definition_lists);
941 assert!(!ext.task_lists);
942 assert!(!ext.strikeout);
943
944 assert!(ext.inline_links);
947 assert!(ext.backtick_code_blocks);
948 assert!(!ext.fenced_divs);
949 assert!(!ext.bracketed_spans);
950 assert!(!ext.header_attributes);
951 }
952
953 #[test]
954 fn myst_core_constructs_off_for_other_flavors() {
955 for flavor in [
956 Flavor::Pandoc,
957 Flavor::Quarto,
958 Flavor::RMarkdown,
959 Flavor::Gfm,
960 Flavor::CommonMark,
961 Flavor::MultiMarkdown,
962 Flavor::Mdsvex,
963 ] {
964 let ext = Extensions::for_flavor(flavor);
965 assert!(
966 !ext.myst_directives
967 && !ext.myst_roles
968 && !ext.myst_targets
969 && !ext.myst_comments
970 && !ext.myst_block_breaks,
971 "MyST core constructs should be off by default for {flavor:?}"
972 );
973 }
974 }
975
976 #[test]
977 fn mdsvex_defaults_match_remark_parse_8_gfm() {
978 let ext = Extensions::for_flavor(Flavor::Mdsvex);
979
980 assert!(ext.svelte_template);
982 assert!(ext.raw_html);
983 assert!(ext.yaml_metadata_block);
984
985 assert!(ext.pipe_tables);
987 assert!(ext.strikeout);
988 assert!(ext.task_lists);
989 assert!(ext.autolink_bare_uris);
990
991 assert!(!ext.footnotes);
993 assert!(!ext.tex_math_dollars);
994 assert!(!ext.emoji);
995 assert!(!ext.alerts);
996
997 assert_eq!(Dialect::for_flavor(Flavor::Mdsvex), Dialect::CommonMark);
1000 assert!(!ext.header_attributes);
1001 assert!(!ext.bracketed_spans);
1002 assert!(!ext.fenced_divs);
1003 assert!(!ext.raw_attribute);
1004 assert!(!ext.inline_code_attributes);
1005 }
1006
1007 #[test]
1008 fn merge_with_flavor_accepts_four_space_rule_override() {
1009 let mut overrides = HashMap::new();
1010 overrides.insert("four-space-rule".to_string(), true);
1011 let ext = Extensions::merge_with_flavor(overrides, Flavor::Pandoc);
1012 assert!(ext.four_space_rule);
1013 }
1014}
1015
1016#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1017#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1018pub enum PandocCompat {
1019 #[cfg_attr(feature = "serde", serde(rename = "latest"))]
1024 Latest,
1025 #[cfg_attr(
1027 feature = "serde",
1028 serde(rename = "3.7", alias = "3-7", alias = "v3.7", alias = "v3-7")
1029 )]
1030 V3_7,
1031 #[default]
1033 #[cfg_attr(
1034 feature = "serde",
1035 serde(rename = "3.9", alias = "3-9", alias = "v3.9", alias = "v3-9")
1036 )]
1037 V3_9,
1038}
1039
1040impl PandocCompat {
1041 pub const PINNED_LATEST: Self = Self::V3_9;
1043
1044 pub fn effective(self) -> Self {
1045 match self {
1046 Self::Latest => Self::PINNED_LATEST,
1047 other => other,
1048 }
1049 }
1050}
1051
1052#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1063#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1064#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
1065#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1066pub enum Dialect {
1067 #[default]
1070 Pandoc,
1071 CommonMark,
1073}
1074
1075impl Dialect {
1076 pub fn for_flavor(flavor: Flavor) -> Self {
1078 match flavor {
1079 Flavor::CommonMark | Flavor::Gfm | Flavor::Mdsvex | Flavor::Myst => Dialect::CommonMark,
1080 Flavor::Pandoc | Flavor::Quarto | Flavor::RMarkdown | Flavor::MultiMarkdown => {
1081 Dialect::Pandoc
1082 }
1083 }
1084 }
1085}
1086
1087#[derive(Debug, Clone)]
1088#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1089#[cfg_attr(feature = "serde", serde(default, rename_all = "kebab-case"))]
1090#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1091pub struct ParserOptions {
1092 pub flavor: Flavor,
1093 pub dialect: Dialect,
1094 pub extensions: Extensions,
1095 pub pandoc_compat: PandocCompat,
1097 #[cfg_attr(feature = "serde", serde(default, alias = "crossref_prefixes"))]
1104 pub crossref_prefixes: Vec<String>,
1105 #[cfg_attr(feature = "serde", serde(skip))]
1116 pub refdef_labels: Option<Arc<HashSet<String>>>,
1117}
1118
1119impl Default for ParserOptions {
1120 fn default() -> Self {
1121 let flavor = Flavor::default();
1122 Self {
1123 flavor,
1124 dialect: Dialect::for_flavor(flavor),
1125 extensions: Extensions::for_flavor(flavor),
1126 pandoc_compat: PandocCompat::default(),
1127 crossref_prefixes: Vec::new(),
1128 refdef_labels: None,
1129 }
1130 }
1131}
1132
1133impl ParserOptions {
1134 pub fn effective_pandoc_compat(&self) -> PandocCompat {
1135 self.pandoc_compat.effective()
1136 }
1137}
1138
1139#[cfg(feature = "schema")]
1140impl schemars::JsonSchema for Flavor {
1141 fn schema_name() -> std::borrow::Cow<'static, str> {
1142 "Flavor".into()
1143 }
1144
1145 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1146 schemars::json_schema!({
1150 "type": "string",
1151 "description": "Markdown flavor to parse and format against.",
1152 "enum": [
1153 "pandoc",
1154 "quarto",
1155 "rmarkdown",
1156 "gfm",
1157 "common-mark",
1158 "commonmark",
1159 "multimarkdown",
1160 "mdsvex",
1161 "myst"
1162 ]
1163 })
1164 }
1165}
1166
1167#[cfg(feature = "schema")]
1168impl schemars::JsonSchema for PandocCompat {
1169 fn schema_name() -> std::borrow::Cow<'static, str> {
1170 "PandocCompat".into()
1171 }
1172
1173 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1174 schemars::json_schema!({
1175 "type": "string",
1176 "description": "Compatibility target for ambiguous Pandoc behavior.",
1177 "enum": [
1178 "latest",
1179 "3.7", "3-7", "v3.7", "v3-7",
1180 "3.9", "3-9", "v3.9", "v3-9"
1181 ]
1182 })
1183 }
1184}