yaml_edit/nodes/entry.rs
1//! Entry API for [`Mapping`](crate::Mapping).
2//!
3//! Modelled on [`std::collections::BTreeMap::entry`]. Obtain an [`Entry`]
4//! via [`Mapping::entry`](crate::Mapping::entry).
5//!
6//! ```rust
7//! use std::str::FromStr;
8//! use yaml_edit::Document;
9//!
10//! let doc = Document::from_str("name: Alice\n").unwrap();
11//! let mapping = doc.as_mapping().unwrap();
12//!
13//! mapping.entry("age").or_insert(30);
14//! mapping.entry("name").or_insert("Bob");
15//!
16//! assert_eq!(doc.to_string(), "name: Alice\nage: 30\n");
17//! ```
18
19use crate::as_yaml::{AsYaml, YamlNode};
20use crate::nodes::mapping::{Mapping, MappingEntry};
21use crate::nodes::sequence::Sequence;
22
23/// A view into a single entry in a [`Mapping`], which may either be vacant
24/// or occupied.
25///
26/// Obtained from [`Mapping::entry`](crate::Mapping::entry).
27#[derive(Debug)]
28pub enum Entry<'a, K: AsYaml> {
29 /// The key is already present in the mapping.
30 Occupied(OccupiedEntry<'a>),
31 /// The key is not present in the mapping.
32 Vacant(VacantEntry<'a, K>),
33}
34
35/// A view into an occupied entry in a [`Mapping`].
36#[derive(Debug)]
37pub struct OccupiedEntry<'a> {
38 pub(crate) mapping: &'a Mapping,
39 pub(crate) entry: MappingEntry,
40}
41
42/// A view into a vacant entry in a [`Mapping`].
43#[derive(Debug)]
44pub struct VacantEntry<'a, K: AsYaml> {
45 pub(crate) mapping: &'a Mapping,
46 pub(crate) key: K,
47}
48
49impl<'a, K: AsYaml> Entry<'a, K> {
50 /// Ensure a value is in the entry by inserting `default` if vacant, and
51 /// return the resulting value as a [`YamlNode`].
52 pub fn or_insert(self, default: impl AsYaml) -> YamlNode {
53 match self {
54 Entry::Occupied(o) => o.into_value(),
55 Entry::Vacant(v) => v.insert(default),
56 }
57 }
58
59 /// Ensure a value is in the entry by inserting the result of `default` if
60 /// vacant, and return the resulting value as a [`YamlNode`].
61 ///
62 /// `default` is only called if the entry is vacant.
63 pub fn or_insert_with<V, F>(self, default: F) -> YamlNode
64 where
65 V: AsYaml,
66 F: FnOnce() -> V,
67 {
68 match self {
69 Entry::Occupied(o) => o.into_value(),
70 Entry::Vacant(v) => v.insert(default()),
71 }
72 }
73
74 /// Ensure the entry holds a [`Mapping`] and return it.
75 ///
76 /// If the entry is vacant, an empty mapping is inserted. If the entry is
77 /// occupied but the value is not a mapping, it is replaced with an empty
78 /// mapping. Otherwise the existing nested mapping is returned unchanged.
79 pub fn or_insert_mapping(self) -> Mapping {
80 match self {
81 Entry::Occupied(o) => {
82 if let Some(existing) = o.get_mapping() {
83 return existing;
84 }
85 o.set_value(Mapping::new());
86 o.get_mapping()
87 .expect("value was just replaced with a mapping")
88 }
89 Entry::Vacant(v) => {
90 let VacantEntry { mapping, key } = v;
91 mapping.set(&key, Mapping::new());
92 mapping
93 .get_mapping(&key)
94 .expect("mapping was just inserted")
95 }
96 }
97 }
98
99 /// Ensure the entry holds a [`Sequence`] and return it.
100 ///
101 /// If the entry is vacant, an empty sequence is inserted. If the entry is
102 /// occupied but the value is not a sequence, it is replaced with an empty
103 /// sequence. Otherwise the existing nested sequence is returned unchanged.
104 pub fn or_insert_sequence(self) -> Sequence {
105 match self {
106 Entry::Occupied(o) => {
107 if let Some(existing) = o.get_sequence() {
108 return existing;
109 }
110 o.set_value(Sequence::new());
111 o.get_sequence()
112 .expect("value was just replaced with a sequence")
113 }
114 Entry::Vacant(v) => {
115 let VacantEntry { mapping, key } = v;
116 mapping.set(&key, Sequence::new());
117 mapping
118 .get_sequence(&key)
119 .expect("sequence was just inserted")
120 }
121 }
122 }
123
124 /// Provide in-place access to an occupied entry before any potential
125 /// inserts.
126 ///
127 /// The closure is only called if the entry is occupied. Returns `self`
128 /// unchanged so calls can be chained with [`or_insert`](Self::or_insert)
129 /// and friends.
130 ///
131 /// The tree uses interior mutability, so the closure receives a shared
132 /// reference; `OccupiedEntry::set_value` and friends still work through
133 /// `&self`.
134 pub fn and_modify<F>(self, f: F) -> Self
135 where
136 F: FnOnce(&OccupiedEntry<'a>),
137 {
138 if let Entry::Occupied(ref o) = self {
139 f(o);
140 }
141 self
142 }
143
144 /// Return the key that this entry refers to, as a [`YamlNode`].
145 ///
146 /// For an occupied entry this is the existing key node in the tree. For
147 /// a vacant entry it is the key that would be inserted, rendered as a
148 /// fresh scalar node.
149 pub fn key(&self) -> Option<YamlNode> {
150 match self {
151 Entry::Occupied(o) => o.key(),
152 Entry::Vacant(v) => key_as_yaml_node(&v.key),
153 }
154 }
155}
156
157impl<'a> OccupiedEntry<'a> {
158 /// Return the key of this entry as a [`YamlNode`].
159 ///
160 /// Returns `None` for malformed entries with no key node.
161 pub fn key(&self) -> Option<YamlNode> {
162 self.entry.key_node()
163 }
164
165 /// Return the current value of this entry as a [`YamlNode`].
166 ///
167 /// Returns `None` for malformed entries with no value node.
168 pub fn get(&self) -> Option<YamlNode> {
169 self.entry.value_node()
170 }
171
172 /// Return the value as a nested [`Mapping`], if it is one, looking
173 /// through a tag on it.
174 pub fn get_mapping(&self) -> Option<Mapping> {
175 self.get()?.mapping_content()
176 }
177
178 /// Return the value as a nested [`Sequence`], if it is one, looking
179 /// through a tag on it.
180 pub fn get_sequence(&self) -> Option<Sequence> {
181 self.get()?.sequence_content()
182 }
183
184 /// Replace the value of this entry with `value`.
185 pub fn set_value(&self, value: impl AsYaml) {
186 let flow_context = self.mapping.is_flow_style();
187 self.entry.set_value(value, flow_context);
188 }
189
190 /// Remove this entry from the mapping and return the removed
191 /// [`MappingEntry`] handle. The returned entry is detached from the tree.
192 pub fn remove_entry(self) -> MappingEntry {
193 self.entry.clone().remove();
194 self.entry
195 }
196
197 /// Consume the [`OccupiedEntry`] and return the current value as a
198 /// [`YamlNode`].
199 ///
200 /// Panics if the entry has no value node (indicates malformed input).
201 fn into_value(self) -> YamlNode {
202 self.entry
203 .value_node()
204 .expect("occupied entry has no value node")
205 }
206}
207
208impl<'a, K: AsYaml> VacantEntry<'a, K> {
209 /// Return a reference to the pending key.
210 pub fn key(&self) -> &K {
211 &self.key
212 }
213
214 /// Consume the [`VacantEntry`] and return the key that would have been
215 /// inserted.
216 pub fn into_key(self) -> K {
217 self.key
218 }
219
220 /// Insert `value` into the mapping at this vacant entry's key, and
221 /// return the newly inserted value as a [`YamlNode`].
222 pub fn insert(self, value: impl AsYaml) -> YamlNode {
223 self.mapping.set(&self.key, value);
224 self.mapping
225 .get(&self.key)
226 .expect("value just inserted must be retrievable")
227 }
228}
229
230fn key_as_yaml_node<K: AsYaml>(key: &K) -> Option<YamlNode> {
231 use crate::lex::SyntaxKind;
232 use rowan::GreenNodeBuilder;
233
234 if let Some(node) = key.value_node() {
235 return YamlNode::from_syntax(node);
236 }
237 let mut builder = GreenNodeBuilder::new();
238 builder.start_node(SyntaxKind::ROOT.into());
239 key.build_content(&mut builder, 0, false);
240 builder.finish_node();
241 let root = crate::nodes::SyntaxNode::new_root(builder.finish());
242 crate::nodes::held_node(&root)
243}