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 #[cfg_attr(feature = "serde", serde(alias = "blank_before_header"))]
48 pub blank_before_header: bool,
49 #[cfg_attr(feature = "serde", serde(alias = "header_attributes"))]
51 pub header_attributes: bool,
52 pub auto_identifiers: bool,
54 pub gfm_auto_identifiers: bool,
56 pub implicit_header_references: bool,
58
59 #[cfg_attr(feature = "serde", serde(alias = "blank_before_blockquote"))]
62 pub blank_before_blockquote: bool,
63
64 #[cfg_attr(feature = "serde", serde(alias = "fancy_lists"))]
67 pub fancy_lists: bool,
68 pub startnum: bool,
70 #[cfg_attr(feature = "serde", serde(alias = "example_lists"))]
72 pub example_lists: bool,
73 #[cfg_attr(feature = "serde", serde(alias = "task_lists"))]
75 pub task_lists: bool,
76 #[cfg_attr(feature = "serde", serde(alias = "definition_lists"))]
78 pub definition_lists: bool,
79 #[cfg_attr(feature = "serde", serde(alias = "lists_without_preceding_blankline"))]
81 pub lists_without_preceding_blankline: bool,
82 #[cfg_attr(feature = "serde", serde(alias = "four_space_rule"))]
85 pub four_space_rule: bool,
86
87 #[cfg_attr(feature = "serde", serde(alias = "backtick_code_blocks"))]
90 pub backtick_code_blocks: bool,
91 #[cfg_attr(feature = "serde", serde(alias = "fenced_code_blocks"))]
93 pub fenced_code_blocks: bool,
94 #[cfg_attr(feature = "serde", serde(alias = "fenced_code_attributes"))]
96 pub fenced_code_attributes: bool,
97 pub executable_code: bool,
99 pub rmarkdown_inline_code: bool,
101 pub quarto_inline_code: bool,
103 #[cfg_attr(feature = "serde", serde(alias = "inline_code_attributes"))]
105 pub inline_code_attributes: bool,
106
107 #[cfg_attr(feature = "serde", serde(alias = "simple_tables"))]
110 pub simple_tables: bool,
111 #[cfg_attr(feature = "serde", serde(alias = "multiline_tables"))]
113 pub multiline_tables: bool,
114 #[cfg_attr(feature = "serde", serde(alias = "grid_tables"))]
116 pub grid_tables: bool,
117 #[cfg_attr(feature = "serde", serde(alias = "pipe_tables"))]
119 pub pipe_tables: bool,
120 #[cfg_attr(feature = "serde", serde(alias = "table_captions"))]
122 pub table_captions: bool,
123
124 #[cfg_attr(feature = "serde", serde(alias = "fenced_divs"))]
127 pub fenced_divs: bool,
128 #[cfg_attr(feature = "serde", serde(alias = "native_divs"))]
130 pub native_divs: bool,
131
132 #[cfg_attr(feature = "serde", serde(alias = "line_blocks"))]
135 pub line_blocks: bool,
136
137 #[cfg_attr(feature = "serde", serde(alias = "intraword_underscores"))]
142 pub intraword_underscores: bool,
143 pub strikeout: bool,
145 pub superscript: bool,
147 pub subscript: bool,
148
149 #[cfg_attr(feature = "serde", serde(alias = "inline_links"))]
152 pub inline_links: bool,
153 #[cfg_attr(feature = "serde", serde(alias = "reference_links"))]
155 pub reference_links: bool,
156 #[cfg_attr(feature = "serde", serde(alias = "shortcut_reference_links"))]
158 pub shortcut_reference_links: bool,
159 #[cfg_attr(feature = "serde", serde(alias = "link_attributes"))]
161 pub link_attributes: bool,
162 pub autolinks: bool,
164
165 #[cfg_attr(feature = "serde", serde(alias = "inline_images"))]
168 pub inline_images: bool,
169 #[cfg_attr(feature = "serde", serde(alias = "implicit_figures"))]
171 pub implicit_figures: bool,
172
173 #[cfg_attr(feature = "serde", serde(alias = "tex_math_dollars"))]
176 pub tex_math_dollars: bool,
177 #[cfg_attr(feature = "serde", serde(alias = "tex_math_gfm"))]
179 pub tex_math_gfm: bool,
180 #[cfg_attr(feature = "serde", serde(alias = "tex_math_single_backslash"))]
182 pub tex_math_single_backslash: bool,
183 #[cfg_attr(feature = "serde", serde(alias = "tex_math_double_backslash"))]
185 pub tex_math_double_backslash: bool,
186
187 #[cfg_attr(feature = "serde", serde(alias = "inline_footnotes"))]
190 pub inline_footnotes: bool,
191 pub footnotes: bool,
193
194 pub citations: bool,
197
198 #[cfg_attr(feature = "serde", serde(alias = "bracketed_spans"))]
201 pub bracketed_spans: bool,
202 #[cfg_attr(feature = "serde", serde(alias = "native_spans"))]
204 pub native_spans: bool,
205
206 #[cfg_attr(feature = "serde", serde(alias = "yaml_metadata_block"))]
209 pub yaml_metadata_block: bool,
210 #[cfg_attr(feature = "serde", serde(alias = "pandoc_title_block"))]
212 pub pandoc_title_block: bool,
213 pub mmd_title_block: bool,
215
216 #[cfg_attr(feature = "serde", serde(alias = "raw_html"))]
219 pub raw_html: bool,
220 #[cfg_attr(feature = "serde", serde(alias = "markdown_in_html_blocks"))]
222 pub markdown_in_html_blocks: bool,
223 #[cfg_attr(feature = "serde", serde(alias = "raw_tex"))]
225 pub raw_tex: bool,
226 #[cfg_attr(feature = "serde", serde(alias = "raw_attribute"))]
228 pub raw_attribute: bool,
229
230 #[cfg_attr(feature = "serde", serde(alias = "all_symbols_escapable"))]
233 pub all_symbols_escapable: bool,
234 #[cfg_attr(feature = "serde", serde(alias = "escaped_line_breaks"))]
236 pub escaped_line_breaks: bool,
237
238 #[cfg_attr(feature = "serde", serde(alias = "autolink_bare_uris"))]
242 pub autolink_bare_uris: bool,
243 #[cfg_attr(feature = "serde", serde(alias = "hard_line_breaks"))]
245 pub hard_line_breaks: bool,
246 #[cfg_attr(feature = "serde", serde(alias = "east_asian_line_breaks"))]
248 pub east_asian_line_breaks: bool,
249 pub mmd_header_identifiers: bool,
251 pub mmd_link_attributes: bool,
253 pub alerts: bool,
255 #[cfg_attr(feature = "serde", serde(alias = "python_markdown_admonitions"))]
258 pub python_markdown_admonitions: bool,
259 #[cfg_attr(feature = "serde", serde(alias = "pymdownx_details"))]
261 pub pymdownx_details: bool,
262 pub emoji: bool,
264 pub mark: bool,
266 #[cfg_attr(feature = "serde", serde(alias = "wikilinks_title_after_pipe"))]
268 pub wikilinks_title_after_pipe: bool,
269 #[cfg_attr(feature = "serde", serde(alias = "wikilinks_title_before_pipe"))]
271 pub wikilinks_title_before_pipe: bool,
272 #[cfg_attr(feature = "serde", serde(alias = "spaced_reference_links"))]
274 pub spaced_reference_links: bool,
275
276 #[cfg_attr(feature = "serde", serde(alias = "quarto_callouts"))]
279 pub quarto_callouts: bool,
280 #[cfg_attr(feature = "serde", serde(alias = "quarto_crossrefs"))]
282 pub quarto_crossrefs: bool,
283 #[cfg_attr(feature = "serde", serde(alias = "quarto_shortcodes"))]
285 pub quarto_shortcodes: bool,
286 pub bookdown_references: bool,
288 pub bookdown_equation_references: bool,
290
291 #[cfg_attr(feature = "serde", serde(alias = "svelte_template"))]
296 pub svelte_template: bool,
297
298 #[cfg_attr(feature = "serde", serde(alias = "myst_directives"))]
303 pub myst_directives: bool,
304 #[cfg_attr(feature = "serde", serde(alias = "myst_roles"))]
306 pub myst_roles: bool,
307 #[cfg_attr(feature = "serde", serde(alias = "myst_targets"))]
309 pub myst_targets: bool,
310 #[cfg_attr(feature = "serde", serde(alias = "myst_substitutions"))]
312 pub myst_substitutions: bool,
313 #[cfg_attr(feature = "serde", serde(alias = "myst_comments"))]
315 pub myst_comments: bool,
316 #[cfg_attr(feature = "serde", serde(alias = "myst_colon_fence"))]
319 pub myst_colon_fence: bool,
320 #[cfg_attr(feature = "serde", serde(alias = "myst_block_breaks"))]
324 pub myst_block_breaks: bool,
325}
326
327impl Default for Extensions {
328 fn default() -> Self {
329 Self::for_flavor(Flavor::default())
330 }
331}
332
333impl Extensions {
334 fn none_defaults() -> Self {
335 Self {
336 alerts: false,
337 all_symbols_escapable: false,
338 auto_identifiers: false,
339 autolink_bare_uris: false,
340 autolinks: false,
341 backtick_code_blocks: false,
342 blank_before_blockquote: false,
343 blank_before_header: false,
344 bookdown_references: false,
345 bookdown_equation_references: false,
346 bracketed_spans: false,
347 citations: false,
348 definition_lists: false,
349 lists_without_preceding_blankline: false,
350 emoji: false,
351 escaped_line_breaks: false,
352 example_lists: false,
353 executable_code: false,
354 rmarkdown_inline_code: false,
355 quarto_inline_code: false,
356 fancy_lists: false,
357 fenced_code_attributes: false,
358 fenced_code_blocks: false,
359 fenced_divs: false,
360 footnotes: false,
361 four_space_rule: false,
362 gfm_auto_identifiers: false,
363 grid_tables: false,
364 east_asian_line_breaks: false,
365 hard_line_breaks: false,
366 header_attributes: false,
367 implicit_figures: false,
368 implicit_header_references: false,
369 inline_code_attributes: false,
370 inline_footnotes: false,
371 inline_images: false,
372 inline_links: false,
373 intraword_underscores: false,
374 line_blocks: false,
375 link_attributes: false,
376 mark: false,
377 markdown_in_html_blocks: false,
378 mmd_header_identifiers: false,
379 mmd_link_attributes: false,
380 mmd_title_block: false,
381 multiline_tables: false,
382 native_divs: false,
383 native_spans: false,
384 pandoc_title_block: false,
385 pipe_tables: false,
386 python_markdown_admonitions: false,
387 pymdownx_details: false,
388 quarto_callouts: false,
389 quarto_crossrefs: false,
390 quarto_shortcodes: false,
391 raw_attribute: false,
392 raw_html: false,
393 raw_tex: false,
394 reference_links: false,
395 shortcut_reference_links: false,
396 simple_tables: false,
397 startnum: false,
398 strikeout: false,
399 subscript: false,
400 superscript: false,
401 svelte_template: false,
402 myst_directives: false,
403 myst_roles: false,
404 myst_targets: false,
405 myst_substitutions: false,
406 myst_comments: false,
407 myst_colon_fence: false,
408 myst_block_breaks: false,
409 table_captions: false,
410 task_lists: false,
411 tex_math_dollars: false,
412 tex_math_double_backslash: false,
413 tex_math_gfm: false,
414 tex_math_single_backslash: false,
415 wikilinks_title_after_pipe: false,
416 wikilinks_title_before_pipe: false,
417 spaced_reference_links: false,
418 yaml_metadata_block: false,
419 }
420 }
421
422 pub fn for_flavor(flavor: Flavor) -> Self {
424 match flavor {
425 Flavor::Pandoc => Self::pandoc_defaults(),
426 Flavor::Quarto => Self::quarto_defaults(),
427 Flavor::RMarkdown => Self::rmarkdown_defaults(),
428 Flavor::Gfm => Self::gfm_defaults(),
429 Flavor::CommonMark => Self::commonmark_defaults(),
430 Flavor::MultiMarkdown => Self::multimarkdown_defaults(),
431 Flavor::Mdsvex => Self::mdsvex_defaults(),
432 Flavor::Myst => Self::myst_defaults(),
433 }
434 }
435
436 fn pandoc_defaults() -> Self {
437 Self {
438 auto_identifiers: true,
440 blank_before_blockquote: true,
441 blank_before_header: true,
442 gfm_auto_identifiers: false,
443 header_attributes: true,
444 implicit_header_references: true,
445
446 definition_lists: true,
448 example_lists: true,
449 fancy_lists: true,
450 lists_without_preceding_blankline: false,
451 startnum: true,
452 task_lists: true,
453
454 backtick_code_blocks: true,
456 executable_code: false,
457 rmarkdown_inline_code: false,
458 quarto_inline_code: false,
459 fenced_code_attributes: true,
460 fenced_code_blocks: true,
461 inline_code_attributes: true,
462
463 grid_tables: true,
465 multiline_tables: true,
466 pipe_tables: true,
467 simple_tables: true,
468 table_captions: true,
469
470 fenced_divs: true,
472 native_divs: true,
473
474 line_blocks: true,
476
477 intraword_underscores: true,
479 strikeout: true,
480 subscript: true,
481 superscript: true,
482
483 autolinks: true,
485 inline_links: true,
486 link_attributes: true,
487 reference_links: true,
488 shortcut_reference_links: true,
489
490 implicit_figures: true,
492 inline_images: true,
493
494 tex_math_dollars: true,
496 tex_math_double_backslash: false,
497 tex_math_gfm: false,
498 tex_math_single_backslash: false,
499
500 footnotes: true,
502 inline_footnotes: true,
503
504 citations: true,
506
507 bracketed_spans: true,
509 native_spans: true,
510
511 mmd_title_block: false,
513 pandoc_title_block: true,
514 yaml_metadata_block: true,
515
516 markdown_in_html_blocks: false,
518 raw_attribute: true,
519 raw_html: true,
520 raw_tex: true,
521
522 all_symbols_escapable: true,
524 escaped_line_breaks: true,
525
526 alerts: false,
528 python_markdown_admonitions: false,
529 pymdownx_details: false,
530 autolink_bare_uris: false,
531 east_asian_line_breaks: false,
532 emoji: false,
533 four_space_rule: false,
534 hard_line_breaks: false,
535 mark: false,
536 mmd_header_identifiers: false,
537 mmd_link_attributes: false,
538
539 bookdown_references: false,
541 bookdown_equation_references: false,
542 quarto_callouts: false,
543 quarto_crossrefs: false,
544 quarto_shortcodes: false,
545
546 wikilinks_title_after_pipe: false,
548 wikilinks_title_before_pipe: false,
549
550 spaced_reference_links: false,
552
553 svelte_template: false,
555
556 myst_directives: false,
558 myst_roles: false,
559 myst_targets: false,
560 myst_substitutions: false,
561 myst_comments: false,
562 myst_colon_fence: false,
563 myst_block_breaks: false,
564 }
565 }
566
567 fn quarto_defaults() -> Self {
568 let mut ext = Self::pandoc_defaults();
569
570 ext.executable_code = true;
571 ext.rmarkdown_inline_code = true;
572 ext.quarto_inline_code = true;
573 ext.quarto_callouts = true;
574 ext.quarto_crossrefs = true;
575 ext.quarto_shortcodes = true;
576
577 ext
578 }
579
580 fn rmarkdown_defaults() -> Self {
581 let mut ext = Self::pandoc_defaults();
582
583 ext.bookdown_references = true;
584 ext.bookdown_equation_references = true;
585 ext.executable_code = true;
586 ext.rmarkdown_inline_code = true;
587 ext.quarto_inline_code = false;
588 ext.tex_math_dollars = true;
589 ext.tex_math_single_backslash = true;
590
591 ext
592 }
593
594 fn gfm_defaults() -> Self {
595 let mut ext = Self::none_defaults();
596
597 ext.alerts = true;
598 ext.auto_identifiers = true;
599 ext.autolink_bare_uris = true;
600 ext.autolinks = true;
601 ext.backtick_code_blocks = true;
602 ext.emoji = true;
603 ext.fenced_code_blocks = true;
604 ext.footnotes = true;
605 ext.gfm_auto_identifiers = true;
606 ext.inline_images = true;
607 ext.inline_links = true;
608 ext.pipe_tables = true;
609 ext.raw_html = true;
610 ext.reference_links = true;
611 ext.shortcut_reference_links = true;
612 ext.strikeout = true;
613 ext.task_lists = true;
614 ext.tex_math_dollars = true;
615 ext.tex_math_gfm = true;
616 ext.yaml_metadata_block = true;
617
618 ext
619 }
620
621 fn commonmark_defaults() -> Self {
622 let mut ext = Self::none_defaults();
623 ext.autolinks = true;
633 ext.backtick_code_blocks = true;
634 ext.escaped_line_breaks = true;
635 ext.fenced_code_blocks = true;
636 ext.inline_images = true;
637 ext.inline_links = true;
638 ext.intraword_underscores = true;
639 ext.raw_html = true;
640 ext.reference_links = true;
641 ext.shortcut_reference_links = true;
642 ext
643 }
644
645 fn multimarkdown_defaults() -> Self {
646 let mut ext = Self::none_defaults();
647
648 ext.all_symbols_escapable = true;
649 ext.auto_identifiers = true;
650 ext.backtick_code_blocks = true;
651 ext.definition_lists = true;
652 ext.footnotes = true;
653 ext.implicit_figures = true;
654 ext.implicit_header_references = true;
655 ext.intraword_underscores = true;
656 ext.mmd_header_identifiers = true;
657 ext.mmd_link_attributes = true;
658 ext.mmd_title_block = true;
659 ext.pipe_tables = true;
660 ext.raw_attribute = true;
661 ext.raw_html = true;
662 ext.reference_links = true;
663 ext.shortcut_reference_links = true;
664 ext.subscript = true;
665 ext.superscript = true;
666 ext.tex_math_dollars = true;
667 ext.tex_math_double_backslash = true;
668
669 ext
670 }
671
672 fn mdsvex_defaults() -> Self {
673 let mut ext = Self::gfm_defaults();
689
690 ext.footnotes = false;
691 ext.tex_math_dollars = false;
692 ext.tex_math_gfm = false;
693 ext.emoji = false;
694 ext.alerts = false;
695
696 ext.svelte_template = true;
697
698 ext
699 }
700
701 fn myst_defaults() -> Self {
702 let mut ext = Self::commonmark_defaults();
711
712 ext.myst_directives = true;
713 ext.myst_roles = true;
714 ext.myst_targets = true;
715 ext.myst_comments = true;
716 ext.myst_block_breaks = true;
717
718 ext.pipe_tables = true;
725 ext.footnotes = true;
726 ext.inline_footnotes = false;
727
728 ext.yaml_metadata_block = true;
734
735 ext.myst_substitutions = false;
740 ext.myst_colon_fence = false;
741
742 ext
743 }
744
745 pub fn merge_with_flavor(user_overrides: HashMap<String, bool>, flavor: Flavor) -> Self {
759 let defaults = Self::for_flavor(flavor);
760 Self::merge_overrides(defaults, user_overrides)
761 }
762
763 pub fn apply_overrides(&mut self, user_overrides: HashMap<String, bool>) {
769 *self = Self::merge_overrides(self.clone(), user_overrides);
770 }
771
772 fn merge_overrides(mut base: Extensions, user_overrides: HashMap<String, bool>) -> Self {
773 for (key, value) in user_overrides {
774 base.set_by_name(&key, value);
775 }
776 base
777 }
778}
779
780macro_rules! known_extensions {
785 ( $( $kebab:literal => $field:ident ),* $(,)? ) => {
786 impl Extensions {
787 pub const KNOWN_NAMES: &'static [&'static str] = &[ $($kebab),* ];
792
793 pub fn is_known_name(name: &str) -> bool {
796 let normalized = name.replace('_', "-");
797 Self::KNOWN_NAMES.iter().any(|k| *k == normalized)
798 }
799
800 fn set_by_name(&mut self, name: &str, value: bool) -> bool {
803 match name.replace('_', "-").as_str() {
804 $( $kebab => { self.$field = value; true } )*
805 _ => false,
806 }
807 }
808 }
809 };
810}
811
812known_extensions! {
813 "blank-before-header" => blank_before_header,
814 "header-attributes" => header_attributes,
815 "auto-identifiers" => auto_identifiers,
816 "gfm-auto-identifiers" => gfm_auto_identifiers,
817 "implicit-header-references" => implicit_header_references,
818 "blank-before-blockquote" => blank_before_blockquote,
819 "fancy-lists" => fancy_lists,
820 "startnum" => startnum,
821 "example-lists" => example_lists,
822 "task-lists" => task_lists,
823 "definition-lists" => definition_lists,
824 "lists-without-preceding-blankline" => lists_without_preceding_blankline,
825 "four-space-rule" => four_space_rule,
826 "backtick-code-blocks" => backtick_code_blocks,
827 "fenced-code-blocks" => fenced_code_blocks,
828 "fenced-code-attributes" => fenced_code_attributes,
829 "executable-code" => executable_code,
830 "rmarkdown-inline-code" => rmarkdown_inline_code,
831 "quarto-inline-code" => quarto_inline_code,
832 "inline-code-attributes" => inline_code_attributes,
833 "simple-tables" => simple_tables,
834 "multiline-tables" => multiline_tables,
835 "grid-tables" => grid_tables,
836 "pipe-tables" => pipe_tables,
837 "table-captions" => table_captions,
838 "fenced-divs" => fenced_divs,
839 "native-divs" => native_divs,
840 "line-blocks" => line_blocks,
841 "intraword-underscores" => intraword_underscores,
842 "strikeout" => strikeout,
843 "superscript" => superscript,
844 "subscript" => subscript,
845 "inline-links" => inline_links,
846 "reference-links" => reference_links,
847 "shortcut-reference-links" => shortcut_reference_links,
848 "link-attributes" => link_attributes,
849 "autolinks" => autolinks,
850 "inline-images" => inline_images,
851 "implicit-figures" => implicit_figures,
852 "tex-math-dollars" => tex_math_dollars,
853 "tex-math-gfm" => tex_math_gfm,
854 "tex-math-single-backslash" => tex_math_single_backslash,
855 "tex-math-double-backslash" => tex_math_double_backslash,
856 "inline-footnotes" => inline_footnotes,
857 "footnotes" => footnotes,
858 "citations" => citations,
859 "bracketed-spans" => bracketed_spans,
860 "native-spans" => native_spans,
861 "yaml-metadata-block" => yaml_metadata_block,
862 "pandoc-title-block" => pandoc_title_block,
863 "mmd-title-block" => mmd_title_block,
864 "raw-html" => raw_html,
865 "markdown-in-html-blocks" => markdown_in_html_blocks,
866 "raw-tex" => raw_tex,
867 "raw-attribute" => raw_attribute,
868 "all-symbols-escapable" => all_symbols_escapable,
869 "escaped-line-breaks" => escaped_line_breaks,
870 "autolink-bare-uris" => autolink_bare_uris,
871 "hard-line-breaks" => hard_line_breaks,
872 "east-asian-line-breaks" => east_asian_line_breaks,
873 "mmd-header-identifiers" => mmd_header_identifiers,
874 "mmd-link-attributes" => mmd_link_attributes,
875 "alerts" => alerts,
876 "python-markdown-admonitions" => python_markdown_admonitions,
877 "pymdownx-details" => pymdownx_details,
878 "emoji" => emoji,
879 "mark" => mark,
880 "quarto-callouts" => quarto_callouts,
881 "quarto-crossrefs" => quarto_crossrefs,
882 "quarto-shortcodes" => quarto_shortcodes,
883 "bookdown-references" => bookdown_references,
884 "bookdown-equation-references" => bookdown_equation_references,
885 "wikilinks-title-after-pipe" => wikilinks_title_after_pipe,
886 "wikilinks-title-before-pipe" => wikilinks_title_before_pipe,
887 "spaced-reference-links" => spaced_reference_links,
888 "svelte-template" => svelte_template,
889 "myst-directives" => myst_directives,
890 "myst-roles" => myst_roles,
891 "myst-targets" => myst_targets,
892 "myst-substitutions" => myst_substitutions,
893 "myst-comments" => myst_comments,
894 "myst-colon-fence" => myst_colon_fence,
895 "myst-block-breaks" => myst_block_breaks,
896}
897
898#[cfg(test)]
899mod tests {
900 use super::{Dialect, Extensions, Flavor};
901 use std::collections::HashMap;
902
903 #[test]
904 fn merge_with_flavor_keeps_known_extension_overrides() {
905 let mut overrides = HashMap::new();
906 overrides.insert("intraword-underscores".to_string(), false);
907 let ext = Extensions::merge_with_flavor(overrides, Flavor::Pandoc);
908 assert!(!ext.intraword_underscores);
909 }
910
911 #[test]
912 fn merge_with_flavor_ignores_unknown_extension_overrides() {
913 let mut overrides = HashMap::new();
914 overrides.insert("smart".to_string(), true);
915 overrides.insert("smart-quotes".to_string(), true);
916 let ext = Extensions::merge_with_flavor(overrides, Flavor::Gfm);
917 assert!(ext.strikeout, "known defaults should remain intact");
918 }
919
920 #[test]
921 fn lists_without_preceding_blankline_defaults_false_for_pandoc_and_gfm() {
922 assert!(!Extensions::for_flavor(Flavor::Pandoc).lists_without_preceding_blankline);
923 assert!(!Extensions::for_flavor(Flavor::Gfm).lists_without_preceding_blankline);
924 }
925
926 #[test]
927 fn merge_with_flavor_accepts_lists_without_preceding_blankline_override() {
928 let mut overrides = HashMap::new();
929 overrides.insert("lists-without-preceding-blankline".to_string(), true);
930 let ext = Extensions::merge_with_flavor(overrides, Flavor::Pandoc);
931 assert!(ext.lists_without_preceding_blankline);
932 }
933
934 #[test]
935 fn four_space_rule_defaults_off_for_every_flavor() {
936 for flavor in [
937 Flavor::Pandoc,
938 Flavor::Quarto,
939 Flavor::RMarkdown,
940 Flavor::Gfm,
941 Flavor::CommonMark,
942 Flavor::MultiMarkdown,
943 Flavor::Mdsvex,
944 Flavor::Myst,
945 ] {
946 assert!(
947 !Extensions::for_flavor(flavor).four_space_rule,
948 "four_space_rule should be off by default for {flavor:?}"
949 );
950 }
951 }
952
953 #[test]
954 fn svelte_template_defaults_off_for_non_mdsvex_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::Myst,
963 ] {
964 assert!(
965 !Extensions::for_flavor(flavor).svelte_template,
966 "svelte_template should be off by default for {flavor:?}"
967 );
968 }
969 }
970
971 #[test]
972 fn myst_uses_commonmark_dialect() {
973 assert_eq!(Dialect::for_flavor(Flavor::Myst), Dialect::CommonMark);
974 }
975
976 #[test]
977 fn myst_defaults_enable_core_constructs_only() {
978 let ext = Extensions::for_flavor(Flavor::Myst);
979
980 assert!(ext.myst_directives);
982 assert!(ext.myst_roles);
983 assert!(ext.myst_targets);
984 assert!(ext.myst_comments);
985 assert!(ext.myst_block_breaks);
986
987 assert!(ext.pipe_tables);
992 assert!(ext.footnotes);
993 assert!(!ext.inline_footnotes);
994
995 assert!(ext.yaml_metadata_block);
998
999 assert!(!ext.myst_colon_fence);
1002 assert!(!ext.myst_substitutions);
1003 assert!(!ext.tex_math_dollars);
1004 assert!(!ext.definition_lists);
1005 assert!(!ext.task_lists);
1006 assert!(!ext.strikeout);
1007
1008 assert!(ext.inline_links);
1011 assert!(ext.backtick_code_blocks);
1012 assert!(!ext.fenced_divs);
1013 assert!(!ext.bracketed_spans);
1014 assert!(!ext.header_attributes);
1015 }
1016
1017 #[test]
1018 fn myst_core_constructs_off_for_other_flavors() {
1019 for flavor in [
1020 Flavor::Pandoc,
1021 Flavor::Quarto,
1022 Flavor::RMarkdown,
1023 Flavor::Gfm,
1024 Flavor::CommonMark,
1025 Flavor::MultiMarkdown,
1026 Flavor::Mdsvex,
1027 ] {
1028 let ext = Extensions::for_flavor(flavor);
1029 assert!(
1030 !ext.myst_directives
1031 && !ext.myst_roles
1032 && !ext.myst_targets
1033 && !ext.myst_comments
1034 && !ext.myst_block_breaks,
1035 "MyST core constructs should be off by default for {flavor:?}"
1036 );
1037 }
1038 }
1039
1040 #[test]
1041 fn mdsvex_defaults_match_remark_parse_8_gfm() {
1042 let ext = Extensions::for_flavor(Flavor::Mdsvex);
1043
1044 assert!(ext.svelte_template);
1046 assert!(ext.raw_html);
1047 assert!(ext.yaml_metadata_block);
1048
1049 assert!(ext.pipe_tables);
1051 assert!(ext.strikeout);
1052 assert!(ext.task_lists);
1053 assert!(ext.autolink_bare_uris);
1054
1055 assert!(!ext.footnotes);
1057 assert!(!ext.tex_math_dollars);
1058 assert!(!ext.emoji);
1059 assert!(!ext.alerts);
1060
1061 assert_eq!(Dialect::for_flavor(Flavor::Mdsvex), Dialect::CommonMark);
1064 assert!(!ext.header_attributes);
1065 assert!(!ext.bracketed_spans);
1066 assert!(!ext.fenced_divs);
1067 assert!(!ext.raw_attribute);
1068 assert!(!ext.inline_code_attributes);
1069 }
1070
1071 #[test]
1072 fn merge_with_flavor_accepts_four_space_rule_override() {
1073 let mut overrides = HashMap::new();
1074 overrides.insert("four-space-rule".to_string(), true);
1075 let ext = Extensions::merge_with_flavor(overrides, Flavor::Pandoc);
1076 assert!(ext.four_space_rule);
1077 }
1078}
1079
1080#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1081#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1082pub enum PandocCompat {
1083 #[cfg_attr(feature = "serde", serde(rename = "latest"))]
1088 Latest,
1089 #[cfg_attr(
1091 feature = "serde",
1092 serde(rename = "3.7", alias = "3-7", alias = "v3.7", alias = "v3-7")
1093 )]
1094 V3_7,
1095 #[default]
1097 #[cfg_attr(
1098 feature = "serde",
1099 serde(rename = "3.9", alias = "3-9", alias = "v3.9", alias = "v3-9")
1100 )]
1101 V3_9,
1102}
1103
1104impl PandocCompat {
1105 pub const PINNED_LATEST: Self = Self::V3_9;
1107
1108 pub fn effective(self) -> Self {
1109 match self {
1110 Self::Latest => Self::PINNED_LATEST,
1111 other => other,
1112 }
1113 }
1114}
1115
1116#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1128#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
1129#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1130pub enum Dialect {
1131 #[default]
1134 Pandoc,
1135 CommonMark,
1137}
1138
1139impl Dialect {
1140 pub fn for_flavor(flavor: Flavor) -> Self {
1142 match flavor {
1143 Flavor::CommonMark | Flavor::Gfm | Flavor::Mdsvex | Flavor::Myst => Dialect::CommonMark,
1144 Flavor::Pandoc | Flavor::Quarto | Flavor::RMarkdown | Flavor::MultiMarkdown => {
1145 Dialect::Pandoc
1146 }
1147 }
1148 }
1149}
1150
1151#[derive(Debug, Clone)]
1152#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1153#[cfg_attr(feature = "serde", serde(default, rename_all = "kebab-case"))]
1154#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1155pub struct ParserOptions {
1156 pub flavor: Flavor,
1157 pub dialect: Dialect,
1158 pub extensions: Extensions,
1159 pub pandoc_compat: PandocCompat,
1161 #[cfg_attr(feature = "serde", serde(default, alias = "crossref_prefixes"))]
1168 pub crossref_prefixes: Vec<String>,
1169 #[cfg_attr(feature = "serde", serde(skip))]
1180 pub refdef_labels: Option<Arc<HashSet<String>>>,
1181}
1182
1183impl Default for ParserOptions {
1184 fn default() -> Self {
1185 let flavor = Flavor::default();
1186 Self {
1187 flavor,
1188 dialect: Dialect::for_flavor(flavor),
1189 extensions: Extensions::for_flavor(flavor),
1190 pandoc_compat: PandocCompat::default(),
1191 crossref_prefixes: Vec::new(),
1192 refdef_labels: None,
1193 }
1194 }
1195}
1196
1197impl ParserOptions {
1198 pub fn effective_pandoc_compat(&self) -> PandocCompat {
1199 self.pandoc_compat.effective()
1200 }
1201}
1202
1203#[cfg(feature = "schema")]
1204impl schemars::JsonSchema for Flavor {
1205 fn schema_name() -> std::borrow::Cow<'static, str> {
1206 "Flavor".into()
1207 }
1208
1209 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1210 schemars::json_schema!({
1214 "type": "string",
1215 "description": "Markdown flavor to parse and format against.",
1216 "enum": [
1217 "pandoc",
1218 "quarto",
1219 "rmarkdown",
1220 "gfm",
1221 "common-mark",
1222 "commonmark",
1223 "multimarkdown",
1224 "mdsvex",
1225 "myst"
1226 ]
1227 })
1228 }
1229}
1230
1231#[cfg(feature = "schema")]
1232impl schemars::JsonSchema for PandocCompat {
1233 fn schema_name() -> std::borrow::Cow<'static, str> {
1234 "PandocCompat".into()
1235 }
1236
1237 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
1238 schemars::json_schema!({
1239 "type": "string",
1240 "description": "Compatibility target for ambiguous Pandoc behavior.",
1241 "enum": [
1242 "latest",
1243 "3.7", "3-7", "v3.7", "v3-7",
1244 "3.9", "3-9", "v3.9", "v3-9"
1245 ]
1246 })
1247 }
1248}