summaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-09-10 11:57:03 -0400
committerpommicket <pommicket@gmail.com>2025-09-10 11:57:03 -0400
commitf1767908837d0e6a0da2aa73009ce11de6fb359d (patch)
tree8cb03ac742d3e7d2f36ccf38a319b5bc13cf4685 /src/lib.rs
parented0182736a20e0987c6dc9c5e086a30fd1b02f8b (diff)
Remove schemas, add unread_keys
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs122
1 files changed, 51 insertions, 71 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 0763fa7..33d1d2d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -13,9 +13,8 @@ use alloc::vec::Vec;
use alloc::{format, vec};
use core::fmt;
use core::mem::take;
+use core::sync::atomic::{AtomicBool, Ordering};
-mod schema;
-pub use schema::Schema;
#[cfg(test)]
mod tests;
@@ -38,19 +37,6 @@ impl Location {
pub fn line(&self) -> u64 {
self.line
}
-
- /// Dummy location for internal use
- fn dummy() -> Self {
- Self {
- file: Arc::from(""),
- line: 0,
- }
- }
-
- fn is_dummy(&self) -> bool {
- // we never use line number of 0 ordinarily
- self.line == 0
- }
}
impl fmt::Display for Location {
@@ -60,17 +46,18 @@ impl fmt::Display for Location {
}
/// A string value, together with location information about where it is defined.
-#[derive(Clone, Debug)]
+#[derive(Debug)]
struct Value {
value: Box<str>,
defined_at: Location,
+ read: AtomicBool,
}
/// A parsed POM configuration.
#[derive(Clone, Debug, Default)]
pub struct Configuration {
/// List of items in configuration, sorted by key.
- items: Vec<(Box<str>, Value)>,
+ items: Vec<(Box<str>, Arc<Value>)>,
}
impl fmt::Display for Configuration {
@@ -82,7 +69,7 @@ impl fmt::Display for Configuration {
}
}
-/// A parsing or schema error.
+/// A parsing error.
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
@@ -134,18 +121,6 @@ pub enum Error {
/// None of the errors in the array will be [`Error::Multiple`]'s,
/// and the array will contain at least two elements.
Multiple(Box<[Error]>),
- /// Bad type in schema
- SchemaBadType(Location, Box<str>),
- /// Bad maxlength in schema
- SchemaBadMaxLength(Location, Box<str>),
- /// Invalid schema key
- SchemaBadKey(Location, Box<str>),
- /// Value is less than the schema-imposed minimum
- SchemaValueLessThanMin(Location, Box<str>, f64, f64),
- /// Value is greater than the schema-imposed maximum
- SchemaValueGreaterThanMax(Location, Box<str>, f64, f64),
- /// Value is greater than the schema-imposed maxlength
- SchemaValueTooLong(Location, Box<str>, usize, usize),
}
impl fmt::Display for Error {
@@ -204,21 +179,6 @@ impl fmt::Display for Error {
}
Ok(())
}
- Self::SchemaBadType(l, t) => write!(f, "{l}: invalid type: {t:?}"),
- Self::SchemaBadMaxLength(l, m) => write!(f, "{l}: invalid maxlength: {m:?}"),
- Self::SchemaBadKey(l, k) => write!(f, "{l}: invalid schema key: {k}"),
- Self::SchemaValueLessThanMin(l, key, val, min) => write!(
- f,
- "{l}: {key}'s value of {val} is less than the minimum ({min})"
- ),
- Self::SchemaValueGreaterThanMax(l, key, val, max) => write!(
- f,
- "{l}: {key}'s value of {val} is greater than the maximum ({max})"
- ),
- Self::SchemaValueTooLong(l, key, len, maxlen) => write!(
- f,
- "{l}: {key}'s value has length {len}, which exceeds the maximum of {maxlen}"
- ),
}
}
}
@@ -621,7 +581,7 @@ impl Parser {
}
fn load(&mut self, filename: &str, reader: &mut dyn Read) -> Result<Configuration> {
- let mut items: Vec<(Box<str>, Value)> = vec![];
+ let mut items: Vec<(Box<str>, Arc<Value>)> = vec![];
let mut line: Vec<u8> = vec![];
let mut line_number: u64 = 0;
let mut current_section = String::new();
@@ -673,10 +633,11 @@ impl Parser {
self.read_quoted_value(value, reader, &location)?;
items.push((
key.into(),
- Value {
+ Arc::new(Value {
value: value.into(),
defined_at: location,
- },
+ read: AtomicBool::new(false),
+ }),
));
line_number = new_line_number;
} else {
@@ -686,10 +647,11 @@ impl Parser {
}
items.push((
key.into(),
- Value {
+ Arc::new(Value {
value: value.into(),
defined_at: location,
- },
+ read: AtomicBool::new(false),
+ }),
));
}
}
@@ -801,29 +763,25 @@ impl Configuration {
self.into_iter()
}
- /// Same as `iter()` (for now), but explicitly marks that keys should be in sorted order.
- ///
- /// This is used internally so that we know what we have to fix if `iter()`
- /// is ever changed to return a non-sorted iterator.
- fn iter_sorted(&self) -> ConfigurationIter<'_> {
- self.iter()
- }
-
- fn get_val(&self, key: &str) -> Option<&Value> {
+ fn get_val(&self, key: &str, mark_read: bool) -> Option<&Value> {
let idx = self.binary_search_for(key).ok()?;
- Some(&self.items[idx].1)
+ let v = &self.items[idx].1;
+ if mark_read {
+ v.read.store(true, Ordering::Relaxed);
+ }
+ Some(v)
}
/// 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())
+ Some(self.get_val(key, true)?.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> {
- if let Some(val) = self.get_val(key) {
+ if let Some(val) = self.get_val(key, false) {
Some(val.defined_at.clone())
} else {
// Check if `key` has any defined subkeys
@@ -855,7 +813,9 @@ impl Configuration {
/// and `Some(Err(…))` if `key` is defined but not an integer.
#[must_use]
pub fn get_int(&self, key: &str) -> Option<Result<i64>> {
- let Value { value, defined_at } = self.get_val(key)?;
+ let Value {
+ value, defined_at, ..
+ } = self.get_val(key, true)?;
Some(parse_int(defined_at, value.as_ref()))
}
@@ -872,7 +832,9 @@ impl Configuration {
/// and `Some(Err(…))` if `key` is defined but not an unsigned integer.
#[must_use]
pub fn get_uint(&self, key: &str) -> Option<Result<u64>> {
- let Value { value, defined_at } = self.get_val(key)?;
+ let Value {
+ value, defined_at, ..
+ } = self.get_val(key, true)?;
Some(parse_uint(defined_at, value.as_ref()))
}
@@ -889,7 +851,9 @@ impl Configuration {
/// and `Some(Err(…))` if `key` is defined but not a float.
#[must_use]
pub fn get_float(&self, key: &str) -> Option<Result<f64>> {
- let Value { value, defined_at } = self.get_val(key)?;
+ let Value {
+ value, defined_at, ..
+ } = self.get_val(key, true)?;
Some(parse_float(defined_at, value.as_ref()))
}
@@ -907,7 +871,9 @@ impl Configuration {
/// `off`, `no`, `false`, `on`, `yes`, `true`.
#[must_use]
pub fn get_bool(&self, key: &str) -> Option<Result<bool>> {
- let Value { value, defined_at } = self.get_val(key)?;
+ let Value {
+ value, defined_at, ..
+ } = self.get_val(key, true)?;
Some(parse_bool(defined_at, value.as_ref()))
}
@@ -924,7 +890,7 @@ impl Configuration {
/// Commas in list entries can be escaped with `\,`.
#[must_use]
pub fn get_list(&self, key: &str) -> Option<Vec<String>> {
- let value = &self.get_val(key)?.value;
+ let value = &self.get_val(key, true)?.value;
Some(parse_list(value.as_ref()))
}
@@ -967,15 +933,29 @@ impl Configuration {
}
}
- /// Parse `self` as a [`Schema`].
- pub fn to_schema(&self) -> Result<Schema> {
- Schema::try_from(self)
+ /// Returns an iterator over all keys whose values have not been read.
+ ///
+ /// This includes getting them through [`Self::get`], [`Self::get_or_default`], [`Self::get_int`], etc.
+ /// It also includes getting them through [`Self::get`] called on a section obtained via [`Self::section`].
+ ///
+ /// The order of the items returned is arbitrary and may change in future versions without notice.
+ ///
+ /// Beware of race conditions when using this function in a multithreaded program
+ /// (you should wait for all threads to finish reading the configuration before calling this).
+ pub fn unread_keys(&self) -> impl '_ + Iterator<Item = &str> {
+ self.items.iter().filter_map(|(k, v)| {
+ if !v.read.load(Ordering::Relaxed) {
+ Some(k.as_ref())
+ } else {
+ None
+ }
+ })
}
}
/// Opaque type returned by [`Configuration::iter`].
#[derive(Clone, Debug)]
-pub struct ConfigurationIter<'a>(core::slice::Iter<'a, (Box<str>, Value)>);
+pub struct ConfigurationIter<'a>(core::slice::Iter<'a, (Box<str>, Arc<Value>)>);
impl<'a> Iterator for ConfigurationIter<'a> {
type Item = (&'a str, &'a str);