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 &amp; 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 ![b](c) 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    ///         "![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)",
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    ///         "![](javascript:alert(1))",
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, &lt;i&gt;venus&lt;/i&gt;!</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    ///     "&lt;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}