diff options
author | pommicket <pommicket@gmail.com> | 2025-09-08 17:19:39 -0400 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2025-09-08 17:19:39 -0400 |
commit | 1632f44e99d8819b0fd5a279ec93f5601c8a7b06 (patch) | |
tree | b6b9584e6d9f9dcccfd0703c655df597fceb0e2b /src | |
parent | 73e9bfa44c5b104bd322bd7011bebb367853d58c (diff) |
Add subkeys method
Diffstat (limited to 'src')
-rw-r--r-- | src/lib.rs | 74 |
1 files changed, 60 insertions, 14 deletions
@@ -640,6 +640,7 @@ impl Configuration { // the impact on performance is not really important. Parser::default().load(filename, &mut reader) } + /// Load a configuration from a file path. #[cfg(feature = "std")] pub fn load_path<P: AsRef<std::path::Path>>(path: P) -> Result<Self> { @@ -648,27 +649,36 @@ impl Configuration { let file = std::fs::File::open(p).map_err(|e| Error::IO(filename.clone().into(), e))?; Configuration::load(&filename, std::io::BufReader::new(file)) } + + /// Binary search `self.items`. + /// + /// See [`std::slice::binary_search_by`]. + fn binary_search_for(&self, key: &str) -> core::result::Result<usize, usize> { + self.items.binary_search_by(|(k, _)| k.as_ref().cmp(key)) + } + + /// Returns the index of the first key which is greater than `key` + "." + fn first_subkey_index(&self, key: &str) -> usize { + let key_dot = format!("{key}."); + self.binary_search_for(&key_dot) + .expect_err("items should not contain a key ending in .") + } + /// Extract a section out of a configuration. /// /// More specifically, this will give you the configuration consisting of all /// keys starting with `key.` in `self`, together with their values. #[must_use] pub fn section(&self, key: &str) -> Configuration { - let key_dot = format!("{key}."); - let start_idx = self - .items - .binary_search_by(|(k, _)| k.as_ref().cmp(&key_dot)) - .expect_err("items should not contain a key ending in ."); + let start_idx = self.first_subkey_index(key); // NB: / is the next ASCII character after . let key_slash = format!("{key}/"); - let end_idx = self - .items - .binary_search_by(|(k, _)| k.as_ref().cmp(&key_slash)) - .unwrap_or_else(|i| i); + let end_idx = self.binary_search_for(&key_slash).unwrap_or_else(|i| i); Configuration { items: self.items[start_idx..end_idx].to_owned(), } } + /// Get the list of all “direct keys” in this configuration. /// /// More specifically, this returns an iterator of all unique @@ -691,33 +701,58 @@ impl Configuration { Some(first_component) }) } + + /// Get the list of all “direct children” of `key` in this configuration. + /// + /// More specifically, this returns an iterator of all unique + /// (*n*+1)th components of keys in `self` whose first *n* components + /// are equal to `key` (where `key` has *n* components). + /// + /// (So if there were keys `sheep.age`, `sheep.colour`, and `sheep.age.unit`, + /// `subkeys("sheep")` would give an iterator yielding + /// `"age"` and `"colour"` in some order.) + /// + /// The order of items returned is arbitrary and may change + /// in future versions without notice. + pub fn subkeys(&self, key: &str) -> impl '_ + Iterator<Item = &str> { + let key_dot = format!("{key}."); + let start_idx = self.first_subkey_index(key); + (start_idx..).map_while(move |i| { + let this_key = &self.items[i].0; + let suffix = this_key.strip_prefix(&key_dot)?; + Some(suffix.split_once('.').map_or(suffix, |(x, _)| x)) + }) + } + fn get_val(&self, key: &str) -> Option<&Value> { - let idx = self - .items - .binary_search_by(|(k, _)| k.as_ref().cmp(key)) - .ok()?; + let idx = self.binary_search_for(key).ok()?; Some(&self.items[idx].1) } + /// Get value associated with `key`, if any. #[must_use] pub fn get(&self, key: &str) -> Option<&str> { Some(self.get_val(key)?.value.as_ref()) } + /// Get location in the configuration file where `key` is defined, if any. #[must_use] pub fn location(&self, key: &str) -> Option<Location> { Some(self.get_val(key)?.defined_at.clone()) } + /// Returns `true` if `key` is defined in this configuration. #[must_use] pub fn has(&self, key: &str) -> bool { self.get(key).is_some() } + /// Get value associated with `key`, or else use `default` if it isn't defined. #[must_use] pub fn get_or_default<'a>(&'a self, key: &str, default: &'a str) -> &'a str { self.get(key).unwrap_or(default) } + /// Get value associated with `key`, and parse it as an integer. /// /// Returns `None` if `key` is not defined, @@ -727,12 +762,14 @@ impl Configuration { let Value { value, defined_at } = self.get_val(key)?; Some(parse_int(defined_at, value.as_ref())) } + /// Get value associated with `key`, and parse it as an integer, or else use `default`. /// /// Returns `Err(…)` if `key` is defined but not an integer. pub fn get_int_or_default(&self, key: &str, default: i64) -> Result<i64> { self.get_int(key).unwrap_or(Ok(default)) } + /// Get value associated with `key`, and parse it as an unsigned integer. /// /// Returns `None` if `key` is not defined, @@ -742,12 +779,14 @@ impl Configuration { let Value { value, defined_at } = self.get_val(key)?; Some(parse_uint(defined_at, value.as_ref())) } + /// Get value associated with `key`, and parse it as an unsinged integer, or else use `default`. /// /// Returns `Err(…)` if `key` is defined but not an unsigned integer. pub fn get_uint_or_default(&self, key: &str, default: u64) -> Result<u64> { self.get_uint(key).unwrap_or(Ok(default)) } + /// Get value associated with `key`, and parse it as a float. /// /// Returns `None` if `key` is not defined, @@ -757,12 +796,14 @@ impl Configuration { let Value { value, defined_at } = self.get_val(key)?; Some(parse_float(defined_at, value.as_ref())) } + /// Get value associated with `key`, and parse it as a float, or else use `default`. /// /// Returns `Err(…)` if `key` is defined but not a float. pub fn get_float_or_default(&self, key: &str, default: f64) -> Result<f64> { self.get_float(key).unwrap_or(Ok(default)) } + /// Get value associated with `key`, and parse it as a boolean. /// /// Returns `None` if `key` is not defined, @@ -773,6 +814,7 @@ impl Configuration { let Value { value, defined_at } = self.get_val(key)?; Some(parse_bool(defined_at, value.as_ref())) } + /// Get value associated with `key`, and parse it as a boolean, or else use `default`. /// /// Returns `Err(…)` if `key` is defined but not equal to one of @@ -780,6 +822,7 @@ impl Configuration { pub fn get_bool_or_default(&self, key: &str, default: bool) -> Result<bool> { self.get_bool(key).unwrap_or(Ok(default)) } + /// Get value associated with `key`, and parse it as a comma-separated list. /// /// Commas in list entries can be escaped with `\,`. @@ -788,6 +831,7 @@ impl Configuration { let value = &self.get_val(key)?.value; Some(parse_list(value.as_ref())) } + /// Get value associated with `key`, and parse it as a comma-separated list, or else use `default`. /// /// Commas in list entries can be escaped with `\,`. @@ -802,11 +846,12 @@ impl Configuration { self.get_list(key) .unwrap_or_else(|| default.into_iter().map(|s| s.as_ref().to_owned()).collect()) } + /// Merge `conf` into `self`, preferring values in `conf`. pub fn merge(&mut self, conf: &Configuration) { let mut must_sort = false; for (key, value) in &conf.items { - if let Ok(i) = self.items.binary_search_by(|(k, _)| k.cmp(key)) { + if let Ok(i) = self.binary_search_for(key) { self.items[i].1 = value.clone(); } else { self.items.push((key.clone(), value.clone())); @@ -817,6 +862,7 @@ impl Configuration { self.items.sort_by(|(k1, _), (k2, _)| k1.cmp(k2)); } } + /// Check that `self` follows the given schema. /// /// See the [POM specification](https://www.pom.computer/spec.html) for a description |