ephemeris_documents/yaml/source/
read.rs1mod bounds;
4mod fit;
5mod opaque;
6mod value;
7
8use std::borrow::Cow;
9
10use yaml_rt::{CollectionStyle, NodeId, SemanticKind, Span, YamlDoc, YamlScalarStyle};
11
12use self::bounds::Bounds;
13use self::fit::{Collected, Parsed};
14use self::value::Site;
15use super::leaf::Leaf;
16use super::mapping::{Entry, Mapping};
17use super::opaque::Opaque;
18use super::text::Text;
19use super::{Tree, Value};
20use crate::error::{DocumentError, InternalError};
21use crate::yaml::CANONICAL_STEP;
22
23pub(super) struct Reader<'a> {
28 text: &'a str,
29 document: &'a YamlDoc,
30 step: usize,
31}
32
33struct Block {
35 entries: Vec<Entry>,
36 indent: usize,
37}
38
39impl<'a> Reader<'a> {
40 pub(super) fn new(text: &'a str, document: &'a YamlDoc) -> Self {
41 Self {
42 text,
43 document,
44 step: CANONICAL_STEP,
45 }
46 .with_detected_step()
47 }
48
49 pub(super) fn tree(&self) -> Result<Tree, DocumentError> {
50 let root = match self.document.document_count() {
51 0 => None,
52 _ => self
53 .document
54 .document_root(0)
55 .map_err(DocumentError::yaml_parse)?,
56 };
57 let Some(root) = root else {
58 let empty = Mapping::block(Text::from(self.text), Vec::new(), 0, self.step);
59 return Ok(Tree::new(
60 Text::default(),
61 Value::Mapping(empty),
62 Text::default(),
63 ));
64 };
65 let span = self.extent(root)?;
66 let line = self.line_start(span.start);
67 let style = self.mapping_style(root);
68 let site = Site::Root { line };
69 let editable = self.mapping(root, span, site)?;
70 if self.is_flow_with_entries(root) {
71 let Parsed { value, end } = self.opaque(site, root, span)?;
72 return Ok(Tree::new(
73 Text::from(self.slice(0, line)?),
74 value,
75 Text::from(self.slice(end, self.text.len())?),
76 ));
77 }
78 if let (
79 Some(Parsed {
80 value: mut mapping,
81 end,
82 }),
83 Some(style),
84 ) = (editable, style)
85 {
86 let header = match style {
87 CollectionStyle::Block => mapping.take_header(),
88 CollectionStyle::Flow => Text::from(self.slice(0, line)?),
89 };
90 let footer = Text::from(self.slice(end, self.text.len())?);
91 return Ok(Tree::new(header, Value::Mapping(mapping), footer));
92 }
93 let text = Text::from(self.slice(line, self.text.len())?);
94 let node = if style.is_some() {
95 Value::Opaque(Opaque::raw(text))
96 } else {
97 Value::Leaf(Leaf::raw(text))
98 };
99 Ok(Tree::new(
100 Text::from(self.slice(0, line)?),
101 node,
102 Text::default(),
103 ))
104 }
105
106 fn block(&self, mapping: NodeId, from: usize) -> Result<Option<Parsed<Block>>, DocumentError> {
110 let mut entries = Collected::default();
111 let mut indent = None;
112 let mut cursor = from;
113 for (key, value) in self.document.mapping_entries(mapping) {
114 let Some(parsed) = self.entry(key, value, cursor)? else {
115 return Ok(None);
116 };
117 let column = parsed.value.indent();
118 if *indent.get_or_insert(column) != column {
119 return Ok(None);
120 }
121 entries.reject_duplicate(parsed.value.key())?;
122 cursor = parsed.end;
123 entries.push(parsed.value);
124 }
125 Ok(indent.map(|indent| Parsed {
126 value: Block {
127 entries: entries.into_vec(),
128 indent,
129 },
130 end: cursor,
131 }))
132 }
133
134 fn entry(
135 &self,
136 key: NodeId,
137 value: NodeId,
138 cursor: usize,
139 ) -> Result<Option<Parsed<Entry>>, DocumentError> {
140 let Some(name) = self.scalar(key) else {
141 return Ok(None);
142 };
143 let key_span = self.range(key)?;
144 let line = self.line_start(key_span.start);
145 if line < cursor
146 || self
147 .slice(line, key_span.start)?
148 .bytes()
149 .any(|byte| byte != b' ')
150 {
151 return Ok(None);
152 }
153 let Some(colon_end) = self.colon_end(key_span.end) else {
154 return Ok(None);
155 };
156 let indent = key_span.start.saturating_sub(line);
157 let span = self.extent(value)?;
158 let Parsed { value, end } = self.value(&name, value, span, colon_end, indent)?;
159 Ok(Some(Parsed {
160 value: Entry::new(
161 Text::from(self.slice(cursor, line)?),
162 name,
163 Text::from(self.slice(line, colon_end)?),
164 value,
165 ),
166 end,
167 }))
168 }
169
170 fn leaf(
173 &self,
174 colon_end: usize,
175 value: NodeId,
176 span: Bounds,
177 ) -> Result<Parsed<Leaf>, DocumentError> {
178 let anchor = if span.is_empty() {
179 colon_end
180 } else {
181 span.end.max(colon_end)
182 };
183 let end = self.line_end(anchor);
184 let split = self.comment_start(value, span, anchor);
185 let leaf = Leaf::new(
186 Text::from(self.slice(colon_end, split)?),
187 Text::from(self.slice(split, end)?),
188 self.scalar(value).filter(|_| !span.is_empty()),
189 );
190 Ok(Parsed { value: leaf, end })
191 }
192
193 fn scalar(&self, node: NodeId) -> Option<String> {
195 matches!(
196 self.document.semantic_kind(node),
197 Some(SemanticKind::Scalar { style: _ })
198 )
199 .then(|| self.document.scalar_value(node).ok())
200 .flatten()
201 .map(Cow::into_owned)
202 }
203
204 fn comment_start(&self, value: NodeId, span: Bounds, fallback: usize) -> usize {
210 let plain = matches!(
211 self.document.semantic_kind(value),
212 Some(SemanticKind::Scalar {
213 style: YamlScalarStyle::Plain
214 })
215 );
216 let Some(text) = self.text.get(span.start..span.end).filter(|_| plain) else {
217 return fallback;
218 };
219 Text::comment_start(text).map_or(fallback, |offset| span.start.saturating_add(offset))
220 }
221
222 fn empty_braces(&self, node: NodeId, span: Bounds) -> Option<usize> {
225 if self.document.mapping_entries(node).next().is_some() {
226 return None;
227 }
228 let inner = self.text.get(span.start..span.end)?.strip_suffix('}')?;
229 let open = inner.rfind('{')?;
230 inner
231 .get(open.checked_add(1)?..)?
232 .chars()
233 .all(char::is_whitespace)
234 .then_some(span.start.checked_add(open)?)
235 }
236
237 fn colon_end(&self, key_end: usize) -> Option<usize> {
239 let rest = self.text.get(key_end..)?;
240 let spaces = rest
241 .len()
242 .saturating_sub(rest.trim_start_matches([' ', '\t']).len());
243 let colon = key_end.checked_add(spaces)?;
244 (self.text.get(colon..)?.starts_with(':')).then_some(colon.checked_add(1)?)
245 }
246
247 fn extent(&self, node: NodeId) -> Result<Bounds, DocumentError> {
252 self.document
253 .children(node)
254 .try_fold(self.range(node)?, |covered, child| {
255 Ok(covered.cover(self.extent(child)?))
256 })
257 }
258
259 fn range(&self, node: NodeId) -> Result<Bounds, DocumentError> {
260 let Span { start, end } = self
261 .document
262 .node(node)
263 .map(yaml_rt::Node::span)
264 .ok_or(InternalError::YamlNodeMissing)?;
265 Ok(Bounds::new(
266 usize::try_from(start).unwrap_or(usize::MAX),
267 usize::try_from(end).unwrap_or(usize::MAX),
268 ))
269 }
270
271 fn slice(&self, start: usize, end: usize) -> Result<&'a str, DocumentError> {
272 self.text
273 .get(start..end)
274 .ok_or_else(|| InternalError::YamlSpanInvalid { start, end }.into())
275 }
276
277 fn line_start(&self, offset: usize) -> usize {
279 self.text
280 .get(..offset)
281 .and_then(|before| before.rfind('\n'))
282 .map_or(0, |newline| newline.saturating_add(1))
283 }
284
285 fn line_end(&self, offset: usize) -> usize {
288 let after_break = offset
289 .checked_sub(1)
290 .is_some_and(|previous| self.text.get(previous..offset) == Some("\n"));
291 if after_break {
292 return offset;
293 }
294 self.text
295 .get(offset..)
296 .and_then(|rest| rest.find('\n'))
297 .map_or(self.text.len(), |newline| {
298 offset.saturating_add(newline).saturating_add(1)
299 })
300 }
301
302 fn with_detected_step(mut self) -> Self {
303 if let Ok(root) = self.document.root_mapping() {
304 self.step = self.detect_step(root).unwrap_or(CANONICAL_STEP);
305 }
306 self
307 }
308
309 fn detect_step(&self, mapping: NodeId) -> Option<usize> {
311 self.document
312 .mapping_entries(mapping)
313 .find_map(|(key, value)| {
314 let is_block = matches!(
315 self.document.semantic_kind(value),
316 Some(SemanticKind::Mapping {
317 style: CollectionStyle::Block
318 })
319 );
320 if !is_block {
321 return None;
322 }
323 let (child, _) = self.document.mapping_entries(value).next()?;
324 let key_start = self.range(key).ok()?.start;
325 let child_start = self.range(child).ok()?.start;
326 let parent = key_start.saturating_sub(self.line_start(key_start));
327 let inner = child_start.saturating_sub(self.line_start(child_start));
328 inner
329 .checked_sub(parent)
330 .filter(|step| *step > 0)
331 .or_else(|| self.detect_step(value))
332 })
333 }
334}