summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-09-07 22:51:58 -0400
committerpommicket <pommicket@gmail.com>2025-09-07 22:51:58 -0400
commit8d5b16efda7c21fabd3cec655d143ed3e78ea5e6 (patch)
treeabef05b4f14321b34ba1b7c603d1eb7512aa5972 /src
parent575bb3913b01ec83490d61f0540ccdcb4d861845 (diff)
More parser
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs126
1 files changed, 124 insertions, 2 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 9e08e06..55a407c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
+use alloc::borrow::Cow;
#[cfg(not(feature = "std"))]
use alloc::collections::BTreeMap as Map;
use alloc::sync::Arc;
@@ -36,6 +37,54 @@ pub struct Configuration {
values: Arc<Map<Box<str>, Value>>,
}
+impl fmt::Display for Configuration {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut lines = vec![];
+ fn format_value(val: &str) -> Cow<str> {
+ if val.chars().all(|c| !c.is_ascii_control())
+ && !val.starts_with(['\'', '"', '`'])
+ && !val.starts_with(char::is_whitespace)
+ {
+ return Cow::Borrowed(val);
+ }
+ let mut quoted = String::from("\"");
+ for c in val.chars() {
+ if c == '"' {
+ quoted.push_str("\\\"");
+ } else if c == '\n' {
+ quoted.push_str("\\n");
+ } else if c == '\r' {
+ quoted.push_str("\\r");
+ } else if c == '\t' {
+ quoted.push_str("\\t");
+ } else if c == '\\' {
+ quoted.push('\\');
+ } else if c.is_ascii_control() {
+ quoted.push_str(&format!("\\x{:02x}", c as u32));
+ } else {
+ quoted.push(c);
+ }
+ }
+ quoted.push('"');
+ Cow::Owned(quoted)
+ }
+ fn add_lines(lines: &mut Vec<String>, prefix: &str, conf: &Configuration) {
+ for (key, val) in conf.values.iter() {
+ lines.push(format!("{prefix}{key}: {}", format_value(&val.value)));
+ }
+ for (key, child) in conf.children.iter() {
+ add_lines(lines, &format!("{prefix}{key}."), child);
+ }
+ }
+ add_lines(&mut lines, "", self);
+ lines.sort();
+ for line in lines {
+ writeln!(f, "{line}")?;
+ }
+ Ok(())
+ }
+}
+
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
@@ -62,8 +111,71 @@ pub enum Error {
}
impl fmt::Display for Error {
- fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
- todo!()
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::IO(message, io_err) => write!(f, "{message}: {io_err}"),
+ Self::IllegalCharacter(location, c) => {
+ write!(f, "{location}: illegal character {}", c.escape_debug())
+ }
+ Self::InvalidUtf8(location) => write!(f, "{location}: invalid UTF-8"),
+ Self::BadInt(location, value) => {
+ write!(f, "{location}: invalid integer: {}", value.escape_debug())
+ }
+ Self::BadUInt(location, value) => write!(
+ f,
+ "{location}: invalid (unsigned) integer: {}",
+ value.escape_debug()
+ ),
+ Self::BadFloat(location, value) => {
+ write!(f, "{location}: invalid number: {}", value.escape_debug())
+ }
+ Self::BadBool(location, value) => write!(
+ f,
+ "{location}: value {} should be off/false/no or on/true/yes",
+ value.escape_debug()
+ ),
+ Self::UnmatchedLeftBrace(location) => write!(f, "{location}: [ has no matching ]"),
+ Self::InvalidKey(location, key) => {
+ write!(f, "{location}: invalid key {}", key.escape_debug())
+ }
+ Self::InvalidValue(location) => write!(f, "{location}: value contains null characters"),
+ Self::InvalidLine(location) => write!(
+ f,
+ "{location}: line should either start with [ or contain ="
+ ),
+ Self::StrayCharsAfterQuotedString(location) => {
+ write!(f, "{location}: stray characters after string value")
+ }
+ Self::UnterminatedString(location, delimiter) => {
+ write!(f, "{location}: missing {delimiter} to close string")
+ }
+ Self::InvalidEscapeSequence(location, sequence) => write!(
+ f,
+ "{location}: invalid escape sequence {} (try using \\\\ instead of \\ maybe?)",
+ sequence.escape_debug()
+ ),
+ Self::Multiple(errs) => {
+ let mut first = true;
+ for err in errs {
+ if !first {
+ writeln!(f)?;
+ }
+ first = false;
+ write!(f, "{err}")?;
+ }
+ Ok(())
+ }
+ }
+ }
+}
+
+impl core::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ if let Error::IO(_, e) = self {
+ Some(e)
+ } else {
+ None
+ }
}
}
@@ -484,6 +596,16 @@ impl Configuration {
}
node.clone()
}
+ pub fn keys(&self) -> impl '_ + Iterator<Item = &str> {
+ self.values
+ .keys()
+ .chain(
+ self.children
+ .keys()
+ .filter(|&k| !self.values.contains_key(k)),
+ )
+ .map(|x| x.as_ref())
+ }
fn get_val(&self, key: &str) -> Option<&Value> {
let Some(last_dot) = key.rfind('.') else {
return self.values.get(key);