use std::collections::HashMap;
type Branch<'a> = HashMap<&'a str, Node<'a>>;
#[derive(Clone, Debug, PartialEq)]
pub enum Node<'a> {
Branch(Branch<'a>),
Leaf(&'a str),
}
pub fn parse(s: &str) -> Option<Branch<'_>> {
let mut it = s.lines().filter(|i| !i.trim().is_empty()).peekable();
let i = it.peek()?;
let dcl = i.find(':')?;
if dcl != 0 && i[dcl + 1..].find(':').is_some() {
parse_colsv(it)
} else {
parse_groups(it)
}
}
fn parse_colsv<'a>(it: impl Iterator<Item = &'a str>) -> Option<Branch<'a>> {
let mut ret = HashMap::new();
for i in it {
let dcl1 = i.find(':')?;
if dcl1 == 0 {
return None;
}
let kvp = &i[dcl1 + 1..];
let dcl2 = kvp.find(':')?;
let (obj, key, value) = (&i[..dcl1], &kvp[..dcl2], &kvp[dcl2 + 1..]);
let sel = select(&mut ret, obj)?;
if !final_leaf_push(sel, key, value) {
return None;
}
}
Some(ret)
}
#[derive(Default)]
struct GroupParser<'a> {
ret: Branch<'a>,
selobj: Option<(&'a str, HashMap<&'a str, &'a str>)>,
}
fn parse_groups<'a>(it: impl Iterator<Item = &'a str>) -> Option<Branch<'a>> {
let mut gp = GroupParser::default();
for i in it {
let dcl = i.find(':')?;
if dcl == 0 {
if !gp.finish_selobj() {
return None;
}
gp.selobj = Some((&i[1..], HashMap::new()));
} else if let Some((_, ref mut kvm)) = &mut gp.selobj {
kvm.insert(&i[..dcl], i[dcl + 1..].trim_start());
} else {
return None;
}
}
if gp.finish_selobj() {
Some(gp.ret)
} else {
None
}
}
impl<'a> GroupParser<'a> {
fn finish_selobj(&mut self) -> bool {
if let Some((oname, okvm)) = self.selobj.take() {
if let Some(sel) = select(&mut self.ret, oname) {
okvm.into_iter()
.all(|(key, value)| final_leaf_push(sel, key, value))
} else {
false
}
} else {
true
}
}
}
fn final_leaf_push<'a>(sel: &mut Branch<'a>, key: &'a str, value: &'a str) -> bool {
use std::collections::hash_map::Entry;
match sel.entry(key) {
Entry::Occupied(_) => return false,
Entry::Vacant(vac) => vac.insert(Node::Leaf(value)),
};
true
}
fn select<'s, 'a>(ret: &'s mut Branch<'a>, obj: &'a str) -> Option<&'s mut Branch<'a>> {
obj.split('/')
.filter(|i| !i.is_empty())
.try_fold(ret, |sel, i| {
sel.entry(i)
.or_insert_with(|| Node::Branch(HashMap::new()))
.branch_mut()
})
}
impl<'a> Node<'a> {
fn branch_mut<'s>(&'s mut self) -> Option<&'s mut Branch<'a>> {
if let Node::Branch(ref mut x) = self {
Some(x)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ex0() -> HashMap<&'static str, Node<'static>> {
let mut inner2 = HashMap::new();
let mut inner1 = HashMap::new();
inner1.insert("trfull", Node::Leaf("Ethik"));
inner1.insert("tanken", Node::Leaf("Ja "));
inner2.insert("ETH", Node::Branch(inner1));
let mut inner1 = HashMap::new();
inner1.insert("trfull", Node::Leaf("Rufen"));
inner1.insert("tanken", Node::Leaf("Nein"));
inner2.insert("RUF", Node::Branch(inner1));
let mut inner1 = HashMap::new();
inner1.insert("locan ", Node::Leaf("bl uüpp"));
inner1.insert("Fach", Node::Branch(inner2));
let mut inner2 = HashMap::new();
inner2.insert("Orga", Node::Branch(inner1));
let mut inner1 = HashMap::new();
inner1.insert("Schule", Node::Branch(inner2));
inner1
}
#[test]
fn ex0_groups() {
assert_eq!(
parse(
r#"
:Schule/Orga/Fach/ETH
trfull: Ethik
tanken: Ja
:Schule/Orga/Fach/RUF
trfull: Rufen
tanken: Nein
:Schule/Orga
locan : bl uüpp
"#
),
Some(ex0())
);
}
#[test]
fn ex0_colsv() {
assert_eq!(
parse(
r#"
Schule/Orga/Fach/ETH:trfull:Ethik
Schule/Orga/Fach/ETH:tanken:Ja
Schule/Orga/Fach/RUF:trfull:Rufen
Schule/Orga/Fach/RUF:tanken:Nein
Schule/Orga:locan :bl uüpp
"#
),
Some(ex0())
);
}
}