diff options
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/config.rs | 73 | ||||
-rw-r--r-- | src/env.rs | 4 | ||||
-rw-r--r-- | src/file/json.rs | 8 | ||||
-rw-r--r-- | src/file/mod.rs | 4 | ||||
-rw-r--r-- | src/file/toml.rs | 4 | ||||
-rw-r--r-- | src/lib.rs | 5 | ||||
-rw-r--r-- | src/path.rs | 134 | ||||
-rw-r--r-- | src/value.rs | 2 |
9 files changed, 225 insertions, 11 deletions
@@ -14,5 +14,7 @@ default = ["toml"] json = ["serde_json"] [dependencies] +nom = "^2.1" + toml = { version = "0.2.1", optional = true } serde_json = { version = "0.9", optional = true } diff --git a/src/config.rs b/src/config.rs index 126d31b..1de8f44 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,6 @@ use value::Value; use source::{Source, SourceBuilder}; +use path; use std::error::Error; use std::fmt; @@ -213,8 +214,55 @@ impl Config { Ok(()) } - pub fn get<'a>(&'a self, key: &str) -> Option<&'a Value> { - self.cache.get(key) + // Child ( Child ( Identifier( "x" ), "y" ), "z" ) + fn path_get<'a, 'b>(&'a self, expr: path::Expression) -> Option<&'a Value> { + match expr { + path::Expression::Identifier(text) => { + self.cache.get(&text) + } + + path::Expression::Child(expr, member) => { + match self.path_get(*expr) { + Some(&Value::Table(ref table)) => { + table.get(&member) + } + + _ => None + } + } + + path::Expression::Subscript(expr, mut index) => { + match self.path_get(*expr) { + Some(&Value::Array(ref array)) => { + let len = array.len() as i32; + + if index < 0 { + index = len + index; + } + + if index < 0 || index >= len { + None + } else { + Some(&array[index as usize]) + } + } + + _ => None + } + } + } + } + + pub fn get<'a>(&'a self, key_path: &str) -> Option<&'a Value> { + let key_expr: path::Expression = match key_path.parse() { + Ok(expr) => expr, + Err(_) => { + // TODO: Log warning here + return None; + } + }; + + self.path_get(key_expr) } pub fn get_str<'a>(&'a self, key: &str) -> Option<Cow<'a, str>> { @@ -428,4 +476,25 @@ mod test { assert_eq!(m.get("db").unwrap().as_str().unwrap(), "1"); } } + + // Path expression + #[test] + fn test_path() { + use file::{File, FileFormat}; + + let mut c = Config::new(); + + c.merge(File::from_str(r#" + [redis] + address = "localhost:6379" + + [[databases]] + name = "test_db" + options = { trace = true } + "#, FileFormat::Toml)).unwrap(); + + assert_eq!(c.get_str("redis.address").unwrap(), "localhost:6379"); + assert_eq!(c.get_str("databases[0].name").unwrap(), "test_db"); + assert_eq!(c.get_str("databases[0].options.trace").unwrap(), "true"); + } } @@ -34,7 +34,9 @@ impl source::Source for Environment { // Make prefix pattern let prefix_pat = if let Some(ref prefix) = self.prefix { Some(prefix.clone() + "_".into()) - } else { None }; + } else { + None + }; for (key, value) in env::vars() { let mut key = key.to_string(); diff --git a/src/file/json.rs b/src/file/json.rs index d193da4..86612c9 100644 --- a/src/file/json.rs +++ b/src/file/json.rs @@ -21,9 +21,7 @@ impl Content { fn from_json_value(value: &serde_json::Value) -> Value { match *value { - serde_json::Value::String(ref value) => { - Value::String(value.clone()) - } + serde_json::Value::String(ref value) => Value::String(value.clone()), serde_json::Value::Number(ref value) => { if let Some(value) = value.as_i64() { @@ -58,7 +56,9 @@ fn from_json_value(value: &serde_json::Value) -> Value { } // TODO: What's left is JSON Null; how should we handle that? - _ => { unimplemented!(); } + _ => { + unimplemented!(); + } } } diff --git a/src/file/mod.rs b/src/file/mod.rs index e85d082..7e9d1a2 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -158,7 +158,7 @@ impl File<FileSourceFile> { source: FileSourceFile { name: name.into(), path: None, - } + }, } } } @@ -176,7 +176,7 @@ impl<T: FileSource> File<T> { impl File<FileSourceFile> { pub fn path(self, path: &str) -> Self { - File { source: FileSourceFile { path: Some(path.into()), ..self.source } , ..self } + File { source: FileSourceFile { path: Some(path.into()), ..self.source }, ..self } } pub fn namespace(self, namespace: &str) -> Self { diff --git a/src/file/toml.rs b/src/file/toml.rs index 4de23ef..28a1507 100644 --- a/src/file/toml.rs +++ b/src/file/toml.rs @@ -47,7 +47,9 @@ fn from_toml_value(value: &toml::Value) -> Value { Value::Array(l) } - _ => { unimplemented!(); } + _ => { + unimplemented!(); + } } } @@ -1,6 +1,7 @@ #![feature(drop_types_in_const)] #![allow(unknown_lints)] +#![feature(trace_macros)] //! Configuration is gathered by building a `Source` and then merging that source into the //! current state of the configuration. @@ -40,6 +41,9 @@ //! See the [examples](https://github.com/mehcode/config-rs/tree/master/examples) for //! more usage information. +#[macro_use] +extern crate nom; + #[cfg(feature = "toml")] extern crate toml; @@ -50,6 +54,7 @@ mod value; mod source; mod file; mod env; +mod path; mod config; use std::error::Error; diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000..b6d3b10 --- /dev/null +++ b/src/path.rs @@ -0,0 +1,134 @@ +use nom::*; +use std::str::{FromStr, from_utf8}; + +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub enum Expression { + Identifier(String), + Child(Box<Expression>, String), + Subscript(Box<Expression>, i32), +} + +named!(ident_<String>, + map!( + map_res!(is_a!( + "abcdefghijklmnopqrstuvwxyz \ + ABCDEFGHIJKLMNOPQRSTUVWXYZ \ + 0123456789 \ + _-" + ), from_utf8), + |s: &str| { + s.to_string() + } + ) +); + +named!(integer <i32>, + map_res!( + map_res!( + ws!(digit), + from_utf8 + ), + FromStr::from_str + ) +); + +named!(ident<Expression>, map!(ident_, Expression::Identifier)); + +fn postfix(expr: Expression) -> Box<Fn(&[u8]) -> IResult<&[u8], Expression>> { + return Box::new(move |i: &[u8]| { + alt!(i, + do_parse!( + tag!(".") >> + id: ident_ >> + (Expression::Child(Box::new(expr.clone()), id)) + ) | + delimited!( + char!('['), + do_parse!( + negative: opt!(tag!("-")) >> + num: integer >> + (Expression::Subscript( + Box::new(expr.clone()), + num * (if negative.is_none() { 1 } else { -1 }) + )) + ), + char!(']') + ) + ) + }); +} + +fn expr(input: &[u8]) -> IResult<&[u8], Expression> { + match ident(input) { + IResult::Done(mut rem, mut expr) => { + while rem.len() > 0 { + match postfix(expr)(rem) { + IResult::Done(rem_, expr_) => { + rem = rem_; + expr = expr_; + } + + // Forward Incomplete and Error + result @ _ => { + return result; + } + } + } + + IResult::Done(&[], expr) + } + + // Forward Incomplete and Error + result @ _ => result, + } +} + +impl FromStr for Expression { + type Err = ErrorKind; + + fn from_str(s: &str) -> Result<Expression, ErrorKind> { + expr(s.as_bytes()).to_result() + } +} + +#[cfg(test)] +mod test { + use super::*; + use super::Expression::*; + + #[test] + fn test_id() { + let parsed: Expression = "abcd".parse().unwrap(); + assert_eq!(parsed, Identifier("abcd".into())); + } + + #[test] + fn test_id_dash() { + let parsed: Expression = "abcd-efgh".parse().unwrap(); + assert_eq!(parsed, Identifier("abcd-efgh".into())); + } + + #[test] + fn test_child() { + let parsed: Expression = "abcd.efgh".parse().unwrap(); + let expected = Child(Box::new(Identifier("abcd".into())), "efgh".into()); + + assert_eq!(parsed, expected); + } + + #[test] + fn test_subscript() { + let parsed: Expression = "abcd[12]".parse().unwrap(); + let expected = Subscript(Box::new(Identifier("abcd".into())), 12); + + assert_eq!(parsed, expected); + } + + #[test] + fn test_subscript_neg() { + let parsed: Expression = "abcd[-1]".parse().unwrap(); + let expected = Subscript(Box::new(Identifier("abcd".into())), -1); + + assert_eq!(parsed, expected); + } +} diff --git a/src/value.rs b/src/value.rs index 5228d91..4451400 100644 --- a/src/value.rs +++ b/src/value.rs @@ -77,7 +77,7 @@ impl Value { pub fn as_map(&self) -> Option<&HashMap<String, Value>> { match *self { Value::Table(ref value) => Some(value), - _ => None + _ => None, } } } |