use regex::Regex;
use toml::Value;
pub fn parse(toml_str: &str) -> Result<Value, toml::de::Error> {
toml::from_str(toml_str)
}
pub fn get<'a>(value: &'a Value, path: &str) -> Result<Option<Value>, String> {
let mut current = value;
for part in path.split('.') {
current = match current {
Value::Table(map) => {
if part.contains('*') || part.contains('?') {
let pattern = part.replace('*', ".*").replace('?', ".");
let regex = Regex::new(&pattern).map_err(|e| e.to_string())?;
let results: Vec<_> = map
.iter()
.filter(|(key, _)| regex.is_match(key))
.map(|(_, value)| value.clone())
.collect();
return Ok(Some(Value::Array(results)));
} else {
map.get(part).ok_or_else(|| format!("Key not found: {}", part))?
}
}
Value::Array(arr) => {
if part == "#" {
return Ok(Some(Value::Integer(arr.len() as i64)));
} else if part.contains('*') || part.contains('?') {
let pattern = part.replace('*', ".*").replace('?', ".");
let regex = Regex::new(&pattern).map_err(|e| e.to_string())?;
let results: Vec<_> = arr
.iter()
.filter(|item| regex.is_match(&item.to_string()))
.cloned()
.collect();
return Ok(Some(Value::Array(results)));
} else {
if let Ok(index) = part.parse::<usize>() {
arr.get(index).ok_or_else(|| format!("Index out of bounds: {}", index))?
} else {
return Ok(None);
}
}
}
_ => return Ok(None),
};
}
Ok(Some(current.clone()))
}
pub fn get_from_str(toml_str: &str, path: &str) -> Result<Option<Value>, String> {
let value = parse(toml_str).map_err(|e| e.to_string())?;
get(&value, path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_and_get() {
let toml_str = r#"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00Z
"#;
let value = parse(toml_str).unwrap();
assert_eq!(
get(&value, "owner.name").unwrap(),
Some(Value::String("Tom Preston-Werner".to_string()))
);
assert_eq!(
get(&value, "owner.dob").unwrap().unwrap().to_string(),
"1979-05-27T07:32:00Z"
);
}
#[test]
fn test_get_from_str() {
let toml_str = r#"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00Z
"#;
assert_eq!(
get_from_str(toml_str, "owner.name").unwrap(),
Some(Value::String("Tom Preston-Werner".to_string()))
);
assert_eq!(
get_from_str(toml_str, "owner.dob")
.unwrap()
.unwrap()
.to_string(),
"1979-05-27T07:32:00Z"
);
}
#[test]
fn test_get_array() {
let toml_str = r#"
[[people]]
name = "Alice"
age = 30
[[people]]
name = "Bob"
age = 25
"#;
let value = parse(toml_str).unwrap();
assert_eq!(
get(&value, "people.0.name").unwrap(),
Some(Value::String("Alice".to_string()))
);
assert_eq!(
get(&value, "people.1.age").unwrap(),
Some(Value::Integer(25))
);
}
#[test]
fn test_nested_array() {
let toml_str = r#"
[[matrix]]
values = [1, 2, 3]
[[matrix]]
values = [4, 5, 6]
"#;
let value = parse(toml_str).unwrap();
assert_eq!(
get(&value, "matrix.0.values.1").unwrap(),
Some(Value::Integer(2))
);
assert_eq!(
get(&value, "matrix.1.values.2").unwrap(),
Some(Value::Integer(6))
);
}
#[test]
fn test_complex_path() {
let toml_str = r#"
[group]
[[group.people]]
name = "Alice"
age = 30
[[group.people]]
name = "Bob"
age = 25
"#;
let value = parse(toml_str).unwrap();
assert_eq!(
get(&value, "group.people.0.name").unwrap(),
Some(Value::String("Alice".to_string()))
);
assert_eq!(
get(&value, "group.people.1.age").unwrap(),
Some(Value::Integer(25))
);
}
#[test]
fn test_get_array_length() {
let toml_str = r#"
children = ["Sara", "Alex", "Jack"]
"#;
let value = parse(toml_str).unwrap();
assert_eq!(get(&value, "children.#").unwrap(), Some(Value::Integer(3)));
}
#[test]
fn test_get_array_wildcard() {
let toml_str = r#"
children = ["Sara", "Alex", "Jack"]
"#;
let value = parse(toml_str).unwrap();
assert_eq!(
get(&value, "children.2").unwrap(),
Some(Value::String("Jack".to_string()))
);
}
#[test]
fn test_get_array_question_mark() {
let toml_str = r#"
children = ["Sara", "Alex", "Jack"]
"#;
let value = parse(toml_str).unwrap();
assert_eq!(
get(&value, "children.0").unwrap(),
Some(Value::String("Sara".to_string()))
);
}
#[test]
fn test_get_nested_array() {
let toml_str = r#"
friends = [
{ first = "James", last = "Murphy" },
{ first = "Roger", last = "Craig" }
]
"#;
let value = parse(toml_str).unwrap();
let result = get(&value, "friends").unwrap().unwrap();
if let Value::Array(arr) = result {
let first_names: Vec<Value> = arr
.iter()
.filter_map(|item| {
if let Value::Table(map) = item {
map.get("first").cloned()
} else {
None
}
})
.collect();
assert_eq!(
first_names,
vec![
Value::String("James".to_string()),
Value::String("Roger".to_string())
]
);
} else {
panic!("Expected an array");
}
}
}