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