rumdl_lib/utils/parser_options.rs
1use pulldown_cmark::Options;
2
3/// Standard pulldown-cmark options for rumdl parsing.
4///
5/// Uses an explicit allowlist rather than `Options::all()` to prevent
6/// future pulldown-cmark releases from silently changing parse behavior.
7///
8/// Notably excludes `ENABLE_YAML_STYLE_METADATA_BLOCKS` and
9/// `ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS` because rumdl handles
10/// front matter detection independently. These options cause pulldown-cmark
11/// to misinterpret `---` horizontal rules as metadata delimiters,
12/// corrupting code block detection across the entire document.
13pub fn rumdl_parser_options() -> Options {
14 let mut options = Options::empty();
15 options.insert(Options::ENABLE_TABLES);
16 options.insert(Options::ENABLE_FOOTNOTES);
17 options.insert(Options::ENABLE_STRIKETHROUGH);
18 options.insert(Options::ENABLE_TASKLISTS);
19 options.insert(Options::ENABLE_SMART_PUNCTUATION);
20 options.insert(Options::ENABLE_HEADING_ATTRIBUTES);
21 options.insert(Options::ENABLE_MATH);
22 options.insert(Options::ENABLE_GFM);
23 options.insert(Options::ENABLE_DEFINITION_LIST);
24 options.insert(Options::ENABLE_SUPERSCRIPT);
25 options.insert(Options::ENABLE_SUBSCRIPT);
26 options.insert(Options::ENABLE_WIKILINKS);
27 options
28}
29
30/// The byte ranges of the code spans in `content` as a reader without math
31/// finds them, sorted by start.
32///
33/// rumdl reads `$...$` and `$$...$$` as math, and a backtick inside math opens
34/// no code span. CommonMark has no math, and there that backtick opens a span
35/// closing at the next backtick run of its length, inside the math or past it.
36/// A rewrite that has to keep a code span's text as written needs both
37/// readings, so this one comes from the same parse with only math left out.
38pub fn code_span_ranges_without_math(content: &str) -> Vec<(usize, usize)> {
39 if !content.contains('`') {
40 return Vec::new();
41 }
42 let mut options = rumdl_parser_options();
43 options.remove(Options::ENABLE_MATH);
44 pulldown_cmark::Parser::new_ext(content, options)
45 .into_offset_iter()
46 .filter_map(|(event, range)| {
47 matches!(event, pulldown_cmark::Event::Code(_)).then_some((range.start, range.end))
48 })
49 .collect()
50}