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