Skip to content

Commit

Permalink
Make 'Value::find()' able to traverse arrays.
Browse files Browse the repository at this point in the history
  • Loading branch information
thorio committed Aug 31, 2024
1 parent c730bdf commit 4655e48
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 13 deletions.
15 changes: 15 additions & 0 deletions src/figment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,12 @@ impl Figment {
/// [subtree.bark]
/// dog = true
/// cat = false
///
/// [[lives]]
/// cat = 9
///
/// [[lives]]
/// dog = 1
/// "#)?;
///
/// let root = Figment::from(Toml::file("Config.toml"));
Expand All @@ -410,6 +416,15 @@ impl Figment {
/// assert!(not_a_dict.extract_inner::<bool>("cat").is_err());
/// assert!(not_a_dict.extract_inner::<bool>("dog").is_err());
///
/// let cat_lives = root.focus("lives.0");
/// assert_eq!(cat_lives.extract_inner::<u8>("cat").unwrap(), 9);
/// let dog_lives = root.focus("lives.1");
/// assert_eq!(dog_lives.extract_inner::<u8>("dog").unwrap(), 1);
///
/// let invalid_index = root.focus("lives.two");
/// assert!(invalid_index.extract_inner::<bool>("cat").is_err());
/// assert!(invalid_index.extract_inner::<bool>("dog").is_err());
///
/// Ok(())
/// });
/// ```
Expand Down
83 changes: 70 additions & 13 deletions src/value/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ impl Value {
}

/// Looks up and returns the value at path `path`, where `path` is of the
/// form `a.b.c` where `a`, `b`, and `c` are keys to dictionaries. If the
/// key is empty, simply returns `self`. If the key is not empty and `self`
/// or any of the values for non-leaf keys in the path are not dictionaries,
/// returns `None`.
/// form `a.2.c` where `a` and `c` are keys to dictionaries, and `2` is
/// an array index. If the key is empty, simply returns `self`. If the key
/// is not empty and `self` or any of the values for non-leaf keys in the
/// path are not arrays or dictionaries, returns `None`.
///
/// This method consumes `self`. See [`Value::find_ref()`] for a
/// non-consuming variant.
Expand All @@ -116,30 +116,46 @@ impl Value {
/// use figment::{value::Value, util::map};
///
/// let value = Value::from(map! {
/// "apple" => map! {
/// "apple" => Value::from(map! {
/// "bat" => map! {
/// "pie" => 4usize,
/// },
/// "cake" => map! {
/// "pumpkin" => 10usize,
/// }
/// }
/// },
/// }),
/// "duck" => Value::from(vec![
/// map!{
/// "pasta" => 42usize,
/// },
/// map!{
/// "pizza" => 229usize,
/// },
/// ]),
/// });
///
/// assert!(value.clone().find("apple").is_some());
/// assert!(value.clone().find("apple.bat").is_some());
/// assert!(value.clone().find("apple.cake").is_some());
/// assert!(value.clone().find("duck").is_some());
/// assert!(value.clone().find("duck.0").is_some());
/// assert!(value.clone().find("duck.1").is_some());
///
/// assert_eq!(value.clone().find("apple.bat.pie").unwrap().to_u128(), Some(4));
/// assert_eq!(value.clone().find("apple.cake.pumpkin").unwrap().to_u128(), Some(10));
/// assert_eq!(value.clone().find("duck.0.pasta").unwrap().to_u128(), Some(42));
/// assert_eq!(value.clone().find("duck.1.pizza").unwrap().to_u128(), Some(229));
///
/// assert!(value.clone().find("apple.pie").is_none());
/// assert!(value.clone().find("pineapple").is_none());
/// assert!(value.clone().find("duck.55").is_none());
/// assert!(value.clone().find("duck.0.pizza").is_none());
/// assert!(value.clone().find("duck.one").is_none());
/// ```
pub fn find(self, path: &str) -> Option<Value> {
fn find(mut keys: Split<char>, value: Value) -> Option<Value> {
match keys.next() {
Some(k) if !k.is_empty() => find(keys, value.into_dict()?.remove(k)?),
Some(k) if !k.is_empty() => find(keys, value.index(k)?),
Some(_) | None => Some(value)
}
}
Expand All @@ -156,30 +172,46 @@ impl Value {
/// use figment::{value::Value, util::map};
///
/// let value = Value::from(map! {
/// "apple" => map! {
/// "apple" => Value::from(map! {
/// "bat" => map! {
/// "pie" => 4usize,
/// },
/// "cake" => map! {
/// "pumpkin" => 10usize,
/// }
/// }
/// },
/// }),
/// "duck" => Value::from(vec![
/// map!{
/// "pasta" => 42usize,
/// },
/// map!{
/// "pizza" => 229usize,
/// },
/// ]),
/// });
///
/// assert!(value.find_ref("apple").is_some());
/// assert!(value.find_ref("apple.bat").is_some());
/// assert!(value.find_ref("apple.cake").is_some());
/// assert!(value.find_ref("duck").is_some());
/// assert!(value.find_ref("duck.0").is_some());
/// assert!(value.find_ref("duck.1").is_some());
///
/// assert_eq!(value.find_ref("apple.bat.pie").unwrap().to_u128(), Some(4));
/// assert_eq!(value.find_ref("apple.cake.pumpkin").unwrap().to_u128(), Some(10));
/// assert_eq!(value.find_ref("duck.0.pasta").unwrap().to_u128(), Some(42));
/// assert_eq!(value.find_ref("duck.1.pizza").unwrap().to_u128(), Some(229));
///
/// assert!(value.find_ref("apple.pie").is_none());
/// assert!(value.find_ref("pineapple").is_none());
/// assert!(value.find_ref("duck.55").is_none());
/// assert!(value.find_ref("duck.0.pizza").is_none());
/// assert!(value.find_ref("duck.one").is_none());
/// ```
pub fn find_ref<'a>(&'a self, path: &str) -> Option<&'a Value> {
pub fn find_ref(&self, path: &str) -> Option<&Value> {
fn find<'v>(mut keys: Split<char>, value: &'v Value) -> Option<&'v Value> {
match keys.next() {
Some(k) if !k.is_empty() => find(keys, value.as_dict()?.get(k)?),
Some(k) if !k.is_empty() => find(keys, value.index_ref(k)?),
Some(_) | None => Some(value)
}
}
Expand Down Expand Up @@ -394,6 +426,31 @@ impl Value {
}
}

/// Attempts to retrieve a nested value through dictionary key or array index.
pub(crate) fn index(self, key: &str) -> Option<Value> {
fn try_remove<T>(mut vec: Vec<T>, key: &str) -> Option<T> {
let index = key.parse().ok()?;
vec.get(index)?;

Some(vec.swap_remove(index))
}

match self {
Self::Dict(_, mut dict) => dict.remove(key),
Self::Array(_, array) => try_remove(array, key),
_ => None,
}
}

/// Same as [`Self::index()`] but works on references.
pub(crate) fn index_ref(&self, key: &str) -> Option<&Value> {
match self {
Self::Dict(_, dict) => dict.get(key),
Self::Array(_, array) => array.get(key.parse::<usize>().ok()?),
_ => None,
}
}

pub(crate) fn tag_mut(&mut self) -> &mut Tag {
match self {
Value::String(tag, ..) => tag,
Expand Down

0 comments on commit 4655e48

Please sign in to comment.