summaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs74
1 files changed, 60 insertions, 14 deletions
diff --git a/src/lib.rs b/src/lib.rs
index c2d7771..bd01f59 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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