pulldown_cmark_to_cmark/
source_range.rs

1use super::{cmark_resume_one_event, fmt, Borrow, Error, Event, Options, Range, State};
2
3/// Serialize a stream of [pulldown-cmark-Events][Event] while preserving the escape characters in `source`.
4/// Each input [Event] is accompanied by an optional [Range] that maps it back to the `source` string.
5///
6/// Different from [`cmark_resume_with_options`](super::cmark_resume_with_options), which always escape
7/// Markdown special characters like `#` or `[`, this function only escapes a special character if
8/// it is escaped in `source`.
9///
10/// 1. **source**
11///     * Markdown source from which `event_and_ranges` are created.
12/// 1. **event_and_ranges**
13///    * An iterator over [`Event`]-range pairs, for example as returned by [`pulldown_cmark::OffsetIter`].
14///      Must match what's provided in `source`.
15/// 1. **formatter**
16///    * A format writer, can be a `String`.
17/// 1. **state**
18///    * The optional initial state of the serialization, useful when the operation should be resumed.
19/// 1. **options**
20///    * Customize the appearance of the serialization. All otherwise magic values are contained
21///      here.
22///
23/// *Returns* the [`State`] of the serialization on success. You can use it as initial state in the
24/// next call if you are halting event serialization.
25///
26/// *Errors* if the underlying buffer fails (which is unlikely) or if the [`Event`] stream
27/// iterated over by `event_and_ranges` cannot ever be produced by deserializing valid Markdown.
28/// Each failure mode corresponds to one of [`Error`]'s variants.
29pub fn cmark_resume_with_source_range_and_options<'a, I, E, F>(
30    event_and_ranges: I,
31    source: &'a str,
32    mut formatter: F,
33    state: Option<State<'a>>,
34    options: Options<'_>,
35) -> Result<State<'a>, Error>
36where
37    I: Iterator<Item = (E, Option<Range<usize>>)>,
38    E: Borrow<Event<'a>>,
39    F: fmt::Write,
40{
41    let mut state = state.unwrap_or_default();
42    for (event, range) in event_and_ranges {
43        let update_event_end_index = !matches!(*event.borrow(), Event::Start(_));
44        let prevent_escape_leading_special_characters = match (&range, event.borrow()) {
45            // Headers and tables can have special characters that aren't at the start
46            // of the line, because headers end with `#` and tables have pipes in the middle.
47            _ if state.current_heading.is_some() || !state.table_alignments.is_empty() => false,
48            // IMPORTANT: Any changes that allow anything other than `Text`
49            // breaks the assumption below.
50            (Some(range), Event::Text(_)) => {
51                range.start <= state.last_event_end_index ||
52                // Some source characters are not captured,
53                // so check the previous character.
54                source.as_bytes().get(range.start.saturating_sub(1)) != Some(&b'\\')
55            }
56            _ => false,
57        } && !state.is_in_code_block();
58        if prevent_escape_leading_special_characters {
59            // Hack to not escape leading special characters.
60            state.code_block = Some(crate::CodeBlockKind::Fenced);
61        }
62        cmark_resume_one_event(event, &mut formatter, &mut state, &options)?;
63        if prevent_escape_leading_special_characters {
64            // Assumption: this case only happens when `event` is `Text`,
65            // so `state.is_in_code_block` should not be changed to `true`.
66            // Also, `state.is_in_code_block` was `false`.
67            state.code_block = None;
68        }
69
70        if let (true, Some(range)) = (update_event_end_index, range) {
71            state.last_event_end_index = range.end;
72        }
73    }
74    Ok(state)
75}
76
77/// As [`cmark_resume_with_source_range_and_options`], but with default [`Options`].
78pub fn cmark_resume_with_source_range<'a, I, E, F>(
79    event_and_ranges: I,
80    source: &'a str,
81    formatter: F,
82    state: Option<State<'a>>,
83) -> Result<State<'a>, Error>
84where
85    I: Iterator<Item = (E, Option<Range<usize>>)>,
86    E: Borrow<Event<'a>>,
87    F: fmt::Write,
88{
89    cmark_resume_with_source_range_and_options(event_and_ranges, source, formatter, state, Options::default())
90}
91
92/// As [`cmark_resume_with_source_range_and_options`], but with the [`State`] finalized.
93pub fn cmark_with_source_range_and_options<'a, I, E, F>(
94    event_and_ranges: I,
95    source: &'a str,
96    mut formatter: F,
97    options: Options<'_>,
98) -> Result<State<'a>, Error>
99where
100    I: Iterator<Item = (E, Option<Range<usize>>)>,
101    E: Borrow<Event<'a>>,
102    F: fmt::Write,
103{
104    let state = cmark_resume_with_source_range_and_options(
105        event_and_ranges,
106        source,
107        &mut formatter,
108        Default::default(),
109        options,
110    )?;
111    state.finalize(formatter)
112}
113
114/// As [`cmark_with_source_range_and_options`], but with default [`Options`].
115pub fn cmark_with_source_range<'a, I, E, F>(
116    event_and_ranges: I,
117    source: &'a str,
118    mut formatter: F,
119) -> Result<State<'a>, Error>
120where
121    I: Iterator<Item = (E, Option<Range<usize>>)>,
122    E: Borrow<Event<'a>>,
123    F: fmt::Write,
124{
125    cmark_with_source_range_and_options(event_and_ranges, source, &mut formatter, Default::default())
126}