confiner 0.2.1

A config language for things that look like trees.
Documentation
use super::*;

#[derive(Default)]
pub struct Loader
{
    by_name:    HashMap<String, grammar::Block>,
    refs:       HashMap<String, grammar::Block>,
    roots:      Vec<grammar::Block>,
    to_include: HashSet<PathBuf>
}

impl Loader
{
    fn add_elem(&mut self, elem: grammar::DocumentElem) -> StdResult
    {
        match elem
        {
            grammar::DocumentElem::Root(block) =>
            {
                self.record_names(&block)?;
                self.roots.push(block);
            }
            grammar::DocumentElem::Ref(block) =>
            {
                self.record_names(&block)?;

                match &block.name
                {
                    Some(name) =>
                        { self.refs.insert(name.clone(), block); }
                    None =>
                        { return Err(From::from("Ref blocks must be named")); }
                }
            }
            grammar::DocumentElem::Include(path) =>
            {
                self.to_include.insert(path);
            }
        }

        Ok(())
    }

    fn record_names(&mut self, block: &grammar::Block) -> StdResult
    {
        if let Some(name) = &block.name
        {
            if self.by_name.contains_key(name)
            {
                return Err(From::from(
                    format!("Multiple blocks named {}", name)
                ));
            }

            self.by_name.insert(name.to_owned(), block.clone());
        }

        for (_, maybe_child) in block.children.iter()
        {
            if let grammar::Child::Defined(child) = maybe_child
            {
                self.record_names(&child)?;
            }
        }

        Ok(())
    }

    pub fn add_document(&mut self, s: &str, path: impl AsRef<Path>)
        -> StdResult
    {
        let doc = grammar::document(s, path.as_ref())?;
        for elem in doc.elems
        {
            self.add_elem(elem)?;
        }

        Ok(())
    }

    pub fn children<'a>(&'a self, block: &'a grammar::Block)
        -> StdResult<Vec<grammar::Block>>
    {
        self.children_with_maybe_role(block, None)
    }

    pub fn children_with_role<'a>(
        &'a self,
        block: &'a grammar::Block,
        role:  &str
    )
        -> StdResult<Vec<grammar::Block>>
    {
        self.children_with_maybe_role(block, Some(role))
    }

    fn children_with_maybe_role<'a>(
        &'a self,
        block: &'a grammar::Block,
        role:  Option<&str>
    )
        -> StdResult<Vec<grammar::Block>>
    {
        block.children
            .iter()
            .filter(|(it_role, _)| it_role.as_ref().map(String::as_str) == role)
            .map(|(_, child)| match child
                {
                    grammar::Child::Defined(block) => Ok(block.clone()),
                    grammar::Child::Ref(name) =>
                        self.refs.get(name)
                            .cloned()
                            .ok_or_else(|| StdError::from(
                                format!("No block named '{}'", name)
                            ))
                }
            )
            .collect()
    }

    pub fn roots<'a>(&'a self) -> impl Iterator<Item=grammar::Block> + 'a
    {
        self.roots.iter().cloned()
    }

    pub fn to_include(&self) -> &HashSet<PathBuf> { &self.to_include }
}

#[test]
fn test_loader()
{
    use serde::Deserialize;

    let mut loader = Loader::default();
    loader.add_document(r#"
        : server {
            addr = "0.0.0.0:8080",
            &service_1,
            &service_2
        }

        &service_1: service_kind_1 {
            key = 1
        }
    "#, "/").unwrap();

    loader.add_document(r#"
        &service_2: service_kind_2 {
            key = 2
        }
    "#, "/").unwrap();

    #[derive(PartialEq, serde::Deserialize, Debug)]
    struct Server { addr: String }

    #[derive(PartialEq, serde::Deserialize, Debug)]
    struct Service { key: i32 }

    let roots: Vec<_> = loader.roots().collect();

    assert_eq!(roots.len(), 1);
    assert_eq!(
        Server::deserialize(&*roots[0]).unwrap(),
        Server { addr: "0.0.0.0:8080".to_owned() }
    );

    let services: Vec<_> = loader.children(&roots[0]).unwrap();

    assert_eq!(services.len(), 2);
    assert_eq!(
        Service::deserialize(&*services[0]).unwrap(),
        Service { key: 1 }
    );
    assert_eq!(services[0].kind,                   "service_kind_1");
    assert_eq!(&services[0].name.clone().unwrap(), "service_1");

    assert_eq!(
        Service::deserialize(&*services[1]).unwrap(),
        Service { key: 2 }
    );
    assert_eq!(services[1].kind,                  "service_kind_2");
    assert_eq!(services[1].name.clone().unwrap(), "service_2");
}