Skip to main content

socketry_markdown/
configuration.rs

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