ephemeris_documents/toml/
node.rs1use std::mem;
4
5use toml_edit::{InlineTable, Item, Table, TableLike, Value as TomlValue};
6
7use super::Toml;
8use super::decor::{prefix_str, with_first_decor_mut};
9use super::layout::Siblings;
10use super::order::TableOrder;
11use crate::merge::Node;
12
13impl Toml {
14 fn key_comment(parent: &Item, key: &str) -> Option<String> {
16 let (key, _) = parent.as_table_like()?.get_key_value(key)?;
17 prefix_str(key.leaf_decor())
18 .filter(|prefix| !prefix.is_empty())
19 .map(str::to_owned)
20 }
21
22 fn set_key_comment(table: &mut dyn TableLike, key: &str, comment: Option<String>) {
23 if let (Some(comment), Some(mut key)) = (comment, table.key_mut(key)) {
24 key.leaf_decor_mut().set_prefix(comment);
25 }
26 }
27
28 fn trim_leading_blank_lines(table: &mut Table) {
31 with_first_decor_mut(table, |decor| {
32 let Some(prefix) = prefix_str(decor) else {
33 return;
34 };
35 let trimmed = prefix.trim_start_matches(['\n', '\r']);
36 if trimmed.len() != prefix.len() {
37 let trimmed = trimmed.to_owned();
38 decor.set_prefix(trimmed);
39 }
40 });
41 }
42
43 fn replace_keeping_decor(old: &Item, mut new: Item) -> Item {
48 match (old, &mut new) {
49 (Item::Value(old), Item::Value(new)) => *new.decor_mut() = old.decor().clone(),
50 (Item::Table(old), Item::Table(new)) => {
51 let comment = prefix_str(new.decor()).unwrap_or_default().to_owned();
52 if comment.is_empty() {
53 *new.decor_mut() = old.decor().clone();
54 } else if !comment.starts_with(['\n', '\r']) {
55 let blank: String = prefix_str(old.decor())
56 .unwrap_or_default()
57 .to_owned()
58 .chars()
59 .take_while(|c| matches!(c, '\n' | '\r'))
60 .collect();
61 new.decor_mut()
62 .set_prefix([blank.as_str(), comment.as_str()].concat());
63 }
64 new.set_position(old.position());
65 }
66 _ => {}
67 }
68 new
69 }
70}
71
72impl Node for Item {
73 fn is_container(&self) -> bool {
74 self.as_table_like().is_some()
75 }
76
77 fn keys(&self) -> impl Iterator<Item = &str> + '_ {
78 self.as_table_like()
79 .into_iter()
80 .flat_map(|table| table.iter().map(|(key, _)| key))
81 }
82
83 fn child(&self, key: &str) -> Option<&Self> {
84 self.as_table_like()?.get(key)
85 }
86
87 fn child_mut(&mut self, key: &str) -> Option<&mut Self> {
88 self.as_table_like_mut()?.get_mut(key)
89 }
90
91 fn adopt(&mut self, key: &str, source: &Self) {
92 let Some(desired) = source.child(key) else {
93 return;
94 };
95 let comment = Toml::key_comment(source, key);
96 let mut node = desired.clone();
97 TableOrder::forget(&mut node);
98 let mut header_comment = None;
99 if self.child(key).is_none() {
100 Siblings::of(self).append(key, node);
101 } else if let Some(slot) = self
102 .as_table_like_mut()
103 .and_then(|table| table.get_mut(key))
104 {
105 let replaced = mem::take(slot);
106 if let (Self::Table(old), Self::Value(_)) = (&replaced, &node) {
107 header_comment = Some(prefix_str(old.decor()).unwrap_or_default().to_owned());
108 }
109 *slot = Toml::replace_keeping_decor(&replaced, node);
110 }
111 let Some(mut target) = self
112 .as_table_like_mut()
113 .and_then(|table| table.key_mut(key))
114 else {
115 return;
116 };
117 if let Some(text) = header_comment {
118 let decor = target.leaf_decor_mut();
119 decor.set_suffix(" ");
120 if !text.is_empty() {
121 decor.set_prefix(text);
122 }
123 }
124 if let Some(text) = comment {
125 target.leaf_decor_mut().set_prefix(text);
126 }
127 }
128
129 fn adopt_shell(&mut self, key: &str, source: &Self) {
130 let Some(table) = self.as_table_like_mut() else {
131 return;
132 };
133 match source.child(key) {
134 Some(Self::Table(own)) if !own.is_dotted() => {
135 let mut shell = Table::new();
136 *shell.decor_mut() = own.decor().clone();
137 let comment = prefix_str(shell.decor()).unwrap_or_default().to_owned();
138 if !table.is_empty() && !comment.is_empty() && !comment.starts_with(['\n', '\r']) {
139 shell.decor_mut().set_prefix(format!("\n{comment}"));
140 }
141 table.insert(key, Self::Table(shell));
142 }
143 Some(Self::Table(_)) => {
144 let mut shell = Table::new();
145 shell.set_dotted(true);
146 table.insert(key, Self::Table(shell));
147 }
148 Some(Self::Value(_)) => {
149 let shell = Self::Value(TomlValue::InlineTable(InlineTable::new()));
150 table.insert(key, shell);
151 Toml::set_key_comment(table, key, Toml::key_comment(source, key));
152 }
153 Some(Self::ArrayOfTables(_) | Self::None) | None => {}
154 }
155 }
156
157 fn remove(&mut self, key: &str) {
158 if let Self::Table(table) = self {
159 let was_first = table.iter().next().is_some_and(|(first, _)| first == key);
160 table.remove(key);
161 if was_first {
162 Toml::trim_leading_blank_lines(table);
163 }
164 } else if let Some(table) = self.as_table_like_mut() {
165 table.remove(key);
166 }
167 }
168}