From 8253cbea32f431aa9c5cd077690b2166704c989d Mon Sep 17 00:00:00 2001 From: pommicket Date: Sun, 7 Sep 2025 17:38:11 -0400 Subject: Start rust library --- .gitignore | 1 + Cargo.lock | 7 ++ Cargo.toml | 10 +++ rustfmt.toml | 1 + src/lib.rs | 225 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 244 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 rustfmt.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..61170a9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "pom-rs" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fbe4df6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "pom-rs" +version = "0.1.0" +edition = "2024" + +[dependencies] + +[features] +default = ["std"] +std = [] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..218e203 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true 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, + 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) + } + } +} -- cgit v1.2.3