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}