ephemeris_documents/yaml/source/
mapping.rs1use std::fmt;
4
5use super::Value;
6use super::text::{Origin, Reindent, Text};
7use crate::yaml::CANONICAL_STEP;
8use crate::yaml::scalar_text::ScalarText;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub(crate) struct Mapping {
13 shape: Shape,
14 entries: Vec<Entry>,
15 indent: usize,
16 step: usize,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21enum Shape {
22 Block(Text),
25 EmptyFlow {
29 before: Text,
30 braces: Text,
31 after: Text,
32 },
33}
34
35impl Mapping {
36 pub(super) const fn block(
39 opening: Text,
40 entries: Vec<Entry>,
41 indent: usize,
42 step: usize,
43 ) -> Self {
44 Self {
45 shape: Shape::Block(opening),
46 entries,
47 indent,
48 step,
49 }
50 }
51
52 pub(super) const fn empty_flow(
54 before: Text,
55 braces: Text,
56 after: Text,
57 indent: usize,
58 step: usize,
59 ) -> Self {
60 Self {
61 shape: Shape::EmptyFlow {
62 before,
63 braces,
64 after,
65 },
66 entries: Vec::new(),
67 indent,
68 step,
69 }
70 }
71
72 pub(super) fn reopened(mut self, opening: Text, indent: usize, step: usize) -> Self {
75 self.reindent_to(indent, step);
76 self.shape = Shape::Block(opening);
77 self
78 }
79
80 pub(super) fn take_header(&mut self) -> Text {
83 self.entries
84 .first_mut()
85 .map(|entry| entry.leading.take_before_comment_block())
86 .unwrap_or_default()
87 }
88
89 pub(crate) fn keys(&self) -> impl Iterator<Item = &str> {
90 self.entries.iter().map(Entry::key)
91 }
92
93 pub(crate) fn get(&self, key: &str) -> Option<&Entry> {
94 self.entries.iter().find(|entry| entry.key == key)
95 }
96
97 pub(crate) fn get_mut(&mut self, key: &str) -> Option<&mut Entry> {
98 self.entries.iter_mut().find(|entry| entry.key == key)
99 }
100
101 pub(crate) fn insert(&mut self, mut entry: Entry) {
109 entry.reindent_to(self.indent, self.step);
110 entry.value.terminate();
111 self.expand();
112 if let Some(existing) = self.get_mut(&entry.key) {
113 existing.replace_keeping_decor(entry);
114 return;
115 }
116 self.terminate();
117 self.entries.push(entry);
118 }
119
120 pub(crate) fn remove(&mut self, key: &str) {
123 let Some(index) = self.entries.iter().position(|entry| entry.key == key) else {
124 return;
125 };
126 self.entries.remove(index);
127 if self.entries.is_empty() {
128 self.collapse();
129 }
130 }
131
132 pub(super) fn comment(&self) -> Option<&str> {
134 Text::comment_in(self.key_line().as_str())
135 }
136
137 pub(super) fn keep_comment(&mut self, comment: &str) {
139 if self.comment().is_some() {
140 return;
141 }
142 let text = self.key_line_mut();
143 if !text.is_empty() {
144 *text = Text::from(format!("{}{comment}\n", text.as_str().trim_end()));
145 }
146 }
147
148 const fn key_line(&self) -> &Text {
150 let (Shape::Block(text)
151 | Shape::EmptyFlow {
152 before: _,
153 braces: _,
154 after: text,
155 }) = &self.shape;
156 text
157 }
158
159 const fn key_line_mut(&mut self) -> &mut Text {
160 let (Shape::Block(text)
161 | Shape::EmptyFlow {
162 before: _,
163 braces: _,
164 after: text,
165 }) = &mut self.shape;
166 text
167 }
168
169 pub(super) fn collapse(&mut self) {
172 let opening = match &self.shape {
173 Shape::Block(opening) => opening,
174 Shape::EmptyFlow {
175 before: _,
176 braces: _,
177 after: _,
178 } => return,
179 };
180 if opening.is_empty() {
181 self.shape = Shape::EmptyFlow {
182 before: Text::default(),
183 braces: Text::from("{}"),
184 after: Text::from("\n"),
185 };
186 return;
187 }
188 let line = opening.as_str().trim_end_matches('\n');
189 let comment = Text::comment_start(line).unwrap_or(line.len());
190 let (properties, note) = line.split_at_checked(comment).unwrap_or((line, ""));
191 let note = note.trim_start();
192 let before = Text::from(format!("{} ", properties.trim_end()));
193 let after = Text::from(if note.is_empty() {
194 "\n".to_owned()
195 } else {
196 format!(" {note}\n")
197 });
198 self.shape = Shape::EmptyFlow {
199 before,
200 braces: Text::from("{}"),
201 after,
202 };
203 }
204
205 fn reindent_to(&mut self, indent: usize, step: usize) {
208 self.indent = indent;
209 self.step = step;
210 for entry in &mut self.entries {
211 entry.reindent_to(indent, step);
212 }
213 }
214
215 pub(super) fn terminate(&mut self) {
217 match (self.entries.last_mut(), &mut self.shape) {
218 (
219 _,
220 Shape::EmptyFlow {
221 before: _,
222 braces: _,
223 after,
224 },
225 ) => after.terminate(),
226 (Some(last), Shape::Block(_)) => last.value.terminate(),
227 (None, Shape::Block(opening)) if !opening.is_empty() => opening.terminate(),
228 (None, Shape::Block(_)) => {}
229 }
230 }
231
232 fn expand(&mut self) {
239 let Shape::EmptyFlow {
240 before,
241 braces: _,
242 after,
243 } = &self.shape
244 else {
245 return;
246 };
247 let before = before.as_str();
248 let kept = before.trim_end();
249 let owned = self.indent > 0 || before.starts_with([' ', '\t']) || !kept.is_empty();
250 let mut opening = Text::from(format!("{kept}{after}"));
251 if owned {
252 opening.terminate();
253 } else if after.as_str().trim().is_empty() {
254 opening = Text::default();
255 }
256 self.shape = Shape::Block(opening);
257 }
258}
259
260impl fmt::Display for Mapping {
261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262 match &self.shape {
263 Shape::Block(opening) => write!(f, "{opening}")?,
264 Shape::EmptyFlow {
265 before,
266 braces,
267 after,
268 } => write!(f, "{before}{braces}{after}")?,
269 }
270 self.entries
271 .iter()
272 .try_for_each(|entry| write!(f, "{entry}"))
273 }
274}
275
276#[derive(Debug, Clone, PartialEq, Eq)]
278pub(crate) struct Entry {
279 leading: Text,
280 key: String,
281 head: Text,
282 value: Value,
283}
284
285impl Entry {
286 pub(super) const fn new(leading: Text, key: String, head: Text, value: Value) -> Self {
287 Self {
288 leading,
289 key,
290 head,
291 value,
292 }
293 }
294
295 pub(super) fn root(value: Value) -> Self {
297 Self::new(Text::default(), String::new(), Text::default(), value)
298 }
299
300 pub(crate) fn empty_mapping() -> Self {
302 let mapping = Mapping::empty_flow(
303 Text::from(" "),
304 Text::from("{}"),
305 Text::from("\n"),
306 0,
307 CANONICAL_STEP,
308 );
309 Self::root(Value::Mapping(mapping))
310 }
311
312 #[must_use]
314 pub(crate) fn empty_with_decor(&self) -> Self {
315 let mut empty = Self::empty_mapping();
316 empty.leading.clone_from(&self.leading);
317 empty.head.clone_from(&self.head);
318 empty.key.clone_from(&self.key);
319 empty
320 }
321
322 #[must_use]
325 pub(crate) fn named(mut self, key: &str) -> Self {
326 let indent = " ".repeat(self.indent());
327 self.head = Text::from(format!("{indent}{}:", ScalarText::key(key)));
328 key.clone_into(&mut self.key);
329 self
330 }
331
332 fn replace_keeping_decor(&mut self, incoming: Self) {
335 if !incoming.leading.as_str().trim().is_empty() {
336 self.leading = incoming.leading;
337 }
338 let comment = self.value.comment().map(str::to_owned);
339 self.value = incoming.value;
340 if let Some(comment) = comment {
341 self.value.keep_comment(&comment);
342 }
343 self.value.terminate();
344 }
345
346 pub(crate) fn key(&self) -> &str {
347 &self.key
348 }
349
350 pub(crate) const fn value(&self) -> &Value {
351 &self.value
352 }
353
354 pub(crate) const fn value_mut(&mut self) -> &mut Value {
355 &mut self.value
356 }
357
358 pub(super) fn into_value(self) -> Value {
359 self.value
360 }
361
362 pub(super) fn indent(&self) -> usize {
364 self.head
365 .as_str()
366 .bytes()
367 .take_while(|byte| *byte == b' ')
368 .count()
369 }
370
371 fn reindent_to(&mut self, indent: usize, step: usize) {
375 let shift = Reindent::new(self.indent(), indent);
376 self.leading.reindent(shift, Origin::LineStart);
377 self.head.reindent(shift, Origin::LineStart);
378 match &mut self.value {
379 Value::Leaf(leaf) => leaf.reindent(shift),
380 Value::Opaque(opaque) => opaque.reindent(shift),
381 Value::Mapping(mapping) => mapping.reindent_to(indent.saturating_add(step), step),
382 }
383 }
384}
385
386impl fmt::Display for Entry {
387 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388 write!(f, "{}{}{}", self.leading, self.head, self.value)
389 }
390}