#![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, 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, defined_at: Location, } #[derive(Clone, Debug, Default)] pub struct Configuration { // wrap in an Arc for cheap cloning children: Arc, Configuration>>, values: Arc, Value>>, } #[non_exhaustive] pub enum Error { #[cfg(feature = "std")] IO(std::io::Error), BadInt(Location, Box), BadUInt(Location, Box), BadFloat(Location, Box), BadBool(Location, Box), } #[cfg(feature = "std")] impl From for Error { fn from(value: std::io::Error) -> Self { Self::IO(value) } } pub type Result = std::result::Result; fn parse_int(location: &Location, string: &str) -> Result { 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 { 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 { 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 { 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 { todo!() } impl Configuration { #[cfg(feature = "std")] pub fn load(_reader: R) -> Result { todo!() } pub fn load_str(_s: &str) -> Result { 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 { 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> { 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 { self.get_int(key).unwrap_or(Ok(default)) } pub fn get_uint(&self, key: &str) -> Option> { 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 { self.get_uint(key).unwrap_or(Ok(default)) } pub fn get_float(&self, key: &str) -> Option> { 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 { self.get_float(key).unwrap_or(Ok(default)) } pub fn get_bool(&self, key: &str) -> Option> { 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 { self.get_bool(key).unwrap_or(Ok(default)) } pub fn get_list(&self, key: &str) -> Option> { 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, ) -> Vec { 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) } } }