summaryrefslogtreecommitdiffstats
path: root/src/file/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/file/mod.rs')
-rw-r--r--src/file/mod.rs236
1 files changed, 55 insertions, 181 deletions
diff --git a/src/file/mod.rs b/src/file/mod.rs
index 7f7c0fb..7ab77d8 100644
--- a/src/file/mod.rs
+++ b/src/file/mod.rs
@@ -1,219 +1,93 @@
-use std::env;
-use std::error::Error;
-use std::io::{self, Read};
-use std::fs;
-use std::path::PathBuf;
+mod format;
+pub mod source;
-use source::{Source, SourceBuilder};
+use source::Source;
+use error::*;
+use value::Value;
+use std::collections::HashMap;
-mod nil;
+use self::source::FileSource;
+pub use self::format::FileFormat;
-#[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<Source + Send + Sync>, Box<Error>> {
- 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<Source + Send + Sync>, Box<Error>>;
-}
-
-pub struct FileSourceString(String);
-
-impl FileSource for FileSourceString {
- fn try_build(&self,
- format: FileFormat,
- namespace: Option<&String>)
- -> Result<Box<Source + Send + Sync>, Box<Error>> {
- 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<String>,
-}
-
-impl FileSourceFile {
- // Find configuration file
- // Use algorithm similar to .git detection by git
- fn find_file(&self, format: FileFormat) -> Result<PathBuf, Box<Error>> {
- // 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<Source + Send + Sync>, Box<Error>> {
- // 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<T: FileSource> {
- /// Source of the file
+pub struct File<T>
+ where T: FileSource
+{
source: T,
/// Namespace to restrict configuration from the file
namespace: Option<String>,
- /// Format of file (which dictates what driver to use); Defauts to TOML.
- format: FileFormat,
+ /// Format of file (which dictates what driver to use).
+ format: Option<FileFormat>,
/// A required File will error if it cannot be found
required: bool,
}
-impl File<FileSourceString> {
- pub fn from_str(s: &str, format: FileFormat) -> File<FileSourceString> {
+impl File<source::string::FileSourceString> {
+ pub fn from_str(s: &str, format: FileFormat) -> Self {
File {
- format: format,
+ format: Some(format),
required: true,
namespace: None,
- source: FileSourceString(s.into()),
+ source: s.into(),
}
}
}
-impl File<FileSourceFile> {
- pub fn new(name: &str, format: FileFormat) -> File<FileSourceFile> {
+impl File<source::file::FileSourceFile> {
+ pub fn new(name: &str, format: FileFormat) -> Self {
File {
- format: format,
+ format: Some(format),
required: true,
namespace: None,
- source: FileSourceFile {
- name: name.into(),
- path: None,
- },
+ source: source::file::FileSourceFile::new(name),
}
}
}
impl<T: FileSource> File<T> {
- pub fn required(self, required: bool) -> File<T> {
- File { required: required, ..self }
+ pub fn required(mut self, required: bool) -> Self {
+ self.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<Source + Send + Sync>, Box<Error>> {
- self.source.try_build(self.format, self.namespace.as_ref())
+ pub fn namespace(mut self, namespace: &str) -> Self {
+ self.namespace = Some(namespace.into());
+ self
}
}
-impl File<FileSourceFile> {
- pub fn path(self, path: &str) -> Self {
- File { source: FileSourceFile { path: Some(path.into()), ..self.source }, ..self }
- }
-}
+impl<T: FileSource> Source for File<T> {
+ fn collect(&self) -> Result<HashMap<String, Value>> {
+ // Coerce the file contents to a string
+ let (uri, contents) = match self.source.resolve(self.format).map_err(|err| {
+ ConfigError::Foreign(err)
+ }) {
+ Ok((uri, contents)) => (uri, contents),
+
+ Err(error) => {
+ if !self.required {
+ return Ok(HashMap::new());
+ }
+
+ return Err(error);
+ }
+ };
+
+ // Parse the string using the given format
+ let result = self.format.unwrap().parse(uri.as_ref(), &contents, self.namespace.as_ref()).map_err(|cause| {
+ ConfigError::FileParse {
+ uri: uri,
+ cause: cause
+ }
+ });
-impl<T: FileSource> SourceBuilder for File<T> {
- // Use try_build but only pass an error through if this source
- // is required
- fn build(&self) -> Result<Box<Source + Send + Sync>, Box<Error>> {
- if self.required {
- self.try_build()
+ if result.is_err() && !self.required {
+ // Ignore fails and just go with it if its not required
+ Ok(HashMap::new())
} else {
- self.try_build().or_else(|_| Ok(Box::new(nil::Nil {})))
+ result
}
}
}