ephemeris_documents/markdown/
reconcile.rs1use std::ops::Range;
4
5use super::ast::{MarkdownBlock, MarkdownInline, MarkdownSelector};
6use super::scan::{Targets, Text};
7use crate::error::{DocumentError, InternalError, MarkdownError};
8use crate::source::Source;
9
10impl MarkdownSelector {
11 fn validate(&self) -> Result<(), MarkdownError> {
16 match self {
17 Self::Heading { level: _, content } if content.contains(['\r', '\n']) => {
18 Err(MarkdownError::HeadingContentMultiline)
19 }
20 Self::Heading {
21 level: _,
22 content: _,
23 }
24 | Self::Marker { name: _, blocks: _ } => Ok(()),
25 }
26 }
27
28 fn check_shadowing(&self, others: &[Self]) -> Result<(), MarkdownError> {
30 let Self::Marker { name, blocks } = self else {
31 return Ok(());
32 };
33 for other in others {
34 if let Self::Heading { level, content: _ } = other
35 && blocks.iter().any(|block| block.contains_heading(*level))
36 {
37 return Err(MarkdownError::HeadingInsideMarker {
38 level: level.get(),
39 name: String::from(name),
40 });
41 }
42 }
43 Ok(())
44 }
45
46 pub(super) fn validate_all(selectors: &[Self]) -> Result<(), MarkdownError> {
52 selectors.iter().try_for_each(Self::validate)?;
53 selectors
54 .iter()
55 .try_for_each(|selector| selector.check_shadowing(selectors))
56 }
57
58 pub(super) fn reconcile_all(
65 source: &Source<'_>,
66 selectors: &[Self],
67 ) -> Result<Vec<u8>, DocumentError> {
68 let Some(text) = source.text() else {
69 return Err(MarkdownError::DocumentMissing.into());
70 };
71 Self::validate_all(selectors)?;
72 let text = Text::new(text);
73 let targets = Targets::scan(text)?;
74 let edits = selectors
75 .iter()
76 .map(|selector| selector.resolve(&targets))
77 .collect::<Result<Vec<_>, _>>()?;
78 let edits = Edits(edits);
79 edits.check_overlap()?;
80 Ok(source.write(&edits.splice(text)?))
81 }
82
83 fn resolve(&self, targets: &Targets<'_>) -> Result<Edit, DocumentError> {
85 match self {
86 Self::Heading { level, content } => Ok(Edit {
87 range: targets.heading(*level)?,
88 replacement: MarkdownBlock::render_all(&[MarkdownBlock::Heading {
89 level: *level,
90 content: vec![MarkdownInline::Text {
91 value: content.clone(),
92 }],
93 }])?,
94 }),
95 Self::Marker { name, blocks } => {
96 let rendered = MarkdownBlock::render_all(blocks)?;
97 Ok(Edit {
98 range: targets.marker(&String::from(name))?,
99 replacement: if rendered.is_empty() {
100 "\n".to_owned()
101 } else {
102 format!("\n{rendered}\n")
103 },
104 })
105 }
106 }
107 }
108}
109
110struct Edit {
112 range: Range<usize>,
113 replacement: String,
114}
115
116struct Edits(Vec<Edit>);
118
119impl Edits {
120 fn check_overlap(&self) -> Result<(), MarkdownError> {
122 for (index, left) in self.0.iter().enumerate() {
123 let left = &left.range;
124 let mut rest = self.0.iter().skip(index.saturating_add(1));
125 if rest.any(|edit| {
126 let right = &edit.range;
127 left == right || (left.start < right.end && right.start < left.end)
128 }) {
129 return Err(MarkdownError::SelectorsOverlapping);
130 }
131 }
132 Ok(())
133 }
134
135 fn splice(mut self, text: Text<'_>) -> Result<String, InternalError> {
137 self.0.sort_by_key(|edit| edit.range.start);
138 let mut output = String::with_capacity(text.len());
139 let mut cursor = 0;
140 for edit in self.0 {
141 output.push_str(text.slice(cursor..edit.range.start)?);
142 output.push_str(&edit.replacement);
143 cursor = edit.range.end;
144 }
145 output.push_str(text.slice(cursor..)?);
146 Ok(output)
147 }
148}
149
150#[cfg(test)]
151mod tests;