Skip to main content

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