summaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs225
1 files changed, 225 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..465f927
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,225 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+extern crate alloc;
+#[cfg(not(feature = "std"))]
+use alloc::collections::BTreeMap as Map;
+use alloc::sync::Arc;
+#[cfg(feature = "std")]
+use std::collections::HashMap as Map;
+
+/// File and line information
+#[derive(Clone, Debug)]
+pub struct Location {
+ file: Arc<str>,
+ line: u64,
+}
+
+impl core::fmt::Display for Location {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "{}:{}", self.file, self.line)
+ }
+}
+
+/// A string value, together with location information about where it is defined.
+#[derive(Clone, Debug)]
+struct Value {
+ value: Box<str>,
+ defined_at: Location,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct Configuration {
+ // wrap in an Arc for cheap cloning
+ children: Arc<Map<Box<str>, Configuration>>,
+ values: Arc<Map<Box<str>, Value>>,
+}
+
+#[non_exhaustive]
+pub enum Error {
+ #[cfg(feature = "std")]
+ IO(std::io::Error),
+ BadInt(Location, Box<str>),
+ BadUInt(Location, Box<str>),
+ BadFloat(Location, Box<str>),
+ BadBool(Location, Box<str>),
+}
+
+#[cfg(feature = "std")]
+impl From<std::io::Error> for Error {
+ fn from(value: std::io::Error) -> Self {
+ Self::IO(value)
+ }
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+fn parse_int(location: &Location, string: &str) -> Result<i64> {
+ let bad_int = || Error::BadInt(location.clone(), string.into());
+ if !string
+ .bytes()
+ .all(|c| c.is_ascii_hexdigit() || c == b'x' || c == b'X' || c == b'-' || c == b'+')
+ {
+ return Err(bad_int());
+ }
+ let signless = string.strip_prefix(['-', '+']).unwrap_or(string);
+ let mut base = 10;
+ let baseless = signless
+ .strip_prefix("0x")
+ .or_else(|| signless.strip_prefix("0X"))
+ .unwrap_or_else(|| {
+ base = 16;
+ signless
+ });
+ for digit in baseless.bytes() {
+ if base == 10 && !digit.is_ascii_digit() {
+ return Err(bad_int());
+ }
+ }
+ string.parse().map_err(|_| bad_int())
+}
+
+fn parse_uint(location: &Location, string: &str) -> Result<u64> {
+ let bad_uint = || Error::BadUInt(location.clone(), string.into());
+ if !string
+ .bytes()
+ .all(|c| c.is_ascii_hexdigit() || c == b'x' || c == b'X' || c == b'+')
+ {
+ return Err(bad_uint());
+ }
+ let signless = string.strip_prefix('+').unwrap_or(string);
+ let mut base = 10;
+ let baseless = signless
+ .strip_prefix("0x")
+ .or_else(|| signless.strip_prefix("0X"))
+ .unwrap_or_else(|| {
+ base = 16;
+ signless
+ });
+ for digit in baseless.bytes() {
+ if base == 10 && !digit.is_ascii_digit() {
+ return Err(bad_uint());
+ }
+ }
+ let val = signless.parse().map_err(|_| bad_uint())?;
+ if val > i64::MAX as u64 {
+ return Err(bad_uint());
+ }
+ Ok(val)
+}
+
+fn parse_float(location: &Location, string: &str) -> Result<f64> {
+ let bad_float = || Error::BadFloat(location.clone(), string.into());
+ if !string.bytes().all(|c| {
+ c.is_ascii_digit() || c == b'.' || c == b'+' || c == b'-' || c == b'e' || c == b'E'
+ }) {
+ return Err(bad_float());
+ }
+ string.parse().map_err(|_| bad_float())
+}
+
+fn parse_bool(location: &Location, string: &str) -> Result<bool> {
+ match string {
+ "yes" | "on" | "true" => Ok(true),
+ "no" | "off" | "false" => Ok(false),
+ _ => Err(Error::BadBool(location.clone(), string.into())),
+ }
+}
+
+fn parse_list(_location: &Location, _string: &str) -> Vec<String> {
+ todo!()
+}
+
+impl Configuration {
+ #[cfg(feature = "std")]
+ pub fn load<R: std::io::BufRead>(_reader: R) -> Result<Configuration> {
+ todo!()
+ }
+ pub fn load_str(_s: &str) -> Result<Configuration> {
+ todo!()
+ }
+ pub fn section(&self, key: &str) -> Configuration {
+ let mut node = self;
+ for component in key.split('.') {
+ node = match self.children.get(component) {
+ Some(x) => x,
+ None => return Configuration::default(),
+ };
+ }
+ node.clone()
+ }
+ fn get_val(&self, key: &str) -> Option<&Value> {
+ let Some(last_dot) = key.rfind('.') else {
+ return self.values.get(key);
+ };
+ let (path, last_component) = key.split_at(last_dot);
+ let mut node = self;
+ for component in path.split('.') {
+ node = self.children.get(component)?;
+ }
+ node.values.get(last_component)
+ }
+ pub fn get(&self, key: &str) -> Option<&str> {
+ Some(self.get_val(key)?.value.as_ref())
+ }
+ pub fn location(&self, key: &str) -> Option<Location> {
+ Some(self.get_val(key)?.defined_at.clone())
+ }
+ pub fn has(&self, key: &str) -> bool {
+ self.get(key).is_some()
+ }
+ pub fn get_or_default<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
+ self.get(key).unwrap_or(default)
+ }
+ pub fn get_int(&self, key: &str) -> Option<Result<i64>> {
+ let Value { value, defined_at } = self.get_val(key)?;
+ Some(parse_int(defined_at, value.as_ref()))
+ }
+ pub fn get_int_or_default(&self, key: &str, default: i64) -> Result<i64> {
+ self.get_int(key).unwrap_or(Ok(default))
+ }
+ pub fn get_uint(&self, key: &str) -> Option<Result<u64>> {
+ let Value { value, defined_at } = self.get_val(key)?;
+ Some(parse_uint(defined_at, value.as_ref()))
+ }
+ pub fn get_uint_or_default(&self, key: &str, default: u64) -> Result<u64> {
+ self.get_uint(key).unwrap_or(Ok(default))
+ }
+ pub fn get_float(&self, key: &str) -> Option<Result<f64>> {
+ let Value { value, defined_at } = self.get_val(key)?;
+ Some(parse_float(defined_at, value.as_ref()))
+ }
+ pub fn get_float_or_default(&self, key: &str, default: f64) -> Result<f64> {
+ self.get_float(key).unwrap_or(Ok(default))
+ }
+ pub fn get_bool(&self, key: &str) -> Option<Result<bool>> {
+ let Value { value, defined_at } = self.get_val(key)?;
+ Some(parse_bool(defined_at, value.as_ref()))
+ }
+ pub fn get_bool_or_default(&self, key: &str, default: bool) -> Result<bool> {
+ self.get_bool(key).unwrap_or(Ok(default))
+ }
+ pub fn get_list(&self, key: &str) -> Option<Vec<String>> {
+ let Value { value, defined_at } = self.get_val(key)?;
+ Some(parse_list(defined_at, value.as_ref()))
+ }
+ pub fn get_list_or_default(
+ &self,
+ key: &str,
+ default: impl FnOnce() -> Vec<String>,
+ ) -> Vec<String> {
+ self.get_list(key).unwrap_or_else(default)
+ }
+ /// Merge `conf` into `self`, preferring values in `conf`.
+ pub fn merge(&mut self, conf: &Configuration) {
+ let new_values = Arc::make_mut(&mut self.values);
+ // merge conf.values into self.values
+ for (key, val) in conf.values.iter() {
+ new_values.insert(key.clone(), val.clone());
+ }
+ // merge conf.children into self.children
+ let new_children = Arc::make_mut(&mut self.children);
+ for (key, child) in conf.children.iter() {
+ new_children.entry(key.clone()).or_default().merge(child)
+ }
+ }
+}