From 2dc6a74b84825f65142c1fa7d3e67cd4f35ee3cb Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Wed, 8 Mar 2017 11:09:37 -0800 Subject: Initial work on deep serde integration --- src/config.rs | 709 ------------------------------------------------------- src/env.rs | 60 ----- src/file/json.rs | 87 ------- src/file/mod.rs | 219 ----------------- src/file/nil.rs | 13 - src/file/toml.rs | 74 ------ src/file/yaml.rs | 101 -------- src/lib.rs | 89 ------- src/path.rs | 134 ----------- src/source.rs | 12 - src/value.rs | 157 ------------ 11 files changed, 1655 deletions(-) delete mode 100644 src/config.rs delete mode 100644 src/env.rs delete mode 100644 src/file/json.rs delete mode 100644 src/file/mod.rs delete mode 100644 src/file/nil.rs delete mode 100644 src/file/toml.rs delete mode 100644 src/file/yaml.rs delete mode 100644 src/lib.rs delete mode 100644 src/path.rs delete mode 100644 src/source.rs delete mode 100644 src/value.rs (limited to 'src') diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index ad4cda2..0000000 --- a/src/config.rs +++ /dev/null @@ -1,709 +0,0 @@ -use value::Value; -use source::{Source, SourceBuilder}; -use path; - -use std::error::Error; -use std::fmt; -use std::str::FromStr; -use std::collections::HashMap; - -#[derive(Default, Debug)] -pub struct FrozenError {} - -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" - } -} - -// Underlying storage for the configuration -enum ConfigStore { - Mutable { - defaults: HashMap, - overrides: HashMap, - - // Ordered list of sources - sources: Vec>, - }, - - // TODO: Will be used for frozen configuratino soon - #[allow(dead_code)] - Frozen, -} - -impl Default for ConfigStore { - fn default() -> Self { - ConfigStore::Mutable { - defaults: HashMap::new(), - overrides: HashMap::new(), - sources: Vec::new(), - } - } -} - -fn merge_in_all(r: &mut HashMap, map: &HashMap) { - 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, - 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); - - Some(&mut array[index as usize]) - } - } - - _ => None, - } - } - } -} - -fn require_table(r: &mut HashMap, key: &String) { - if r.contains_key(key) { - // Coerce to table - match *r.get(key).unwrap() { - Value::Table(_) => { - // Do nothing; already table - } - - _ => { - // Override with empty table - r.insert(key.clone(), Value::Table(HashMap::new())); - } - } - } else { - // Insert table - r.insert(key.clone(), Value::Table(HashMap::new())); - } -} - -fn path_set(root: &mut HashMap, 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); - } - } - - _ => { - root.insert(text, value.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); - } - } - } - } - } - - 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 - } - } - } - } - } -} - -fn path_set_str(root: &mut HashMap, key: &str, value: &Value) { - match path::Expression::from_str(key) { - Ok(expr) => { - path_set(root, expr, value); - } - - Err(_) => { - // TODO: Log warning here - } - }; -} - -impl ConfigStore { - fn merge(&mut self, source: T) -> Result<(), Box> - where T: SourceBuilder + Send + Sync - { - if let ConfigStore::Mutable { ref mut sources, .. } = *self { - sources.push(source.build()?); - - Ok(()) - } else { - Err(FrozenError::default().into()) - } - } - - fn set_default(&mut self, key: &str, value: T) -> Result<(), Box> - where T: Into - { - if let ConfigStore::Mutable { ref mut defaults, .. } = *self { - path_set_str(defaults, &key.to_lowercase(), &value.into()); - - Ok(()) - } else { - Err(FrozenError::default().into()) - } - } - - fn set(&mut self, key: &str, value: T) -> Result<(), Box> - where T: Into - { - 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, Box> { - if let ConfigStore::Mutable { ref overrides, ref sources, ref defaults } = *self { - let mut r = HashMap::::new(); - - merge_in_all(&mut r, defaults); - - for source in sources { - merge_in_all(&mut r, &source.collect()); - } - - 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, -} - -impl Config { - pub fn new() -> Self { - Default::default() - } - - /// Merge in configuration values from the given source. - pub fn merge(&mut self, source: T) -> Result<(), Box> - 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(&mut self, key: &str, value: T) -> Result<(), Box> - where T: Into - { - self.store.set_default(key, value)?; - self.refresh()?; - - Ok(()) - } - - /// Sets an override for this key. - pub fn set(&mut self, key: &str, value: T) -> Result<(), Box> - where T: Into - { - self.store.set(key, value)?; - self.refresh()?; - - Ok(()) - } - - /// 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> { - self.cache = self.store.collect()?; - - Ok(()) - } - - // 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), - - 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(&self, key_path: &str) -> Option { - 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 { - self.get(key).and_then(Value::into_str) - } - - pub fn get_int(&self, key: &str) -> Option { - self.get(key).and_then(Value::into_int) - } - - pub fn get_float(&self, key: &str) -> Option { - self.get(key).and_then(Value::into_float) - } - - pub fn get_bool(&self, key: &str) -> Option { - self.get(key).and_then(Value::into_bool) - } - - pub fn get_table(&self, key: &str) -> Option> { - self.get(key).and_then(Value::into_table) - } - - pub fn get_array(self, key: &str) -> Option> { - self.get(key).and_then(Value::into_array) - } -} - -#[cfg(test)] -mod test { - use std::collections::HashMap; - use super::{Value, Config}; - - // Retrieval of a non-existent key - #[test] - fn test_not_found() { - let c = Config::new(); - - assert_eq!(c.get_int("key"), None); - } - - // Explicit override - #[test] - fn test_default_override() { - let mut c = Config::new(); - - c.set_default("key_1", false).unwrap(); - c.set_default("key_2", false).unwrap(); - - assert!(!c.get_bool("key_1").unwrap()); - assert!(!c.get_bool("key_2").unwrap()); - - c.set("key_2", true).unwrap(); - - assert!(!c.get_bool("key_1").unwrap()); - assert!(c.get_bool("key_2").unwrap()); - } - - // Storage and retrieval of String values - #[test] - fn test_str() { - let mut c = Config::new(); - - c.set("key", "value").unwrap(); - - assert_eq!(c.get_str("key").unwrap(), "value"); - } - - // Storage and retrieval of Boolean values - #[test] - fn test_bool() { - let mut c = Config::new(); - - c.set("key", true).unwrap(); - - assert_eq!(c.get_bool("key").unwrap(), true); - } - - // Storage and retrieval of Float values - #[test] - fn test_float() { - let mut c = Config::new(); - - c.set("key", 3.14).unwrap(); - - assert_eq!(c.get_float("key").unwrap(), 3.14); - } - - // Storage and retrieval of Integer values - #[test] - fn test_int() { - let mut c = Config::new(); - - c.set("key", 42).unwrap(); - - assert_eq!(c.get_int("key").unwrap(), 42); - } - - // Storage of various values and retrieval as String - #[test] - fn test_retrieve_str() { - let mut c = Config::new(); - - c.set("key_1", 115).unwrap(); - c.set("key_2", 1.23).unwrap(); - c.set("key_3", false).unwrap(); - - assert_eq!(c.get_str("key_1").unwrap(), "115"); - assert_eq!(c.get_str("key_2").unwrap(), "1.23"); - assert_eq!(c.get_str("key_3").unwrap(), "false"); - } - - // Storage of various values and retrieval as Integer - #[test] - fn test_retrieve_int() { - let mut c = Config::new(); - - c.set("key_1", "121").unwrap(); - c.set("key_2", 5.12).unwrap(); - c.set("key_3", 5.72).unwrap(); - c.set("key_4", false).unwrap(); - c.set("key_5", true).unwrap(); - c.set("key_6", "asga").unwrap(); - - assert_eq!(c.get_int("key_1"), Some(121)); - assert_eq!(c.get_int("key_2"), Some(5)); - assert_eq!(c.get_int("key_3"), Some(6)); - assert_eq!(c.get_int("key_4"), Some(0)); - assert_eq!(c.get_int("key_5"), Some(1)); - assert_eq!(c.get_int("key_6"), None); - } - - // Storage of various values and retrieval as Float - #[test] - fn test_retrieve_float() { - let mut c = Config::new(); - - c.set("key_1", "121").unwrap(); - c.set("key_2", "121.512").unwrap(); - c.set("key_3", 5).unwrap(); - c.set("key_4", false).unwrap(); - c.set("key_5", true).unwrap(); - c.set("key_6", "asga").unwrap(); - - assert_eq!(c.get_float("key_1"), Some(121.0)); - assert_eq!(c.get_float("key_2"), Some(121.512)); - assert_eq!(c.get_float("key_3"), Some(5.0)); - assert_eq!(c.get_float("key_4"), Some(0.0)); - assert_eq!(c.get_float("key_5"), Some(1.0)); - assert_eq!(c.get_float("key_6"), None); - } - - // Storage of various values and retrieval as Boolean - #[test] - fn test_retrieve_bool() { - let mut c = Config::new(); - - c.set("key_1", "121").unwrap(); - c.set("key_2", "1").unwrap(); - c.set("key_3", "0").unwrap(); - c.set("key_4", "true").unwrap(); - c.set("key_5", "").unwrap(); - c.set("key_6", 51).unwrap(); - c.set("key_7", 0).unwrap(); - c.set("key_8", 12.12).unwrap(); - c.set("key_9", 1.0).unwrap(); - c.set("key_10", 0.0).unwrap(); - c.set("key_11", "asga").unwrap(); - - assert_eq!(c.get_bool("key_1"), None); - assert_eq!(c.get_bool("key_2"), Some(true)); - assert_eq!(c.get_bool("key_3"), Some(false)); - assert_eq!(c.get_bool("key_4"), Some(true)); - assert_eq!(c.get_bool("key_5"), None); - assert_eq!(c.get_bool("key_6"), Some(true)); - assert_eq!(c.get_bool("key_7"), Some(false)); - assert_eq!(c.get_bool("key_8"), Some(true)); - assert_eq!(c.get_bool("key_9"), Some(true)); - assert_eq!(c.get_bool("key_10"), Some(false)); - assert_eq!(c.get_bool("key_11"), None); - } - - #[test] - fn test_slice() { - let mut c = Config::new(); - - c.set("values", - vec![Value::Integer(10), Value::Integer(325), Value::Integer(12)]) - .unwrap(); - - let values = c.get_array("values").unwrap(); - - assert_eq!(values.len(), 3); - assert_eq!(values[1].clone().into_int(), Some(325)); - } - - #[test] - fn test_slice_into() { - let mut c = Config::new(); - - c.set("values", vec![10, 325, 12]) - .unwrap(); - - let values = c.get_array("values").unwrap(); - - assert_eq!(values.len(), 3); - assert_eq!(values[1].clone().into_int(), Some(325)); - - } - - #[test] - fn test_map() { - let mut c = Config::new(); - - { - let mut m = HashMap::new(); - m.insert("port".into(), Value::Integer(6379)); - m.insert("address".into(), Value::String("::1".into())); - - c.set("redis", m).unwrap(); - } - - { - let m = c.get_table("redis").unwrap(); - - assert_eq!(m.get("port").cloned().unwrap().into_int().unwrap(), 6379); - assert_eq!(m.get("address").cloned().unwrap().into_str().unwrap(), - "::1"); - } - - { - let mut m = HashMap::new(); - m.insert("address".into(), Value::String("::0".into())); - m.insert("db".into(), Value::Integer(1)); - - c.set("redis", m).unwrap(); - } - - { - let m = c.get_table("redis").unwrap(); - - assert_eq!(m.get("port").cloned().unwrap().into_int().unwrap(), 6379); - assert_eq!(m.get("address").cloned().unwrap().into_str().unwrap(), - "::0"); - assert_eq!(m.get("db").cloned().unwrap().into_str().unwrap(), "1"); - } - } - - #[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"); - } - - #[test] - fn test_map_into() { - let mut c = Config::new(); - - { - let mut m = HashMap::new(); - m.insert("port".into(), 6379); - m.insert("db".into(), 2); - - c.set("redis", m).unwrap(); - } - - { - let m = c.get_table("redis").unwrap(); - - assert_eq!(m.get("port").cloned().unwrap().into_int().unwrap(), 6379); - assert_eq!(m.get("db").cloned().unwrap().into_int().unwrap(), 2); - } - } - - #[test] - fn test_map_set_coerce() { - let mut c = Config::new(); - - // Coerce value to table - c.set("redis", 10).unwrap(); - c.set("redis.port", 6379).unwrap(); - - assert_eq!(c.get_int("redis.port"), Some(6379)); - - // Coerce nil to table - c.set("server.port", 80).unwrap(); - - assert_eq!(c.get_int("server.port"), Some(80)); - } - - #[test] - fn test_slice_set_coerce() { - let mut c = Config::new(); - - // Coerce nil to slice - c.set("values[2]", 45).unwrap(); - - assert_eq!(c.get_int("values[2]"), Some(45)); - } - - #[test] - fn test_file_namespace() { - use file::{File, FileFormat}; - - let mut c = Config::new(); - let text = r#" - [development] - port = 8080 - - [production] - port = 80 - "#; - - c.merge(File::from_str(text, FileFormat::Toml).namespace("development")).unwrap(); - - assert_eq!(c.get_int("port"), Some(8080)); - - c.merge(File::from_str(text, FileFormat::Toml).namespace("production")).unwrap(); - - assert_eq!(c.get_int("port"), Some(80)); - } -} diff --git a/src/env.rs b/src/env.rs deleted file mode 100644 index 5c80bad..0000000 --- a/src/env.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::env; -use std::error::Error; -use std::collections::HashMap; - -use source; -use value::Value; - -#[derive(Clone)] -pub struct Environment { - /// Optional prefix that would restrict environment consideration - /// to only variables which begin with that prefix. - prefix: Option, -} - -impl Environment { - pub fn new<'a, T>(prefix: T) -> Environment - where T: Into> - { - Environment { prefix: prefix.into().map(String::from) } - } -} - -impl source::SourceBuilder for Environment { - fn build(&self) -> Result, Box> { - Ok(Box::new(self.clone())) - } -} - -impl source::Source for Environment { - fn collect(&self) -> HashMap { - // Iterate through environment variables - let mut r = HashMap::new(); - - // Make prefix pattern - let prefix_pat = if let Some(ref prefix) = self.prefix { - Some(prefix.clone() + "_".into()) - } else { - None - }; - - for (key, value) in env::vars() { - let mut key = key.to_string(); - - // Check if key matches prefix - if let Some(ref prefix_pat) = prefix_pat { - if key.starts_with(prefix_pat) { - // Remove the prefix from the key - key = key[prefix_pat.len()..].to_string(); - } else { - // Skip this key - continue; - } - } - - r.insert(key, Value::String(value)); - } - - r - } -} diff --git a/src/file/json.rs b/src/file/json.rs deleted file mode 100644 index e6cc2f4..0000000 --- a/src/file/json.rs +++ /dev/null @@ -1,87 +0,0 @@ -use serde_json; - -use source::Source; -use std::error::Error; -use std::collections::HashMap; -use value::Value; - -pub struct Content { - // Root table of the TOML document - root: serde_json::Value, -} - -impl Content { - pub fn parse(text: &str, namespace: Option<&String>) -> Result, Box> { - // Parse - let mut root: serde_json::Value = serde_json::from_str(text)?; - - // Limit to namespace - if let Some(namespace) = namespace { - if let serde_json::Value::Object(mut root_map) = root { - if let Some(value) = root_map.remove(namespace) { - root = value; - } else { - // TODO: Warn? - root = serde_json::Value::Object(serde_json::Map::new()); - } - } - } - - Ok(Box::new(Content { root: root })) - } -} - -fn from_json_value(value: &serde_json::Value) -> Value { - match *value { - serde_json::Value::String(ref value) => Value::String(value.clone()), - - serde_json::Value::Number(ref value) => { - if let Some(value) = value.as_i64() { - Value::Integer(value) - } else if let Some(value) = value.as_f64() { - Value::Float(value) - } else { - unreachable!(); - } - } - - serde_json::Value::Bool(value) => Value::Boolean(value), - - serde_json::Value::Object(ref table) => { - let mut m = HashMap::new(); - - for (key, value) in table { - m.insert(key.clone(), from_json_value(value)); - } - - Value::Table(m) - } - - serde_json::Value::Array(ref array) => { - let mut l = Vec::new(); - - for value in array { - l.push(from_json_value(value)); - } - - Value::Array(l) - } - - // TODO: What's left is JSON Null; how should we handle that? - _ => { - unimplemented!(); - } - } -} - -impl Source for Content { - fn collect(&self) -> HashMap { - if let Value::Table(table) = from_json_value(&self.root) { - table - } else { - // TODO: Better handle a non-object at root - // NOTE: I never want to support that but a panic is bad - panic!("expected object at JSON root"); - } - } -} diff --git a/src/file/mod.rs b/src/file/mod.rs deleted file mode 100644 index 7f7c0fb..0000000 --- a/src/file/mod.rs +++ /dev/null @@ -1,219 +0,0 @@ -use std::env; -use std::error::Error; -use std::io::{self, Read}; -use std::fs; -use std::path::PathBuf; - -use source::{Source, SourceBuilder}; - -mod nil; - -#[cfg(feature = "toml")] -mod toml; - -#[cfg(feature = "json")] -mod json; - -#[cfg(feature = "yaml")] -mod yaml; - -#[derive(Clone, Copy)] -pub enum FileFormat { - /// TOML (parsed with toml) - #[cfg(feature = "toml")] - Toml, - - /// JSON (parsed with serde_json) - #[cfg(feature = "json")] - Json, - - /// YAML (parsed with yaml_rust) - #[cfg(feature = "yaml")] - Yaml, -} - -impl FileFormat { - fn extensions(&self) -> Vec<&'static str> { - match *self { - #[cfg(feature = "toml")] - FileFormat::Toml => vec!["toml"], - - #[cfg(feature = "json")] - FileFormat::Json => vec!["json"], - - #[cfg(feature = "yaml")] - FileFormat::Yaml => vec!["yaml", "yml"], - } - } - - #[allow(unused_variables)] - fn parse(&self, text: &str, namespace: Option<&String>) -> Result, Box> { - match *self { - #[cfg(feature = "toml")] - FileFormat::Toml => toml::Content::parse(text, namespace), - - #[cfg(feature = "json")] - FileFormat::Json => json::Content::parse(text, namespace), - - #[cfg(feature = "yaml")] - FileFormat::Yaml => yaml::Content::parse(text, namespace), - } - } -} - -pub trait FileSource { - fn try_build(&self, - format: FileFormat, - namespace: Option<&String>) - -> Result, Box>; -} - -pub struct FileSourceString(String); - -impl FileSource for FileSourceString { - fn try_build(&self, - format: FileFormat, - namespace: Option<&String>) - -> Result, Box> { - format.parse(&self.0, namespace) - } -} - -pub struct FileSourceFile { - /// Basename of configuration file - name: String, - - /// Directory where configuration file is found - /// When not specified, the current working directory (CWD) is considered - path: Option, -} - -impl FileSourceFile { - // Find configuration file - // Use algorithm similar to .git detection by git - fn find_file(&self, format: FileFormat) -> Result> { - // Build expected configuration file - let mut basename = PathBuf::new(); - let extensions = format.extensions(); - - if let Some(ref path) = self.path { - basename.push(path.clone()); - } - - basename.push(self.name.clone()); - - // Find configuration file (algorithm similar to .git detection by git) - let mut dir = env::current_dir()?; - - loop { - let mut filename = dir.as_path().join(basename.clone()); - for ext in &extensions { - filename.set_extension(ext); - - if filename.is_file() { - // File exists and is a file - return Ok(filename); - } - } - - // Not found.. travse up via the dir - if !dir.pop() { - // Failed to find the configuration file - return Err(io::Error::new(io::ErrorKind::NotFound, - format!("configuration file \"{}\" not found", - basename.to_string_lossy())) - .into()); - } - } - } -} - -impl FileSource for FileSourceFile { - fn try_build(&self, - format: FileFormat, - namespace: Option<&String>) - -> Result, Box> { - // Find file - let filename = self.find_file(format)?; - - // Read contents from file - let mut file = fs::File::open(filename)?; - let mut text = String::new(); - file.read_to_string(&mut text)?; - - // Parse the file - format.parse(&text, namespace) - } -} - -pub struct File { - /// Source of the file - source: T, - - /// Namespace to restrict configuration from the file - namespace: Option, - - /// Format of file (which dictates what driver to use); Defauts to TOML. - format: FileFormat, - - /// A required File will error if it cannot be found - required: bool, -} - -impl File { - pub fn from_str(s: &str, format: FileFormat) -> File { - File { - format: format, - required: true, - namespace: None, - source: FileSourceString(s.into()), - } - } -} - -impl File { - pub fn new(name: &str, format: FileFormat) -> File { - File { - format: format, - required: true, - namespace: None, - source: FileSourceFile { - name: name.into(), - path: None, - }, - } - } -} - -impl File { - pub fn required(self, required: bool) -> File { - File { required: required, ..self } - } - - pub fn namespace(self, namespace: &str) -> Self { - File { namespace: Some(namespace.into()), ..self } - } - - // Build normally and return error on failure - fn try_build(&self) -> Result, Box> { - self.source.try_build(self.format, self.namespace.as_ref()) - } -} - -impl File { - pub fn path(self, path: &str) -> Self { - File { source: FileSourceFile { path: Some(path.into()), ..self.source }, ..self } - } -} - -impl SourceBuilder for File { - // Use try_build but only pass an error through if this source - // is required - fn build(&self) -> Result, Box> { - if self.required { - self.try_build() - } else { - self.try_build().or_else(|_| Ok(Box::new(nil::Nil {}))) - } - } -} diff --git a/src/file/nil.rs b/src/file/nil.rs deleted file mode 100644 index f6d801a..0000000 --- a/src/file/nil.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::collections::HashMap; - -use source::Source; -use value::Value; - -// Nil source that does nothing to easily allow for optional files -pub struct Nil {} - -impl Source for Nil { - fn collect(&self) -> HashMap { - HashMap::new() - } -} diff --git a/src/file/toml.rs b/src/file/toml.rs deleted file mode 100644 index 9b07cf0..0000000 --- a/src/file/toml.rs +++ /dev/null @@ -1,74 +0,0 @@ -use toml; -use source::Source; -use std::collections::{HashMap, BTreeMap}; -use std::error::Error; -use value::Value; - -pub struct Content { - // Root table of the TOML document - root: toml::Value, -} - -impl Content { - pub fn parse(text: &str, namespace: Option<&String>) -> Result, Box> { - // Parse - let mut parser = toml::Parser::new(text); - // TODO: Get a solution to make this return an Error-able - let mut root = parser.parse().unwrap(); - - // Limit to namespace - if let Some(namespace) = namespace { - if let Some(toml::Value::Table(table)) = root.remove(namespace) { - root = table; - } else { - // TODO: Warn? - root = BTreeMap::new(); - } - } - - Ok(Box::new(Content { root: toml::Value::Table(root) })) - } -} - -fn from_toml_value(value: &toml::Value) -> Value { - match *value { - toml::Value::String(ref value) => Value::String(value.clone()), - toml::Value::Float(value) => Value::Float(value), - toml::Value::Integer(value) => Value::Integer(value), - toml::Value::Boolean(value) => Value::Boolean(value), - - toml::Value::Table(ref table) => { - let mut m = HashMap::new(); - - for (key, value) in table { - m.insert(key.clone(), from_toml_value(value)); - } - - Value::Table(m) - } - - toml::Value::Array(ref array) => { - let mut l = Vec::new(); - - for value in array { - l.push(from_toml_value(value)); - } - - Value::Array(l) - } - - _ => { - unimplemented!(); - } - } -} - -impl Source for Content { - fn collect(&self) -> HashMap { - if let Value::Table(table) = from_toml_value(&self.root) { - table - } else { - unreachable!(); - } - } -} diff --git a/src/file/yaml.rs b/src/file/yaml.rs deleted file mode 100644 index 95a64b4..0000000 --- a/src/file/yaml.rs +++ /dev/null @@ -1,101 +0,0 @@ -use yaml_rust as yaml; - -use source::Source; -use std::error::Error; -use std::fmt; -use std::collections::{BTreeMap, HashMap}; -use std::mem; -use value::Value; - -pub struct Content { - // Root table of the YAML document - root: yaml::Yaml, -} - -impl Content { - pub fn parse(text: &str, namespace: Option<&String>) -> Result, Box> { - let mut docs = yaml::YamlLoader::load_from_str(text)?; - - // Designate root - let mut root = match docs.len() { - 0 => yaml::Yaml::Hash(BTreeMap::new()), - 1 => mem::replace(&mut docs[0], yaml::Yaml::Null), - n => { - return Err(Box::new(MultipleDocumentsError(n))); - } - }; - - // Limit to namespace - if let Some(namespace) = namespace { - if let yaml::Yaml::Hash(mut root_map) = root { - if let Some(value) = root_map.remove(&yaml::Yaml::String(namespace.clone())) { - root = value; - } else { - // TODO: Warn? - root = yaml::Yaml::Hash(BTreeMap::new()); - } - } - } - - Ok(Box::new(Content { root: root })) - } - - pub fn from_yaml(doc: yaml::Yaml) -> Content { - Content { root: doc } - } -} - -fn from_yaml_value<'a>(value: &yaml::Yaml) -> Value { - match *value { - yaml::Yaml::String(ref value) => Value::String(value.clone()), - yaml::Yaml::Real(ref value) => Value::Float(value.parse::().unwrap()), - yaml::Yaml::Integer(value) => Value::Integer(value), - yaml::Yaml::Boolean(value) => Value::Boolean(value), - yaml::Yaml::Hash(ref table) => { - let mut m = HashMap::new(); - for (key, value) in table { - if let Some(k) = key.as_str() { - m.insert(k.to_owned(), from_yaml_value(value)); - } - // TODO: should we do anything for non-string keys? - } - Value::Table(m) - } - yaml::Yaml::Array(ref array) => { - let l: Vec = array.iter().map(from_yaml_value).collect(); - Value::Array(l) - } - // TODO: how should we handle Null and BadValue? - _ => { - unimplemented!(); - } - - } -} - -impl Source for Content { - fn collect(&self) -> HashMap { - if let Value::Table(table) = from_yaml_value(&self.root) { - table - } else { - // TODO: Better handle a non-object at root - // NOTE: I never want to support that but a panic is bad - panic!("expected object at YAML root"); - } - } -} - -#[derive(Debug, Copy, Clone)] -struct MultipleDocumentsError(usize); - -impl fmt::Display for MultipleDocumentsError { - fn fmt(&self, format: &mut fmt::Formatter) -> fmt::Result { - write!(format, "Got {} YAML documents, expected 1", self.0) - } -} - -impl Error for MultipleDocumentsError { - fn description(&self) -> &str { - "More than one YAML document provided" - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 15f9d49..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Configuration is gathered by building a `Source` and then merging that source into the -//! current state of the configuration. -//! -//! ```rust -//! extern crate config; -//! -//! use std::env; -//! use config::{Config, File, FileFormat, Environment}; -//! -//! fn main() { -//! // Create a new local configuration -//! let mut c = Config::new(); -//! -//! // Add 'Settings.toml' -//! c.merge(File::new("Settings", FileFormat::Toml).required(false)).unwrap(); -//! -//! // Add 'Settings.$(RUST_ENV).toml` -//! let name = format!("Settings.{}", env::var("env").unwrap_or("development".into())); -//! c.merge(File::new(&name, FileFormat::Toml).required(false)).unwrap(); -//! -//! // Add environment variables that begin with APP_ -//! c.merge(Environment::new("APP")).unwrap(); -//! } -//! ``` -//! -//! Note that in the above example the calls to `config::merge` could have -//! been re-ordered to influence the priority as each successive merge -//! is evaluated on top of the previous. -//! -//! Configuration values can be retrieved with a call to `config::get` and then -//! coerced into a type with `as_*`. -//! -//! ```rust -//! # extern crate config; -//! # -//! # use std::env; -//! # use config::{Config, File, FileFormat, Environment}; -//! # -//! # fn main() { -//! # // Create a new local configuration -//! # let mut c = Config::new(); -//! # -//! # // Add 'Settings.toml' -//! # c.merge(File::new("Settings", FileFormat::Toml).required(false)).unwrap(); -//! # -//! # // Add 'Settings.$(RUST_ENV).toml` -//! # let name = format!("Settings.{}", env::var("env").unwrap_or("development".into())); -//! # c.merge(File::new(&name, FileFormat::Toml).required(false)).unwrap(); -//! # -//! # // Add environment variables that begin with APP_ -//! # c.merge(Environment::new("APP")).unwrap(); -//! // Get 'debug' and coerce to a boolean -//! if let Some(value) = c.get("debug") { -//! println!("{:?}", value.into_bool()); -//! } -//! -//! // You can use a type suffix -//! println!("{:?}", c.get_bool("debug")); -//! println!("{:?}", c.get_str("debug")); -//! # } -//! ``` -//! -//! 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; - -#[cfg(feature = "json")] -extern crate serde_json; - -#[cfg(feature = "yaml")] -extern crate yaml_rust; - -mod value; -mod source; -mod file; -mod env; -mod path; -mod config; - -pub use source::{Source, SourceBuilder}; -pub use file::{File, FileFormat}; -pub use env::Environment; -pub use value::Value; -pub use config::Config; diff --git a/src/path.rs b/src/path.rs deleted file mode 100644 index b6d3b10..0000000 --- a/src/path.rs +++ /dev/null @@ -1,134 +0,0 @@ -use nom::*; -use std::str::{FromStr, from_utf8}; - -#[derive(Debug, Eq, PartialEq, Clone, Hash)] -pub enum Expression { - Identifier(String), - Child(Box, String), - Subscript(Box, i32), -} - -named!(ident_, - map!( - map_res!(is_a!( - "abcdefghijklmnopqrstuvwxyz \ - ABCDEFGHIJKLMNOPQRSTUVWXYZ \ - 0123456789 \ - _-" - ), from_utf8), - |s: &str| { - s.to_string() - } - ) -); - -named!(integer , - map_res!( - map_res!( - ws!(digit), - from_utf8 - ), - FromStr::from_str - ) -); - -named!(ident, map!(ident_, Expression::Identifier)); - -fn postfix(expr: Expression) -> Box 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 { - 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/source.rs b/src/source.rs deleted file mode 100644 index 2048791..0000000 --- a/src/source.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::error::Error; -use std::collections::HashMap; - -use value::Value; - -pub trait Source { - fn collect(&self) -> HashMap; -} - -pub trait SourceBuilder { - fn build(&self) -> Result, Box>; -} diff --git a/src/value.rs b/src/value.rs deleted file mode 100644 index 5b5a838..0000000 --- a/src/value.rs +++ /dev/null @@ -1,157 +0,0 @@ -use std::convert::From; -use std::collections::HashMap; - -/// A configuration value. -/// -/// Has an underlying or native type that comes from the configuration source -/// but will be coerced into the requested type. -#[derive(Debug, Clone, PartialEq)] -pub enum Value { - Nil, - String(String), - Integer(i64), - Float(f64), - Boolean(bool), - Table(HashMap), - Array(Vec), -} - -impl Value { - /// Converts `self` into a string, if possible. - /// Returns None otherwise. - pub fn into_str(self) -> Option { - match self { - Value::String(value) => Some(value), - Value::Integer(value) => Some(value.to_string()), - Value::Float(value) => Some(value.to_string()), - Value::Boolean(value) => Some(value.to_string()), - - _ => None, - } - } - - /// Converts `self` into a bool, if possible. - /// Returns None otherwise. - pub fn into_bool(self) -> Option { - match self { - Value::Boolean(value) => Some(value), - Value::Integer(value) => Some(value != 0), - Value::Float(value) => Some(value != 0.0), - - Value::String(ref value) => { - match value.to_lowercase().as_ref() { - "1" | "true" | "on" | "yes" => Some(true), - "0" | "false" | "off" | "no" => Some(false), - _ => None, - } - } - - _ => None, - } - } - - /// Converts `self` into an i64, if possible. - /// Returns None otherwise. - pub fn into_int(self) -> Option { - match self { - Value::Integer(value) => Some(value), - Value::String(ref value) => value.parse().ok(), - Value::Boolean(value) => Some(if value { 1 } else { 0 }), - Value::Float(value) => Some(value.round() as i64), - - _ => None, - } - } - - /// Converts `self` into a f64, if possible. - /// Returns None otherwise. - pub fn into_float(self) -> Option { - match self { - Value::Float(value) => Some(value), - Value::String(ref value) => value.parse().ok(), - Value::Integer(value) => Some(value as f64), - Value::Boolean(value) => Some(if value { 1.0 } else { 0.0 }), - - _ => None, - } - } - - /// If the `Value` is a Table, returns the associated Map. - /// Returns None otherwise. - pub fn into_table(self) -> Option> { - match self { - Value::Table(value) => Some(value), - _ => None, - } - } - - /// If the `Value` is an Array, returns the associated Vector. - /// Returns None otherwise. - pub fn into_array(self) -> Option> { - match self { - Value::Array(value) => Some(value), - _ => None, - } - } -} - -// Generalized construction from type into variant is needed -// for setting configuration values - -impl From for Value { - fn from(value: String) -> Value { - Value::String(value.into()) - } -} - -impl<'a> From<&'a str> for Value { - fn from(value: &'a str) -> Value { - Value::String(value.into()) - } -} - -impl From for Value { - fn from(value: i64) -> Value { - Value::Integer(value) - } -} - -impl From for Value { - fn from(value: f64) -> Value { - Value::Float(value) - } -} - -impl From for Value { - fn from(value: bool) -> Value { - Value::Boolean(value) - } -} - -impl From> for Value - where T: Into -{ - fn from(values: HashMap) -> Value { - let mut r = HashMap::new(); - - for (k, v) in values { - r.insert(k.clone(), v.into()); - } - - Value::Table(r) - } -} - -impl From> for Value - where T: Into -{ - fn from(values: Vec) -> Value { - let mut l = Vec::new(); - - for v in values { - l.push(v.into()); - } - - Value::Array(l) - } -} -- cgit v1.2.3