1use regex::Regex;
2use toml::Value;
3
4pub fn parse(toml_str: &str) -> Result<Value, toml::de::Error> {
13 toml::from_str(toml_str)
14}
15
16pub fn get<'a>(value: &'a Value, path: &str) -> Result<Option<Value>, String> {
26 let mut current = value;
27 for part in path.split('.') {
28 current = match current {
29 Value::Table(map) => {
31 if part.contains('*') || part.contains('?') {
32 let pattern = part.replace('*', ".*").replace('?', ".");
34 let regex = Regex::new(&pattern).map_err(|e| e.to_string())?;
35 let results: Vec<_> = map
37 .iter()
38 .filter(|(key, _)| regex.is_match(key))
39 .map(|(_, value)| value.clone())
40 .collect();
41 return Ok(Some(Value::Array(results)));
42 } else {
43 map.get(part).ok_or_else(|| format!("Key not found: {}", part))?
45 }
46 }
47 Value::Array(arr) => {
49 if part == "#" {
50 return Ok(Some(Value::Integer(arr.len() as i64)));
52 } else if part.contains('*') || part.contains('?') {
53 let pattern = part.replace('*', ".*").replace('?', ".");
55 let regex = Regex::new(&pattern).map_err(|e| e.to_string())?;
56 let results: Vec<_> = arr
58 .iter()
59 .filter(|item| regex.is_match(&item.to_string()))
60 .cloned()
61 .collect();
62 return Ok(Some(Value::Array(results)));
63 } else {
64 if let Ok(index) = part.parse::<usize>() {
65 arr.get(index).ok_or_else(|| format!("Index out of bounds: {}", index))?
67 } else {
68 return Ok(None);
69 }
70 }
71 }
72 _ => return Ok(None),
74 };
75 }
76 Ok(Some(current.clone()))
77}
78
79pub fn get_from_str(toml_str: &str, path: &str) -> Result<Option<Value>, String> {
89 let value = parse(toml_str).map_err(|e| e.to_string())?;
90 get(&value, path)
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
99 fn test_parse_and_get() {
100 let toml_str = r#"
101 [owner]
102 name = "Tom Preston-Werner"
103 dob = 1979-05-27T07:32:00Z
104 "#;
105
106 let value = parse(toml_str).unwrap();
107 assert_eq!(
108 get(&value, "owner.name").unwrap(),
109 Some(Value::String("Tom Preston-Werner".to_string()))
110 );
111 assert_eq!(
112 get(&value, "owner.dob").unwrap().unwrap().to_string(),
113 "1979-05-27T07:32:00Z"
114 );
115 }
116
117 #[test]
119 fn test_get_from_str() {
120 let toml_str = r#"
121 [owner]
122 name = "Tom Preston-Werner"
123 dob = 1979-05-27T07:32:00Z
124 "#;
125
126 assert_eq!(
127 get_from_str(toml_str, "owner.name").unwrap(),
128 Some(Value::String("Tom Preston-Werner".to_string()))
129 );
130 assert_eq!(
131 get_from_str(toml_str, "owner.dob")
132 .unwrap()
133 .unwrap()
134 .to_string(),
135 "1979-05-27T07:32:00Z"
136 );
137 }
138
139 #[test]
141 fn test_get_array() {
142 let toml_str = r#"
143 [[people]]
144 name = "Alice"
145 age = 30
146
147 [[people]]
148 name = "Bob"
149 age = 25
150 "#;
151
152 let value = parse(toml_str).unwrap();
153 assert_eq!(
154 get(&value, "people.0.name").unwrap(),
155 Some(Value::String("Alice".to_string()))
156 );
157 assert_eq!(
158 get(&value, "people.1.age").unwrap(),
159 Some(Value::Integer(25))
160 );
161 }
162
163 #[test]
165 fn test_nested_array() {
166 let toml_str = r#"
167 [[matrix]]
168 values = [1, 2, 3]
169
170 [[matrix]]
171 values = [4, 5, 6]
172 "#;
173
174 let value = parse(toml_str).unwrap();
175 assert_eq!(
176 get(&value, "matrix.0.values.1").unwrap(),
177 Some(Value::Integer(2))
178 );
179 assert_eq!(
180 get(&value, "matrix.1.values.2").unwrap(),
181 Some(Value::Integer(6))
182 );
183 }
184
185 #[test]
187 fn test_complex_path() {
188 let toml_str = r#"
189 [group]
190 [[group.people]]
191 name = "Alice"
192 age = 30
193
194 [[group.people]]
195 name = "Bob"
196 age = 25
197 "#;
198
199 let value = parse(toml_str).unwrap();
200 assert_eq!(
201 get(&value, "group.people.0.name").unwrap(),
202 Some(Value::String("Alice".to_string()))
203 );
204 assert_eq!(
205 get(&value, "group.people.1.age").unwrap(),
206 Some(Value::Integer(25))
207 );
208 }
209
210 #[test]
212 fn test_get_array_length() {
213 let toml_str = r#"
214 children = ["Sara", "Alex", "Jack"]
215 "#;
216
217 let value = parse(toml_str).unwrap();
218 assert_eq!(get(&value, "children.#").unwrap(), Some(Value::Integer(3)));
219 }
220
221 #[test]
223 fn test_get_array_wildcard() {
224 let toml_str = r#"
225 children = ["Sara", "Alex", "Jack"]
226 "#;
227
228 let value = parse(toml_str).unwrap();
229 assert_eq!(
230 get(&value, "children.2").unwrap(),
231 Some(Value::String("Jack".to_string()))
232 );
233 }
234
235 #[test]
237 fn test_get_array_question_mark() {
238 let toml_str = r#"
239 children = ["Sara", "Alex", "Jack"]
240 "#;
241
242 let value = parse(toml_str).unwrap();
243 assert_eq!(
244 get(&value, "children.0").unwrap(),
245 Some(Value::String("Sara".to_string()))
246 );
247 }
248
249 #[test]
251 fn test_get_nested_array() {
252 let toml_str = r#"
253 friends = [
254 { first = "James", last = "Murphy" },
255 { first = "Roger", last = "Craig" }
256 ]
257 "#;
258
259 let value = parse(toml_str).unwrap();
260 let result = get(&value, "friends").unwrap().unwrap();
261 if let Value::Array(arr) = result {
262 let first_names: Vec<Value> = arr
263 .iter()
264 .filter_map(|item| {
265 if let Value::Table(map) = item {
266 map.get("first").cloned()
267 } else {
268 None
269 }
270 })
271 .collect();
272 assert_eq!(
273 first_names,
274 vec![
275 Value::String("James".to_string()),
276 Value::String("Roger".to_string())
277 ]
278 );
279 } else {
280 panic!("Expected an array");
281 }
282 }
283}