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