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