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