ncl 0.1.2

Simple configuration file format based on UCL, HCL and JSON
Documentation
#[macro_use] extern crate nom;

pub use value::Value;
pub use object::Object;

use std::str::{self, FromStr};

use nom::{digit, eof, space, not_line_ending, line_ending, alphanumeric, IResult};

pub mod value;
pub mod object;

pub type Key = String;
type Entry = (Key, Value);

named!(comment,
       preceded!(tag!("#"), not_line_ending));

named!(blank,
       chain!(many0!(terminated!(
               many0!(alt!(comment | space)),
               line_ending)),
               || { &b""[..] }));

named!(boolean<Value>,
       map!(
           map_res!(
               map_res!(
                   alt!(tag!("true") | tag!("false")),
                   str::from_utf8),
                   <bool as FromStr>::from_str),
                   From::from));

named!(number<Value>,
       map!(
           map_res!(
               map_res!(
                   digit,
                   str::from_utf8),
                   <i64 as FromStr>::from_str),
                   From::from));

named!(string<Value>,
       map!(
           map_res!(
               delimited!(tag!("\""), take_until!("\""), tag!("\"")),
               str::from_utf8),
               From::from));

named!(entries<Object>,
       map!(many0!(entry), From::from));

named!(object_begin,
       chain!(space? ~ tag!("{") ~ alt!(blank | space)?, || { &b""[..] }));

named!(object_end,
       chain!(space? ~ tag!("}"), || { &b""[..] }));

named!(object<Value>,
       map!(delimited!(object_begin, entries, object_end),
       From::from));

named!(value<Value>,
       delimited!(opt!(space), alt!(number | boolean | string | object), opt!(space)));

named!(key<Key>,
       map_res!(
           chain!(key: alphanumeric ~
                  space?,
                  || { str::from_utf8(key).unwrap() }),
                  FromStr::from_str));

named!(entry<Entry>,
       alt!(
           chain!(space? ~
                  key: key ~
                  space? ~
                  value: object ~
                  blank?,
                  || { (key, value) }) |
           chain!(space? ~
                  key: key ~
                  tag!("=") ~
                  value: value ~
                  blank?,
                  || { (key, value) })));

named!(root<Object>,
       terminated!(delimited!(opt!(blank), entries, many0!(alt!(space | line_ending | comment))), eof));

#[derive(Debug)]
pub enum Error {
    ParserFailed(String),
    Incomplete
}

pub fn parse<T: AsRef<[u8]>>(input: T) -> Result<Object, Error> {
    match root(input.as_ref()) {
        IResult::Done(_, object) => Ok(object),
        IResult::Error(err) => Err(Error::ParserFailed(format!("{:?}", err))),
        IResult::Incomplete(_) => Err(Error::Incomplete)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let data = parse("str = \"test\"\nnum = 42\nbool = true\nobj = { num = 666 }").unwrap();

        assert_eq!(data.get("str"), Some(&Value::Str("test".to_string())));
        assert_eq!(data.get("num"), Some(&Value::Num(42)));
        assert_eq!(data.get("bool"), Some(&Value::Bool(true)));

        let obj = match data.get("obj").unwrap() {
            &Value::Object(ref obj) => obj,
            _ => { assert!(false); unreachable!() }
        };

        assert_eq!(obj.get("num"), Some(&Value::Num(666)));
    }

    #[test]
    fn it_fails() {
        let data = parse("str = \"test\"\nnum = 42\nbool = true\nobj = { num = 666");

        assert!(data.is_err(), "data is: {:?}", data);
    }
}