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/file/json.rs | 87 ---------------------- src/file/mod.rs | 219 ------------------------------------------------------- src/file/nil.rs | 13 ---- src/file/toml.rs | 74 ------------------- src/file/yaml.rs | 101 ------------------------- 5 files changed, 494 deletions(-) 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 (limited to 'src/file') 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" - } -} -- cgit v1.2.3