ephemeris_documents/yaml/source/read/
opaque.rs1use yaml_rt::{CollectionStyle, NodeId};
4
5use super::fit::Parsed;
6use super::value::Site;
7use super::{Bounds, Mapping, Reader, Text, Tree, Value};
8use crate::error::{DocumentError, YamlError};
9use crate::yaml::source::Opaque;
10
11impl Reader<'_> {
12 pub(super) fn opaque(
15 &self,
16 site: Site,
17 node: NodeId,
18 span: Bounds,
19 ) -> Result<Parsed<Value>, DocumentError> {
20 let Parsed { value: leaf, end } = self.leaf(site.before(), node, span)?;
21 let text = leaf.into_text();
22 let opaque = match self.flow_rewrite(site, node, span, end)? {
23 Some(block) => Opaque::flow(text, block),
24 None => Opaque::raw(text),
25 };
26 Ok(Parsed {
27 value: Value::Opaque(opaque),
28 end,
29 })
30 }
31
32 pub(super) fn is_flow_with_entries(&self, node: NodeId) -> bool {
34 self.mapping_style(node) == Some(CollectionStyle::Flow)
35 && self.document.mapping_entries(node).next().is_some()
36 }
37
38 fn flow_rewrite(
42 &self,
43 site: Site,
44 node: NodeId,
45 span: Bounds,
46 end: usize,
47 ) -> Result<Option<Mapping>, DocumentError> {
48 if !self.is_flow_with_entries(node) {
49 return Ok(None);
50 }
51 let text = self.slice(span.start, span.end)?;
52 let Some(brace) = text.find('{').and_then(|open| span.start.checked_add(open)) else {
53 return Ok(None);
54 };
55 if Text::comment_start(text).is_some() {
56 return Ok(None);
57 }
58 let indent = site.flow_indent(self.step);
59 let Some(source) = self.block_source(node, 0)? else {
60 return Ok(None);
61 };
62 let tree = match Tree::parse(&source) {
63 Ok(tree) => tree,
64 Err(error @ DocumentError::Yaml(YamlError::DuplicateKey { path: _, key: _ })) => {
65 return Err(error);
66 }
67 Err(_) => return Ok(None),
68 };
69 let before = self.slice(site.before(), brace)?;
70 let after = Text::from(self.slice(span.end, end)?);
71 let kept = before.trim_end();
72 let owned = indent > 0 || before.starts_with([' ', '\t']) || !kept.is_empty();
73 let mut opening = Text::from(format!("{kept}{after}"));
74 if owned {
75 opening.terminate();
76 } else if after.as_str().trim().is_empty() {
77 opening = Text::default();
78 }
79 Ok(tree
80 .into_mapping()
81 .map(|mapping| mapping.reopened(opening, indent, self.step)))
82 }
83
84 fn push_line(out: &mut String, pad: &str, key: &str, rest: &str) {
85 out.push_str(pad);
86 out.push_str(key);
87 if !rest.is_empty() {
88 out.push(' ');
89 out.push_str(rest);
90 }
91 out.push('\n');
92 }
93
94 fn block_source(&self, node: NodeId, indent: usize) -> Result<Option<String>, DocumentError> {
98 let pad = " ".repeat(indent);
99 let mut out = String::new();
100 for (key, value) in self.document.mapping_entries(node) {
101 if self.scalar(key).is_none() {
102 return Ok(None);
103 }
104 let key_span = self.range(key)?;
105 let Some(colon_end) = self.colon_end(key_span.end) else {
106 return Ok(None);
107 };
108 let key_text = self.slice(key_span.start, colon_end)?;
109 let span = self.extent(value)?;
110 if key_text.contains('\n') || (!span.is_empty() && span.start < colon_end) {
111 return Ok(None);
112 }
113 let value_text = if span.is_empty() {
114 ""
115 } else {
116 self.slice(colon_end, span.end)?
117 };
118 if self.is_flow_with_entries(value) {
119 let Some(open) = value_text.find('{') else {
120 return Ok(None);
121 };
122 let properties = value_text.get(..open).unwrap_or_default().trim();
123 let nested = indent.saturating_add(self.step);
124 let Some(inner) = self.block_source(value, nested)? else {
125 return Ok(None);
126 };
127 Self::push_line(&mut out, &pad, key_text, properties);
128 out.push_str(&inner);
129 } else if value_text.contains('\n') {
130 return Ok(None);
131 } else {
132 Self::push_line(&mut out, &pad, key_text, value_text.trim());
133 }
134 }
135 Ok(Some(out))
136 }
137}