Skip to main content

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}