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