use std::{borrow::Borrow, hash::Hash, io, str::FromStr};
#[cfg(feature = "preserve_order")]
pub use indexmap;
#[cfg(feature = "preserve_order")]
use indexmap::IndexMap;
#[cfg(not(feature = "preserve_order"))]
use std::collections::BTreeMap;
pub mod _derive;
mod derive;
mod parser;
#[doc(hidden)]
pub use derive::Derpscfg;
pub use derive::{
Decode, DecodeCardinality, DecodeParamsCardinality, DecodeScalar, Decoder, parse,
};
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("unknown directive: {0}")]
UnknownDirective(String),
#[error("missing item: {0}")]
MissingValue(String),
#[error("cardinality mismatch: {0}")]
Cardinality(String),
#[error("conversion error: {0}")]
FromStr(String),
#[error("duplicate key: {0}")]
DuplicateKey(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>;
impl Scfg {
pub fn new() -> Self {
Default::default()
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &Vec<Directive>)> {
self.directives.iter()
}
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)
}
}
#[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(())
}
}