use std::{
borrow::Borrow,
collections::{LinkedList, VecDeque},
fmt::Display,
hash::Hash,
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
#[cfg(feature = "preserve_order")]
use indexmap::IndexMap;
#[cfg(not(feature = "preserve_order"))]
use std::collections::BTreeMap;
mod parser;
pub mod prelude;
pub type ParseError = parser::Error;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("parser error: {0}")]
Parse(#[from] ParseError),
#[error("excess parameters: {0}")]
ExcessParam(String),
#[error("cardinality mismatch: {0}")]
Cardinality(String),
#[error("conversion error: {0}")]
FromStr(String),
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct Scfg {
directives: Map<String, Vec<Directive>>,
}
#[cfg(not(feature = "preserve_order"))]
type Map<K, V> = BTreeMap<K, V>;
#[cfg(feature = "preserve_order")]
type Map<K, V> = IndexMap<K, V>;
pub use derpscfg_derive::Derpscfg;
pub fn parse<T>(input: &str) -> Result<T, Error>
where
T: TryFrom<Scfg, Error = Error>,
{
let p = input.parse::<Scfg>()?;
T::try_from(p)
}
pub trait Decode<T>: Sized {
fn decode(field: &'static str, d: Directive) -> Result<Vec<T>, Error>;
}
pub trait DecodeScalar: Sized {
fn decode_scalar(field: &'static str, d: Directive) -> Result<Vec<Self>, Error>;
}
pub trait DecodeCardinality<S, T>: Sized {
fn cardinality(field: &'static str, d: Vec<Directive>) -> Result<T, Error>;
}
pub trait DecodeParamsCardinality<S, T>: Sized {
fn params_cardinality(field: &'static str, d: &mut Directive) -> Result<T, Error>;
}
macro_rules! decode_from_str {
($i:ident) => {
impl DecodeScalar for $i {
fn decode_scalar(f: &'static str, d: Directive) -> Result<Vec<$i>, Error> {
if d.child().is_some() {
return Err(Error::FromStr(format!(
"'{f}' should be scalar value, but has child node"
)));
}
d.params()
.into_iter()
.map(|p| {
$i::from_str(p).map_err(|e| {
Error::FromStr(format!("invalid value '{p}' for '{f}': {e}"))
})
})
.collect()
}
}
};
}
decode_from_str!(bool);
decode_from_str!(char);
decode_from_str!(u8);
decode_from_str!(u16);
decode_from_str!(u32);
decode_from_str!(u64);
decode_from_str!(u128);
decode_from_str!(i8);
decode_from_str!(i16);
decode_from_str!(i32);
decode_from_str!(i64);
decode_from_str!(i128);
decode_from_str!(f32);
decode_from_str!(f64);
decode_from_str!(usize);
decode_from_str!(isize);
decode_from_str!(String);
decode_from_str!(IpAddr);
decode_from_str!(Ipv4Addr);
decode_from_str!(Ipv6Addr);
impl<T> Decode<T> for T
where
T: DecodeScalar,
{
fn decode(f: &'static str, d: Directive) -> Result<Vec<T>, Error> {
T::decode_scalar(f, d)
}
}
impl Decode<Directive> for Directive {
fn decode(_f: &'static str, d: Directive) -> Result<Vec<Directive>, Error> {
Ok(vec![d])
}
}
impl Scfg {
pub fn new() -> Self {
Default::default()
}
pub fn get<Q>(&self, name: &Q) -> Option<&Directive>
where
String: Borrow<Q>,
Q: Ord + Eq + Hash + ?Sized,
{
self.directives.get(name).and_then(|d| d.first())
}
pub fn get_all<Q>(&self, name: &Q) -> Option<&[Directive]>
where
String: Borrow<Q>,
Q: Ord + Eq + Hash + ?Sized,
{
self.directives.get(name).map(|ds| ds.as_ref())
}
pub fn get_all_mut<Q>(&mut self, name: &Q) -> Option<&mut Vec<Directive>>
where
String: Borrow<Q>,
Q: Ord + Eq + Hash + ?Sized,
{
self.directives.get_mut(name)
}
pub fn contains<Q>(&self, name: &Q) -> bool
where
String: Borrow<Q>,
Q: Ord + Eq + Hash + ?Sized,
{
self.directives.contains_key(name)
}
pub fn add(&mut self, name: impl Into<String>) -> &mut Directive {
self.add_directive(name, Directive::default())
}
fn add_directive(&mut self, name: impl Into<String>, directive: Directive) -> &mut Directive {
let entry = self.directives.entry(name.into()).or_default();
entry.push(directive);
entry.last_mut().unwrap()
}
pub fn remove<Q>(&mut self, name: &Q) -> Option<Vec<Directive>>
where
String: Borrow<Q>,
Q: Ord + Eq + Hash + ?Sized,
{
#[cfg(feature = "preserve_order")]
{
self.directives.shift_remove(name)
}
#[cfg(not(feature = "preserve_order"))]
{
self.directives.remove(name)
}
}
pub fn remove_entry<Q>(&mut self, name: &Q) -> Option<(String, Vec<Directive>)>
where
String: Borrow<Q>,
Q: Ord + Eq + Hash + ?Sized,
{
#[cfg(feature = "preserve_order")]
{
self.directives.shift_remove_entry(name)
}
#[cfg(not(feature = "preserve_order"))]
{
self.directives.remove_entry(name)
}
}
pub fn write<W>(&self, writer: &mut W) -> io::Result<()>
where
W: io::Write,
{
self.write_with_indent(0, writer)
}
fn write_with_indent<W>(&self, indent: usize, wtr: &mut W) -> io::Result<()>
where
W: io::Write,
{
let mut prefix = "";
for (name, directives) in &self.directives {
for directive in directives {
wtr.write_all(prefix.as_ref())?;
prefix = "";
for _ in 0..indent {
write!(wtr, "\t")?;
}
write!(wtr, "{}", shell_words::quote(name))?;
for param in &directive.params {
write!(wtr, " {}", shell_words::quote(param))?;
}
if let Some(ref child) = directive.child {
wtr.write_all(b" {\n")?;
child.write_with_indent(indent + 1, wtr)?;
for _ in 0..indent {
wtr.write_all(b"\t")?;
}
wtr.write_all(b"}")?;
prefix = "\n";
}
wtr.write_all(b"\n")?;
}
}
Ok(())
}
}
impl FromStr for Scfg {
type Err = ParseError;
fn from_str(src: &str) -> Result<Self, Self::Err> {
let r = std::io::Cursor::new(src.as_bytes());
parser::document(r)
}
}
impl<K: Into<String>> std::iter::FromIterator<(K, Directive)> for Scfg {
fn from_iter<T>(it: T) -> Self
where
T: IntoIterator<Item = (K, Directive)>,
{
let mut scfg = Self::default();
for (name, directive) in it {
let name = name.into();
scfg.directives
.entry(name)
.or_insert_with(Vec::new)
.push(directive);
}
scfg
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct Directive {
params: Vec<String>,
child: Option<Scfg>,
}
impl Directive {
pub fn new() -> Self {
Default::default()
}
pub fn params(&self) -> &[String] {
&self.params
}
pub fn pop_param(&mut self) -> Option<String> {
if self.params.is_empty() {
None
} else {
Some(self.params.remove(0))
}
}
pub fn take_params(&mut self) -> Vec<String> {
std::mem::take(&mut self.params)
}
pub fn append_param(&mut self, param: impl Into<String>) -> &mut Self {
self.params.push(param.into());
self
}
pub fn clear_params(&mut self) {
self.params.clear();
}
pub fn child(&self) -> Option<&Scfg> {
self.child.as_ref()
}
pub fn take_child(&mut self) -> Option<Scfg> {
self.child.take()
}
pub fn get_or_create_child(&mut self) -> &mut Scfg {
self.child.get_or_insert_with(Scfg::new)
}
}
pub struct Decoder;
impl<T> DecodeCardinality<T, T> for Decoder
where
T: Decode<T>,
{
fn cardinality(f: &'static str, d: Vec<Directive>) -> Result<T, Error> {
if d.len() != 1 {
Err(Error::Cardinality(format!(
"found {} values for '{f}', but must be exactly one",
d.len()
)))
} else {
let r = T::decode(f, d.into_iter().next().unwrap())?;
if r.len() != 1 {
Err(Error::Cardinality(format!(
"found {} values for '{f}', but must be exactly one",
r.len()
)))
} else {
Ok(r.into_iter().next().unwrap())
}
}
}
}
impl<T> DecodeCardinality<T, Option<T>> for Decoder
where
T: Decode<T>,
{
fn cardinality(f: &'static str, d: Vec<Directive>) -> Result<Option<T>, Error> {
if d.len() > 1 {
Err(Error::Cardinality(format!(
"found {} values for '{f}', but must be one or none",
d.len()
)))
} else {
let r = d.into_iter().next().map(|d| T::decode(f, d)).transpose()?;
r.and_then(|v| {
if v.len() > 1 {
Some(Err(Error::Cardinality(format!(
"found {} values for '{f}', but must be one or none",
v.len()
))))
} else {
v.into_iter().next().map(|t| Ok(t))
}
})
.transpose()
}
}
}
macro_rules! decode_cardinality {
($i:ident) => {
impl<T> DecodeCardinality<T, $i<T>> for Decoder
where
T: Decode<T>,
{
fn cardinality(f: &'static str, d: Vec<Directive>) -> Result<$i<T>, Error> {
let r: Vec<_> = d.into_iter().map(|d| T::decode(f, d)).collect::<Vec<_>>();
let t: Result<Vec<Vec<T>>, _> = r.into_iter().collect();
Ok(t?.into_iter().flat_map(|i| i).collect())
}
}
};
}
decode_cardinality!(Vec);
decode_cardinality!(VecDeque);
decode_cardinality!(LinkedList);
impl<T, E> DecodeParamsCardinality<T, T> for Decoder
where
T: FromStr<Err = E>,
E: Display + Sized,
{
fn params_cardinality(f: &'static str, d: &mut Directive) -> Result<T, Error> {
let s = d.pop_param().ok_or(Error::Cardinality(format!(
"'{f}' has no more parameters, but must have at least one"
)))?;
T::from_str(s.as_str())
.map_err(|e| Error::FromStr(format!("invalid parameter value '{s}' for '{f}': {e}")))
}
}
impl<T, E> DecodeParamsCardinality<T, Option<T>> for Decoder
where
T: FromStr<Err = E>,
E: Display + Sized,
{
fn params_cardinality(f: &'static str, d: &mut Directive) -> Result<Option<T>, Error> {
d.pop_param()
.map(|s| {
T::from_str(s.as_str()).map_err(|e| {
Error::FromStr(format!("invalid parameter value '{s}' for '{f}': {e}"))
})
})
.transpose()
}
}
macro_rules! decode_params_cardinality {
($i:ident) => {
impl<T, E> DecodeParamsCardinality<T, $i<T>> for Decoder
where
T: FromStr<Err = E>,
E: Display + Sized,
{
fn params_cardinality(f: &'static str, d: &mut Directive) -> Result<$i<T>, Error> {
d.take_params()
.into_iter()
.map(|s| {
T::from_str(&s).map_err(|e| {
Error::FromStr(format!("invalid parameter value '{s}' for '{f}': {e}"))
})
})
.collect()
}
}
};
}
decode_params_cardinality!(Vec);
decode_params_cardinality!(VecDeque);
decode_params_cardinality!(LinkedList);
#[cfg(test)]
mod test {
use super::*;
type Result = std::result::Result<(), Box<dyn std::error::Error>>;
#[test]
fn flat() -> Result {
let src = r#"dir1 param1 param2 param3
dir2
dir3 param1
# comment
dir4 "param 1" 'param 2'
"#;
let cfg = Scfg::from_str(src)?;
let exp = vec![
(
"dir1",
Directive {
params: vec!["param1".into(), "param2".into(), "param3".into()],
child: None,
},
),
(
"dir2",
Directive {
params: vec![],
child: None,
},
),
(
"dir3",
Directive {
params: vec!["param1".into()],
child: None,
},
),
(
"dir4",
Directive {
params: vec!["param 1".into(), "param 2".into()],
child: None,
},
),
]
.into_iter()
.collect::<Scfg>();
assert_eq!(cfg, exp);
Ok(())
}
#[test]
fn simple_blocks() -> Result {
let src = r#"block1 {
dir1 param1 param2
dir2 param1
}
block2 {
}
block3 {
# comment
}
block4 param1 "param2" {
dir1
}"#;
let cfg = Scfg::from_str(src)?;
let mut exp = Scfg::new();
let block1 = exp.add("block1");
let block = block1.get_or_create_child();
block
.add("dir1")
.append_param("param1")
.append_param("param2");
block.add("dir2").append_param("param1");
exp.add("block2").get_or_create_child();
exp.add("block3").get_or_create_child();
exp.add("block4")
.append_param("param1")
.append_param("param2")
.get_or_create_child()
.add("dir1");
assert_eq!(cfg, exp);
Ok(())
}
#[test]
fn nested() -> Result {
let src = r#"block1 {
block2 {
dir1 param1
}
block3 {
}
}
block4 {
block5 {
block6 param1 {
dir1
}
}
dir1
}"#;
let cfg = Scfg::from_str(src)?;
let mut exp = Scfg::new();
let block1 = exp.add("block1").get_or_create_child();
block1
.add("block2")
.get_or_create_child()
.add("dir1")
.append_param("param1");
block1.add("block3").get_or_create_child();
let block4 = exp.add("block4").get_or_create_child();
block4
.add("block5")
.get_or_create_child()
.add("block6")
.append_param("param1")
.get_or_create_child()
.add("dir1");
block4.add("dir1");
assert_eq!(cfg, exp);
Ok(())
}
#[test]
fn write() -> Result {
let src = r#"dir1 param1 param2 param3
dir2
dir3 param1
# comment
dir4 "param 1" 'param 2'
"#;
let doc = Scfg::from_str(src)?;
let mut out = Vec::new();
doc.write(&mut out)?;
let exp = r#"dir1 param1 param2 param3
dir2
dir3 param1
dir4 'param 1' 'param 2'
"#;
assert_eq!(std::str::from_utf8(&out)?, exp);
Ok(())
}
#[test]
fn write_block() -> Result {
let src = r#"block1 {
dir1 param1 param2
dir2 param1
}
block2 {
}
block3 {
# comment
}
block4 param1 "param2" {
dir1
}"#;
let doc = Scfg::from_str(src)?;
let mut out = Vec::new();
doc.write(&mut out)?;
let exp = r#"block1 {
dir1 param1 param2
dir2 param1
}
block2 {
}
block3 {
}
block4 param1 param2 {
dir1
}
"#;
assert_eq!(std::str::from_utf8(&out)?, exp);
Ok(())
}
}