core_markdown/configuration.rs
1// Released under the MIT License.
2// Copyright, 2022, by Bernhard Berger.
3// Copyright, 2022-2025, by Titus Wormer.
4// Copyright, 2023, by cel.
5// Copyright, 2025, by Ophir Lojkine.
6// Copyright, 2026, by Samuel Williams.
7
8use crate::util::{
9 line_ending::LineEnding,
10 mdx::{EsmParse as MdxEsmParse, ExpressionParse as MdxExpressionParse},
11};
12use alloc::{boxed::Box, fmt, string::String};
13
14/// Control which constructs are enabled.
15///
16/// Not all constructs can be configured.
17/// Notably, blank lines and paragraphs cannot be turned off.
18///
19/// ## Examples
20///
21/// ```
22/// use core_markdown::Constructs;
23/// # fn main() {
24///
25/// // Use the default trait to get `CommonMark` constructs:
26/// let commonmark = Constructs::default();
27///
28/// // To turn on all of GFM, use the `gfm` method:
29/// let gfm = Constructs::gfm();
30///
31/// // Or, mix and match:
32/// let custom = Constructs {
33/// math_flow: true,
34/// math_text: true,
35/// ..Constructs::gfm()
36/// };
37/// # }
38/// ```
39#[allow(clippy::struct_excessive_bools)]
40#[derive(Clone, Debug, Eq, PartialEq)]
41#[cfg_attr(
42 feature = "serde",
43 derive(serde::Serialize, serde::Deserialize),
44 serde(rename_all = "camelCase")
45)]
46pub struct Constructs {
47 /// Attention.
48 ///
49 /// ```markdown
50 /// > | a *b* c **d**.
51 /// ^^^ ^^^^^
52 /// ```
53 pub attention: bool,
54 /// Autolink.
55 ///
56 /// ```markdown
57 /// > | a <https://example.com> b <user@example.org>.
58 /// ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
59 /// ```
60 pub autolink: bool,
61 /// Block quote.
62 ///
63 /// ```markdown
64 /// > | > a
65 /// ^^^
66 /// ```
67 pub block_quote: bool,
68 /// Character escape.
69 ///
70 /// ```markdown
71 /// > | a \* b
72 /// ^^
73 /// ```
74 pub character_escape: bool,
75 /// Character reference.
76 ///
77 /// ```markdown
78 /// > | a & b
79 /// ^^^^^
80 /// ```
81 pub character_reference: bool,
82 /// Code (indented).
83 ///
84 /// ```markdown
85 /// > | a
86 /// ^^^^^
87 /// ```
88 pub code_indented: bool,
89 /// Code (fenced).
90 ///
91 /// ```markdown
92 /// > | ~~~js
93 /// ^^^^^
94 /// > | console.log(1)
95 /// ^^^^^^^^^^^^^^
96 /// > | ~~~
97 /// ^^^
98 /// ```
99 pub code_fenced: bool,
100 /// Code (text).
101 ///
102 /// ```markdown
103 /// > | a `b` c
104 /// ^^^
105 /// ```
106 pub code_text: bool,
107 /// Definition.
108 ///
109 /// ```markdown
110 /// > | [a]: b "c"
111 /// ^^^^^^^^^^
112 /// ```
113 pub definition: bool,
114 /// Frontmatter with an unindented opening fence on the first document line,
115 /// outside list items and blockquotes, and a matching closing fence.
116 ///
117 /// All forms produce a [`Frontmatter`][crate::mdast::Frontmatter] node.
118 /// Untagged `---` and `+++` imply YAML and TOML. A format hint after either
119 /// delimiter, or a language-tagged backtick/tilde code fence, supplies an
120 /// explicit language. The parser preserves the raw body and authored info
121 /// string; HTML omits the frontmatter.
122 ///
123 /// ````markdown
124 /// > | ---
125 /// ^^^
126 /// > | title: Neptune
127 /// ^^^^^^^^^^^^^^
128 /// > | ---
129 /// ^^^
130 /// ````
131 pub frontmatter: bool,
132 /// GFM: autolink literal.
133 ///
134 /// ```markdown
135 /// > | https://example.com
136 /// ^^^^^^^^^^^^^^^^^^^
137 /// ```
138 pub gfm_autolink_literal: bool,
139 /// GFM: footnote definition.
140 ///
141 /// ```markdown
142 /// > | [^a]: b
143 /// ^^^^^^^
144 /// ```
145 pub gfm_footnote_definition: bool,
146 /// GFM: footnote label start.
147 ///
148 /// ```markdown
149 /// > | a[^b]
150 /// ^^
151 /// ```
152 pub gfm_label_start_footnote: bool,
153 ///
154 /// ```markdown
155 /// > | a ~b~ c.
156 /// ^^^
157 /// ```
158 pub gfm_strikethrough: bool,
159 /// GFM: table.
160 ///
161 /// ```markdown
162 /// > | | a |
163 /// ^^^^^
164 /// > | | - |
165 /// ^^^^^
166 /// > | | b |
167 /// ^^^^^
168 /// ```
169 pub gfm_table: bool,
170 /// GFM: task list item.
171 ///
172 /// ```markdown
173 /// > | * [x] y.
174 /// ^^^
175 /// ```
176 pub gfm_task_list_item: bool,
177 /// Hard break (escape).
178 ///
179 /// ```markdown
180 /// > | a\
181 /// ^
182 /// | b
183 /// ```
184 pub hard_break_escape: bool,
185 /// Hard break (trailing).
186 ///
187 /// ```markdown
188 /// > | a␠␠
189 /// ^^
190 /// | b
191 /// ```
192 pub hard_break_trailing: bool,
193 /// Heading (atx).
194 ///
195 /// ```markdown
196 /// > | # a
197 /// ^^^
198 /// ```
199 pub heading_atx: bool,
200 /// Heading (setext).
201 ///
202 /// ```markdown
203 /// > | a
204 /// ^^
205 /// > | ==
206 /// ^^
207 /// ```
208 pub heading_setext: bool,
209 /// HTML (flow).
210 ///
211 /// ```markdown
212 /// > | <div>
213 /// ^^^^^
214 /// ```
215 pub html_flow: bool,
216 /// HTML (text).
217 ///
218 /// ```markdown
219 /// > | a <b> c
220 /// ^^^
221 /// ```
222 pub html_text: bool,
223 /// Label start (image).
224 ///
225 /// ```markdown
226 /// > | a  d
227 /// ^^
228 /// ```
229 pub label_start_image: bool,
230 /// Label start (link).
231 ///
232 /// ```markdown
233 /// > | a [b](c) d
234 /// ^
235 /// ```
236 pub label_start_link: bool,
237 /// Label end.
238 ///
239 /// ```markdown
240 /// > | a [b](c) d
241 /// ^^^^
242 /// ```
243 pub label_end: bool,
244 /// List items.
245 ///
246 /// ```markdown
247 /// > | * a
248 /// ^^^
249 /// ```
250 pub list_item: bool,
251 /// Math (flow).
252 ///
253 /// ```markdown
254 /// > | $$
255 /// ^^
256 /// > | \frac{1}{2}
257 /// ^^^^^^^^^^^
258 /// > | $$
259 /// ^^
260 /// ```
261 pub math_flow: bool,
262 /// Math (text).
263 ///
264 /// ```markdown
265 /// > | a $b$ c
266 /// ^^^
267 /// ```
268 pub math_text: bool,
269 /// MDX: ESM.
270 ///
271 /// ```markdown
272 /// > | import a from 'b'
273 /// ^^^^^^^^^^^^^^^^^
274 /// ```
275 ///
276 /// > 👉 **Note**: to support ESM, you *must* pass
277 /// > [`mdx_esm_parse`][MdxEsmParse] in [`ParseOptions`][] too.
278 /// > Otherwise, ESM is treated as normal markdown.
279 pub mdx_esm: bool,
280 /// MDX: expression (flow).
281 ///
282 /// ```markdown
283 /// > | {Math.PI}
284 /// ^^^^^^^^^
285 /// ```
286 ///
287 /// > 👉 **Note**: You *can* pass
288 /// > [`mdx_expression_parse`][MdxExpressionParse] in [`ParseOptions`][]
289 /// > too, to parse expressions according to a certain grammar (typically,
290 /// > a programming language).
291 /// > Otherwise, expressions are parsed with a basic algorithm that only
292 /// > cares about braces.
293 pub mdx_expression_flow: bool,
294 /// MDX: expression (text).
295 ///
296 /// ```markdown
297 /// > | a {Math.PI} c
298 /// ^^^^^^^^^
299 /// ```
300 ///
301 /// > 👉 **Note**: You *can* pass
302 /// > [`mdx_expression_parse`][MdxExpressionParse] in [`ParseOptions`][]
303 /// > too, to parse expressions according to a certain grammar (typically,
304 /// > a programming language).
305 /// > Otherwise, expressions are parsed with a basic algorithm that only
306 /// > cares about braces.
307 pub mdx_expression_text: bool,
308 /// MDX: JSX (flow).
309 ///
310 /// ```markdown
311 /// > | <Component />
312 /// ^^^^^^^^^^^^^
313 /// ```
314 ///
315 /// > 👉 **Note**: You *must* pass `html_flow: false` to use this,
316 /// > as it’s preferred when on over `mdx_jsx_flow`.
317 ///
318 /// > 👉 **Note**: You *can* pass
319 /// > [`mdx_expression_parse`][MdxExpressionParse] in [`ParseOptions`][]
320 /// > too, to parse expressions in JSX according to a certain grammar
321 /// > (typically, a programming language).
322 /// > Otherwise, expressions are parsed with a basic algorithm that only
323 /// > cares about braces.
324 pub mdx_jsx_flow: bool,
325 /// MDX: JSX (text).
326 ///
327 /// ```markdown
328 /// > | a <Component /> c
329 /// ^^^^^^^^^^^^^
330 /// ```
331 ///
332 /// > 👉 **Note**: You *must* pass `html_text: false` to use this,
333 /// > as it’s preferred when on over `mdx_jsx_text`.
334 ///
335 /// > 👉 **Note**: You *can* pass
336 /// > [`mdx_expression_parse`][MdxExpressionParse] in [`ParseOptions`][]
337 /// > too, to parse expressions in JSX according to a certain grammar
338 /// > (typically, a programming language).
339 /// > Otherwise, expressions are parsed with a basic algorithm that only
340 /// > cares about braces.
341 pub mdx_jsx_text: bool,
342 /// Thematic break.
343 ///
344 /// ```markdown
345 /// > | ***
346 /// ^^^
347 /// ```
348 pub thematic_break: bool,
349}
350
351impl Default for Constructs {
352 /// `CommonMark`.
353 ///
354 /// `CommonMark` is a relatively strong specification of how markdown
355 /// works.
356 /// Most markdown parsers try to follow it.
357 ///
358 /// For more information, see the `CommonMark` specification:
359 /// <https://spec.commonmark.org>.
360 fn default() -> Self {
361 Self {
362 attention: true,
363 autolink: true,
364 block_quote: true,
365 character_escape: true,
366 character_reference: true,
367 code_indented: true,
368 code_fenced: true,
369 code_text: true,
370 definition: true,
371 frontmatter: false,
372 gfm_autolink_literal: false,
373 gfm_label_start_footnote: false,
374 gfm_footnote_definition: false,
375 gfm_strikethrough: false,
376 gfm_table: false,
377 gfm_task_list_item: false,
378 hard_break_escape: true,
379 hard_break_trailing: true,
380 heading_atx: true,
381 heading_setext: true,
382 html_flow: true,
383 html_text: true,
384 label_start_image: true,
385 label_start_link: true,
386 label_end: true,
387 list_item: true,
388 math_flow: false,
389 math_text: false,
390 mdx_esm: false,
391 mdx_expression_flow: false,
392 mdx_expression_text: false,
393 mdx_jsx_flow: false,
394 mdx_jsx_text: false,
395 thematic_break: true,
396 }
397 }
398}
399
400impl Constructs {
401 /// GFM.
402 ///
403 /// GFM stands for **GitHub flavored markdown**.
404 /// GFM extends `CommonMark` and adds support for autolink literals,
405 /// footnotes, strikethrough, tables, and tasklists.
406 ///
407 /// For more information, see the GFM specification:
408 /// <https://github.github.com/gfm/>.
409 pub fn gfm() -> Self {
410 Self {
411 gfm_autolink_literal: true,
412 gfm_footnote_definition: true,
413 gfm_label_start_footnote: true,
414 gfm_strikethrough: true,
415 gfm_table: true,
416 gfm_task_list_item: true,
417 ..Self::default()
418 }
419 }
420
421 /// MDX.
422 ///
423 /// This turns on `CommonMark`, turns off some conflicting constructs
424 /// (autolinks, code (indented), and HTML), and turns on MDX (ESM,
425 /// expressions, and JSX).
426 ///
427 /// For more information, see the MDX website:
428 /// <https://mdxjs.com>.
429 ///
430 /// > 👉 **Note**: to support ESM, you *must* pass
431 /// > [`mdx_esm_parse`][MdxEsmParse] in [`ParseOptions`][] too.
432 /// > Otherwise, ESM is treated as normal markdown.
433 /// >
434 /// > You *can* pass
435 /// > [`mdx_expression_parse`][MdxExpressionParse]
436 /// > to parse expressions according to a certain grammar (typically, a
437 /// > programming language).
438 /// > Otherwise, expressions are parsed with a basic algorithm that only
439 /// > cares about braces.
440 pub fn mdx() -> Self {
441 Self {
442 autolink: false,
443 code_indented: false,
444 html_flow: false,
445 html_text: false,
446 mdx_esm: true,
447 mdx_expression_flow: true,
448 mdx_expression_text: true,
449 mdx_jsx_flow: true,
450 mdx_jsx_text: true,
451 ..Self::default()
452 }
453 }
454}
455
456/// Configuration that describes how to compile to HTML.
457///
458/// You likely either want to turn on the dangerous options
459/// (`allow_dangerous_html`, `allow_dangerous_protocol`) when dealing with
460/// input you trust, or want to customize how GFM footnotes are compiled
461/// (typically because the input markdown is not in English).
462///
463/// ## Examples
464///
465/// ```
466/// use core_markdown::CompileOptions;
467/// # fn main() {
468///
469/// // Use the default trait to get safe defaults:
470/// let safe = CompileOptions::default();
471///
472/// // Live dangerously / trust the author:
473/// let danger = CompileOptions {
474/// allow_dangerous_html: true,
475/// allow_dangerous_protocol: true,
476/// ..CompileOptions::default()
477/// };
478///
479/// // In French:
480/// let enFrançais = CompileOptions {
481/// gfm_footnote_back_label: Some("Arrière".into()),
482/// gfm_footnote_label: Some("Notes de bas de page".into()),
483/// ..CompileOptions::default()
484/// };
485/// # }
486/// ```
487#[allow(clippy::struct_excessive_bools)]
488#[derive(Clone, Debug, Default)]
489#[cfg_attr(
490 feature = "serde",
491 derive(serde::Serialize, serde::Deserialize),
492 serde(default, rename_all = "camelCase")
493)]
494pub struct CompileOptions {
495 /// Whether to allow all values in images.
496 ///
497 /// The default is `false`,
498 /// which lets `allow_dangerous_protocol` control protocol safety for
499 /// both links and images.
500 ///
501 /// Pass `true` to allow all values as `src` on images,
502 /// regardless of `allow_dangerous_protocol`.
503 /// This is safe because the
504 /// [HTML specification][whatwg-html-image-processing]
505 /// does not allow executable code in images.
506 ///
507 /// [whatwg-html-image-processing]: https://html.spec.whatwg.org/multipage/images.html#images-processing-model
508 ///
509 /// ## Examples
510 ///
511 /// ```
512 /// use core_markdown::{to_html_with_options, CompileOptions, Options};
513 /// # fn main() -> Result<(), core_markdown::message::Message> {
514 ///
515 /// // By default, some protocols in image sources are dropped:
516 /// assert_eq!(
517 /// to_html_with_options(
518 /// "",
519 /// &Options::default()
520 /// )?,
521 /// "<p><img src=\"\" alt=\"\" /></p>"
522 /// );
523 ///
524 /// // Turn `allow_any_img_src` on to allow all values as `src` on images.
525 /// // This is safe because browsers do not execute code in images.
526 /// assert_eq!(
527 /// to_html_with_options(
528 /// ")",
529 /// &Options {
530 /// compile: CompileOptions {
531 /// allow_any_img_src: true,
532 /// ..CompileOptions::default()
533 /// },
534 /// ..Options::default()
535 /// }
536 /// )?,
537 /// "<p><img src=\"javascript:alert(1)\" alt=\"\" /></p>"
538 /// );
539 /// # Ok(())
540 /// # }
541 /// ```
542 pub allow_any_img_src: bool,
543
544 /// Whether to allow (dangerous) HTML.
545 ///
546 /// The default is `false`, which still parses the HTML according to
547 /// `CommonMark` but shows the HTML as text instead of as elements.
548 ///
549 /// Pass `true` for trusted content to get actual HTML elements.
550 ///
551 /// When using GFM, make sure to also turn off `gfm_tagfilter`.
552 /// Otherwise, some dangerous HTML is still ignored.
553 ///
554 /// ## Examples
555 ///
556 /// ```
557 /// use core_markdown::{to_html, to_html_with_options, CompileOptions, Options};
558 /// # fn main() -> Result<(), core_markdown::message::Message> {
559 ///
560 /// // `markdown-rs` is safe by default:
561 /// assert_eq!(
562 /// to_html("Hi, <i>venus</i>!"),
563 /// "<p>Hi, <i>venus</i>!</p>"
564 /// );
565 ///
566 /// // Turn `allow_dangerous_html` on to allow potentially dangerous HTML:
567 /// assert_eq!(
568 /// to_html_with_options(
569 /// "Hi, <i>venus</i>!",
570 /// &Options {
571 /// compile: CompileOptions {
572 /// allow_dangerous_html: true,
573 /// ..CompileOptions::default()
574 /// },
575 /// ..Options::default()
576 /// }
577 /// )?,
578 /// "<p>Hi, <i>venus</i>!</p>"
579 /// );
580 /// # Ok(())
581 /// # }
582 /// ```
583 pub allow_dangerous_html: bool,
584
585 /// Whether to allow dangerous protocols in links and images.
586 ///
587 /// The default is `false`, which drops URLs in links and images that use
588 /// dangerous protocols.
589 ///
590 /// Pass `true` for trusted content to support all protocols.
591 ///
592 /// URLs that have no protocol (which means it’s relative to the current
593 /// page, such as `./some/page.html`) and URLs that have a safe protocol
594 /// (for images: `http`, `https`; for links: `http`, `https`, `irc`,
595 /// `ircs`, `mailto`, `xmpp`), are safe.
596 /// All other URLs are dangerous and dropped.
597 ///
598 /// When the option `allow_all_protocols_in_img` is enabled,
599 /// `allow_dangerous_protocol` only applies to links.
600 ///
601 /// This is safe because the
602 /// [HTML specification][whatwg-html-image-processing]
603 /// does not allow executable code in images.
604 /// All modern browsers respect this.
605 ///
606 /// [whatwg-html-image-processing]: https://html.spec.whatwg.org/multipage/images.html#images-processing-model
607 ///
608 /// ## Examples
609 ///
610 /// ```
611 /// use core_markdown::{to_html, to_html_with_options, CompileOptions, Options};
612 /// # fn main() -> Result<(), core_markdown::message::Message> {
613 ///
614 /// // `markdown-rs` is safe by default:
615 /// assert_eq!(
616 /// to_html("<javascript:alert(1)>"),
617 /// "<p><a href=\"\">javascript:alert(1)</a></p>"
618 /// );
619 ///
620 /// // Turn `allow_dangerous_protocol` on to allow potentially dangerous protocols:
621 /// assert_eq!(
622 /// to_html_with_options(
623 /// "<javascript:alert(1)>",
624 /// &Options {
625 /// compile: CompileOptions {
626 /// allow_dangerous_protocol: true,
627 /// ..CompileOptions::default()
628 /// },
629 /// ..Options::default()
630 /// }
631 /// )?,
632 /// "<p><a href=\"javascript:alert(1)\">javascript:alert(1)</a></p>"
633 /// );
634 /// # Ok(())
635 /// # }
636 /// ```
637 pub allow_dangerous_protocol: bool,
638
639 // To do: `doc_markdown` is broken.
640 #[allow(clippy::doc_markdown)]
641 /// Default line ending to use when compiling to HTML, for line endings not
642 /// in `value`.
643 ///
644 /// Generally, `markdown-rs` copies line endings (`\r`, `\n`, `\r\n`) in
645 /// the markdown document over to the compiled HTML.
646 /// In some cases, such as `> a`, CommonMark requires that extra line
647 /// endings are added: `<blockquote>\n<p>a</p>\n</blockquote>`.
648 ///
649 /// To create that line ending, the document is checked for the first line
650 /// ending that is used.
651 /// If there is no line ending, `default_line_ending` is used.
652 /// If that isn’t configured, `\n` is used.
653 ///
654 /// ## Examples
655 ///
656 /// ```
657 /// use core_markdown::{to_html, to_html_with_options, CompileOptions, LineEnding, Options};
658 /// # fn main() -> Result<(), core_markdown::message::Message> {
659 ///
660 /// // `markdown-rs` uses `\n` by default:
661 /// assert_eq!(
662 /// to_html("> a"),
663 /// "<blockquote>\n<p>a</p>\n</blockquote>"
664 /// );
665 ///
666 /// // Define `default_line_ending` to configure the default:
667 /// assert_eq!(
668 /// to_html_with_options(
669 /// "> a",
670 /// &Options {
671 /// compile: CompileOptions {
672 /// default_line_ending: LineEnding::CarriageReturnLineFeed,
673 /// ..CompileOptions::default()
674 /// },
675 /// ..Options::default()
676 /// }
677 /// )?,
678 /// "<blockquote>\r\n<p>a</p>\r\n</blockquote>"
679 /// );
680 /// # Ok(())
681 /// # }
682 /// ```
683 pub default_line_ending: LineEnding,
684
685 /// Textual label to describe the backreference back to footnote calls.
686 ///
687 /// The default value is `"Back to content"`.
688 /// Change it when the markdown is not in English.
689 ///
690 /// This label is used in the `aria-label` attribute on each backreference
691 /// (the `↩` links).
692 /// It affects users of assistive technology.
693 ///
694 /// ## Examples
695 ///
696 /// ```
697 /// use core_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
698 /// # fn main() -> Result<(), core_markdown::message::Message> {
699 ///
700 /// // `"Back to content"` is used by default:
701 /// assert_eq!(
702 /// to_html_with_options(
703 /// "[^a]\n\n[^a]: b",
704 /// &Options::gfm()
705 /// )?,
706 /// "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
707 /// );
708 ///
709 /// // Pass `gfm_footnote_back_label` to use something else:
710 /// assert_eq!(
711 /// to_html_with_options(
712 /// "[^a]\n\n[^a]: b",
713 /// &Options {
714 /// parse: ParseOptions::gfm(),
715 /// compile: CompileOptions {
716 /// gfm_footnote_back_label: Some("Arrière".into()),
717 /// ..CompileOptions::gfm()
718 /// }
719 /// }
720 /// )?,
721 /// "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Arrière\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
722 /// );
723 /// # Ok(())
724 /// # }
725 /// ```
726 pub gfm_footnote_back_label: Option<String>,
727
728 /// Prefix to use before the `id` attribute on footnotes to prevent them
729 /// from *clobbering*.
730 ///
731 /// The default is `"user-content-"`.
732 /// Pass `Some("".into())` for trusted markdown and when you are careful
733 /// with polyfilling.
734 /// You could pass a different prefix.
735 ///
736 /// DOM clobbering is this:
737 ///
738 /// ```html
739 /// <p id="x"></p>
740 /// <script>alert(x) // `x` now refers to the `p#x` DOM element</script>
741 /// ```
742 ///
743 /// The above example shows that elements are made available by browsers,
744 /// by their ID, on the `window` object.
745 /// This is a security risk because you might be expecting some other
746 /// variable at that place.
747 /// It can also break polyfills.
748 /// Using a prefix solves these problems.
749 ///
750 /// ## Examples
751 ///
752 /// ```
753 /// use core_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
754 /// # fn main() -> Result<(), core_markdown::message::Message> {
755 ///
756 /// // `"user-content-"` is used by default:
757 /// assert_eq!(
758 /// to_html_with_options(
759 /// "[^a]\n\n[^a]: b",
760 /// &Options::gfm()
761 /// )?,
762 /// "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
763 /// );
764 ///
765 /// // Pass `gfm_footnote_clobber_prefix` to use something else:
766 /// assert_eq!(
767 /// to_html_with_options(
768 /// "[^a]\n\n[^a]: b",
769 /// &Options {
770 /// parse: ParseOptions::gfm(),
771 /// compile: CompileOptions {
772 /// gfm_footnote_clobber_prefix: Some("".into()),
773 /// ..CompileOptions::gfm()
774 /// }
775 /// }
776 /// )?,
777 /// "<p><sup><a href=\"#fn-a\" id=\"fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"fn-a\">\n<p>b <a href=\"#fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
778 /// );
779 /// # Ok(())
780 /// # }
781 /// ```
782 pub gfm_footnote_clobber_prefix: Option<String>,
783
784 /// Attributes to use on the footnote label.
785 ///
786 /// The default value is `"class=\"sr-only\""`.
787 /// Change it to show the label and add other attributes.
788 ///
789 /// This label is typically hidden visually (assuming a `sr-only` CSS class
790 /// is defined that does that), and thus affects screen readers only.
791 /// If you do have such a class, but want to show this section to everyone,
792 /// pass an empty string.
793 /// You can also add different attributes.
794 ///
795 /// > 👉 **Note**: `id="footnote-label"` is always added, because footnote
796 /// > calls use it with `aria-describedby` to provide an accessible label.
797 ///
798 /// ## Examples
799 ///
800 /// ```
801 /// use core_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
802 /// # fn main() -> Result<(), core_markdown::message::Message> {
803 ///
804 /// // `"class=\"sr-only\""` is used by default:
805 /// assert_eq!(
806 /// to_html_with_options(
807 /// "[^a]\n\n[^a]: b",
808 /// &Options::gfm()
809 /// )?,
810 /// "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
811 /// );
812 ///
813 /// // Pass `gfm_footnote_label_attributes` to use something else:
814 /// assert_eq!(
815 /// to_html_with_options(
816 /// "[^a]\n\n[^a]: b",
817 /// &Options {
818 /// parse: ParseOptions::gfm(),
819 /// compile: CompileOptions {
820 /// gfm_footnote_label_attributes: Some("class=\"footnote-heading\"".into()),
821 /// ..CompileOptions::gfm()
822 /// }
823 /// }
824 /// )?,
825 /// "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"footnote-heading\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
826 /// );
827 /// # Ok(())
828 /// # }
829 /// ```
830 pub gfm_footnote_label_attributes: Option<String>,
831
832 /// HTML tag name to use for the footnote label element.
833 ///
834 /// The default value is `"h2"`.
835 /// Change it to match your document structure.
836 ///
837 /// This label is typically hidden visually (assuming a `sr-only` CSS class
838 /// is defined that does that), and thus affects screen readers only.
839 /// If you do have such a class, but want to show this section to everyone,
840 /// pass different attributes with the `gfm_footnote_label_attributes`
841 /// option.
842 ///
843 /// ## Examples
844 ///
845 /// ```
846 /// use core_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
847 /// # fn main() -> Result<(), core_markdown::message::Message> {
848 ///
849 /// // `"h2"` is used by default:
850 /// assert_eq!(
851 /// to_html_with_options(
852 /// "[^a]\n\n[^a]: b",
853 /// &Options::gfm()
854 /// )?,
855 /// "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
856 /// );
857 ///
858 /// // Pass `gfm_footnote_label_tag_name` to use something else:
859 /// assert_eq!(
860 /// to_html_with_options(
861 /// "[^a]\n\n[^a]: b",
862 /// &Options {
863 /// parse: ParseOptions::gfm(),
864 /// compile: CompileOptions {
865 /// gfm_footnote_label_tag_name: Some("h1".into()),
866 /// ..CompileOptions::gfm()
867 /// }
868 /// }
869 /// )?,
870 /// "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h1 id=\"footnote-label\" class=\"sr-only\">Footnotes</h1>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
871 /// );
872 /// # Ok(())
873 /// # }
874 /// ```
875 pub gfm_footnote_label_tag_name: Option<String>,
876
877 /// Textual label to use for the footnotes section.
878 ///
879 /// The default value is `"Footnotes"`.
880 /// Change it when the markdown is not in English.
881 ///
882 /// This label is typically hidden visually (assuming a `sr-only` CSS class
883 /// is defined that does that), and thus affects screen readers only.
884 /// If you do have such a class, but want to show this section to everyone,
885 /// pass different attributes with the `gfm_footnote_label_attributes`
886 /// option.
887 ///
888 /// ## Examples
889 ///
890 /// ```
891 /// use core_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
892 /// # fn main() -> Result<(), core_markdown::message::Message> {
893 ///
894 /// // `"Footnotes"` is used by default:
895 /// assert_eq!(
896 /// to_html_with_options(
897 /// "[^a]\n\n[^a]: b",
898 /// &Options::gfm()
899 /// )?,
900 /// "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
901 /// );
902 ///
903 /// // Pass `gfm_footnote_label` to use something else:
904 /// assert_eq!(
905 /// to_html_with_options(
906 /// "[^a]\n\n[^a]: b",
907 /// &Options {
908 /// parse: ParseOptions::gfm(),
909 /// compile: CompileOptions {
910 /// gfm_footnote_label: Some("Notes de bas de page".into()),
911 /// ..CompileOptions::gfm()
912 /// }
913 /// }
914 /// )?,
915 /// "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Notes de bas de page</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
916 /// );
917 /// # Ok(())
918 /// # }
919 /// ```
920 pub gfm_footnote_label: Option<String>,
921
922 /// Whether or not GFM task list html `<input>` items are enabled.
923 ///
924 /// This determines whether or not the user of the browser is able
925 /// to click and toggle generated checkbox items. The default is false.
926 ///
927 /// ## Examples
928 ///
929 /// ```
930 /// use core_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
931 /// # fn main() -> Result<(), core_markdown::message::Message> {
932 ///
933 /// // With `gfm_task_list_item_checkable`, generated `<input type="checkbox" />`
934 /// // tags do not contain the attribute `disabled=""` and are thus toggleable by
935 /// // browser users.
936 /// assert_eq!(
937 /// to_html_with_options(
938 /// "* [x] y.",
939 /// &Options {
940 /// parse: ParseOptions::gfm(),
941 /// compile: CompileOptions {
942 /// gfm_task_list_item_checkable: true,
943 /// ..CompileOptions::gfm()
944 /// }
945 /// }
946 /// )?,
947 /// "<ul>\n<li><input type=\"checkbox\" checked=\"\" /> y.</li>\n</ul>"
948 /// );
949 /// # Ok(())
950 /// # }
951 /// ```
952 pub gfm_task_list_item_checkable: bool,
953
954 /// Whether to support the GFM tagfilter.
955 ///
956 /// This option does nothing if `allow_dangerous_html` is not turned on.
957 /// The default is `false`, which does not apply the GFM tagfilter to HTML.
958 /// Pass `true` for output that is a bit closer to GitHub’s actual output.
959 ///
960 /// The tagfilter is kinda weird and kinda useless.
961 /// The tag filter is a naïve attempt at XSS protection.
962 /// You should use a proper HTML sanitizing algorithm instead.
963 ///
964 /// ## Examples
965 ///
966 /// ```
967 /// use core_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
968 /// # fn main() -> Result<(), core_markdown::message::Message> {
969 ///
970 /// // With `allow_dangerous_html`, `markdown-rs` passes HTML through untouched:
971 /// assert_eq!(
972 /// to_html_with_options(
973 /// "<iframe>",
974 /// &Options {
975 /// parse: ParseOptions::gfm(),
976 /// compile: CompileOptions {
977 /// allow_dangerous_html: true,
978 /// ..CompileOptions::default()
979 /// }
980 /// }
981 /// )?,
982 /// "<iframe>"
983 /// );
984 ///
985 /// // Pass `gfm_tagfilter: true` to make some of that safe:
986 /// assert_eq!(
987 /// to_html_with_options(
988 /// "<iframe>",
989 /// &Options {
990 /// parse: ParseOptions::gfm(),
991 /// compile: CompileOptions {
992 /// allow_dangerous_html: true,
993 /// gfm_tagfilter: true,
994 /// ..CompileOptions::default()
995 /// }
996 /// }
997 /// )?,
998 /// "<iframe>"
999 /// );
1000 /// # Ok(())
1001 /// # }
1002 /// ```
1003 ///
1004 /// ## References
1005 ///
1006 /// * [*§ 6.1 Disallowed Raw HTML (extension)* in GFM](https://github.github.com/gfm/#disallowed-raw-html-extension-)
1007 /// * [`cmark-gfm#extensions/tagfilter.c`](https://github.com/github/cmark-gfm/blob/master/extensions/tagfilter.c)
1008 pub gfm_tagfilter: bool,
1009
1010 /// Whether to add unique `id` attributes to headings for in-page links
1011 /// and tables of contents.
1012 ///
1013 /// IDs use the same text normalization and duplicate suffixes as
1014 /// [`mdast::Headings::extract`][crate::mdast::Headings::extract].
1015 ///
1016 /// The default is `false`.
1017 pub heading_ids: bool,
1018}
1019
1020impl CompileOptions {
1021 /// GFM.
1022 ///
1023 /// GFM stands for **GitHub flavored markdown**.
1024 /// On the compilation side, GFM turns on the GFM tag filter.
1025 /// The tagfilter is useless, but it’s included here for consistency, and
1026 /// this method exists for parity to parse options.
1027 ///
1028 /// For more information, see the GFM specification:
1029 /// <https://github.github.com/gfm/>.
1030 pub fn gfm() -> Self {
1031 Self {
1032 gfm_tagfilter: true,
1033 ..Self::default()
1034 }
1035 }
1036}
1037
1038/// Configuration that describes how to parse from markdown.
1039///
1040/// You can use this:
1041///
1042/// * To control what markdown constructs are turned on and off
1043/// * To control some of those constructs
1044/// * To add support for certain programming languages when parsing MDX
1045///
1046/// In most cases, you will want to use the default trait or `gfm` method.
1047///
1048/// ## Examples
1049///
1050/// ```
1051/// use core_markdown::ParseOptions;
1052/// # fn main() {
1053///
1054/// // Use the default trait to parse markdown according to `CommonMark`:
1055/// let commonmark = ParseOptions::default();
1056///
1057/// // Use the `gfm` method to parse markdown according to GFM:
1058/// let gfm = ParseOptions::gfm();
1059/// # }
1060/// ```
1061#[allow(clippy::struct_excessive_bools)]
1062#[cfg_attr(
1063 feature = "serde",
1064 derive(serde::Serialize, serde::Deserialize),
1065 serde(default, rename_all = "camelCase")
1066)]
1067pub struct ParseOptions {
1068 // Note: when adding fields, don’t forget to add them to `fmt::Debug` below.
1069 /// Which constructs to enable and disable.
1070 ///
1071 /// The default is to follow `CommonMark`.
1072 ///
1073 /// ## Examples
1074 ///
1075 /// ```
1076 /// use core_markdown::{to_html, to_html_with_options, Constructs, Options, ParseOptions};
1077 /// # fn main() -> Result<(), core_markdown::message::Message> {
1078 ///
1079 /// // `markdown-rs` follows CommonMark by default:
1080 /// assert_eq!(
1081 /// to_html(" indented code?"),
1082 /// "<pre><code>indented code?\n</code></pre>"
1083 /// );
1084 ///
1085 /// // Pass `constructs` to choose what to enable and disable:
1086 /// assert_eq!(
1087 /// to_html_with_options(
1088 /// " indented code?",
1089 /// &Options {
1090 /// parse: ParseOptions {
1091 /// constructs: Constructs {
1092 /// code_indented: false,
1093 /// ..Constructs::default()
1094 /// },
1095 /// ..ParseOptions::default()
1096 /// },
1097 /// ..Options::default()
1098 /// }
1099 /// )?,
1100 /// "<p>indented code?</p>"
1101 /// );
1102 /// # Ok(())
1103 /// # }
1104 /// ```
1105 #[cfg_attr(feature = "serde", serde(default))]
1106 pub constructs: Constructs,
1107
1108 /// Whether to parse a language prefix, such as `ruby:`, before inline code.
1109 ///
1110 /// When enabled, the prefix is removed from the surrounding text and
1111 /// stored as the inline code node's `lang` value. The default is `false`.
1112 #[cfg_attr(feature = "serde", serde(default))]
1113 pub inline_code_info: bool,
1114
1115 /// Whether indented type 6 and type 7 HTML blocks continue across blank
1116 /// lines when later content keeps a consistent indentation.
1117 ///
1118 /// The default is `false`, which follows `CommonMark`'s blank-line rule.
1119 #[cfg_attr(feature = "serde", serde(default))]
1120 pub html_block_blank_lines: bool,
1121
1122 /// Whether `:` is allowed in HTML tag names for namespace prefixes such
1123 /// as `svg:circle`.
1124 ///
1125 /// The default is `false` to preserve `CommonMark` parsing of text such as
1126 /// `<m:abc>`.
1127 #[cfg_attr(feature = "serde", serde(default))]
1128 pub html_tag_namespaces: bool,
1129
1130 /// Whether to support GFM strikethrough with a single tilde
1131 ///
1132 /// This option does nothing if `gfm_strikethrough` is not turned on in
1133 /// `constructs`.
1134 /// This option does not affect strikethrough with double tildes.
1135 ///
1136 /// The default is `true`, which follows how markdown on `github.com`
1137 /// works, as strikethrough with single tildes is supported.
1138 /// Pass `false`, to follow the GFM spec more strictly, by not allowing
1139 /// strikethrough with single tildes.
1140 ///
1141 /// ## Examples
1142 ///
1143 /// ```
1144 /// use core_markdown::{to_html_with_options, Constructs, Options, ParseOptions};
1145 /// # fn main() -> Result<(), core_markdown::message::Message> {
1146 ///
1147 /// // `markdown-rs` supports single tildes by default:
1148 /// assert_eq!(
1149 /// to_html_with_options(
1150 /// "~a~",
1151 /// &Options {
1152 /// parse: ParseOptions {
1153 /// constructs: Constructs::gfm(),
1154 /// ..ParseOptions::default()
1155 /// },
1156 /// ..Options::default()
1157 /// }
1158 /// )?,
1159 /// "<p><del>a</del></p>"
1160 /// );
1161 ///
1162 /// // Pass `gfm_strikethrough_single_tilde: false` to turn that off:
1163 /// assert_eq!(
1164 /// to_html_with_options(
1165 /// "~a~",
1166 /// &Options {
1167 /// parse: ParseOptions {
1168 /// constructs: Constructs::gfm(),
1169 /// gfm_strikethrough_single_tilde: false,
1170 /// ..ParseOptions::default()
1171 /// },
1172 /// ..Options::default()
1173 /// }
1174 /// )?,
1175 /// "<p>~a~</p>"
1176 /// );
1177 /// # Ok(())
1178 /// # }
1179 /// ```
1180 #[cfg_attr(feature = "serde", serde(default))]
1181 pub gfm_strikethrough_single_tilde: bool,
1182
1183 /// Whether to support math (text) with a single dollar
1184 ///
1185 /// This option does nothing if `math_text` is not turned on in
1186 /// `constructs`.
1187 /// This option does not affect math (text) with two or more dollars.
1188 ///
1189 /// The default is `true`, which is more close to how code (text) and
1190 /// Pandoc work, as it allows math with a single dollar to form.
1191 /// However, single dollars can interfere with “normal” dollars in text.
1192 /// Pass `false`, to only allow math (text) to form when two or more
1193 /// dollars are used.
1194 /// If you pass `false`, you can still use two or more dollars for text
1195 /// math.
1196 ///
1197 /// ## Examples
1198 ///
1199 /// ```
1200 /// use core_markdown::{to_html_with_options, Constructs, Options, ParseOptions};
1201 /// # fn main() -> Result<(), core_markdown::message::Message> {
1202 ///
1203 /// // `markdown-rs` supports single dollars by default:
1204 /// assert_eq!(
1205 /// to_html_with_options(
1206 /// "$a$",
1207 /// &Options {
1208 /// parse: ParseOptions {
1209 /// constructs: Constructs {
1210 /// math_text: true,
1211 /// ..Constructs::default()
1212 /// },
1213 /// ..ParseOptions::default()
1214 /// },
1215 /// ..Options::default()
1216 /// }
1217 /// )?,
1218 /// "<p><code class=\"language-math math-inline\">a</code></p>"
1219 /// );
1220 ///
1221 /// // Pass `math_text_single_dollar: false` to turn that off:
1222 /// assert_eq!(
1223 /// to_html_with_options(
1224 /// "$a$",
1225 /// &Options {
1226 /// parse: ParseOptions {
1227 /// constructs: Constructs {
1228 /// math_text: true,
1229 /// ..Constructs::default()
1230 /// },
1231 /// math_text_single_dollar: false,
1232 /// ..ParseOptions::default()
1233 /// },
1234 /// ..Options::default()
1235 /// }
1236 /// )?,
1237 /// "<p>$a$</p>"
1238 /// );
1239 /// # Ok(())
1240 /// # }
1241 /// ```
1242 #[cfg_attr(feature = "serde", serde(default))]
1243 pub math_text_single_dollar: bool,
1244
1245 /// Function to parse expressions with.
1246 ///
1247 /// This function can be used to add support for arbitrary programming
1248 /// languages within expressions.
1249 ///
1250 /// It only makes sense to pass this when compiling to a syntax tree
1251 /// with [`to_mdast()`][crate::to_mdast()].
1252 ///
1253 /// For an example that adds support for JavaScript with SWC, see
1254 /// `tests/test_utils/mod.rs`.
1255 #[cfg_attr(feature = "serde", serde(skip))]
1256 pub mdx_expression_parse: Option<Box<MdxExpressionParse>>,
1257
1258 /// Function to parse ESM with.
1259 ///
1260 /// This function can be used to add support for arbitrary programming
1261 /// languages within ESM blocks, however, the keywords (`export`,
1262 /// `import`) are currently hardcoded JavaScript-specific.
1263 ///
1264 /// > 👉 **Note**: please raise an issue if you’re interested in working on
1265 /// > MDX that is aware of, say, Rust, or other programming languages.
1266 ///
1267 /// It only makes sense to pass this when compiling to a syntax tree
1268 /// with [`to_mdast()`][crate::to_mdast()].
1269 ///
1270 /// For an example that adds support for JavaScript with SWC, see
1271 /// `tests/test_utils/mod.rs`.
1272 #[cfg_attr(feature = "serde", serde(skip))]
1273 pub mdx_esm_parse: Option<Box<MdxEsmParse>>,
1274 // Note: when adding fields, don’t forget to add them to `fmt::Debug` below.
1275}
1276
1277impl fmt::Debug for ParseOptions {
1278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1279 f.debug_struct("ParseOptions")
1280 .field("constructs", &self.constructs)
1281 .field("inline_code_info", &self.inline_code_info)
1282 .field("html_block_blank_lines", &self.html_block_blank_lines)
1283 .field("html_tag_namespaces", &self.html_tag_namespaces)
1284 .field(
1285 "gfm_strikethrough_single_tilde",
1286 &self.gfm_strikethrough_single_tilde,
1287 )
1288 .field("math_text_single_dollar", &self.math_text_single_dollar)
1289 .field(
1290 "mdx_expression_parse",
1291 &self.mdx_expression_parse.as_ref().map(|_d| "[Function]"),
1292 )
1293 .field(
1294 "mdx_esm_parse",
1295 &self.mdx_esm_parse.as_ref().map(|_d| "[Function]"),
1296 )
1297 .finish()
1298 }
1299}
1300
1301impl Default for ParseOptions {
1302 /// `CommonMark` defaults.
1303 fn default() -> Self {
1304 Self {
1305 constructs: Constructs::default(),
1306 inline_code_info: false,
1307 html_block_blank_lines: false,
1308 html_tag_namespaces: false,
1309 gfm_strikethrough_single_tilde: true,
1310 math_text_single_dollar: true,
1311 mdx_expression_parse: None,
1312 mdx_esm_parse: None,
1313 }
1314 }
1315}
1316
1317impl ParseOptions {
1318 /// GFM.
1319 ///
1320 /// GFM stands for GitHub flavored markdown.
1321 /// GFM extends `CommonMark` and adds support for autolink literals,
1322 /// footnotes, strikethrough, tables, and tasklists.
1323 ///
1324 /// For more information, see the GFM specification:
1325 /// <https://github.github.com/gfm/>
1326 pub fn gfm() -> Self {
1327 Self {
1328 constructs: Constructs::gfm(),
1329 ..Self::default()
1330 }
1331 }
1332
1333 /// MDX.
1334 ///
1335 /// This turns on `CommonMark`, turns off some conflicting constructs
1336 /// (autolinks, code (indented), and HTML), and turns on MDX (ESM,
1337 /// expressions, and JSX).
1338 ///
1339 /// For more information, see the MDX website:
1340 /// <https://mdxjs.com>.
1341 ///
1342 /// > 👉 **Note**: to support ESM, you *must* pass
1343 /// > [`mdx_esm_parse`][MdxEsmParse] in [`ParseOptions`][] too.
1344 /// > Otherwise, ESM is treated as normal markdown.
1345 /// >
1346 /// > You *can* pass
1347 /// > [`mdx_expression_parse`][MdxExpressionParse]
1348 /// > to parse expressions according to a certain grammar (typically, a
1349 /// > programming language).
1350 /// > Otherwise, expressions are parsed with a basic algorithm that only
1351 /// > cares about braces.
1352 pub fn mdx() -> Self {
1353 Self {
1354 constructs: Constructs::mdx(),
1355 ..Self::default()
1356 }
1357 }
1358}
1359
1360/// Configuration that describes how to parse from markdown and compile to
1361/// HTML.
1362///
1363/// In most cases, you will want to use the default trait or `gfm` method.
1364///
1365/// ## Examples
1366///
1367/// ```
1368/// use core_markdown::Options;
1369/// # fn main() {
1370///
1371/// // Use the default trait to compile markdown to HTML according to `CommonMark`:
1372/// let commonmark = Options::default();
1373///
1374/// // Use the `gfm` method to compile markdown to HTML according to GFM:
1375/// let gfm = Options::gfm();
1376/// # }
1377/// ```
1378#[allow(clippy::struct_excessive_bools)]
1379#[derive(Debug, Default)]
1380#[cfg_attr(
1381 feature = "serde",
1382 derive(serde::Serialize, serde::Deserialize),
1383 serde(default)
1384)]
1385pub struct Options {
1386 /// Configuration that describes how to parse from markdown.
1387 pub parse: ParseOptions,
1388 /// Configuration that describes how to compile to HTML.
1389 pub compile: CompileOptions,
1390}
1391
1392impl Options {
1393 /// GFM.
1394 ///
1395 /// GFM stands for GitHub flavored markdown.
1396 /// GFM extends `CommonMark` and adds support for autolink literals,
1397 /// footnotes, strikethrough, tables, and tasklists.
1398 /// On the compilation side, GFM turns on the GFM tag filter.
1399 /// The tagfilter is useless, but it’s included here for consistency.
1400 ///
1401 /// For more information, see the GFM specification:
1402 /// <https://github.github.com/gfm/>
1403 pub fn gfm() -> Self {
1404 Self {
1405 parse: ParseOptions::gfm(),
1406 compile: CompileOptions::gfm(),
1407 }
1408 }
1409}
1410
1411#[cfg(test)]
1412#[path = "configuration/tests.rs"]
1413mod tests;