toml_edit/
item.rs

1use std::str::FromStr;
2
3use toml_datetime::Datetime;
4
5use crate::array_of_tables::ArrayOfTables;
6use crate::table::TableLike;
7use crate::{Array, InlineTable, Table, Value};
8
9/// Type representing either a value, a table, an array of tables, or none.
10#[derive(Debug, Default)]
11pub enum Item {
12    /// Type representing none.
13    #[default]
14    None,
15    /// Type representing value.
16    Value(Value),
17    /// Type representing table.
18    Table(Table),
19    /// Type representing array of tables.
20    ArrayOfTables(ArrayOfTables),
21}
22
23impl Item {
24    /// Sets `self` to the given item if `self` is none and
25    /// returns a mutable reference to `self`.
26    pub fn or_insert(&mut self, item: Item) -> &mut Item {
27        if self.is_none() {
28            *self = item;
29        }
30        self
31    }
32}
33
34// TODO: This should be generated by macro or derive
35/// Downcasting
36impl Item {
37    /// Text description of value type
38    pub fn type_name(&self) -> &'static str {
39        match self {
40            Item::None => "none",
41            Item::Value(v) => v.type_name(),
42            Item::Table(..) => "table",
43            Item::ArrayOfTables(..) => "array of tables",
44        }
45    }
46
47    /// Index into a TOML array or map. A string index can be used to access a
48    /// value in a map, and a usize index can be used to access an element of an
49    /// array.
50    ///
51    /// Returns `None` if:
52    /// - The type of `self` does not match the type of the
53    ///   index, for example if the index is a string and `self` is an array or a
54    ///   number.
55    /// - The given key does not exist in the map
56    ///   or the given index is not within the bounds of the array.
57    pub fn get<I: crate::index::Index>(&self, index: I) -> Option<&Item> {
58        index.index(self)
59    }
60
61    /// Mutably index into a TOML array or map. A string index can be used to
62    /// access a value in a map, and a usize index can be used to access an
63    /// element of an array.
64    ///
65    /// Returns `None` if:
66    /// - The type of `self` does not match the type of the
67    ///   index, for example if the index is a string and `self` is an array or a
68    ///   number.
69    /// - The given key does not exist in the map
70    ///   or the given index is not within the bounds of the array.
71    pub fn get_mut<I: crate::index::Index>(&mut self, index: I) -> Option<&mut Item> {
72        index.index_mut(self)
73    }
74
75    /// Casts `self` to [`Value`]
76    pub fn as_value(&self) -> Option<&Value> {
77        match *self {
78            Item::Value(ref v) => Some(v),
79            _ => None,
80        }
81    }
82    /// Casts `self` to [`Table`]
83    ///
84    /// <div class="warning">
85    ///
86    /// To operate on both [`Table`]s and [`InlineTable`]s, see [`Item::as_table_like`]
87    ///
88    /// </div>
89    pub fn as_table(&self) -> Option<&Table> {
90        match *self {
91            Item::Table(ref t) => Some(t),
92            _ => None,
93        }
94    }
95    /// Casts `self` to [`ArrayOfTables`]
96    pub fn as_array_of_tables(&self) -> Option<&ArrayOfTables> {
97        match *self {
98            Item::ArrayOfTables(ref a) => Some(a),
99            _ => None,
100        }
101    }
102    /// Casts `self` to mutable [`Value`].
103    pub fn as_value_mut(&mut self) -> Option<&mut Value> {
104        match *self {
105            Item::Value(ref mut v) => Some(v),
106            _ => None,
107        }
108    }
109    /// Casts `self` to mutable [`Table`]
110    ///
111    /// <div class="warning">
112    ///
113    /// To operate on both [`Table`]s and [`InlineTable`]s, see [`Item::as_table_like_mut`]
114    ///
115    /// </div>
116    pub fn as_table_mut(&mut self) -> Option<&mut Table> {
117        match *self {
118            Item::Table(ref mut t) => Some(t),
119            _ => None,
120        }
121    }
122    /// Casts `self` to mutable [`ArrayOfTables`]
123    pub fn as_array_of_tables_mut(&mut self) -> Option<&mut ArrayOfTables> {
124        match *self {
125            Item::ArrayOfTables(ref mut a) => Some(a),
126            _ => None,
127        }
128    }
129    /// Casts `self` to [`Value`]
130    pub fn into_value(self) -> Result<Value, Self> {
131        match self {
132            Item::None => Err(self),
133            Item::Value(v) => Ok(v),
134            Item::Table(v) => {
135                let v = v.into_inline_table();
136                Ok(Value::InlineTable(v))
137            }
138            Item::ArrayOfTables(v) => {
139                let v = v.into_array();
140                Ok(Value::Array(v))
141            }
142        }
143    }
144    /// In-place convert to a value
145    pub fn make_value(&mut self) {
146        let other = std::mem::take(self);
147        let other = other.into_value().map(Item::Value).unwrap_or(Item::None);
148        *self = other;
149    }
150    /// Casts `self` to [`Table`]
151    ///
152    /// <div class="warning">
153    ///
154    /// This does not include [`InlineTable`]s
155    ///
156    /// </div>
157    pub fn into_table(self) -> Result<Table, Self> {
158        match self {
159            Item::Table(t) => Ok(t),
160            Item::Value(Value::InlineTable(t)) => Ok(t.into_table()),
161            _ => Err(self),
162        }
163    }
164    /// Casts `self` to [`ArrayOfTables`]
165    pub fn into_array_of_tables(self) -> Result<ArrayOfTables, Self> {
166        match self {
167            Item::ArrayOfTables(a) => Ok(a),
168            Item::Value(Value::Array(a)) => {
169                if a.is_empty() {
170                    Err(Item::Value(Value::Array(a)))
171                } else if a.iter().all(|v| v.is_inline_table()) {
172                    let mut aot = ArrayOfTables::new();
173                    aot.values = a.values;
174                    for value in aot.values.iter_mut() {
175                        value.make_item();
176                    }
177                    Ok(aot)
178                } else {
179                    Err(Item::Value(Value::Array(a)))
180                }
181            }
182            _ => Err(self),
183        }
184    }
185    // Starting private because the name is unclear
186    pub(crate) fn make_item(&mut self) {
187        let other = std::mem::take(self);
188        let other = match other.into_table().map(Item::Table) {
189            Ok(i) => i,
190            Err(i) => i,
191        };
192        let other = match other.into_array_of_tables().map(Item::ArrayOfTables) {
193            Ok(i) => i,
194            Err(i) => i,
195        };
196        *self = other;
197    }
198    /// Returns true if `self` is a [`Value`]
199    pub fn is_value(&self) -> bool {
200        self.as_value().is_some()
201    }
202    /// Returns true if `self` is a [`Table`]
203    ///
204    /// <div class="warning">
205    ///
206    /// To operate on both [`Table`]s and [`InlineTable`]s, see [`Item::is_table_like`]
207    ///
208    /// </div>
209    pub fn is_table(&self) -> bool {
210        self.as_table().is_some()
211    }
212    /// Returns true if `self` is an [`ArrayOfTables`]
213    pub fn is_array_of_tables(&self) -> bool {
214        self.as_array_of_tables().is_some()
215    }
216    /// Returns true if `self` is `None`.
217    pub fn is_none(&self) -> bool {
218        matches!(*self, Item::None)
219    }
220
221    // Duplicate Value downcasting API
222
223    /// Casts `self` to integer.
224    pub fn as_integer(&self) -> Option<i64> {
225        self.as_value().and_then(Value::as_integer)
226    }
227
228    /// Returns true if `self` is an integer.
229    pub fn is_integer(&self) -> bool {
230        self.as_integer().is_some()
231    }
232
233    /// Casts `self` to float.
234    pub fn as_float(&self) -> Option<f64> {
235        self.as_value().and_then(Value::as_float)
236    }
237
238    /// Returns true if `self` is a float.
239    pub fn is_float(&self) -> bool {
240        self.as_float().is_some()
241    }
242
243    /// Casts `self` to boolean.
244    pub fn as_bool(&self) -> Option<bool> {
245        self.as_value().and_then(Value::as_bool)
246    }
247
248    /// Returns true if `self` is a boolean.
249    pub fn is_bool(&self) -> bool {
250        self.as_bool().is_some()
251    }
252
253    /// Casts `self` to str.
254    pub fn as_str(&self) -> Option<&str> {
255        self.as_value().and_then(Value::as_str)
256    }
257
258    /// Returns true if `self` is a string.
259    pub fn is_str(&self) -> bool {
260        self.as_str().is_some()
261    }
262
263    /// Casts `self` to date-time.
264    pub fn as_datetime(&self) -> Option<&Datetime> {
265        self.as_value().and_then(Value::as_datetime)
266    }
267
268    /// Returns true if `self` is a date-time.
269    pub fn is_datetime(&self) -> bool {
270        self.as_datetime().is_some()
271    }
272
273    /// Casts `self` to array.
274    pub fn as_array(&self) -> Option<&Array> {
275        self.as_value().and_then(Value::as_array)
276    }
277
278    /// Casts `self` to mutable array.
279    pub fn as_array_mut(&mut self) -> Option<&mut Array> {
280        self.as_value_mut().and_then(Value::as_array_mut)
281    }
282
283    /// Returns true if `self` is an array.
284    pub fn is_array(&self) -> bool {
285        self.as_array().is_some()
286    }
287
288    /// Casts `self` to inline table.
289    pub fn as_inline_table(&self) -> Option<&InlineTable> {
290        self.as_value().and_then(Value::as_inline_table)
291    }
292
293    /// Casts `self` to mutable inline table.
294    pub fn as_inline_table_mut(&mut self) -> Option<&mut InlineTable> {
295        self.as_value_mut().and_then(Value::as_inline_table_mut)
296    }
297
298    /// Returns true if `self` is an inline table.
299    pub fn is_inline_table(&self) -> bool {
300        self.as_inline_table().is_some()
301    }
302
303    /// Casts `self` to either a table or an inline table.
304    pub fn as_table_like(&self) -> Option<&dyn TableLike> {
305        self.as_table()
306            .map(|t| t as &dyn TableLike)
307            .or_else(|| self.as_inline_table().map(|t| t as &dyn TableLike))
308    }
309
310    /// Casts `self` to either a table or an inline table.
311    pub fn as_table_like_mut(&mut self) -> Option<&mut dyn TableLike> {
312        match self {
313            Item::Table(t) => Some(t as &mut dyn TableLike),
314            Item::Value(Value::InlineTable(t)) => Some(t as &mut dyn TableLike),
315            _ => None,
316        }
317    }
318
319    /// Returns true if `self` is either a table, or an inline table.
320    pub fn is_table_like(&self) -> bool {
321        self.as_table_like().is_some()
322    }
323
324    /// The location within the original document
325    ///
326    /// This generally requires an [`ImDocument`][crate::ImDocument].
327    pub fn span(&self) -> Option<std::ops::Range<usize>> {
328        match self {
329            Item::None => None,
330            Item::Value(v) => v.span(),
331            Item::Table(v) => v.span(),
332            Item::ArrayOfTables(v) => v.span(),
333        }
334    }
335
336    pub(crate) fn despan(&mut self, input: &str) {
337        match self {
338            Item::None => {}
339            Item::Value(v) => v.despan(input),
340            Item::Table(v) => v.despan(input),
341            Item::ArrayOfTables(v) => v.despan(input),
342        }
343    }
344}
345
346impl Clone for Item {
347    #[inline(never)]
348    fn clone(&self) -> Self {
349        match self {
350            Item::None => Item::None,
351            Item::Value(v) => Item::Value(v.clone()),
352            Item::Table(v) => Item::Table(v.clone()),
353            Item::ArrayOfTables(v) => Item::ArrayOfTables(v.clone()),
354        }
355    }
356}
357
358#[cfg(feature = "parse")]
359impl FromStr for Item {
360    type Err = crate::TomlError;
361
362    /// Parses a value from a &str
363    fn from_str(s: &str) -> Result<Self, Self::Err> {
364        let value = s.parse::<Value>()?;
365        Ok(Item::Value(value))
366    }
367}
368
369impl<'b> From<&'b Item> for Item {
370    fn from(s: &'b Item) -> Self {
371        s.clone()
372    }
373}
374
375impl From<Table> for Item {
376    fn from(s: Table) -> Self {
377        Item::Table(s)
378    }
379}
380
381impl From<ArrayOfTables> for Item {
382    fn from(s: ArrayOfTables) -> Self {
383        Item::ArrayOfTables(s)
384    }
385}
386
387impl<V: Into<Value>> From<V> for Item {
388    fn from(s: V) -> Self {
389        Item::Value(s.into())
390    }
391}
392
393#[cfg(feature = "display")]
394impl std::fmt::Display for Item {
395    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396        match &self {
397            Item::None => Ok(()),
398            Item::Value(v) => v.fmt(f),
399            Item::Table(v) => v.fmt(f),
400            Item::ArrayOfTables(v) => v.fmt(f),
401        }
402    }
403}
404
405/// Returns a formatted value.
406///
407/// Since formatting is part of a `Value`, the right hand side of the
408/// assignment needs to be decorated with a space before the value.
409/// The `value` function does just that.
410///
411/// # Examples
412/// ```rust
413/// # #[cfg(feature = "display")] {
414/// # #[cfg(feature = "parse")] {
415/// # use toml_edit::*;
416/// let mut table = Table::default();
417/// let mut array = Array::default();
418/// array.push("hello");
419/// array.push("\\, world"); // \ is only allowed in a literal string
420/// table["key1"] = value("value1");
421/// table["key2"] = value(42);
422/// table["key3"] = value(array);
423/// assert_eq!(table.to_string(),
424/// r#"key1 = "value1"
425/// key2 = 42
426/// key3 = ["hello", '\, world']
427/// "#);
428/// # }
429/// # }
430/// ```
431pub fn value<V: Into<Value>>(v: V) -> Item {
432    Item::Value(v.into())
433}
434
435/// Returns an empty table.
436pub fn table() -> Item {
437    Item::Table(Table::new())
438}
439
440/// Returns an empty array of tables.
441pub fn array() -> Item {
442    Item::ArrayOfTables(ArrayOfTables::new())
443}
444
445#[test]
446#[cfg(feature = "parse")]
447#[cfg(feature = "display")]
448fn string_roundtrip() {
449    value("hello").to_string().parse::<Item>().unwrap();
450}