1use toml::value::{Table, Value};
2
3pub(crate) trait TomlExt {
4 fn read(&self, key: &str) -> Option<&Value>;
5 fn read_mut(&mut self, key: &str) -> Option<&mut Value>;
6 fn insert(&mut self, key: &str, value: Value);
7 fn delete(&mut self, key: &str) -> Option<Value>;
8}
9
10impl TomlExt for Value {
11 fn read(&self, key: &str) -> Option<&Value> {
12 if let Some((head, tail)) = split(key) {
13 self.get(head)?.read(tail)
14 } else {
15 self.get(key)
16 }
17 }
18
19 fn read_mut(&mut self, key: &str) -> Option<&mut Value> {
20 if let Some((head, tail)) = split(key) {
21 self.get_mut(head)?.read_mut(tail)
22 } else {
23 self.get_mut(key)
24 }
25 }
26
27 fn insert(&mut self, key: &str, value: Value) {
28 if !self.is_table() {
29 *self = Value::Table(Table::new());
30 }
31
32 let table = self.as_table_mut().expect("unreachable");
33
34 if let Some((head, tail)) = split(key) {
35 table
36 .entry(head)
37 .or_insert_with(|| Value::Table(Table::new()))
38 .insert(tail, value);
39 } else {
40 table.insert(key.to_string(), value);
41 }
42 }
43
44 fn delete(&mut self, key: &str) -> Option<Value> {
45 if let Some((head, tail)) = split(key) {
46 self.get_mut(head)?.delete(tail)
47 } else if let Some(table) = self.as_table_mut() {
48 table.remove(key)
49 } else {
50 None
51 }
52 }
53}
54
55fn split(key: &str) -> Option<(&str, &str)> {
56 let ix: usize = key.find('.')?;
57
58 let (head: &str, tail: &str) = key.split_at(mid:ix);
59 // splitting will leave the "."
60 let tail: &str = &tail[1..];
61
62 Some((head, tail))
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use std::str::FromStr;
69
70 #[test]
71 fn read_simple_table() {
72 let src = "[table]";
73 let value = Value::from_str(src).unwrap();
74
75 let got = value.read("table").unwrap();
76
77 assert!(got.is_table());
78 }
79
80 #[test]
81 fn read_nested_item() {
82 let src = "[table]\nnested=true";
83 let value = Value::from_str(src).unwrap();
84
85 let got = value.read("table.nested").unwrap();
86
87 assert_eq!(got, &Value::Boolean(true));
88 }
89
90 #[test]
91 fn insert_item_at_top_level() {
92 let mut value = Value::Table(Table::default());
93 let item = Value::Boolean(true);
94
95 value.insert("first", item.clone());
96
97 assert_eq!(value.get("first").unwrap(), &item);
98 }
99
100 #[test]
101 fn insert_nested_item() {
102 let mut value = Value::Table(Table::default());
103 let item = Value::Boolean(true);
104
105 value.insert("first.second", item.clone());
106
107 let inserted = value.read("first.second").unwrap();
108 assert_eq!(inserted, &item);
109 }
110
111 #[test]
112 fn delete_a_top_level_item() {
113 let src = "top = true";
114 let mut value = Value::from_str(src).unwrap();
115
116 let got = value.delete("top").unwrap();
117
118 assert_eq!(got, Value::Boolean(true));
119 }
120
121 #[test]
122 fn delete_a_nested_item() {
123 let src = "[table]\n nested = true";
124 let mut value = Value::from_str(src).unwrap();
125
126 let got = value.delete("table.nested").unwrap();
127
128 assert_eq!(got, Value::Boolean(true));
129 }
130}
131