extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use crate::{ConfigError, Node, Origin};
#[derive(Debug, Clone)]
pub struct Section<'a> {
map: Option<&'a BTreeMap<String, Node>>,
prefix: String,
}
static EMPTY: BTreeMap<String, Node> = BTreeMap::new();
#[must_use]
pub fn first_origin(node: &Node) -> Origin {
fn walk(node: &Node, depth: usize) -> Option<&Origin> {
if depth > crate::MAX_NESTING_DEPTH {
return None;
}
match node {
Node::Scalar(_, origin) => Some(origin),
Node::Seq(items) => items.iter().find_map(|n| walk(n, depth + 1)),
Node::Map(map) => map.values().find_map(|n| walk(n, depth + 1)),
}
}
walk(node, 0).cloned().unwrap_or(Origin::Default)
}
fn invalid(setting: String, origin: Origin, message: impl Into<String>) -> ConfigError {
ConfigError::Invalid {
setting,
origin,
message: message.into(),
}
}
impl<'a> Section<'a> {
pub fn root(node: Option<&'a Node>) -> Result<Self, ConfigError> {
Self::of(node, "")
}
pub fn of(node: Option<&'a Node>, prefix: impl Into<String>) -> Result<Self, ConfigError> {
let prefix = prefix.into();
match node {
None => Ok(Self { map: None, prefix }),
Some(Node::Map(map)) => Ok(Self {
map: Some(map),
prefix,
}),
Some(other) => Err(invalid(
prefix,
first_origin(other),
"expected a map of settings",
)),
}
}
fn path(&self, key: &str) -> String {
if self.prefix.is_empty() {
key.to_string()
} else {
alloc::format!("{}.{key}", self.prefix)
}
}
#[must_use]
pub fn path_of(&self, key: &str) -> String {
self.path(key)
}
#[must_use]
pub fn node(&self, key: &str) -> Option<&'a Node> {
self.map.unwrap_or(&EMPTY).get(key)
}
pub fn only(&self, allowed: &[&str]) -> Result<(), ConfigError> {
for (key, node) in self.map.unwrap_or(&EMPTY) {
if !allowed.contains(&key.as_str()) {
return Err(invalid(
self.path(key),
first_origin(node),
alloc::format!("unknown setting; known here: {}", allowed.join(", ")),
));
}
}
Ok(())
}
pub fn section(&self, key: &str) -> Result<Section<'a>, ConfigError> {
Section::of(self.node(key), self.path(key))
}
pub fn scalar(&self, key: &str) -> Result<Option<(&'a str, &'a Origin)>, ConfigError> {
match self.node(key) {
None => Ok(None),
Some(Node::Scalar(value, origin)) => Ok(Some((value.as_str(), origin))),
Some(other) => Err(invalid(
self.path(key),
first_origin(other),
"expected a single value",
)),
}
}
pub fn string(&self, key: &str) -> Result<Option<String>, ConfigError> {
Ok(self.scalar(key)?.map(|(v, _)| v.to_string()))
}
pub fn parse<T: core::str::FromStr>(
&self,
key: &str,
expect: &str,
) -> Result<Option<T>, ConfigError> {
match self.scalar(key)? {
None => Ok(None),
Some((value, origin)) => value.trim().parse::<T>().map(Some).map_err(|_| {
invalid(
self.path(key),
origin.clone(),
alloc::format!("got `{value}`, expected {expect}"),
)
}),
}
}
pub fn boolean(&self, key: &str) -> Result<Option<bool>, ConfigError> {
match self.scalar(key)? {
None => Ok(None),
Some((value, origin)) => match value.trim() {
"true" | "1" => Ok(Some(true)),
"false" | "0" => Ok(Some(false)),
other => Err(invalid(
self.path(key),
origin.clone(),
alloc::format!("got `{other}`, expected true/false"),
)),
},
}
}
pub fn choice(&self, key: &str, choices: &[&str]) -> Result<Option<String>, ConfigError> {
match self.scalar(key)? {
None => Ok(None),
Some((value, origin)) => {
let value = value.trim();
if choices.contains(&value) {
Ok(Some(value.to_string()))
} else {
Err(invalid(
self.path(key),
origin.clone(),
alloc::format!("got `{value}`, expected one of {}", choices.join(", ")),
))
}
}
}
}
pub fn strings(&self, key: &str) -> Result<Option<Vec<String>>, ConfigError> {
match self.node(key) {
None => Ok(None),
Some(Node::Scalar(value, _)) => Ok(Some(alloc::vec![value.clone()])),
Some(Node::Seq(items)) => items
.iter()
.map(|item| match item {
Node::Scalar(value, _) => Ok(value.clone()),
other => Err(invalid(
self.path(key),
first_origin(other),
"expected a list of values",
)),
})
.collect::<Result<Vec<_>, _>>()
.map(Some),
Some(other) => Err(invalid(
self.path(key),
first_origin(other),
"expected a list of values",
)),
}
}
pub fn items(&self, key: &str) -> Result<Vec<(String, &'a Node)>, ConfigError> {
match self.node(key) {
None => Ok(Vec::new()),
Some(Node::Seq(items)) => Ok(items
.iter()
.enumerate()
.map(|(i, item)| (alloc::format!("{}[{i}]", self.path(key)), item))
.collect()),
Some(other) => Err(invalid(
self.path(key),
first_origin(other),
"expected a list",
)),
}
}
}
#[must_use]
pub fn split_topic_shorthand(text: &str) -> (String, Option<String>) {
let bytes = text.as_bytes();
for (i, b) in bytes.iter().enumerate() {
let lone =
*b == b':' && bytes.get(i + 1) != Some(&b':') && (i == 0 || bytes[i - 1] != b':');
if lone {
return (
text[..i].trim().to_string(),
Some(text[i + 1..].trim().to_string()),
);
}
}
(text.trim().to_string(), None)
}