summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--examples/pattern/Cargo.toml6
-rw-r--r--examples/pattern/conf/00-default.toml1
-rw-r--r--examples/pattern/conf/05-some.yml2
-rw-r--r--examples/pattern/conf/99-extra.json5
-rw-r--r--examples/pattern/src/main.rs50
-rw-r--r--src/config.rs22
-rw-r--r--src/file/format/json.rs25
-rw-r--r--src/file/format/mod.rs9
-rw-r--r--src/file/format/toml.rs25
-rw-r--r--src/file/format/yaml.rs26
-rw-r--r--src/file/mod.rs54
-rw-r--r--src/file/source/file.rs28
-rw-r--r--src/source.rs69
-rw-r--r--tests/errors.rs6
15 files changed, 187 insertions, 142 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9fccaa5..00d1108 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## 0.6.0 – Upcoming
+ - Remove `namespace` option for File
- Add builder pattern to condense configuration
```rust
diff --git a/examples/pattern/Cargo.toml b/examples/pattern/Cargo.toml
new file mode 100644
index 0000000..98c5330
--- /dev/null
+++ b/examples/pattern/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "pattern"
+version = "0.1.0"
+
+[dependencies]
+config = { path = "../../", features = ["glob"] }
diff --git a/examples/pattern/conf/00-default.toml b/examples/pattern/conf/00-default.toml
new file mode 100644
index 0000000..7b95e7a
--- /dev/null
+++ b/examples/pattern/conf/00-default.toml
@@ -0,0 +1 @@
+debug = false
diff --git a/examples/pattern/conf/05-some.yml b/examples/pattern/conf/05-some.yml
new file mode 100644
index 0000000..52555a0
--- /dev/null
+++ b/examples/pattern/conf/05-some.yml
@@ -0,0 +1,2 @@
+secret: THIS IS SECRET
+debug: true
diff --git a/examples/pattern/conf/99-extra.json b/examples/pattern/conf/99-extra.json
new file mode 100644
index 0000000..26f908c
--- /dev/null
+++ b/examples/pattern/conf/99-extra.json
@@ -0,0 +1,5 @@
+{
+ "that": 3,
+ "this": 1230,
+ "key": "sdgnjklsdjklgds"
+}
diff --git a/examples/pattern/src/main.rs b/examples/pattern/src/main.rs
new file mode 100644
index 0000000..aaf164d
--- /dev/null
+++ b/examples/pattern/src/main.rs
@@ -0,0 +1,50 @@
+extern crate glob;
+extern crate config;
+
+use std::path::Path;
+use std::collections::HashMap;
+use config::*;
+use glob::glob;
+
+fn main() {
+ // Option 1
+ // --------
+ // Gather all conf files from conf/ manually
+ let settings = Config::default()
+ // File::with_name(..) is shorthand for File::from(Path::new(..))
+ .merge(File::with_name("conf/00-default.toml"))
+ .merge(File::from(Path::new("conf/05-some.yml")))
+ .merge(File::from(Path::new("conf/99-extra.json")))
+ .unwrap();
+
+ // Print out our settings (as a HashMap)
+ println!("\n{:?} \n\n-----------",
+ settings.deserialize::<HashMap<String, String>>().unwrap());
+
+ // Option 2
+ // --------
+ // Gather all conf files from conf/ manually, but put in 1 merge call.
+ let settings = Config::default()
+ .merge(vec![File::with_name("conf/00-default.toml"),
+ File::from(Path::new("conf/05-some.yml")),
+ File::from(Path::new("conf/99-extra.json"))])
+ .unwrap();
+
+ // Print out our settings (as a HashMap)
+ println!("\n{:?} \n\n-----------",
+ settings.deserialize::<HashMap<String, String>>().unwrap());
+
+ // Option 3
+ // --------
+ // Gather all conf files from conf/ using glob and put in 1 merge call.
+ let settings = Config::default()
+ .merge(glob("conf/*")
+ .unwrap()
+ .map(|path| File::from(path.unwrap()))
+ .collect::<Vec<_>>())
+ .unwrap();
+
+ // Print out our settings (as a HashMap)
+ println!("\n{:?} \n\n-----------",
+ settings.deserialize::<HashMap<String, String>>().unwrap());
+}
diff --git a/src/config.rs b/src/config.rs
index c8fc2e4..f1d5e93 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -89,26 +89,8 @@ impl Config {
}
// Add sources
- for source in sources {
- let props = match source.collect() {
- Ok(props) => props,
- Err(error) => {
- return ConfigResult(Err(error));
- }
- };
-
- for (key, val) in &props {
- match path::Expression::from_str(key) {
- // Set using the path
- Ok(expr) => expr.set(&mut cache, val.clone()),
-
- // Set diretly anyway
- _ => {
- path::Expression::Identifier(key.clone())
- .set(&mut cache, val.clone())
- }
- }
- }
+ if let Err(error) = sources.collect_to(&mut cache) {
+ return ConfigResult(Err(error));
}
// Add overrides
diff --git a/src/file/format/json.rs b/src/file/format/json.rs
index 290e17d..ed6c787 100644
--- a/src/file/format/json.rs
+++ b/src/file/format/json.rs
@@ -4,31 +4,10 @@ use std::collections::HashMap;
use std::error::Error;
use value::{Value, ValueKind};
-pub fn parse(uri: Option<&String>,
- text: &str,
- namespace: Option<&String>)
- -> Result<HashMap<String, Value>, Box<Error>> {
+pub fn parse(uri: Option<&String>, text: &str) -> Result<HashMap<String, Value>, Box<Error>> {
// Parse a JSON object value from the text
- let mut root: serde_json::Value = serde_json::from_str(text)?;
-
- // Limit to namespace
- if let Some(namespace) = namespace {
- root = serde_json::Value::Object(match root {
- serde_json::Value::Object(ref mut table) => {
- if let Some(serde_json::Value::Object(table)) =
- table.remove(namespace) {
- table
- } else {
- serde_json::Map::new()
- }
- }
-
- _ => serde_json::Map::new(),
- });
- };
-
// TODO: Have a proper error fire if the root of a file is ever not a Table
- let value = from_json_value(uri, &root);
+ let value = from_json_value(uri, &serde_json::from_str(text)?);
match value.kind {
ValueKind::Table(map) => Ok(map),
diff --git a/src/file/format/mod.rs b/src/file/format/mod.rs
index 3fc15a6..1988513 100644
--- a/src/file/format/mod.rs
+++ b/src/file/format/mod.rs
@@ -57,18 +57,17 @@ impl FileFormat {
#[allow(unused_variables)]
pub fn parse(&self,
uri: Option<&String>,
- text: &str,
- namespace: Option<&String>)
+ text: &str)
-> Result<HashMap<String, Value>, Box<Error>> {
match *self {
#[cfg(feature = "toml")]
- FileFormat::Toml => toml::parse(uri, text, namespace),
+ FileFormat::Toml => toml::parse(uri, text),
#[cfg(feature = "json")]
- FileFormat::Json => json::parse(uri, text, namespace),
+ FileFormat::Json => json::parse(uri, text),
#[cfg(feature = "yaml")]
- FileFormat::Yaml => yaml::parse(uri, text, namespace),
+ FileFormat::Yaml => yaml::parse(uri, text),
}
}
}
diff --git a/src/file/format/toml.rs b/src/file/format/toml.rs
index 2df1fca..6c835a4 100644
--- a/src/file/format/toml.rs
+++ b/src/file/format/toml.rs
@@ -4,31 +4,10 @@ use std::collections::{HashMap, BTreeMap};
use std::error::Error;
use value::{Value, ValueKind};
-pub fn parse(uri: Option<&String>,
- text: &str,
- namespace: Option<&String>)
- -> Result<HashMap<String, Value>, Box<Error>> {
+pub fn parse(uri: Option<&String>, text: &str) -> Result<HashMap<String, Value>, Box<Error>> {
// Parse a TOML value from the provided text
- let mut root: toml::Value = toml::from_str(text)?;
-
- // Limit to namespace
- if let Some(namespace) = namespace {
- root = toml::Value::Table(match root {
- toml::Value::Table(ref mut table) => {
- if let Some(toml::Value::Table(table)) =
- table.remove(namespace) {
- table
- } else {
- BTreeMap::new()
- }
- }
-
- _ => BTreeMap::new(),
- });
- }
-
// TODO: Have a proper error fire if the root of a file is ever not a Table
- let value = from_toml_value(uri, &root);
+ let value = from_toml_value(uri, &toml::from_str(text)?);
match value.kind {
ValueKind::Table(map) => Ok(map),
diff --git a/src/file/format/yaml.rs b/src/file/format/yaml.rs
index b1afce7..1cba0d3 100644
--- a/src/file/format/yaml.rs
+++ b/src/file/format/yaml.rs
@@ -6,14 +6,10 @@ use std::collections::{BTreeMap, HashMap};
use std::mem;
use value::{Value, ValueKind};
-pub fn parse(uri: Option<&String>,
- text: &str,
- namespace: Option<&String>)
- -> Result<HashMap<String, Value>, Box<Error>> {
+pub fn parse(uri: Option<&String>, text: &str) -> Result<HashMap<String, Value>, Box<Error>> {
+ // Parse a YAML object from file
let mut docs = yaml::YamlLoader::load_from_str(text)?;
-
- // Designate root
- let mut root = match docs.len() {
+ let root = match docs.len() {
0 => yaml::Yaml::Hash(BTreeMap::new()),
1 => mem::replace(&mut docs[0], yaml::Yaml::Null),
n => {
@@ -21,22 +17,6 @@ pub fn parse(uri: Option<&String>,
}
};
- // Limit to namespace
- if let Some(namespace) = namespace {
- root = yaml::Yaml::Hash(match root {
- yaml::Yaml::Hash(ref mut table) => {
- if let Some(yaml::Yaml::Hash(table)) =
- table.remove(&yaml::Yaml::String(namespace.clone())) {
- table
- } else {
- BTreeMap::new()
- }
- }
-
- _ => BTreeMap::new(),
- });
- };
-
// TODO: Have a proper error fire if the root of a file is ever not a Table
let value = from_yaml_value(uri, &root);
match value.kind {
diff --git a/src/file/mod.rs b/src/file/mod.rs
index 671f579..45deb92 100644
--- a/src/file/mod.rs
+++ b/src/file/mod.rs
@@ -5,7 +5,7 @@ use source::Source;
use error::*;
use value::Value;
use std::collections::HashMap;
-use std::path::Path;
+use std::path::{Path, PathBuf};
use self::source::FileSource;
pub use self::format::FileFormat;
@@ -16,9 +16,6 @@ pub struct File<T>
{
source: T,
- /// Namespace to restrict configuration from the file
- namespace: Option<String>,
-
/// Format of file (which dictates what driver to use).
format: Option<FileFormat>,
@@ -31,7 +28,6 @@ impl File<source::string::FileSourceString> {
File {
format: Some(format),
required: true,
- namespace: None,
source: s.into(),
}
}
@@ -42,8 +38,7 @@ impl File<source::file::FileSourceFile> {
File {
format: Some(format),
required: true,
- namespace: None,
- source: source::file::FileSourceFile::new(name),
+ source: source::file::FileSourceFile::new(name.into()),
}
}
@@ -53,20 +48,39 @@ impl File<source::file::FileSourceFile> {
File {
format: None,
required: true,
- namespace: None,
- source: source::file::FileSourceFile::new(name),
+ source: source::file::FileSourceFile::new(name.into()),
+ }
+ }
+}
+
+impl<'a> From<&'a Path> for File<source::file::FileSourceFile> {
+ fn from(path: &'a Path) -> Self {
+ File {
+ format: None,
+ required: true,
+ source: source::file::FileSourceFile::new(path.to_path_buf()),
+ }
+ }
+}
+
+impl From<PathBuf> for File<source::file::FileSourceFile> {
+ fn from(path: PathBuf) -> Self {
+ File {
+ format: None,
+ required: true,
+ source: source::file::FileSourceFile::new(path),
}
}
}
impl<T: FileSource> File<T> {
- pub fn required(mut self, required: bool) -> Self {
- self.required = required;
+ pub fn format(mut self, format: FileFormat) -> Self {
+ self.format = Some(format);
self
}
- pub fn namespace(mut self, namespace: &str) -> Self {
- self.namespace = Some(namespace.into());
+ pub fn required(mut self, required: bool) -> Self {
+ self.required = required;
self
}
}
@@ -96,13 +110,11 @@ impl<T: FileSource> Source for File<T>
};
// Parse the string using the given format
- format
- .parse(uri.as_ref(), &contents, self.namespace.as_ref())
- .map_err(|cause| {
- ConfigError::FileParse {
- uri: uri,
- cause: cause,
- }
- })
+ format.parse(uri.as_ref(), &contents).map_err(|cause| {
+ ConfigError::FileParse {
+ uri: uri,
+ cause: cause,
+ }
+ })
}
}
diff --git a/src/file/source/file.rs b/src/file/source/file.rs
index 426c0b5..790933f 100644
--- a/src/file/source/file.rs
+++ b/src/file/source/file.rs
@@ -15,36 +15,20 @@ use super::{FileFormat, FileSource};
/// Describes a file sourced from a file
#[derive(Clone, Debug)]
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<String>,
+ /// Path of configuration file
+ name: PathBuf,
}
impl FileSourceFile {
- pub fn new(name: &str) -> FileSourceFile {
- FileSourceFile {
- name: name.into(),
- path: None,
- }
+ pub fn new(name: PathBuf) -> FileSourceFile {
+ FileSourceFile { name: name }
}
fn find_file(&self,
format_hint: Option<FileFormat>)
-> Result<(PathBuf, FileFormat), Box<Error>> {
- // Build expected configuration file
- let mut basename = PathBuf::new();
-
- if let Some(ref path) = self.path {
- basename.push(path.clone());
- }
-
- basename.push(self.name.clone());
-
// First check for an _exact_ match
- let mut filename = env::current_dir()?.as_path().join(basename.clone());
+ let mut filename = env::current_dir()?.as_path().join(self.name.clone());
if filename.is_file() {
return match format_hint {
Some(format) => Ok((filename, format)),
@@ -92,7 +76,7 @@ impl FileSourceFile {
Err(Box::new(io::Error::new(io::ErrorKind::NotFound,
format!("configuration file \"{}\" not found",
- basename.to_string_lossy()))))
+ self.name.to_string_lossy()))))
}
}
diff --git a/src/source.rs b/src/source.rs
index c8a0e83..9bf28c5 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -1,7 +1,9 @@
use error::*;
use std::fmt::Debug;
-use value::Value;
+use std::str::FromStr;
+use value::{Value, ValueKind};
use std::collections::HashMap;
+use path;
/// Describes a generic _source_ of configuration properties.
pub trait Source: Debug {
@@ -10,6 +12,27 @@ pub trait Source: Debug {
/// Collect all configuration properties available from this source and return
/// a HashMap.
fn collect(&self) -> Result<HashMap<String, Value>>;
+
+ fn collect_to(&self, cache: &mut Value) -> Result<()> {
+ let props = match self.collect() {
+ Ok(props) => props,
+ Err(error) => {
+ return Err(error);
+ }
+ };
+
+ for (key, val) in &props {
+ match path::Expression::from_str(key) {
+ // Set using the path
+ Ok(expr) => expr.set(cache, val.clone()),
+
+ // Set diretly anyway
+ _ => path::Expression::Identifier(key.clone()).set(cache, val.clone()),
+ }
+ }
+
+ Ok(())
+ }
}
impl Clone for Box<Source + Send + Sync> {
@@ -17,3 +40,47 @@ impl Clone for Box<Source + Send + Sync> {
self.clone_into_box()
}
}
+
+impl Source for Vec<Box<Source + Send + Sync>> {
+ fn clone_into_box(&self) -> Box<Source + Send + Sync> {
+ Box::new((*self).clone())
+ }
+
+ fn collect(&self) -> Result<HashMap<String, Value>> {
+ let mut cache: Value = HashMap::<String, Value>::new().into();
+
+ for source in self {
+ source.collect_to(&mut cache)?;
+ }
+
+ if let ValueKind::Table(table) = cache.kind {
+ Ok(table)
+ } else {
+ unreachable!();
+ }
+ }
+}
+
+impl<T> Source for Vec<T>
+ where T: Source + Sync + Send,
+ T: Clone,
+ T: 'static
+{
+ fn clone_into_box(&self) -> Box<Source + Send + Sync> {
+ Box::new((*self).clone())
+ }
+
+ fn collect(&self) -> Result<HashMap<String, Value>> {
+ let mut cache: Value = HashMap::<String, Value>::new().into();
+
+ for source in self {
+ source.collect_to(&mut cache)?;
+ }
+
+ if let ValueKind::Table(table) = cache.kind {
+ Ok(table)
+ } else {
+ unreachable!();
+ }
+ }
+}
diff --git a/tests/errors.rs b/tests/errors.rs
index fa3b5ca..835ead1 100644
--- a/tests/errors.rs
+++ b/tests/errors.rs
@@ -17,8 +17,7 @@ fn test_error_parse() {
assert!(res.is_err());
assert_eq!(res.unwrap_err().to_string(),
- "invalid number at line 2 in tests/Settings-invalid.toml"
- .to_string());
+ "invalid number at line 2 in tests/Settings-invalid.toml".to_string());
}
#[test]
@@ -42,6 +41,5 @@ fn test_error_type_detached() {
assert!(res.is_err());
assert_eq!(res.unwrap_err().to_string(),
- "invalid type: string \"fals\", expected a boolean"
- .to_string());
+ "invalid type: string \"fals\", expected a boolean".to_string());
}