diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 225 |
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) + } + } +} |