Skip to main content

markdown_ppp/parser/
config.rs

1use nom::IResult;
2use std::cell::RefCell;
3use std::collections::HashMap;
4use std::rc::Rc;
5use std::sync::{Arc, OnceLock};
6
7/// Function type for mapping elements.
8type ElementMapFn<ELT> = Rc<RefCell<Box<dyn FnMut(ELT) -> ELT>>>;
9
10/// Function type for mapping elements.
11type ElementFlatMapFn<ELT> = Rc<RefCell<Box<dyn FnMut(ELT) -> Vec<ELT>>>>;
12
13/// Function type for custom block parsers.
14type CustomBlockParserFn =
15    Rc<RefCell<Box<dyn for<'a> FnMut(&'a str) -> IResult<&'a str, Vec<crate::ast::Block>>>>>;
16
17/// Function type for custom inline parsers.
18type CustomInlineParserFn =
19    Rc<RefCell<Box<dyn for<'a> FnMut(&'a str) -> IResult<&'a str, Vec<crate::ast::Inline>>>>>;
20
21/// Default value of [`MarkdownParserConfig::with_max_nesting_depth`].
22pub const DEFAULT_MAX_NESTING_DEPTH: usize = 32;
23
24/// Behavior of the parser when encountering certain elements.
25#[derive(Clone)]
26pub enum ElementBehavior<ELT> {
27    /// The parser will parse the element normally.
28    Parse,
29
30    /// The parser will ignore the element and not parse it. In this case, alternative
31    /// parsers will be tried.
32    Ignore,
33
34    /// Parse element but do not include it in the output.
35    Skip,
36
37    /// Parse the element and apply a custom function to it.
38    Map(ElementMapFn<ELT>),
39
40    /// Parse the element and apply a custom function to it which returns an array of elements.
41    FlatMap(ElementFlatMapFn<ELT>),
42}
43
44/// A configuration for the Markdown parser.
45#[derive(Clone)]
46pub struct MarkdownParserConfig {
47    /// If true, the parser will allow headings without a space after the hash marks.
48    pub(crate) allow_no_space_in_headings: bool,
49
50    /// A map of HTML entities to their corresponding `Entity` structs. Shared, so
51    /// that the default table is built once per process rather than once per parse.
52    pub(crate) html_entities_map: Arc<HashMap<String, &'static entities::Entity>>,
53
54    /// Maximum nesting depth of container blocks and inline elements.
55    /// See [`MarkdownParserConfig::with_max_nesting_depth`].
56    pub(crate) max_nesting_depth: usize,
57
58    /// The behavior of the parser when encountering blockquotes.
59    pub(crate) block_blockquote_behavior: ElementBehavior<crate::ast::Block>,
60
61    /// The behavior of the parser when encountering GitHub alerts.
62    pub(crate) block_github_alert_behavior: ElementBehavior<crate::ast::Block>,
63
64    /// The behavior of the parser when encountering headings in style 1 (e.g., `# Heading`).
65    pub(crate) block_heading_v1_behavior: ElementBehavior<crate::ast::Block>,
66
67    /// The behavior of the parser when encountering headings in style 2 (e.g., `Heading\n===`).
68    pub(crate) block_heading_v2_behavior: ElementBehavior<crate::ast::Block>,
69
70    /// The behavior of the parser when encountering thematic breaks (e.g., `---`).
71    pub(crate) block_thematic_break_behavior: ElementBehavior<crate::ast::Block>,
72
73    /// The behavior of the parser when encountering lists.
74    pub(crate) block_list_behavior: ElementBehavior<crate::ast::Block>,
75
76    /// The behavior of the parser when encountering code blocks.
77    pub(crate) block_code_block_behavior: ElementBehavior<crate::ast::Block>,
78
79    /// The behavior of the parser when encountering HTML blocks.
80    pub(crate) block_html_block_behavior: ElementBehavior<crate::ast::Block>,
81
82    /// The behavior of the parser when encountering footnote definitions.
83    pub(crate) block_footnote_definition_behavior: ElementBehavior<crate::ast::Block>,
84
85    /// The behavior of the parser when encountering link definitions.
86    pub(crate) block_link_definition_behavior: ElementBehavior<crate::ast::Block>,
87
88    /// The behavior of the parser when encountering tables.
89    pub(crate) block_table_behavior: ElementBehavior<crate::ast::Block>,
90
91    /// The behavior of the parser when encountering block paragraphs.
92    pub(crate) block_paragraph_behavior: ElementBehavior<crate::ast::Block>,
93
94    /// The behavior of the parser when encountering inline autolinks.
95    pub(crate) inline_autolink_behavior: ElementBehavior<crate::ast::Inline>,
96
97    /// The behavior of the parser when encountering inline links.
98    pub(crate) inline_link_behavior: ElementBehavior<crate::ast::Inline>,
99
100    /// The behavior of the parser when encountering inline footnote references.
101    pub(crate) inline_footnote_reference_behavior: ElementBehavior<crate::ast::Inline>,
102
103    /// The behavior of the parser when encountering inline reference links.
104    pub(crate) inline_reference_link_behavior: ElementBehavior<crate::ast::Inline>,
105
106    /// The behavior of the parser when encountering inline hard newlines.
107    pub(crate) inline_hard_newline_behavior: ElementBehavior<crate::ast::Inline>,
108
109    /// The behavior of the parser when encountering inline images.
110    pub(crate) inline_image_behavior: ElementBehavior<crate::ast::Inline>,
111
112    /// The behavior of the parser when encountering inline code spans.
113    pub(crate) inline_code_span_behavior: ElementBehavior<crate::ast::Inline>,
114
115    /// The behavior of the parser when encountering inline emphasis.
116    pub(crate) inline_emphasis_behavior: ElementBehavior<crate::ast::Inline>,
117
118    /// The behavior of the parser when encountering inline strikethrough.
119    pub(crate) inline_strikethrough_behavior: ElementBehavior<crate::ast::Inline>,
120
121    /// The behavior of the parser when encountering inline text.
122    pub(crate) inline_text_behavior: ElementBehavior<crate::ast::Inline>,
123
124    /// A custom parser for blocks. This is a function that takes a string and returns a `Block`.
125    pub(crate) custom_block_parser: Option<CustomBlockParserFn>,
126
127    /// A custom parser for inlines. This is a function that takes a string and returns a `Inline`.
128    pub(crate) custom_inline_parser: Option<CustomInlineParserFn>,
129}
130
131impl Default for MarkdownParserConfig {
132    fn default() -> Self {
133        Self {
134            allow_no_space_in_headings: false,
135            html_entities_map: Self::make_html_entities_map(),
136            max_nesting_depth: DEFAULT_MAX_NESTING_DEPTH,
137            block_blockquote_behavior: ElementBehavior::Parse,
138            block_github_alert_behavior: ElementBehavior::Parse,
139            block_heading_v1_behavior: ElementBehavior::Parse,
140            block_heading_v2_behavior: ElementBehavior::Parse,
141            block_thematic_break_behavior: ElementBehavior::Parse,
142            block_list_behavior: ElementBehavior::Parse,
143            block_code_block_behavior: ElementBehavior::Parse,
144            block_html_block_behavior: ElementBehavior::Parse,
145            block_footnote_definition_behavior: ElementBehavior::Parse,
146            block_link_definition_behavior: ElementBehavior::Parse,
147            block_table_behavior: ElementBehavior::Parse,
148            block_paragraph_behavior: ElementBehavior::Parse,
149            inline_autolink_behavior: ElementBehavior::Parse,
150            inline_link_behavior: ElementBehavior::Parse,
151            inline_footnote_reference_behavior: ElementBehavior::Parse,
152            inline_reference_link_behavior: ElementBehavior::Parse,
153            inline_hard_newline_behavior: ElementBehavior::Parse,
154            inline_image_behavior: ElementBehavior::Parse,
155            inline_code_span_behavior: ElementBehavior::Parse,
156            inline_emphasis_behavior: ElementBehavior::Parse,
157            inline_strikethrough_behavior: ElementBehavior::Parse,
158            inline_text_behavior: ElementBehavior::Parse,
159            custom_block_parser: None,
160            custom_inline_parser: None,
161        }
162    }
163}
164
165impl MarkdownParserConfig {
166    fn make_html_entities_map() -> Arc<HashMap<String, &'static entities::Entity>> {
167        static DEFAULT: OnceLock<Arc<HashMap<String, &'static entities::Entity>>> = OnceLock::new();
168        DEFAULT
169            .get_or_init(|| {
170                let mut map = HashMap::with_capacity(entities::ENTITIES.len());
171                for entity in entities::ENTITIES.iter() {
172                    map.insert(entity.entity.to_string(), entity);
173                }
174                Arc::new(map)
175            })
176            .clone()
177    }
178
179    /// Enable the parser to allow headings without a space after the hash marks.
180    pub fn with_allow_no_space_in_headings(self) -> Self {
181        Self {
182            allow_no_space_in_headings: true,
183            ..self
184        }
185    }
186
187    /// Set the maximum nesting depth of the document.
188    ///
189    /// Every container block (blockquote, list item, footnote definition, GitHub alert)
190    /// and every inline element with nested content (emphasis, strikethrough, link label)
191    /// adds one level of nesting. When the depth exceeds `depth`,
192    /// [`parse_markdown`](crate::parser::parse_markdown) returns an error with
193    /// [`nom::error::ErrorKind::TooLarge`].
194    ///
195    /// The limit bounds both the parse time and the stack usage on adversarial input
196    /// (e.g. thousands of `>` markers). Defaults to [`DEFAULT_MAX_NESTING_DEPTH`].
197    pub fn with_max_nesting_depth(self, depth: usize) -> Self {
198        Self {
199            max_nesting_depth: depth,
200            ..self
201        }
202    }
203
204    /// Set a custom map of HTML entities.
205    pub fn with_html_entities_map(
206        self,
207        html_entities_map: HashMap<String, &'static entities::Entity>,
208    ) -> Self {
209        Self {
210            html_entities_map: Arc::new(html_entities_map),
211            ..self
212        }
213    }
214
215    /// Set the behavior of the parser when encountering blockquotes.
216    pub fn with_block_blockquote_behavior(
217        self,
218        behavior: ElementBehavior<crate::ast::Block>,
219    ) -> Self {
220        Self {
221            block_blockquote_behavior: behavior,
222            ..self
223        }
224    }
225
226    /// Set the behavior of the parser when encountering GitHub alerts.
227    pub fn with_block_github_alert_behavior(
228        self,
229        behavior: ElementBehavior<crate::ast::Block>,
230    ) -> Self {
231        Self {
232            block_github_alert_behavior: behavior,
233            ..self
234        }
235    }
236
237    /// Set the behavior of the parser when encountering headings in style 1 (e.g., `# Heading`).
238    pub fn with_block_heading_v1_behavior(
239        self,
240        behavior: ElementBehavior<crate::ast::Block>,
241    ) -> Self {
242        Self {
243            block_heading_v1_behavior: behavior,
244            ..self
245        }
246    }
247
248    /// Set the behavior of the parser when encountering headings in style 2 (e.g., `Heading\n===`).
249    pub fn with_block_heading_v2_behavior(
250        self,
251        behavior: ElementBehavior<crate::ast::Block>,
252    ) -> Self {
253        Self {
254            block_heading_v2_behavior: behavior,
255            ..self
256        }
257    }
258
259    /// Set the behavior of the parser when encountering thematic breaks (e.g., `---`).
260    pub fn with_block_thematic_break_behavior(
261        self,
262        behavior: ElementBehavior<crate::ast::Block>,
263    ) -> Self {
264        Self {
265            block_thematic_break_behavior: behavior,
266            ..self
267        }
268    }
269
270    /// Set the behavior of the parser when encountering lists.
271    pub fn with_block_list_behavior(self, behavior: ElementBehavior<crate::ast::Block>) -> Self {
272        Self {
273            block_list_behavior: behavior,
274            ..self
275        }
276    }
277
278    /// Set the behavior of the parser when encountering code blocks.
279    pub fn with_block_code_block_behavior(
280        self,
281        behavior: ElementBehavior<crate::ast::Block>,
282    ) -> Self {
283        Self {
284            block_code_block_behavior: behavior,
285            ..self
286        }
287    }
288
289    /// Set the behavior of the parser when encountering HTML blocks.
290    pub fn with_block_html_block_behavior(
291        self,
292        behavior: ElementBehavior<crate::ast::Block>,
293    ) -> Self {
294        Self {
295            block_html_block_behavior: behavior,
296            ..self
297        }
298    }
299
300    /// Set the behavior of the parser when encountering footnote definitions.
301    pub fn with_block_footnote_definition_behavior(
302        self,
303        behavior: ElementBehavior<crate::ast::Block>,
304    ) -> Self {
305        Self {
306            block_footnote_definition_behavior: behavior,
307            ..self
308        }
309    }
310
311    /// Set the behavior of the parser when encountering link definitions.
312    pub fn with_block_link_definition_behavior(
313        self,
314        behavior: ElementBehavior<crate::ast::Block>,
315    ) -> Self {
316        Self {
317            block_link_definition_behavior: behavior,
318            ..self
319        }
320    }
321
322    /// Set the behavior of the parser when encountering tables.
323    pub fn with_block_table_behavior(self, behavior: ElementBehavior<crate::ast::Block>) -> Self {
324        Self {
325            block_table_behavior: behavior,
326            ..self
327        }
328    }
329
330    /// Set the behavior of the parser when encountering block paragraphs.
331    pub fn with_block_paragraph_behavior(
332        self,
333        behavior: ElementBehavior<crate::ast::Block>,
334    ) -> Self {
335        Self {
336            block_paragraph_behavior: behavior,
337            ..self
338        }
339    }
340
341    /// Set the behavior of the parser when encountering inline autolinks.
342    pub fn with_inline_autolink_behavior(
343        self,
344        behavior: ElementBehavior<crate::ast::Inline>,
345    ) -> Self {
346        Self {
347            inline_autolink_behavior: behavior,
348            ..self
349        }
350    }
351
352    /// Set the behavior of the parser when encountering inline links.
353    pub fn with_inline_link_behavior(self, behavior: ElementBehavior<crate::ast::Inline>) -> Self {
354        Self {
355            inline_link_behavior: behavior,
356            ..self
357        }
358    }
359
360    /// Set the behavior of the parser when encountering inline footnote references.
361    pub fn with_inline_footnote_reference_behavior(
362        self,
363        behavior: ElementBehavior<crate::ast::Inline>,
364    ) -> Self {
365        Self {
366            inline_footnote_reference_behavior: behavior,
367            ..self
368        }
369    }
370
371    /// Set the behavior of the parser when encountering inline reference links.
372    pub fn with_inline_reference_link_behavior(
373        self,
374        behavior: ElementBehavior<crate::ast::Inline>,
375    ) -> Self {
376        Self {
377            inline_reference_link_behavior: behavior,
378            ..self
379        }
380    }
381
382    /// Set the behavior of the parser when encountering inline hard newlines.
383    pub fn with_inline_hard_newline_behavior(
384        self,
385        behavior: ElementBehavior<crate::ast::Inline>,
386    ) -> Self {
387        Self {
388            inline_hard_newline_behavior: behavior,
389            ..self
390        }
391    }
392
393    /// Set the behavior of the parser when encountering inline images.
394    pub fn with_inline_image_behavior(self, behavior: ElementBehavior<crate::ast::Inline>) -> Self {
395        Self {
396            inline_image_behavior: behavior,
397            ..self
398        }
399    }
400
401    /// Set the behavior of the parser when encountering inline code spans.
402    pub fn with_inline_code_span_behavior(
403        self,
404        behavior: ElementBehavior<crate::ast::Inline>,
405    ) -> Self {
406        Self {
407            inline_code_span_behavior: behavior,
408            ..self
409        }
410    }
411
412    /// Set the behavior of the parser when encountering inline emphasis.
413    pub fn with_inline_emphasis_behavior(
414        self,
415        behavior: ElementBehavior<crate::ast::Inline>,
416    ) -> Self {
417        Self {
418            inline_emphasis_behavior: behavior,
419            ..self
420        }
421    }
422
423    /// Set the behavior of the parser when encountering inline strikethrough.
424    pub fn with_inline_strikethrough_behavior(
425        self,
426        behavior: ElementBehavior<crate::ast::Inline>,
427    ) -> Self {
428        Self {
429            inline_strikethrough_behavior: behavior,
430            ..self
431        }
432    }
433
434    /// Set the behavior of the parser when encountering inline text.
435    pub fn with_inline_text_behavior(self, behavior: ElementBehavior<crate::ast::Inline>) -> Self {
436        Self {
437            inline_text_behavior: behavior,
438            ..self
439        }
440    }
441
442    /// Set a custom parser for blocks.
443    pub fn with_custom_block_parser(self, parser: CustomBlockParserFn) -> Self {
444        Self {
445            custom_block_parser: Some(parser),
446            ..self
447        }
448    }
449
450    /// Set a custom parser for inlines.
451    pub fn with_custom_inline_parser(self, parser: CustomInlineParserFn) -> Self {
452        Self {
453            custom_inline_parser: Some(parser),
454            ..self
455        }
456    }
457}