summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRyan Leckey <leckey.ryan@gmail.com>2017-02-07 17:09:41 -0800
committerRyan Leckey <leckey.ryan@gmail.com>2017-02-07 17:09:41 -0800
commit9239ee5c27eb6d87098b10cb8e1f4ea9465d1add (patch)
treef4a3fb1098a94ca1fc148360ab90434c06400b4f /src
parent115fe07e2c11aa72e91a5ce9b028ed1c1ff7d806 (diff)
Add path resolution using a strict subset of JSONPath
Diffstat (limited to 'src')
-rw-r--r--src/config.rs73
-rw-r--r--src/env.rs4
-rw-r--r--src/file/json.rs8
-rw-r--r--src/file/mod.rs4
-rw-r--r--src/file/toml.rs4
-rw-r--r--src/lib.rs5
-rw-r--r--src/path.rs134
-rw-r--r--src/value.rs2
8 files changed, 223 insertions, 11 deletions
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");
+ }
}
diff --git a/src/env.rs b/src/env.rs
index 60e020e..dd237db 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -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!();
+ }
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 5f8957a..89ffc69 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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,
}
}
}