comrak/parser/options.rs
1//! Configuration for the parser and renderer. Extensions affect both.
2
3#[cfg(feature = "bon")]
4use bon::Builder;
5use std::collections::HashMap;
6use std::fmt::{self, Debug, Formatter};
7use std::panic::RefUnwindSafe;
8use std::str;
9use std::sync::Arc;
10
11use crate::adapters::{CodefenceRendererAdapter, HeadingAdapter, SyntaxHighlighterAdapter};
12use crate::parser::ResolvedReference;
13
14#[derive(Default, Debug, Clone)]
15#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
16/// Umbrella options struct.
17pub struct Options<'c> {
18 /// Enable CommonMark extensions.
19 pub extension: Extension<'c>,
20
21 /// Configure parse-time options.
22 pub parse: Parse<'c>,
23
24 /// Configure render-time options.
25 pub render: Render,
26}
27
28#[derive(Default, Debug, Clone)]
29#[cfg_attr(feature = "bon", derive(Builder))]
30#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
31/// Options to select extensions.
32pub struct Extension<'c> {
33 /// Enables the
34 /// [strikethrough extension](https://github.github.com/gfm/#strikethrough-extension-)
35 /// from the GFM spec.
36 ///
37 /// ```rust
38 /// # use comrak::{markdown_to_html, Options};
39 /// let mut options = Options::default();
40 /// options.extension.strikethrough = true;
41 /// assert_eq!(markdown_to_html("Hello ~world~ there.\n", &options),
42 /// "<p>Hello <del>world</del> there.</p>\n");
43 /// ```
44 #[cfg_attr(feature = "bon", builder(default))]
45 pub strikethrough: bool,
46
47 /// Enables the
48 /// [tagfilter extension](https://github.github.com/gfm/#disallowed-raw-html-extension-)
49 /// from the GFM spec.
50 ///
51 /// ```rust
52 /// # use comrak::{markdown_to_html, Options};
53 /// let mut options = Options::default();
54 /// options.extension.tagfilter = true;
55 /// options.render.r#unsafe = true;
56 /// assert_eq!(markdown_to_html("Hello <xmp>.\n\n<xmp>", &options),
57 /// "<p>Hello <xmp>.</p>\n<xmp>\n");
58 /// ```
59 #[cfg_attr(feature = "bon", builder(default))]
60 pub tagfilter: bool,
61
62 /// Enables the [table extension](https://github.github.com/gfm/#tables-extension-)
63 /// from the GFM spec.
64 ///
65 /// ```rust
66 /// # use comrak::{markdown_to_html, Options};
67 /// let mut options = Options::default();
68 /// options.extension.table = true;
69 /// assert_eq!(markdown_to_html("| a | b |\n|---|---|\n| c | d |\n", &options),
70 /// "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n\
71 /// <tbody>\n<tr>\n<td>c</td>\n<td>d</td>\n</tr>\n</tbody>\n</table>\n");
72 /// ```
73 #[cfg_attr(feature = "bon", builder(default))]
74 pub table: bool,
75
76 /// Enables the [autolink extension](https://github.github.com/gfm/#autolinks-extension-)
77 /// from the GFM spec.
78 ///
79 /// ```rust
80 /// # use comrak::{markdown_to_html, Options};
81 /// let mut options = Options::default();
82 /// options.extension.autolink = true;
83 /// assert_eq!(markdown_to_html("Hello www.github.com.\n", &options),
84 /// "<p>Hello <a href=\"http://www.github.com\">www.github.com</a>.</p>\n");
85 /// ```
86 #[cfg_attr(feature = "bon", builder(default))]
87 pub autolink: bool,
88
89 /// Enables the
90 /// [task list items extension](https://github.github.com/gfm/#task-list-items-extension-)
91 /// from the GFM spec.
92 ///
93 /// Note that the spec does not define the precise output, so only the bare essentials are
94 /// rendered.
95 ///
96 /// ```rust
97 /// # use comrak::{markdown_to_html, Options};
98 /// let mut options = Options::default();
99 /// options.extension.tasklist = true;
100 /// options.render.r#unsafe = true;
101 /// assert_eq!(markdown_to_html("* [x] Done\n* [ ] Not done\n", &options),
102 /// "<ul>\n<li><input type=\"checkbox\" checked=\"\" disabled=\"\" /> Done</li>\n\
103 /// <li><input type=\"checkbox\" disabled=\"\" /> Not done</li>\n</ul>\n");
104 /// ```
105 #[cfg_attr(feature = "bon", builder(default))]
106 pub tasklist: bool,
107
108 /// Enables the superscript Comrak extension.
109 ///
110 /// ```rust
111 /// # use comrak::{markdown_to_html, Options};
112 /// let mut options = Options::default();
113 /// options.extension.superscript = true;
114 /// assert_eq!(markdown_to_html("e = mc^2^.\n", &options),
115 /// "<p>e = mc<sup>2</sup>.</p>\n");
116 /// ```
117 #[cfg_attr(feature = "bon", builder(default))]
118 pub superscript: bool,
119
120 /// Enables the header IDs Comrak extension, with the given ID prefix.
121 ///
122 /// When set, each heading gains an anchor element with an `id` attribute
123 /// formed by prefixing the slugified heading text. This is useful for
124 /// namespacing heading anchors to avoid collisions when rendered Markdown
125 /// is embedded alongside other content on a page (e.g. GitHub uses the
126 /// prefix `"user-content-"` for this purpose).
127 ///
128 /// ```rust
129 /// # use comrak::{markdown_to_html, Options};
130 /// let mut options = Options::default();
131 /// options.extension.header_id_prefix = Some("user-content-".to_string());
132 /// assert_eq!(markdown_to_html("# README\n", &options),
133 /// "<h1 id=\"user-content-readme\">README<a href=\"#readme\" aria-label=\"Link to heading 'README'\" data-heading-content=\"README\" class=\"anchor\"></a></h1>\n");
134 /// ```
135 pub header_id_prefix: Option<String>,
136
137 /// When enabled alongside [`header_id_prefix`](#structfield.header_id_prefix), the header ID
138 /// prefix is also applied to the `href` anchor in the generated link.
139 ///
140 /// Has no effect if `header_id_prefix` is `None`.
141 ///
142 /// ```rust
143 /// # use comrak::{markdown_to_html, Options};
144 /// let mut options = Options::default();
145 /// options.extension.header_id_prefix = Some("user-content-".to_string());
146 /// options.extension.header_id_prefix_in_href = true;
147 /// assert_eq!(markdown_to_html("# README\n", &options),
148 /// "<h1 id=\"user-content-readme\">README<a href=\"#user-content-readme\" aria-label=\"Link to heading 'README'\" data-heading-content=\"README\" class=\"anchor\"></a></h1>\n");
149 /// ```
150 #[cfg_attr(feature = "bon", builder(default))]
151 pub header_id_prefix_in_href: bool,
152
153 /// Enables the footnotes extension per `cmark-gfm`.
154 ///
155 /// For usage, see `src/tests.rs`. The extension is modelled after
156 /// [Kramdown](https://kramdown.gettalong.org/syntax.html#footnotes).
157 ///
158 /// ```rust
159 /// # use comrak::{markdown_to_html, Options};
160 /// let mut options = Options::default();
161 /// options.extension.footnotes = true;
162 /// assert_eq!(markdown_to_html("Hi[^x].\n\n[^x]: A greeting.\n", &options),
163 /// "<p>Hi<sup class=\"footnote-ref\"><a href=\"#fn-x\" id=\"fnref-x\" data-footnote-ref>1</a></sup>.</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-x\">\n<p>A greeting. <a href=\"#fnref-x\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n</ol>\n</section>\n");
164 /// ```
165 #[cfg_attr(feature = "bon", builder(default))]
166 pub footnotes: bool,
167
168 /// Enables the inline footnotes extension.
169 ///
170 /// Allows inline footnote syntax `^[content]` where the content can include
171 /// inline markup. Inline footnotes are automatically converted to regular
172 /// footnotes with auto-generated names and share the same numbering sequence.
173 ///
174 /// Requires `footnotes` to be enabled as well.
175 ///
176 /// ```rust
177 /// # use comrak::{markdown_to_html, Options};
178 /// let mut options = Options::default();
179 /// options.extension.footnotes = true;
180 /// options.extension.inline_footnotes = true;
181 /// assert_eq!(markdown_to_html("Hi^[An inline note].\n", &options),
182 /// "<p>Hi<sup class=\"footnote-ref\"><a href=\"#fn-__inline_1\" id=\"fnref-__inline_1\" data-footnote-ref>1</a></sup>.</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-__inline_1\">\n<p>An inline note <a href=\"#fnref-__inline_1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n</ol>\n</section>\n");
183 /// ```
184 #[cfg_attr(feature = "bon", builder(default))]
185 pub inline_footnotes: bool,
186
187 /// Enables the description lists extension.
188 ///
189 /// Each term must be defined in one paragraph, followed by a blank line,
190 /// and then by the details. Details begins with a colon.
191 ///
192 /// Not (yet) compatible with render.sourcepos.
193 ///
194 /// ```markdown
195 /// First term
196 ///
197 /// : Details for the **first term**
198 ///
199 /// Second term
200 ///
201 /// : Details for the **second term**
202 ///
203 /// More details in second paragraph.
204 /// ```
205 ///
206 /// ```rust
207 /// # use comrak::{markdown_to_html, Options};
208 /// let mut options = Options::default();
209 /// options.extension.description_lists = true;
210 /// assert_eq!(markdown_to_html("Term\n\n: Definition", &options),
211 /// "<dl>\n<dt>Term</dt>\n<dd>\n<p>Definition</p>\n</dd>\n</dl>\n");
212 /// ```
213 #[cfg_attr(feature = "bon", builder(default))]
214 pub description_lists: bool,
215
216 /// Enables the front matter extension.
217 ///
218 /// Front matter, which begins with the delimiter string at the beginning of the file and ends
219 /// at the end of the next line that contains only the delimiter, is passed through unchanged
220 /// in markdown output and omitted from HTML output.
221 ///
222 /// ```markdown
223 /// ---
224 /// layout: post
225 /// title: Formatting Markdown with Comrak
226 /// ---
227 ///
228 /// # Shorter Title
229 ///
230 /// etc.
231 /// ```
232 ///
233 /// ```rust
234 /// # use comrak::{markdown_to_html, Options};
235 /// let mut options = Options::default();
236 /// options.extension.front_matter_delimiter = Some("---".to_owned());
237 /// assert_eq!(
238 /// markdown_to_html("---\nlayout: post\n---\nText\n", &options),
239 /// markdown_to_html("Text\n", &Options::default()));
240 /// ```
241 ///
242 /// ```rust
243 /// # use comrak::{format_commonmark, Arena, Options};
244 /// use comrak::parse_document;
245 /// let mut options = Options::default();
246 /// options.extension.front_matter_delimiter = Some("---".to_owned());
247 /// let arena = Arena::new();
248 /// let input = "---\nlayout: post\n---\nText\n";
249 /// let root = parse_document(&arena, input, &options);
250 /// let mut buf = String::new();
251 /// format_commonmark(&root, &options, &mut buf);
252 /// assert_eq!(buf, input);
253 /// ```
254 pub front_matter_delimiter: Option<String>,
255
256 /// Enables the multiline block quote extension.
257 ///
258 /// Place `>>>` before and after text to make it into
259 /// a block quote.
260 ///
261 /// ```markdown
262 /// Paragraph one
263 ///
264 /// >>>
265 /// Paragraph two
266 ///
267 /// - one
268 /// - two
269 /// >>>
270 /// ```
271 ///
272 /// ```rust
273 /// # use comrak::{markdown_to_html, Options};
274 /// let mut options = Options::default();
275 /// options.extension.multiline_block_quotes = true;
276 /// assert_eq!(markdown_to_html(">>>\nparagraph\n>>>", &options),
277 /// "<blockquote>\n<p>paragraph</p>\n</blockquote>\n");
278 /// ```
279 #[cfg_attr(feature = "bon", builder(default))]
280 pub multiline_block_quotes: bool,
281
282 /// Enables GitHub style alerts
283 ///
284 /// ```md
285 /// > [!note]
286 /// > Something of note
287 /// ```
288 ///
289 /// ```rust
290 /// # use comrak::{markdown_to_html, Options};
291 /// let mut options = Options::default();
292 /// options.extension.alerts = true;
293 /// assert_eq!(markdown_to_html("> [!note]\n> Something of note", &options),
294 /// "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\">Note</p>\n<p>Something of note</p>\n</div>\n");
295 /// ```
296 #[cfg_attr(feature = "bon", builder(default))]
297 pub alerts: bool,
298
299 /// Enables math using dollar syntax.
300 ///
301 /// ```markdown
302 /// Inline math $1 + 2$ and display math $$x + y$$
303 ///
304 /// $$
305 /// x^2
306 /// $$
307 /// ```
308 ///
309 /// ```rust
310 /// # use comrak::{markdown_to_html, Options};
311 /// let mut options = Options::default();
312 /// options.extension.math_dollars = true;
313 /// assert_eq!(markdown_to_html("$1 + 2$ and $$x = y$$", &options),
314 /// "<p><span data-math-style=\"inline\">1 + 2</span> and <span data-math-style=\"display\">x = y</span></p>\n");
315 /// assert_eq!(markdown_to_html("$$\nx^2\n$$\n", &options),
316 /// "<p><span data-math-style=\"display\">\nx^2\n</span></p>\n");
317 /// ```
318 #[cfg_attr(feature = "bon", builder(default))]
319 pub math_dollars: bool,
320
321 /// Enables math using LaTeX-style delimiters.
322 ///
323 /// ```markdown
324 /// Inline math \(1 + 2\) and display math \[x + y\]
325 /// ```
326 ///
327 /// ```rust
328 /// # use comrak::{markdown_to_html, Options};
329 /// let mut options = Options::default();
330 /// options.extension.math_latex = true;
331 /// assert_eq!(markdown_to_html("\\(1 + 2\\) and \\[x = y\\]", &options),
332 /// "<p><span data-math-style=\"inline\">1 + 2</span> and <span data-math-style=\"display\">x = y</span></p>\n");
333 /// ```
334 #[cfg_attr(feature = "bon", builder(default))]
335 pub math_latex: bool,
336
337 /// Enables math using code syntax.
338 ///
339 /// ````markdown
340 /// Inline math $`1 + 2`$
341 ///
342 /// ```math
343 /// x^2
344 /// ```
345 /// ````
346 ///
347 /// ```rust
348 /// # use comrak::{markdown_to_html, Options};
349 /// let mut options = Options::default();
350 /// options.extension.math_code = true;
351 /// assert_eq!(markdown_to_html("$`1 + 2`$", &options),
352 /// "<p><code data-math-style=\"inline\">1 + 2</code></p>\n");
353 /// assert_eq!(markdown_to_html("```math\nx^2\n```\n", &options),
354 /// "<pre><code class=\"language-math\" data-math-style=\"display\">x^2\n</code></pre>\n");
355 /// ```
356 #[cfg_attr(feature = "bon", builder(default))]
357 pub math_code: bool,
358
359 #[cfg(feature = "shortcodes")]
360 #[cfg_attr(docsrs, doc(cfg(feature = "shortcodes")))]
361 /// Phrases wrapped inside of ':' blocks will be replaced with emojis.
362 ///
363 /// ```rust
364 /// # use comrak::{markdown_to_html, Options};
365 /// let mut options = Options::default();
366 /// assert_eq!(markdown_to_html("Happy Friday! :smile:", &options),
367 /// "<p>Happy Friday! :smile:</p>\n");
368 ///
369 /// options.extension.shortcodes = true;
370 /// assert_eq!(markdown_to_html("Happy Friday! :smile:", &options),
371 /// "<p>Happy Friday! 😄</p>\n");
372 /// ```
373 #[cfg_attr(feature = "bon", builder(default))]
374 pub shortcodes: bool,
375
376 /// Enables wikilinks using title after pipe syntax
377 ///
378 /// ````markdown
379 /// [[url|link label]]
380 /// ````
381 ///
382 /// When both this option and [`wikilinks_title_before_pipe`][0] are enabled, this option takes
383 /// precedence.
384 ///
385 /// [0]: Self::wikilinks_title_before_pipe
386 ///
387 /// ```rust
388 /// # use comrak::{markdown_to_html, Options};
389 /// let mut options = Options::default();
390 /// options.extension.wikilinks_title_after_pipe = true;
391 /// assert_eq!(markdown_to_html("[[url|link label]]", &options),
392 /// "<p><a href=\"url\" data-wikilink=\"true\">link label</a></p>\n");
393 /// ```
394 #[cfg_attr(feature = "bon", builder(default))]
395 pub wikilinks_title_after_pipe: bool,
396
397 /// Enables wikilinks using title before pipe syntax
398 ///
399 /// ````markdown
400 /// [[link label|url]]
401 /// ````
402 /// When both this option and [`wikilinks_title_after_pipe`][0] are enabled,
403 /// [`wikilinks_title_after_pipe`][0] takes precedence.
404 ///
405 /// [0]: Self::wikilinks_title_after_pipe
406 ///
407 /// ```rust
408 /// # use comrak::{markdown_to_html, Options};
409 /// let mut options = Options::default();
410 /// options.extension.wikilinks_title_before_pipe = true;
411 /// assert_eq!(markdown_to_html("[[link label|url]]", &options),
412 /// "<p><a href=\"url\" data-wikilink=\"true\">link label</a></p>\n");
413 /// ```
414 #[cfg_attr(feature = "bon", builder(default))]
415 pub wikilinks_title_before_pipe: bool,
416
417 /// Enables underlines using double underscores
418 ///
419 /// ```md
420 /// __underlined text__
421 /// ```
422 ///
423 /// ```rust
424 /// # use comrak::{markdown_to_html, Options};
425 /// let mut options = Options::default();
426 /// options.extension.underline = true;
427 ///
428 /// assert_eq!(markdown_to_html("__underlined text__", &options),
429 /// "<p><u>underlined text</u></p>\n");
430 /// ```
431 #[cfg_attr(feature = "bon", builder(default))]
432 pub underline: bool,
433
434 /// Enables subscript text using single tildes.
435 ///
436 /// If the strikethrough option is also enabled, this overrides the single
437 /// tilde case to output subscript text.
438 ///
439 /// ```md
440 /// H~2~O
441 /// ```
442 ///
443 /// ```rust
444 /// # use comrak::{markdown_to_html, Options};
445 /// let mut options = Options::default();
446 /// options.extension.subscript = true;
447 ///
448 /// assert_eq!(markdown_to_html("H~2~O", &options),
449 /// "<p>H<sub>2</sub>O</p>\n");
450 /// ```
451 #[cfg_attr(feature = "bon", builder(default))]
452 pub subscript: bool,
453
454 /// Enables spoilers using double vertical bars
455 ///
456 /// ```md
457 /// Darth Vader is ||Luke's father||
458 /// ```
459 ///
460 /// ```rust
461 /// # use comrak::{markdown_to_html, Options};
462 /// let mut options = Options::default();
463 /// options.extension.spoiler = true;
464 ///
465 /// assert_eq!(markdown_to_html("Darth Vader is ||Luke's father||", &options),
466 /// "<p>Darth Vader is <span class=\"spoiler\">Luke's father</span></p>\n");
467 /// ```
468 #[cfg_attr(feature = "bon", builder(default))]
469 pub spoiler: bool,
470
471 /// Requires at least one space after a `>` character to generate a blockquote,
472 /// and restarts blockquote nesting across unique lines of input
473 ///
474 /// ```md
475 /// >implying implications
476 ///
477 /// > one
478 /// > > two
479 /// > three
480 /// ```
481 ///
482 /// ```rust
483 /// # use comrak::{markdown_to_html, Options};
484 /// let mut options = Options::default();
485 /// options.extension.greentext = true;
486 ///
487 /// assert_eq!(markdown_to_html(">implying implications", &options),
488 /// "<p>>implying implications</p>\n");
489 ///
490 /// assert_eq!(markdown_to_html("> one\n> > two\n> three", &options),
491 /// concat!(
492 /// "<blockquote>\n",
493 /// "<p>one</p>\n",
494 /// "<blockquote>\n<p>two</p>\n</blockquote>\n",
495 /// "<p>three</p>\n",
496 /// "</blockquote>\n"));
497 /// ```
498 #[cfg_attr(feature = "bon", builder(default))]
499 pub greentext: bool,
500
501 /// Wraps embedded image URLs using a function or custom trait object.
502 ///
503 /// ```rust
504 /// # use std::sync::Arc;
505 /// # use comrak::{markdown_to_html, Options};
506 /// let mut options = Options::default();
507 ///
508 /// options.extension.image_url_rewriter = Some(Arc::new(
509 /// |url: &str| format!("https://safe.example.com?url={}", url)
510 /// ));
511 ///
512 /// assert_eq!(markdown_to_html("", &options),
513 /// "<p><img src=\"https://safe.example.com?url=http://unsafe.example.com/bad.png\" alt=\"\" /></p>\n");
514 /// ```
515 #[cfg_attr(feature = "arbitrary", arbitrary(value = None))]
516 pub image_url_rewriter: Option<Arc<dyn URLRewriter + 'c>>,
517
518 /// Wraps link URLs using a function or custom trait object.
519 ///
520 /// ```rust
521 /// # use std::sync::Arc;
522 /// # use comrak::{markdown_to_html, Options};
523 /// let mut options = Options::default();
524 ///
525 /// options.extension.link_url_rewriter = Some(Arc::new(
526 /// |url: &str| format!("https://safe.example.com/norefer?url={}", url)
527 /// ));
528 ///
529 /// assert_eq!(markdown_to_html("[my link](http://unsafe.example.com/bad)", &options),
530 /// "<p><a href=\"https://safe.example.com/norefer?url=http://unsafe.example.com/bad\">my link</a></p>\n");
531 /// ```
532 #[cfg_attr(feature = "arbitrary", arbitrary(value = None))]
533 pub link_url_rewriter: Option<Arc<dyn URLRewriter + 'c>>,
534
535 /// Recognizes many emphasis that appear in CJK contexts but are not recognized by plain CommonMark.
536 ///
537 /// ```md
538 /// **この文は重要です。**但这句话并不重要。
539 /// ```
540 ///
541 /// ```rust
542 /// # use comrak::{markdown_to_html, Options};
543 /// let mut options = Options::default();
544 /// options.extension.cjk_friendly_emphasis = true;
545 ///
546 /// assert_eq!(markdown_to_html("**この文は重要です。**但这句话并不重要。", &options),
547 /// "<p><strong>この文は重要です。</strong>但这句话并不重要。</p>\n");
548 /// ```
549 #[cfg_attr(feature = "bon", builder(default))]
550 pub cjk_friendly_emphasis: bool,
551
552 /// Enables block scoped subscript that acts similar to a header.
553 ///
554 /// ```md
555 /// -# subtext
556 /// ```
557 ///
558 /// ```rust
559 /// # use comrak::{markdown_to_html, Options};
560 /// let mut options = Options::default();
561 /// options.extension.subtext = true;
562 ///
563 /// assert_eq!(markdown_to_html("-# subtext", &options),
564 /// "<p><sub>subtext</sub></p>\n");
565 /// ```
566 #[cfg_attr(feature = "bon", builder(default))]
567 pub subtext: bool,
568
569 /// Enables highlighting (mark) using `==`.
570 ///
571 /// ```md
572 /// Hey, ==this is important!==
573 /// ```
574 ///
575 /// ```rust
576 /// # use comrak::{markdown_to_html, Options};
577 /// let mut options = Options::default();
578 /// options.extension.highlight = true;
579 ///
580 /// assert_eq!(markdown_to_html("Hey, ==this is important!==", &options),
581 /// "<p>Hey, <mark>this is important!</mark></p>\n");
582 /// ```
583 #[cfg_attr(feature = "bon", builder(default))]
584 pub highlight: bool,
585
586 /// Enables inserted text using `++`.
587 ///
588 /// ```md
589 /// This is ++added text++
590 /// ```
591 ///
592 /// ```rust
593 /// # use comrak::{markdown_to_html, Options};
594 /// let mut options = Options::default();
595 /// options.extension.insert = true;
596 ///
597 /// assert_eq!(markdown_to_html("This is ++added text++", &options),
598 /// "<p>This is <ins>added text</ins></p>\n");
599 /// ```
600 #[cfg_attr(feature = "bon", builder(default))]
601 pub insert: bool,
602
603 #[cfg(feature = "phoenix_heex")]
604 #[cfg_attr(docsrs, doc(cfg(feature = "phoenix_heex")))]
605 /// Enables Phoenix HEEx template syntax support.
606 ///
607 /// Recognizes Phoenix HEEx directives, tags, and inline expressions.
608 ///
609 /// ```rust
610 /// # use comrak::{markdown_to_html, Options};
611 /// let mut options = Options::default();
612 /// options.extension.phoenix_heex = true;
613 /// ```
614 #[cfg_attr(feature = "bon", builder(default))]
615 pub phoenix_heex: bool,
616
617 /// Enables the container block directive extension.
618 ///
619 /// Container block directives are container blocks that start and end with `:::`.
620 /// The info string after the opening `:::` is used as the block type.
621 ///
622 /// ```md
623 /// :::warning
624 /// A paragraph.
625 ///
626 /// - item one
627 /// - item two
628 /// :::
629 /// ```
630 ///
631 /// ```rust
632 /// # use comrak::{markdown_to_html, Options};
633 /// let mut options = Options::default();
634 /// options.extension.block_directive = true;
635 ///
636 /// assert_eq!(markdown_to_html(":::warning\nparagraph\n:::", &options),
637 /// "<div class=\"warning\">\n<p>paragraph</p>\n</div>\n");
638 /// ```
639 #[cfg_attr(feature = "bon", builder(default))]
640 pub block_directive: bool,
641
642 /// Parse attributes in setext and ATX headers.
643 /// ```rust
644 /// # use comrak::{parse_document, Arena, Options, nodes::NodeValue};
645 /// let mut options = Options::default();
646 /// options.extension.header_attributes = true;
647 /// let arena = Arena::new();
648 /// let input = "## Catgirl {author=\"City Girl\"}\n";
649 /// let root = parse_document(&arena, input, &options);
650 /// for node in root.descendants() {
651 /// let ast = node.data();
652 /// if let NodeValue::Heading(_) = ast.value {
653 /// assert_eq!(ast.attrs.as_ref().unwrap().pairs,
654 /// &[("author".to_string(), "City Girl".to_string())]);
655 /// }
656 /// }
657 /// ```
658 #[cfg(feature = "attributes")]
659 #[cfg_attr(feature = "bon", builder(default))]
660 pub header_attributes: bool,
661
662 /// Parse attributes in fenced code blocks' info strings.
663 /// ```rust
664 /// # use comrak::{parse_document, Arena, Options, nodes::NodeValue};
665 /// let mut options = Options::default();
666 /// options.extension.fenced_code_attributes = true;
667 /// let arena = Arena::new();
668 /// let input = "```german {#beispel}\nÄhm... egal.\n```\n";
669 /// let root = parse_document(&arena, input, &options);
670 /// for node in root.descendants() {
671 /// let ast = node.data();
672 /// if let NodeValue::CodeBlock(_) = ast.value {
673 /// assert_eq!(ast.attrs.as_ref().unwrap().id,
674 /// Some("beispel".to_string()));
675 /// }
676 /// }
677 /// ```
678 #[cfg(feature = "attributes")]
679 #[cfg_attr(feature = "bon", builder(default))]
680 pub fenced_code_attributes: bool,
681
682 /// Parse attributes immediately following inline code spans.
683 /// ```rust
684 /// # use comrak::{parse_document, Arena, Options, nodes::NodeValue};
685 /// let mut options = Options::default();
686 /// options.extension.inline_code_attributes = true;
687 /// let arena = Arena::new();
688 /// let input = "More inline spans should be `syntax-highlighted`{.common-lisp}.";
689 /// let root = parse_document(&arena, input, &options);
690 /// for node in root.descendants() {
691 /// let ast = node.data();
692 /// if let NodeValue::Code(_) = ast.value {
693 /// assert_eq!(ast.attrs.as_ref().unwrap().classes,
694 /// &["common-lisp".to_string()]);
695 /// }
696 /// }
697 /// ```
698 #[cfg(feature = "attributes")]
699 #[cfg_attr(feature = "bon", builder(default))]
700 pub inline_code_attributes: bool,
701
702 /// Parse attributes immediately following links and images.
703 /// ```rust
704 /// # use comrak::{parse_document, Arena, Options, nodes::NodeValue};
705 /// let mut options = Options::default();
706 /// options.extension.link_attributes = true;
707 /// let arena = Arena::new();
708 /// let input = "For instance:\n\n{data-date=2012-04-03}\n";
709 /// let root = parse_document(&arena, input, &options);
710 /// for node in root.descendants() {
711 /// let ast = node.data();
712 /// if let NodeValue::Image(_) = ast.value {
713 /// assert_eq!(ast.attrs.as_ref().unwrap().pairs,
714 /// &[("data-date".to_string(), "2012-04-03".to_string())]);
715 /// }
716 /// }
717 /// ```
718 #[cfg(feature = "attributes")]
719 #[cfg_attr(feature = "bon", builder(default))]
720 pub link_attributes: bool,
721}
722
723impl Extension<'_> {
724 pub(crate) fn wikilinks(&self) -> Option<WikiLinksMode> {
725 match (
726 self.wikilinks_title_before_pipe,
727 self.wikilinks_title_after_pipe,
728 ) {
729 (false, false) => None,
730 (true, false) => Some(WikiLinksMode::TitleFirst),
731 (_, _) => Some(WikiLinksMode::UrlFirst),
732 }
733 }
734}
735
736#[non_exhaustive]
737#[derive(Debug, Clone, PartialEq, Eq, Copy)]
738#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
739/// Selects between wikilinks with the title first or the URL first.
740pub enum WikiLinksMode {
741 /// Indicates that the URL precedes the title. For example: `[[http://example.com|link
742 /// title]]`.
743 UrlFirst,
744
745 /// Indicates that the title precedes the URL. For example: `[[link title|http://example.com]]`.
746 TitleFirst,
747}
748
749/// Trait for link and image URL rewrite extensions.
750pub trait URLRewriter: RefUnwindSafe + Send + Sync {
751 /// Converts the given URL from Markdown to its representation when output as HTML.
752 fn to_html(&self, url: &str) -> String;
753}
754
755impl Debug for dyn URLRewriter + '_ {
756 fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
757 formatter.write_str("<dyn URLRewriter>")
758 }
759}
760
761impl<F> URLRewriter for F
762where
763 F: for<'a> Fn(&'a str) -> String,
764 F: RefUnwindSafe + Send + Sync,
765{
766 fn to_html(&self, url: &str) -> String {
767 self(url)
768 }
769}
770
771#[derive(Default, Clone, Debug)]
772#[cfg_attr(feature = "bon", derive(Builder))]
773#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
774/// Options for parser functions.
775pub struct Parse<'c> {
776 /// Punctuation (quotes, full-stops and hyphens) are converted into 'smart' punctuation.
777 ///
778 /// ```rust
779 /// # use comrak::{markdown_to_html, Options};
780 /// let mut options = Options::default();
781 /// assert_eq!(markdown_to_html("'Hello,' \"world\" ...", &options),
782 /// "<p>'Hello,' "world" ...</p>\n");
783 ///
784 /// options.parse.smart = true;
785 /// assert_eq!(markdown_to_html("'Hello,' \"world\" ...", &options),
786 /// "<p>‘Hello,’ “world” …</p>\n");
787 /// ```
788 #[cfg_attr(feature = "bon", builder(default))]
789 pub smart: bool,
790
791 /// The default info string for fenced code blocks.
792 ///
793 /// ```rust
794 /// # use comrak::{markdown_to_html, Options};
795 /// let mut options = Options::default();
796 /// assert_eq!(markdown_to_html("```\nfn hello();\n```\n", &options),
797 /// "<pre><code>fn hello();\n</code></pre>\n");
798 ///
799 /// options.parse.default_info_string = Some("rust".into());
800 /// assert_eq!(markdown_to_html("```\nfn hello();\n```\n", &options),
801 /// "<pre><code class=\"language-rust\">fn hello();\n</code></pre>\n");
802 /// ```
803 pub default_info_string: Option<String>,
804
805 /// Whether or not a simple `x` or `X` is used for tasklist or any other symbol is allowed.
806 #[cfg_attr(feature = "bon", builder(default))]
807 pub relaxed_tasklist_matching: bool,
808
809 /// Whether tasklist items can be parsed in table cells. At present, the
810 /// tasklist item must be the only content in the cell. Both tables and
811 /// tasklists much be enabled for this to work.
812 ///
813 /// ```rust
814 /// # use comrak::{markdown_to_html, Options};
815 /// let mut options = Options::default();
816 /// options.extension.table = true;
817 /// options.extension.tasklist = true;
818 /// assert_eq!(markdown_to_html("| val |\n| - |\n| [ ] |\n", &options),
819 /// "<table>\n<thead>\n<tr>\n<th>val</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>[ ]</td>\n</tr>\n</tbody>\n</table>\n");
820 ///
821 /// options.parse.tasklist_in_table = true;
822 /// assert_eq!(markdown_to_html("| val |\n| - |\n| [ ] |\n", &options),
823 /// "<table>\n<thead>\n<tr>\n<th>val</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>\n<input type=\"checkbox\" disabled=\"\" /> </td>\n</tr>\n</tbody>\n</table>\n");
824 /// ```
825 #[cfg_attr(feature = "bon", builder(default))]
826 pub tasklist_in_table: bool,
827
828 /// Relax parsing of autolinks, allow links to be detected inside brackets
829 /// and allow all url schemes. It is intended to allow a very specific type of autolink
830 /// detection, such as `[this http://and.com that]` or `{http://foo.com}`, on a best can basis.
831 ///
832 /// ```rust
833 /// # use comrak::{markdown_to_html, Options};
834 /// let mut options = Options::default();
835 /// options.extension.autolink = true;
836 /// assert_eq!(markdown_to_html("[https://foo.com]", &options),
837 /// "<p>[https://foo.com]</p>\n");
838 ///
839 /// options.parse.relaxed_autolinks = true;
840 /// assert_eq!(markdown_to_html("[https://foo.com]", &options),
841 /// "<p>[<a href=\"https://foo.com\">https://foo.com</a>]</p>\n");
842 /// ```
843 #[cfg_attr(feature = "bon", builder(default))]
844 pub relaxed_autolinks: bool,
845
846 /// Ignore setext headings in input.
847 ///
848 /// ```rust
849 /// # use comrak::{markdown_to_html, Options};
850 /// let mut options = Options::default();
851 /// let input = "setext heading\n---";
852 ///
853 /// assert_eq!(markdown_to_html(input, &options),
854 /// "<h2>setext heading</h2>\n");
855 ///
856 /// options.parse.ignore_setext = true;
857 /// assert_eq!(markdown_to_html(input, &options),
858 /// "<p>setext heading</p>\n<hr />\n");
859 /// ```
860 #[cfg_attr(feature = "bon", builder(default))]
861 pub ignore_setext: bool,
862
863 /// In case the parser encounters any potential links that have a broken
864 /// reference (e.g `[foo]` when there is no `[foo]: url` entry at the
865 /// bottom) the provided callback will be called with the reference name,
866 /// both in normalized form and unmodified, and the returned pair will be
867 /// used as the link destination and title if not [`None`].
868 ///
869 /// ```rust
870 /// # use std::{str, sync::Arc};
871 /// # use comrak::{markdown_to_html, options::BrokenLinkReference, Options, ResolvedReference};
872 /// let cb = |link_ref: BrokenLinkReference| match link_ref.normalized {
873 /// "foo" => Some(ResolvedReference {
874 /// url: "https://www.rust-lang.org/".to_string(),
875 /// title: "The Rust Language".to_string(),
876 /// }),
877 /// _ => None,
878 /// };
879 ///
880 /// let mut options = Options::default();
881 /// options.parse.broken_link_callback = Some(Arc::new(cb));
882 ///
883 /// let output = markdown_to_html(
884 /// "# Cool input!\nWow look at this cool [link][foo]. A [broken link] renders as text.",
885 /// &options,
886 /// );
887 ///
888 /// assert_eq!(output,
889 /// "<h1>Cool input!</h1>\n<p>Wow look at this cool \
890 /// <a href=\"https://www.rust-lang.org/\" title=\"The Rust Language\">link</a>. \
891 /// A [broken link] renders as text.</p>\n");
892 /// ```
893 #[cfg_attr(feature = "arbitrary", arbitrary(default))]
894 pub broken_link_callback: Option<Arc<dyn BrokenLinkCallback + 'c>>,
895
896 /// Leave footnote definitions in place in the document tree, rather than
897 /// reordering them to the end. This will also cause unreferenced footnote
898 /// definitions to remain in the tree, rather than being removed.
899 ///
900 /// Comrak's default formatters expect this option to be turned off, so use
901 /// with care if you use the default formatters.
902 ///
903 /// ```rust
904 /// # use comrak::{Arena, parse_document, Node, Options};
905 /// let mut options = Options::default();
906 /// options.extension.footnotes = true;
907 /// let arena = Arena::new();
908 /// let input = concat!(
909 /// "Remember burning a CD?[^cd]\n",
910 /// "\n",
911 /// "[^cd]: In the Old Days, a 4x burner was considered good.\n",
912 /// "\n",
913 /// "[^dvd]: And DVD-RWs? Those were something else.\n",
914 /// "\n",
915 /// "Me neither.",
916 /// );
917 ///
918 /// fn node_kinds<'a>(doc: Node<'a>) -> Vec<&'static str> {
919 /// doc.descendants().map(|n| n.data().value.xml_node_name()).collect()
920 /// }
921 ///
922 /// let root = parse_document(&arena, input, &options);
923 /// assert_eq!(
924 /// node_kinds(root),
925 /// &["document", "paragraph", "text", "footnote_reference", "paragraph", "text",
926 /// "footnote_definition", "paragraph", "text"],
927 /// );
928 ///
929 /// options.parse.leave_footnote_definitions = true;
930 ///
931 /// let root = parse_document(&arena, input, &options);
932 /// assert_eq!(
933 /// node_kinds(root),
934 /// &["document", "paragraph", "text", "footnote_reference", "footnote_definition",
935 /// "paragraph", "text", "footnote_definition", "paragraph", "text", "paragraph", "text"],
936 /// );
937 /// ```
938 #[cfg_attr(feature = "bon", builder(default))]
939 pub leave_footnote_definitions: bool,
940
941 /// Leave escaped characters in an `Escaped` node in the document tree.
942 ///
943 /// ```rust
944 /// # use comrak::{Arena, parse_document, Node, Options};
945 /// let mut options = Options::default();
946 /// let arena = Arena::new();
947 /// let input = "Notify user \\@example";
948 ///
949 /// fn node_kinds<'a>(doc: Node<'a>) -> Vec<&'static str> {
950 /// doc.descendants().map(|n| n.data().value.xml_node_name()).collect()
951 /// }
952 ///
953 /// let root = parse_document(&arena, input, &options);
954 /// assert_eq!(
955 /// node_kinds(root),
956 /// &["document", "paragraph", "text"],
957 /// );
958 ///
959 /// options.parse.escaped_char_spans = true;
960 /// let root = parse_document(&arena, input, &options);
961 /// assert_eq!(
962 /// node_kinds(root),
963 /// &["document", "paragraph", "text", "escaped", "text", "text"],
964 /// );
965 /// ```
966 ///
967 /// Note that enabling the `escaped_char_spans` render option will cause
968 /// this option to be enabled.
969 #[cfg_attr(feature = "bon", builder(default))]
970 pub escaped_char_spans: bool,
971
972 /// When enabled, the [`column`][crate::nodes::LineColumn::column] values in
973 /// [`Sourcepos`][crate::nodes::Sourcepos] are counted as Unicode characters
974 /// (i.e. `char`s) rather than as UTF-8 bytes.
975 ///
976 /// By default, column values follow cmark behaviour: each byte of a
977 /// multi-byte UTF-8 character counts as a separate column. Enabling this
978 /// option converts those byte-based columns to character-based columns after
979 /// parsing, so that a 3-byte character such as `好` occupies only one
980 /// column position instead of three.
981 ///
982 /// ```rust
983 /// # use comrak::{Arena, parse_document, Options};
984 /// let arena = Arena::new();
985 /// let mut options = Options::default();
986 ///
987 /// // Default (byte-based): "好" spans columns 1-3
988 /// let root = parse_document(&arena, "好", &options);
989 /// let sp = root.first_child().unwrap().data().sourcepos;
990 /// assert_eq!(sp.start.column, 1);
991 /// assert_eq!(sp.end.column, 3);
992 ///
993 /// // Char-based: "好" occupies only column 1
994 /// options.parse.sourcepos_chars = true;
995 /// let root = parse_document(&arena, "好", &options);
996 /// let sp = root.first_child().unwrap().data().sourcepos;
997 /// assert_eq!(sp.start.column, 1);
998 /// assert_eq!(sp.end.column, 1);
999 /// ```
1000 #[cfg_attr(feature = "bon", builder(default))]
1001 pub sourcepos_chars: bool,
1002}
1003
1004/// The type of the callback used when a reference link is encountered with no
1005/// matching reference.
1006///
1007/// The details of the broken reference are passed in the
1008/// [`BrokenLinkReference`] argument. If a [`ResolvedReference`] is returned, it
1009/// is used as the link; otherwise, no link is made and the reference text is
1010/// preserved in its entirety.
1011pub trait BrokenLinkCallback: RefUnwindSafe + Send + Sync {
1012 /// Potentially resolve a single broken link reference.
1013 fn resolve(&self, broken_link_reference: BrokenLinkReference) -> Option<ResolvedReference>;
1014}
1015
1016impl Debug for dyn BrokenLinkCallback + '_ {
1017 fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> {
1018 formatter.write_str("<dyn BrokenLinkCallback>")
1019 }
1020}
1021
1022impl<F> BrokenLinkCallback for F
1023where
1024 F: Fn(BrokenLinkReference) -> Option<ResolvedReference>,
1025 F: RefUnwindSafe + Send + Sync,
1026{
1027 fn resolve(&self, broken_link_reference: BrokenLinkReference) -> Option<ResolvedReference> {
1028 self(broken_link_reference)
1029 }
1030}
1031
1032/// Struct to the broken link callback, containing details on the link reference
1033/// which failed to find a match.
1034#[derive(Debug)]
1035pub struct BrokenLinkReference<'l> {
1036 /// The normalized reference link label. Unicode case folding is applied;
1037 /// see <https://github.com/commonmark/commonmark-spec/issues/695> for a
1038 /// discussion on the details of what this exactly means.
1039 pub normalized: &'l str,
1040
1041 /// The original text in the link label.
1042 pub original: &'l str,
1043}
1044
1045#[derive(Default, Debug, Clone, Copy)]
1046#[cfg_attr(feature = "bon", derive(Builder))]
1047#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1048/// Options for formatter functions.
1049pub struct Render {
1050 /// [Soft line breaks](http://spec.commonmark.org/0.27/#soft-line-breaks) in the input
1051 /// translate into hard line breaks in the output.
1052 ///
1053 /// ```rust
1054 /// # use comrak::{markdown_to_html, Options};
1055 /// let mut options = Options::default();
1056 /// assert_eq!(markdown_to_html("Hello.\nWorld.\n", &options),
1057 /// "<p>Hello.\nWorld.</p>\n");
1058 ///
1059 /// options.render.hardbreaks = true;
1060 /// assert_eq!(markdown_to_html("Hello.\nWorld.\n", &options),
1061 /// "<p>Hello.<br />\nWorld.</p>\n");
1062 /// ```
1063 #[cfg_attr(feature = "bon", builder(default))]
1064 pub hardbreaks: bool,
1065
1066 /// GitHub-style `<pre lang="xyz">` is used for fenced code blocks with info tags.
1067 ///
1068 /// ```rust
1069 /// # use comrak::{markdown_to_html, Options};
1070 /// let mut options = Options::default();
1071 /// assert_eq!(markdown_to_html("``` rust\nfn hello();\n```\n", &options),
1072 /// "<pre><code class=\"language-rust\">fn hello();\n</code></pre>\n");
1073 ///
1074 /// options.render.github_pre_lang = true;
1075 /// assert_eq!(markdown_to_html("``` rust\nfn hello();\n```\n", &options),
1076 /// "<pre lang=\"rust\"><code>fn hello();\n</code></pre>\n");
1077 /// ```
1078 #[cfg_attr(feature = "bon", builder(default))]
1079 pub github_pre_lang: bool,
1080
1081 /// Enable full info strings for code blocks
1082 ///
1083 /// ```rust
1084 /// # use comrak::{markdown_to_html, Options};
1085 /// let mut options = Options::default();
1086 /// assert_eq!(markdown_to_html("``` rust extra info\nfn hello();\n```\n", &options),
1087 /// "<pre><code class=\"language-rust\">fn hello();\n</code></pre>\n");
1088 ///
1089 /// options.render.full_info_string = true;
1090 /// let html = markdown_to_html("``` rust extra info\nfn hello();\n```\n", &options);
1091 /// eprintln!("{}", html);
1092 /// assert!(html.contains(r#"data-meta="extra info""#));
1093 /// ```
1094 #[cfg_attr(feature = "bon", builder(default))]
1095 pub full_info_string: bool,
1096
1097 /// The wrap column when outputting CommonMark.
1098 ///
1099 /// ```rust
1100 /// # use comrak::{Arena, parse_document, Options, format_commonmark};
1101 /// # fn main() {
1102 /// # let arena = Arena::new();
1103 /// let mut options = Options::default();
1104 /// let node = parse_document(&arena, "hello hello hello hello hello hello", &options);
1105 /// let mut output = String::new();
1106 /// format_commonmark(node, &options, &mut output).unwrap();
1107 /// assert_eq!(output,
1108 /// "hello hello hello hello hello hello\n");
1109 ///
1110 /// options.render.width = 20;
1111 /// let mut output = String::new();
1112 /// format_commonmark(node, &options, &mut output).unwrap();
1113 /// assert_eq!(output,
1114 /// "hello hello hello\nhello hello hello\n");
1115 /// # }
1116 /// ```
1117 #[cfg_attr(feature = "bon", builder(default))]
1118 pub width: usize,
1119
1120 /// Allow rendering of raw HTML and potentially dangerous links.
1121 ///
1122 /// ```rust
1123 /// # use comrak::{markdown_to_html, Options};
1124 /// let mut options = Options::default();
1125 /// let input = "<script>\nalert('xyz');\n</script>\n\n\
1126 /// Possibly <marquee>annoying</marquee>.\n\n\
1127 /// [Dangerous](javascript:alert(document.cookie)).\n\n\
1128 /// [Safe](http://commonmark.org).\n";
1129 ///
1130 /// assert_eq!(markdown_to_html(input, &options),
1131 /// "<!-- raw HTML omitted -->\n\
1132 /// <p>Possibly <!-- raw HTML omitted -->annoying<!-- raw HTML omitted -->.</p>\n\
1133 /// <p><a href=\"\">Dangerous</a>.</p>\n\
1134 /// <p><a href=\"http://commonmark.org\">Safe</a>.</p>\n");
1135 ///
1136 /// options.render.r#unsafe = true;
1137 /// assert_eq!(markdown_to_html(input, &options),
1138 /// "<script>\nalert(\'xyz\');\n</script>\n\
1139 /// <p>Possibly <marquee>annoying</marquee>.</p>\n\
1140 /// <p><a href=\"javascript:alert(document.cookie)\">Dangerous</a>.</p>\n\
1141 /// <p><a href=\"http://commonmark.org\">Safe</a>.</p>\n");
1142 /// ```
1143 #[cfg_attr(feature = "bon", builder(default))]
1144 pub r#unsafe: bool,
1145
1146 /// Escape raw HTML instead of clobbering it.
1147 /// ```rust
1148 /// # use comrak::{markdown_to_html, Options};
1149 /// let mut options = Options::default();
1150 /// let input = "<i>italic text</i>";
1151 ///
1152 /// assert_eq!(markdown_to_html(input, &options),
1153 /// "<p><!-- raw HTML omitted -->italic text<!-- raw HTML omitted --></p>\n");
1154 ///
1155 /// options.render.escape = true;
1156 /// assert_eq!(markdown_to_html(input, &options),
1157 /// "<p><i>italic text</i></p>\n");
1158 /// ```
1159 #[cfg_attr(feature = "bon", builder(default))]
1160 pub escape: bool,
1161
1162 /// Set the type of [bullet list marker](https://spec.commonmark.org/0.30/#bullet-list-marker) to use. Options are:
1163 ///
1164 /// * [`ListStyleType::Dash`] to use `-` (default)
1165 /// * [`ListStyleType::Plus`] to use `+`
1166 /// * [`ListStyleType::Star`] to use `*`
1167 ///
1168 /// ```rust
1169 /// # use comrak::{markdown_to_commonmark, Options, options::ListStyleType};
1170 /// let mut options = Options::default();
1171 /// let input = "- one\n- two\n- three";
1172 /// assert_eq!(markdown_to_commonmark(input, &options),
1173 /// "- one\n- two\n- three\n"); // default is Dash
1174 ///
1175 /// options.render.list_style = ListStyleType::Plus;
1176 /// assert_eq!(markdown_to_commonmark(input, &options),
1177 /// "+ one\n+ two\n+ three\n");
1178 ///
1179 /// options.render.list_style = ListStyleType::Star;
1180 /// assert_eq!(markdown_to_commonmark(input, &options),
1181 /// "* one\n* two\n* three\n");
1182 /// ```
1183 #[cfg_attr(feature = "bon", builder(default))]
1184 pub list_style: ListStyleType,
1185
1186 /// Include source position attributes in HTML and XML output.
1187 ///
1188 /// Sourcepos information is reliable for core block items excluding
1189 /// lists and list items, all inlines, and most extensions.
1190 /// The description lists extension still has issues; see
1191 /// <https://github.com/kivikakk/comrak/blob/3bb6d4ce/src/tests/description_lists.rs#L60-L125>.
1192 ///
1193 ///
1194 /// ```rust
1195 /// # use comrak::{markdown_to_html, Options};
1196 /// let mut options = Options::default();
1197 /// options.render.sourcepos = true;
1198 /// let input = "Hello *world*!";
1199 /// assert_eq!(markdown_to_html(input, &options),
1200 /// "<p data-sourcepos=\"1:1-1:14\">Hello <em data-sourcepos=\"1:7-1:13\">world</em>!</p>\n");
1201 /// ```
1202 #[cfg_attr(feature = "bon", builder(default))]
1203 pub sourcepos: bool,
1204
1205 /// Wrap escaped characters in a `<span>` to allow any
1206 /// post-processing to recognize them.
1207 ///
1208 /// ```rust
1209 /// # use comrak::{markdown_to_html, Options};
1210 /// let mut options = Options::default();
1211 /// let input = "Notify user \\@example";
1212 ///
1213 /// assert_eq!(markdown_to_html(input, &options),
1214 /// "<p>Notify user @example</p>\n");
1215 ///
1216 /// options.render.escaped_char_spans = true;
1217 /// assert_eq!(markdown_to_html(input, &options),
1218 /// "<p>Notify user <span data-escaped-char>@</span>example</p>\n");
1219 /// ```
1220 ///
1221 /// Enabling this option will cause the `escaped_char_spans` parse option to
1222 /// be enabled.
1223 #[cfg_attr(feature = "bon", builder(default))]
1224 pub escaped_char_spans: bool,
1225
1226 /// Ignore empty links in input.
1227 ///
1228 /// ```rust
1229 /// # use comrak::{markdown_to_html, Options};
1230 /// let mut options = Options::default();
1231 /// let input = "[]()";
1232 ///
1233 /// assert_eq!(markdown_to_html(input, &options),
1234 /// "<p><a href=\"\"></a></p>\n");
1235 ///
1236 /// options.render.ignore_empty_links = true;
1237 /// assert_eq!(markdown_to_html(input, &options), "<p>[]()</p>\n");
1238 /// ```
1239 #[cfg_attr(feature = "bon", builder(default))]
1240 pub ignore_empty_links: bool,
1241
1242 /// Enables GFM quirks in HTML output which break CommonMark compatibility.
1243 ///
1244 /// ```rust
1245 /// # use comrak::{markdown_to_html, Options};
1246 /// let mut options = Options::default();
1247 /// let input = "****abcd**** *_foo_*";
1248 ///
1249 /// assert_eq!(markdown_to_html(input, &options),
1250 /// "<p><strong><strong>abcd</strong></strong> <em><em>foo</em></em></p>\n");
1251 ///
1252 /// options.render.gfm_quirks = true;
1253 /// assert_eq!(markdown_to_html(input, &options),
1254 /// "<p><strong>abcd</strong> <em><em>foo</em></em></p>\n");
1255 /// ```
1256 #[cfg_attr(feature = "bon", builder(default))]
1257 pub gfm_quirks: bool,
1258
1259 /// Prefer fenced code blocks when outputting CommonMark.
1260 ///
1261 /// ```rust
1262 /// # use std::str;
1263 /// # use comrak::{Arena, Options, format_commonmark, parse_document};
1264 /// let arena = Arena::new();
1265 /// let mut options = Options::default();
1266 /// let input = "```\nhello\n```\n";
1267 /// let root = parse_document(&arena, input, &options);
1268 ///
1269 /// let mut buf = String::new();
1270 /// format_commonmark(&root, &options, &mut buf);
1271 /// assert_eq!(buf, " hello\n");
1272 ///
1273 /// buf.clear();
1274 /// options.render.prefer_fenced = true;
1275 /// format_commonmark(&root, &options, &mut buf);
1276 /// assert_eq!(buf, "```\nhello\n```\n");
1277 /// ```
1278 #[cfg_attr(feature = "bon", builder(default))]
1279 pub prefer_fenced: bool,
1280
1281 /// Render the image as a figure element with the title as its caption.
1282 ///
1283 /// ```rust
1284 /// # use comrak::{markdown_to_html, Options};
1285 /// let mut options = Options::default();
1286 /// let input = "";
1287 ///
1288 /// assert_eq!(markdown_to_html(input, &options),
1289 /// "<p><img src=\"https://example.com/image.png\" alt=\"image\" title=\"this is an image\" /></p>\n");
1290 ///
1291 /// options.render.figure_with_caption = true;
1292 /// assert_eq!(markdown_to_html(input, &options),
1293 /// "<p><figure><img src=\"https://example.com/image.png\" alt=\"image\" title=\"this is an image\" /><figcaption>this is an image</figcaption></figure></p>\n");
1294 /// ```
1295 #[cfg_attr(feature = "bon", builder(default))]
1296 pub figure_with_caption: bool,
1297
1298 /// Add classes to the output of the tasklist extension. This allows tasklists to be styled.
1299 ///
1300 /// ```rust
1301 /// # use comrak::{markdown_to_html, Options};
1302 /// let mut options = Options::default();
1303 /// options.extension.tasklist = true;
1304 /// let input = "- [ ] Foo";
1305 ///
1306 /// assert_eq!(markdown_to_html(input, &options),
1307 /// "<ul>\n<li><input type=\"checkbox\" disabled=\"\" /> Foo</li>\n</ul>\n");
1308 ///
1309 /// options.render.tasklist_classes = true;
1310 /// assert_eq!(markdown_to_html(input, &options),
1311 /// "<ul class=\"contains-task-list\">\n<li class=\"task-list-item\"><input type=\"checkbox\" class=\"task-list-item-checkbox\" disabled=\"\" /> Foo</li>\n</ul>\n");
1312 /// ```
1313 #[cfg_attr(feature = "bon", builder(default))]
1314 pub tasklist_classes: bool,
1315
1316 /// How to render alert blocks. Options are:
1317 ///
1318 /// * [`AlertStyleType::Specific`] to use `div`s with `markdown-` prefixed classes (default)
1319 /// * [`AlertStyleType::Semantic`] to use `aside`s with an `admonition` class
1320 ///
1321 /// ```rust
1322 /// # use comrak::{markdown_to_html, Options, options::AlertStyleType};
1323 /// let mut options = Options::default();
1324 /// options.extension.alerts = true;
1325 /// options.render.alert_style = AlertStyleType::Semantic;
1326 /// assert_eq!(markdown_to_html("> [!note]\n> Something of note", &options),
1327 /// "<aside class=\"admonition note\">\n<p class=\"admonition-title\">Note</p>\n<p>Something of note</p>\n</aside>\n");
1328 /// ```
1329 #[cfg_attr(feature = "bon", builder(default))]
1330 pub alert_style: AlertStyleType,
1331
1332 /// Render ordered list with a minimum marker width.
1333 /// Having a width lower than 3 doesn't do anything.
1334 ///
1335 /// ```rust
1336 /// # use comrak::{markdown_to_commonmark, Options};
1337 /// let mut options = Options::default();
1338 /// let input = "1. Something";
1339 ///
1340 /// assert_eq!(markdown_to_commonmark(input, &options),
1341 /// "1. Something\n");
1342 ///
1343 /// options.render.ol_width = 5;
1344 /// assert_eq!(markdown_to_commonmark(input, &options),
1345 /// "1. Something\n");
1346 /// ```
1347 #[cfg_attr(feature = "bon", builder(default))]
1348 pub ol_width: usize,
1349
1350 /// Minimise escapes used in CommonMark output (`-t commonmark`) by removing
1351 /// each individually and seeing if the resulting document roundtrips.
1352 /// Brute-force and expensive, but produces nicer output. Note that the
1353 /// result may not in fact be minimal.
1354 ///
1355 /// ```rust
1356 /// # use comrak::{markdown_to_commonmark, Options};
1357 /// let mut options = Options::default();
1358 /// let input = "__hi";
1359 ///
1360 /// assert_eq!(markdown_to_commonmark(input, &options),
1361 /// "\\_\\_hi\n");
1362 ///
1363 /// options.render.experimental_minimize_commonmark = true;
1364 /// assert_eq!(markdown_to_commonmark(input, &options),
1365 /// "__hi\n");
1366 /// ```
1367 #[cfg_attr(feature = "bon", builder(default))]
1368 pub experimental_minimize_commonmark: bool,
1369
1370 /// Suppress pretty-printing newlines between block-level HTML elements.
1371 ///
1372 /// Normally comrak puts a `\n` after closing tags like `</p>`, `</li>`,
1373 /// etc. With this option on, those newlines are omitted.
1374 ///
1375 /// ```rust
1376 /// # use comrak::{markdown_to_html, Options};
1377 /// let mut options = Options::default();
1378 /// assert_eq!(markdown_to_html("# Hello\n\nWorld.\n", &options),
1379 /// "<h1>Hello</h1>\n<p>World.</p>\n");
1380 ///
1381 /// options.render.compact_html = true;
1382 /// assert_eq!(markdown_to_html("# Hello\n\nWorld.\n", &options),
1383 /// "<h1>Hello</h1><p>World.</p>");
1384 /// ```
1385 #[cfg_attr(feature = "bon", builder(default))]
1386 pub compact_html: bool,
1387}
1388
1389#[derive(Debug, Clone, Copy, Default)]
1390#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1391/// Options for bulleted list rendering in markdown. See [`Render::list_style`] for more details.
1392pub enum ListStyleType {
1393 /// The `-` character
1394 #[default]
1395 Dash = 45,
1396 /// The `+` character
1397 Plus = 43,
1398 /// The `*` character
1399 Star = 42,
1400}
1401
1402#[derive(Debug, Clone, Copy, Default)]
1403#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1404/// Options for alert rendering in markdown. See [`Render::alert_style`] for more details.
1405pub enum AlertStyleType {
1406 /// `div`s with `class="markdown-alert markdown-alert-<type>"`
1407 #[default]
1408 Specific,
1409 /// `aside`s with `class="admonition <type>"`, matching `docutils`' output
1410 Semantic,
1411}
1412
1413#[derive(Default, Debug, Clone)]
1414#[cfg_attr(feature = "bon", derive(Builder))]
1415/// Umbrella plugins struct.
1416pub struct Plugins<'p> {
1417 /// Configure render-time plugins.
1418 #[cfg_attr(feature = "bon", builder(default))]
1419 pub render: RenderPlugins<'p>,
1420}
1421
1422#[derive(Default, Clone)]
1423#[cfg_attr(feature = "bon", derive(Builder))]
1424/// Plugins for alternative rendering.
1425pub struct RenderPlugins<'p> {
1426 /// Provide language-specific renderers for codefence blocks.
1427 ///
1428 /// `math` codefence blocks are handled separately by Comrak's built-in math renderer,
1429 /// so entries keyed by `"math"` in this map are not used.
1430 #[cfg_attr(feature = "bon", builder(default))]
1431 pub codefence_renderers: HashMap<String, &'p dyn CodefenceRendererAdapter>,
1432
1433 /// Provide a syntax highlighter adapter implementation for syntax
1434 /// highlighting of codefence blocks.
1435 ///
1436 /// ```rust
1437 /// # use comrak::{markdown_to_html, Options, options::Plugins, markdown_to_html_with_plugins};
1438 /// # use comrak::adapters::SyntaxHighlighterAdapter;
1439 /// use std::borrow::Cow;
1440 /// use std::collections::HashMap;
1441 /// use std::fmt::{self, Write};
1442 /// let options = Options::default();
1443 /// let mut plugins = Plugins::default();
1444 /// let input = "```rust\nfn main<'a>();\n```";
1445 ///
1446 /// assert_eq!(markdown_to_html_with_plugins(input, &options, &plugins),
1447 /// "<pre><code class=\"language-rust\">fn main<'a>();\n</code></pre>\n");
1448 ///
1449 /// pub struct MockAdapter {}
1450 /// impl SyntaxHighlighterAdapter for MockAdapter {
1451 /// fn write_highlighted(&self, output: &mut dyn fmt::Write, lang: Option<&str>, code: &str) -> fmt::Result {
1452 /// write!(output, "<span class=\"lang-{}\">{}</span>", lang.unwrap(), code)
1453 /// }
1454 ///
1455 /// fn write_pre_tag<'s>(&self, output: &mut dyn fmt::Write, _attributes: HashMap<&'static str, Cow<'s, str>>) -> fmt::Result {
1456 /// output.write_str("<pre lang=\"rust\">")
1457 /// }
1458 ///
1459 /// fn write_code_tag<'s>(&self, output: &mut dyn fmt::Write, _attributes: HashMap<&'static str, Cow<'s, str>>) -> fmt::Result {
1460 /// output.write_str("<code class=\"language-rust\">")
1461 /// }
1462 /// }
1463 ///
1464 /// let adapter = MockAdapter {};
1465 /// plugins.render.codefence_syntax_highlighter = Some(&adapter);
1466 ///
1467 /// assert_eq!(markdown_to_html_with_plugins(input, &options, &plugins),
1468 /// "<pre lang=\"rust\"><code class=\"language-rust\"><span class=\"lang-rust\">fn main<'a>();\n</span></code></pre>\n");
1469 /// ```
1470 pub codefence_syntax_highlighter: Option<&'p dyn SyntaxHighlighterAdapter>,
1471
1472 /// Optional heading adapter
1473 pub heading_adapter: Option<&'p dyn HeadingAdapter>,
1474}
1475
1476impl Debug for RenderPlugins<'_> {
1477 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1478 f.debug_struct("RenderPlugins")
1479 .field(
1480 "codefence_renderers",
1481 &"HashMap<String, impl CodefenceRendererAdapter>",
1482 )
1483 .field(
1484 "codefence_syntax_highlighter",
1485 &"impl SyntaxHighlighterAdapter",
1486 )
1487 .finish()
1488 }
1489}