ephemeris_documents/json/cst/
object.rs1use std::fmt::{self, Write as _};
4use std::mem;
5
6use super::key::Key;
7use super::tail::{Comma, Position, Tail};
8use super::trivia::{Indent, Trivia};
9use super::{Kind, Value};
10use crate::annotation::Annotated;
11use crate::entries::Entries;
12use crate::json::Dialect;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
18pub(crate) struct Object {
19 members: Vec<Member>,
20 inner: Trivia,
22 base: String,
26 unit: Indent,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub(in crate::json) struct DuplicateKey {
33 pub(in crate::json) name: String,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
39pub(crate) struct Member {
40 key: Key,
41 after_key: Trivia,
43 after_colon: Trivia,
45 value: Value,
46 tail: Tail,
47}
48
49impl Member {
50 pub(in crate::json) const fn parsed(
51 key: Key,
52 after_key: Trivia,
53 after_colon: Trivia,
54 value: Value,
55 tail: Tail,
56 ) -> Self {
57 Self {
58 key,
59 after_key,
60 after_colon,
61 value,
62 tail,
63 }
64 }
65
66 fn canonical(name: &str, value: Value, tail: Tail) -> Self {
68 Self::parsed(
69 Key::canonical(name),
70 Trivia::default(),
71 Trivia::new(" ".to_owned()),
72 value,
73 tail,
74 )
75 }
76
77 fn build(name: &str, node: &Annotated, dialect: Dialect, depth: usize) -> Self {
78 let leading = Trivia::above(node.comment.as_ref(), depth, dialect);
79 Self::canonical(
80 name,
81 Value::build(node, dialect, depth, leading),
82 Tail::canonical(),
83 )
84 }
85
86 fn trivia_mut(&mut self, visit: &mut dyn FnMut(&mut Trivia)) {
87 self.value.trivia_mut(visit);
88 visit(&mut self.after_key);
89 visit(&mut self.after_colon);
90 self.tail.trivia_mut(visit);
91 }
92}
93
94enum Layout {
96 Lines { head: String, indent: String },
98 Inline,
100}
101
102impl Layout {
103 fn leading(self, value: &mut Value, from: &str) -> Trivia {
106 match self {
107 Self::Lines { head, indent } => {
108 Object::reindent_incoming(value, from, &indent);
109 let mut leading = mem::take(&mut value.leading);
110 if leading.has_newline() {
111 leading.reindent_from(from, &indent);
112 } else {
113 leading = Trivia::new(["\n", &indent, leading.as_str()].concat());
114 }
115 Trivia::new([head.as_str(), leading.as_str()].concat())
116 }
117 Self::Inline => {
118 Object::reindent_incoming(value, from, "");
119 Trivia::new(" ".to_owned())
120 }
121 }
122 }
123}
124
125impl Object {
126 pub(in crate::json) fn new() -> Self {
127 Self {
128 members: Vec::new(),
129 inner: Trivia::new(String::new()),
130 base: String::new(),
131 unit: Indent::default(),
132 }
133 }
134
135 pub(super) fn build(entries: &Entries, dialect: Dialect, depth: usize) -> Self {
137 let child = depth.saturating_add(1);
138 Self {
139 members: entries
140 .into_iter()
141 .map(|(name, value)| Member::build(name, value, dialect, child))
142 .collect(),
143 inner: if entries.is_empty() {
144 Trivia::default()
145 } else {
146 Trivia::closing(depth)
147 },
148 base: String::new(),
149 unit: Indent::default(),
150 }
151 }
152
153 pub(in crate::json) fn closed_by(mut self, inner: Trivia) -> Self {
155 self.inner = inner;
156 self
157 }
158
159 pub(super) fn indent_unit(&self) -> Option<Indent> {
161 self.detect_unit("")
162 }
163
164 fn detect_unit(&self, base: &str) -> Option<Indent> {
166 for member in &self.members {
167 let leading = &member.value.leading;
168 if !leading.has_newline() {
169 continue;
170 }
171 let own = leading.indent();
172 if let Some(unit) = own.strip_prefix(base).and_then(Indent::from_blanks) {
173 return Some(unit);
174 }
175 if let Kind::Object(child) = &member.value.kind
176 && let Some(unit) = child.detect_unit(own)
177 {
178 return Some(unit);
179 }
180 }
181 None
182 }
183
184 pub(super) fn set_unit(&mut self, unit: &Indent) {
186 self.unit.clone_from(unit);
187 }
188
189 pub(in crate::json) fn push(&mut self, member: Member) -> Result<(), DuplicateKey> {
194 if self.position(member.key.name()).is_some() {
195 return Err(DuplicateKey {
196 name: member.key.name().to_owned(),
197 });
198 }
199 self.members.push(member);
200 Ok(())
201 }
202
203 fn position(&self, name: &str) -> Option<usize> {
204 self.members
205 .iter()
206 .position(|member| member.key.name() == name)
207 }
208
209 pub(in crate::json) fn keys(&self) -> impl Iterator<Item = &str> {
211 self.members.iter().map(|member| member.key.name())
212 }
213
214 pub(in crate::json) fn get(&self, name: &str) -> Option<&Value> {
215 self.members
216 .iter()
217 .find(|member| member.key.name() == name)
218 .map(|member| &member.value)
219 }
220
221 pub(in crate::json) fn descend(&mut self, name: &str) -> Option<&mut Value> {
225 let index = self.position(name)?;
226 let member = self.members.get_mut(index)?;
227 if let Kind::Object(object) = &mut member.value.kind {
228 object.unit.clone_from(&self.unit);
229 let base = if member.value.leading.has_newline() {
230 member.value.leading.indent()
231 } else {
232 &self.base
233 };
234 object.base.clear();
235 object.base.push_str(base);
236 }
237 Some(&mut member.value)
238 }
239
240 pub(in crate::json) fn insert(&mut self, name: &str, mut value: Value) {
254 value.reindent_unit(&self.unit);
255 let from = value.leading.indent().to_owned();
256 let commented = !value.leading.is_blank();
257 match self.position(name) {
258 Some(index) => self.replace_member(index, value, &from),
259 None => self.append_member(name, value, &from, commented),
260 }
261 }
262
263 fn replace_member(&mut self, index: usize, mut value: Value, from: &str) {
266 if !value.leading.is_blank() {
267 self.expand_before(index);
268 }
269 let Some(member) = self.members.get_mut(index) else {
270 return;
271 };
272 let to = member.value.leading.indent().to_owned();
273 Self::reindent_incoming(&mut value, from, &to);
274 if !value.leading.is_blank() && member.value.leading.has_newline() {
275 value.leading.reindent_from(from, &to);
276 member.value.leading = value.leading;
277 }
278 member.value.kind = value.kind;
279 }
280
281 fn append_member(&mut self, name: &str, mut value: Value, from: &str, commented: bool) {
284 let kept = self
285 .members
286 .last()
287 .map_or(Comma::Present, |last| last.tail.comma);
288 let mut layout = self.plan_layout();
289 if self.members.is_empty() {
290 self.expand_empty();
291 } else if commented && matches!(layout, Layout::Inline) {
292 layout = Layout::Lines {
293 head: String::new(),
294 indent: self.expand(),
295 };
296 }
297 let inline = matches!(layout, Layout::Inline);
298 let leading = layout.leading(&mut value, from);
299 let trailing = match self.members.last_mut() {
300 Some(last) if inline && last.tail.trailing.is_blank() => {
301 mem::take(&mut last.tail.trailing)
302 }
303 _ => Trivia::default(),
304 };
305 let tail = Tail::new(Trivia::default(), kept, trailing);
306 self.members.push(Member::canonical(
307 name,
308 Value::new(leading, value.kind),
309 tail,
310 ));
311 }
312
313 fn expand_before(&mut self, index: usize) {
316 let indent = match self.plan_layout() {
317 Layout::Lines { indent, head: _ } => indent,
318 Layout::Inline => self.expand(),
319 };
320 if let Some(member) = self.members.get_mut(index)
321 && !member.value.leading.has_newline()
322 {
323 let text = ["\n", &indent, member.value.leading.as_str().trim_start()].concat();
324 member.value.leading = Trivia::new(text);
325 }
326 }
327
328 fn expand(&mut self) -> String {
331 let indent = [self.base.as_str(), self.unit.as_str()].concat();
332 for member in &mut self.members {
333 let text = ["\n", &indent, member.value.leading.as_str().trim_start()].concat();
334 member.value.leading = Trivia::new(text);
335 if member.tail.trailing.is_blank() {
336 member.tail.trailing = Trivia::default();
337 }
338 }
339 let inner = ["\n", &self.base, self.inner.as_str().trim_start()].concat();
340 self.inner = Trivia::new(inner);
341 indent
342 }
343
344 fn reindent_incoming(value: &mut Value, from: &str, to: &str) {
346 value.kind.reindent_from(from, to);
347 if let Kind::Object(object) = &mut value.kind
348 && object.members.is_empty()
349 {
350 to.clone_into(&mut object.base);
351 }
352 }
353
354 fn plan_layout(&self) -> Layout {
356 if self.members.is_empty() {
357 let kept = self.inner.as_str().trim_end_matches([' ', '\t']);
358 return Layout::Lines {
359 head: kept.strip_suffix('\n').unwrap_or(kept).to_owned(),
360 indent: [self.empty_base().as_str(), self.unit.as_str()].concat(),
361 };
362 }
363 self.members
364 .iter()
365 .rev()
366 .find(|member| member.value.leading.has_newline())
367 .map_or(Layout::Inline, |member| Layout::Lines {
368 head: String::new(),
369 indent: member.value.leading.indent().to_owned(),
370 })
371 }
372
373 fn empty_base(&self) -> String {
376 if self.inner.has_newline() {
377 self.inner.indent().to_owned()
378 } else {
379 self.base.clone()
380 }
381 }
382
383 fn expand_empty(&mut self) {
385 self.inner = Trivia::new(["\n", &self.empty_base()].concat());
386 }
387
388 pub(in crate::json) fn remove(&mut self, name: &str) {
392 let Some(index) = self.position(name) else {
393 return;
394 };
395 let removed = self.members.remove(index);
396 if index == self.members.len()
397 && let Some(last) = self.members.last_mut()
398 {
399 last.tail.comma = removed.tail.comma;
400 }
401 if self.members.is_empty() && self.inner.is_blank() {
402 self.inner = Trivia::default();
403 }
404 }
405
406 pub(super) fn trivia_mut(&mut self, visit: &mut dyn FnMut(&mut Trivia)) {
407 for member in &mut self.members {
408 member.trivia_mut(visit);
409 }
410 visit(&mut self.inner);
411 }
412
413 pub(super) fn write(&self, f: &mut fmt::Formatter<'_>, dialect: Dialect) -> fmt::Result {
414 f.write_char('{')?;
415 for (position, member) in Position::each(&self.members) {
416 write!(
417 f,
418 "{}{}{}:{}",
419 member.value.leading,
420 member.key.spelling(dialect),
421 member.after_key,
422 member.after_colon
423 )?;
424 member.value.kind.write(f, dialect)?;
425 member.tail.write(f, dialect, position)?;
426 }
427 write!(f, "{}}}", self.inner)
428 }
429}