diff options
49 files changed, 2488 insertions, 1431 deletions
diff --git a/.travis.yml b/.travis.yml index c088006..8137b0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,14 @@ +sudo: true language: rust +cache: cargo rust: - - nightly
\ No newline at end of file + - nightly + - beta + - stable + +matrix: + allow_failures: + - rust: nightly + +script: + - cargo test @@ -1,6 +1,6 @@ [package] name = "config" -version = "0.4.1" +version = "0.5.0-pre" description = "Layered configuration system for Rust applications." homepage = "https://github.com/mehcode/config-rs" repository = "https://github.com/mehcode/config-rs" @@ -10,13 +10,18 @@ authors = ["Ryan Leckey <leckey.ryan@gmail.com>"] license = "MIT/Apache-2.0" [features] -default = ["toml"] +default = ["toml", "json", "yaml"] json = ["serde_json"] yaml = ["yaml-rust"] [dependencies] -nom = "^2.1" +serde = "^1.0.8" +nom = "^3.0.0" -toml = { version = "0.2.1", optional = true } -serde_json = { version = "0.9", optional = true } -yaml-rust = { version = "0.3.5", optional = true } +toml = { version = "^0.4.1", optional = true } +serde_json = { version = "^1.0.2", optional = true } +yaml-rust = { version = "^0.3.5", optional = true } + +[dev-dependencies] +serde_derive = "^1.0.8" +float-cmp = "*" diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml deleted file mode 100644 index 7ede162..0000000 --- a/examples/basic/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "basic" -version = "0.1.0" - -[dependencies] -config = { path = "../..", default-features = false } diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs deleted file mode 100644 index 2f947d8..0000000 --- a/examples/basic/src/main.rs +++ /dev/null @@ -1,33 +0,0 @@ -extern crate config; - -fn main() { - let mut c = config::Config::new(); - - // Set defaults for `window.width` and `window.height` - c.set_default("window.title", "Basic").unwrap(); - c.set_default("window.width", 640).unwrap(); - c.set_default("window.height", 480).unwrap(); - c.set_default("debug", true).unwrap(); - - // Note that you can retrieve the stored values as any type as long - // as there exists a reasonable conversion - println!("window.title : {:?}", c.get_str("window.title")); - println!("window.width : {:?}", c.get_str("window.width")); - println!("window.width : {:?}", c.get_int("window.width")); - println!("debug : {:?}", c.get_bool("debug")); - println!("debug : {:?}", c.get_str("debug")); - println!("debug : {:?}", c.get_int("debug")); - - // Attempting to get a value as a type that cannot be reasonably - // converted to will return None - println!("window.title : {:?}", c.get_bool("window.title")); - - // Instead of using a get_* function you can get the variant - // directly - println!("debug : {:?}", c.get("debug")); - println!("debug : {:?}", - c.get("debug").unwrap().into_int()); - - // Attempting to get a value that does not exist will return None - println!("not-found : {:?}", c.get("not-found")); -} diff --git a/examples/file-json/Cargo.toml b/examples/file-json/Cargo.toml deleted file mode 100644 index 7223f35..0000000 --- a/examples/file-json/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "file-json" -version = "0.1.0" - -[dependencies] -config = { path = "../..", default-features = false, features = ["json"] } diff --git a/examples/file-json/Settings.json b/examples/file-json/Settings.json deleted file mode 100644 index 72b28e6..0000000 --- a/examples/file-json/Settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "debug": false, - "pi": 3.14159, - "weight": 150 -} diff --git a/examples/file-json/src/main.rs b/examples/file-json/src/main.rs deleted file mode 100644 index e4ff809..0000000 --- a/examples/file-json/src/main.rs +++ /dev/null @@ -1,12 +0,0 @@ -extern crate config; - -fn main() { - let mut c = config::Config::new(); - - // Read configuration from "Settings.json" - c.merge(config::File::new("Settings", config::FileFormat::Json)).unwrap(); - - println!("debug = {:?}", c.get("debug")); - println!("pi = {:?}", c.get("pi")); - println!("weight = {:?}", c.get("weight")); -} diff --git a/examples/file-toml/Cargo.toml b/examples/file-toml/Cargo.toml deleted file mode 100644 index 6f45799..0000000 --- a/examples/file-toml/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "file-toml" -version = "0.1.0" - -[dependencies] -config = { path = "../.." } diff --git a/examples/file-toml/Settings.toml b/examples/file-toml/Settings.toml deleted file mode 100644 index 28c5fc6..0000000 --- a/examples/file-toml/Settings.toml +++ /dev/null @@ -1,3 +0,0 @@ -debug = false -pi = 3.14159 -weight = 150 diff --git a/examples/file-toml/src/main.rs b/examples/file-toml/src/main.rs deleted file mode 100644 index 85db701..0000000 --- a/examples/file-toml/src/main.rs +++ /dev/null @@ -1,12 +0,0 @@ -extern crate config; - -fn main() { - let mut c = config::Config::new(); - - // Read configuration from "Settings.toml" - c.merge(config::File::new("Settings", config::FileFormat::Toml)).unwrap(); - - println!("debug = {:?}", c.get("debug")); - println!("pi = {:?}", c.get("pi")); - println!("weight = {:?}", c.get("weight")); -} diff --git a/examples/file-yaml/Cargo.toml b/examples/file-yaml/Cargo.toml deleted file mode 100644 index 70176b1..0000000 --- a/examples/file-yaml/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "file-yaml" -version = "0.1.0" - -[dependencies] -config = { path = "../..", default-features = false, features = ["yaml"] } diff --git a/examples/file-yaml/Settings.yaml b/examples/file-yaml/Settings.yaml deleted file mode 100644 index d92f6ad..0000000 --- a/examples/file-yaml/Settings.yaml +++ /dev/null @@ -1,3 +0,0 @@ -debug: false -pi: 3.14159 -weight: 150 diff --git a/examples/file-yaml/src/main.rs b/examples/file-yaml/src/main.rs deleted file mode 100644 index 6d72976..0000000 --- a/examples/file-yaml/src/main.rs +++ /dev/null @@ -1,12 +0,0 @@ -extern crate config; - -fn main() { - let mut c = config::Config::new(); - - // Read configuration from "Settings.yaml" - c.merge(config::File::new("Settings", config::FileFormat::Yaml)).unwrap(); - - println!("debug = {:?}", c.get("debug")); - println!("pi = {:?}", c.get("pi")); - println!("weight = {:?}", c.get("weight")); -} diff --git a/src/config.rs b/src/config.rs index ffe341a..0d84906 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,45 +1,28 @@ -use value::Value; -use source::{Source, SourceBuilder}; -use path; - -use std::error::Error; -use std::fmt; -use std::str::FromStr; use std::collections::HashMap; +use serde::de::Deserialize; -#[derive(Default, Debug)] -pub struct FrozenError {} +use error::*; +use source::Source; -impl fmt::Display for FrozenError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "FrozenError") - } -} - -impl Error for FrozenError { - fn description(&self) -> &'static str { - "configuration is frozen" - } -} +use value::{Value, ValueWithKey}; +use path; -// Underlying storage for the configuration -enum ConfigStore { +enum ConfigKind { + // A mutable configuration. This is the default. Mutable { - defaults: HashMap<String, Value>, - overrides: HashMap<String, Value>, - - // Ordered list of sources + defaults: HashMap<path::Expression, Value>, + overrides: HashMap<path::Expression, Value>, sources: Vec<Box<Source + Send + Sync>>, }, - // TODO: Will be used for frozen configuratino soon - #[allow(dead_code)] + // A frozen configuration. + // Configuration can no longer be mutated. Frozen, } -impl Default for ConfigStore { +impl Default for ConfigKind { fn default() -> Self { - ConfigStore::Mutable { + ConfigKind::Mutable { defaults: HashMap::new(), overrides: HashMap::new(), sources: Vec::new(), @@ -47,665 +30,165 @@ impl Default for ConfigStore { } } -fn merge_in_all(r: &mut HashMap<String, Value>, map: &HashMap<String, Value>) { - for (key, value) in map { - path_set_str(r, key, value); - } -} - -// Child ( Child ( Identifier( "x" ), "y" ), "z" ) -fn path_get_mut<'a>(root: &'a mut HashMap<String, Value>, - expr: path::Expression) - -> Option<&'a mut Value> { - match expr { - path::Expression::Identifier(text) => Some(root.entry(text.clone()).or_insert(Value::Nil)), - - path::Expression::Child(expr, member) => { - match path_get_mut(root, *expr) { - Some(&mut Value::Table(ref mut table)) => { - Some(table.entry(member.clone()).or_insert(Value::Nil)) - } - - Some(v @ _) => { - *v = Value::Table(HashMap::new()); - if let Value::Table(ref mut table) = *v { - Some(table.entry(member.clone()).or_insert(Value::Nil)) - } else { - None - } - } - - _ => None, - } - } - - path::Expression::Subscript(expr, mut index) => { - match path_get_mut(root, *expr) { - Some(&mut Value::Array(ref mut array)) => { - let len = array.len() as i32; - - if index < 0 { - index = len + index; - } - - if index < 0 { - None - } else { - // Ensure there is enough room - array.resize((index + 1) as usize, Value::Nil); +/// A prioritized configuration repository. It maintains a set of +/// configuration sources, fetches values to populate those, and provides +/// them according to the source's priority. +#[derive(Default)] +pub struct Config { + kind: ConfigKind, - Some(&mut array[index as usize]) - } - } + /// Root of the cached configuration. + pub cache: Value, +} - _ => None, - } - } +impl Config { + pub fn new() -> Self { + Config::default() } -} -fn require_table(r: &mut HashMap<String, Value>, key: &String) { - if r.contains_key(key) { - // Coerce to table - match *r.get(key).unwrap() { - Value::Table(_) => { - // Do nothing; already table + /// Merge in a configuration property source. + pub fn merge<T>(&mut self, source: T) -> Result<()> + where T: 'static, + T: Source + Send + Sync + { + match self.kind { + ConfigKind::Mutable { ref mut sources, .. } => { + sources.push(Box::new(source)); } - _ => { - // Override with empty table - r.insert(key.clone(), Value::Table(HashMap::new())); + ConfigKind::Frozen => { + return Err(ConfigError::Frozen); } } - } else { - // Insert table - r.insert(key.clone(), Value::Table(HashMap::new())); - } -} -fn path_set(root: &mut HashMap<String, Value>, expr: path::Expression, value: &Value) { - match expr { - path::Expression::Identifier(text) => { - match *value { - Value::Table(ref table_v) => { - require_table(root, &text); - if let Value::Table(ref mut target) = *root.get_mut(&text).unwrap() { - merge_in_all(target, table_v); - } - } + self.refresh() + } - _ => { - root.insert(text, value.clone()); + /// Refresh the configuration cache with fresh + /// data from added sources. + /// + /// Configuration is automatically refreshed after a mutation + /// operation (`set`, `merge`, `set_default`, etc.). + pub fn refresh(&mut self) -> Result<()> { + self.cache = match self.kind { + // TODO: We need to actually merge in all the stuff + ConfigKind::Mutable { + ref overrides, + ref sources, + ref defaults, + } => { + let mut cache: Value = HashMap::<String, Value>::new().into(); + + // Add defaults + for (key, val) in defaults { + key.set(&mut cache, val.clone()); } - } - } - path::Expression::Child(expr, member) => { - if let Some(parent) = path_get_mut(root, *expr) { - match *parent { - Value::Table(ref mut table) => { - path_set(table, path::Expression::Identifier(member), value); - } - - _ => { - // Coerce to a table and do the insert anyway - *parent = Value::Table(HashMap::new()); - if let Value::Table(ref mut table) = *parent { - path_set(table, path::Expression::Identifier(member), value); - } + // Add sources + for source in sources { + let props = source.collect()?; + for (key, val) in &props { + path::Expression::Identifier(key.clone()).set(&mut cache, val.clone()); } } - } - } - path::Expression::Subscript(inner_expr, mut index) => { - if let Some(parent) = path_get_mut(root, *inner_expr) { - match *parent { - Value::Array(ref mut array) => { - let len = array.len() as i32; - - if index < 0 { - index = len + index; - } - - if index >= 0 { - array[index as usize] = value.clone(); - } - } - - Value::Nil => { - // Add an array and do this again - *parent = Value::Array(Vec::new()); - if let Value::Array(ref mut array) = *parent { - let len = array.len() as i32; - - if index < 0 { - index = len + index; - } - - if index >= 0 { - array.resize((index + 1) as usize, Value::Nil); - array[index as usize] = value.clone(); - } - } - } - - _ => { - // Do nothing - } + // Add overrides + for (key, val) in overrides { + key.set(&mut cache, val.clone()); } - } - } - } -} -fn path_set_str(root: &mut HashMap<String, Value>, key: &str, value: &Value) { - match path::Expression::from_str(key) { - Ok(expr) => { - path_set(root, expr, value); - } - - Err(_) => { - // TODO: Log warning here - } - }; -} + cache + }, -impl ConfigStore { - fn merge<T>(&mut self, source: T) -> Result<(), Box<Error>> - where T: SourceBuilder + Send + Sync - { - if let ConfigStore::Mutable { ref mut sources, .. } = *self { - sources.push(source.build()?); + ConfigKind::Frozen => { + return Err(ConfigError::Frozen); + } + }; - Ok(()) - } else { - Err(FrozenError::default().into()) - } + Ok(()) } - fn set_default<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>> - where T: Into<Value> - { - if let ConfigStore::Mutable { ref mut defaults, .. } = *self { - path_set_str(defaults, &key.to_lowercase(), &value.into()); - - Ok(()) - } else { - Err(FrozenError::default().into()) - } + /// Deserialize the entire configuration. + pub fn deserialize<'de, T: Deserialize<'de>>(&self) -> Result<T> { + T::deserialize(self.cache.clone()) } - fn set<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>> + pub fn set_default<T>(&mut self, key: &str, value: T) -> Result<()> where T: Into<Value> { - if let ConfigStore::Mutable { ref mut overrides, .. } = *self { - path_set_str(overrides, &key.to_lowercase(), &value.into()); - - Ok(()) - } else { - Err(FrozenError::default().into()) - } - } - - fn collect(&self) -> Result<HashMap<String, Value>, Box<Error>> { - if let ConfigStore::Mutable { ref overrides, ref sources, ref defaults } = *self { - let mut r = HashMap::<String, Value>::new(); - - merge_in_all(&mut r, defaults); - - for source in sources { - merge_in_all(&mut r, &source.collect()); + match self.kind { + ConfigKind::Mutable { + ref mut defaults, + .. + } => { + defaults.insert(key.parse()?, value.into()); } - merge_in_all(&mut r, overrides); - - Ok(r) - } else { - Err(FrozenError::default().into()) - } - } -} - -#[derive(Default)] -pub struct Config { - store: ConfigStore, - - /// Top-level table of the cached configuration - /// - /// As configuration sources are merged with `Config::merge`, this - /// cache is updated. - cache: HashMap<String, Value>, -} - -impl Config { - pub fn new() -> Self { - Default::default() - } - - /// Merge in configuration values from the given source. - pub fn merge<T>(&mut self, source: T) -> Result<(), Box<Error>> - where T: SourceBuilder + Send + Sync - { - self.store.merge(source)?; - self.refresh()?; - - Ok(()) - } - - /// Sets the default value for this key. The default value is only used - /// when no other value is provided. - pub fn set_default<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>> - where T: Into<Value> - { - self.store.set_default(key, value)?; - self.refresh()?; + ConfigKind::Frozen => { + return Err(ConfigError::Frozen) + } + }; - Ok(()) + self.refresh() } - /// Sets an override for this key. - pub fn set<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>> + pub fn set<T>(&mut self, key: &str, value: T) -> Result<()> where T: Into<Value> { - self.store.set(key, value)?; - self.refresh()?; - - Ok(()) - } + match self.kind { + ConfigKind::Mutable { + ref mut overrides, + .. + } => { + overrides.insert(key.parse()?, value.into()); + } - /// Refresh the configuration cache with fresh - /// data from associated sources. - /// - /// Configuration is automatically refreshed after a mutation - /// operation (`set`, `merge`, `set_default`, etc.). - pub fn refresh(&mut self) -> Result<(), Box<Error>> { - self.cache = self.store.collect()?; + ConfigKind::Frozen => { + return Err(ConfigError::Frozen) + } + }; - Ok(()) + self.refresh() } - // Child ( Child ( Identifier( "x" ), "y" ), "z" ) - fn path_get<'a>(&'a self, expr: path::Expression) -> Option<&'a Value> { - match expr { - path::Expression::Identifier(text) => self.cache.get(&text), + pub fn get<'de, T: Deserialize<'de>>(&self, key: &'de str) -> Result<T> { + // Parse the key into a path expression + let expr: path::Expression = key.to_lowercase().parse()?; - path::Expression::Child(expr, member) => { - match self.path_get(*expr) { - Some(&Value::Table(ref table)) => table.get(&member), + // Traverse the cache using the path to (possibly) retrieve a value + let value = expr.get(&self.cache).cloned(); - _ => None, - } + match value { + Some(value) => { + // Deserialize the received value into the requested type + T::deserialize(ValueWithKey::new(value, key)) } - 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, - } - } + None => Err(ConfigError::NotFound(key.into())), } } - pub fn get(&self, key_path: &str) -> Option<Value> { - let key_expr: path::Expression = match key_path.to_lowercase().parse() { - Ok(expr) => expr, - Err(_) => { - // TODO: Log warning here - return None; - } - }; - - self.path_get(key_expr).cloned() - } - - pub fn get_str(&self, key: &str) -> Option<String> { + pub fn get_str(&self, key: &str) -> Result<String> { self.get(key).and_then(Value::into_str) } - pub fn get_int(&self, key: &str) -> Option<i64> { + pub fn get_int(&self, key: &str) -> Result<i64> { self.get(key).and_then(Value::into_int) } - pub fn get_float(&self, key: &str) -> Option<f64> { + pub fn get_float(&self, key: &str) -> Result<f64> { self.get(key).and_then(Value::into_float) } |