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