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 & 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  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 /// "",
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 /// ")",
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, <i>venus</i>!</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 /// "<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;