From 40a44a459fde4c4042d5188faedea718a34e2b96 Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Mon, 23 Jan 2017 18:58:19 -0800 Subject: Initial commit. --- src/lib.rs | 241 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/value.rs | 143 +++++++++++++++++++++++++++++++++++ 2 files changed, 384 insertions(+) create mode 100644 src/lib.rs create mode 100644 src/value.rs (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0be9063 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,241 @@ +#![feature(try_from)] + +mod value; + +use value::Value; + +use std::env; +use std::convert::TryFrom; +use std::collections::HashMap; + +#[derive(Default)] +pub struct Config { + defaults: HashMap, + overrides: HashMap, + environ: HashMap, +} + +impl Config { + pub fn new() -> Config { + Default::default() + } + + pub fn set_default(&mut self, key: &str, value: T) + where T: Into + { + self.defaults.insert(key.into(), value.into()); + } + + pub fn set(&mut self, key: &str, value: T) + where T: Into + { + self.overrides.insert(key.into(), value.into()); + } + + pub fn get<'a, T>(&'a mut self, key: &str) -> Option + where T: TryFrom<&'a mut Value>, + T: Default + { + if let Some(value) = self.overrides.get_mut(key) { + T::try_from(value).ok() + } else if let Ok(value) = env::var(key.to_uppercase()) { + // Store the environment variable into an environ + // hash map; we want to return references + + // TODO: Key name needs to go through a transform + self.environ.insert(key.to_lowercase().into(), value.into()); + + T::try_from(self.environ.get_mut(key).unwrap()).ok() + } else if let Some(value) = self.defaults.get_mut(key) { + T::try_from(value).ok() + } else { + None + } + } + + pub fn get_str<'a>(&'a mut self, key: &str) -> Option<&'a str> { + self.get(key) + } + + pub fn get_int(&mut self, key: &str) -> Option { + self.get(key) + } + + pub fn get_float(&mut self, key: &str) -> Option { + self.get(key) + } + + pub fn get_bool(&mut self, key: &str) -> Option { + self.get(key) + } +} + +#[cfg(test)] +mod test { + use std::env; + + // Retrieval of a non-existent key + #[test] + fn test_not_found() { + let mut c = super::Config::new(); + + assert_eq!(c.get_int("key"), None); + } + + // Environment override + #[test] + fn test_env_override() { + let mut c = super::Config::new(); + + c.set_default("key_1", false); + + env::set_var("KEY_1", "1"); + + assert_eq!(c.get_bool("key_1"), Some(true)); + } + + // Explicit override + #[test] + fn test_default_override() { + let mut c = super::Config::new(); + + c.set_default("key_1", false); + c.set_default("key_2", false); + + assert!(!c.get_bool("key_1").unwrap()); + assert!(!c.get_bool("key_2").unwrap()); + + c.set("key_2", true); + + 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 = super::Config::new(); + + c.set("key", "value"); + + assert_eq!(c.get_str("key").unwrap(), "value"); + assert!("value" == c.get::<&str>("key").unwrap()); + } + + // Storage and retrieval of Boolean values + #[test] + fn test_bool() { + let mut c = super::Config::new(); + + c.set("key", true); + + assert_eq!(c.get_bool("key").unwrap(), true); + assert!(false != c.get("key").unwrap()); + } + + // Storage and retrieval of Float values + #[test] + fn test_float() { + let mut c = super::Config::new(); + + c.set("key", 3.14); + + assert_eq!(c.get_float("key").unwrap(), 3.14); + assert!(3.14 >= c.get("key").unwrap()); + } + + // Storage and retrieval of Integer values + #[test] + fn test_int() { + let mut c = super::Config::new(); + + c.set("key", 42); + + assert_eq!(c.get_int("key").unwrap(), 42); + assert!(42 == c.get::("key").unwrap()); + } + + // Storage of various values and retrieval as String + #[test] + fn test_retrieve_str() { + let mut c = super::Config::new(); + + c.set("key_1", 115); + c.set("key_2", 1.23); + c.set("key_3", false); + + assert_eq!(c.get_str("key_1"), Some("115")); + assert_eq!(c.get_str("key_2"), Some("1.23")); + assert_eq!(c.get_str("key_3"), Some("false")); + } + + // Storage of various values and retrieval as Integer + #[test] + fn test_retrieve_int() { + let mut c = super::Config::new(); + + c.set("key_1", "121"); + c.set("key_2", 5.12); + c.set("key_3", 5.72); + c.set("key_4", false); + c.set("key_5", true); + c.set("key_6", "asga"); + + 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 = super::Config::new(); + + c.set("key_1", "121"); + c.set("key_2", "121.512"); + c.set("key_3", 5); + c.set("key_4", false); + c.set("key_5", true); + c.set("key_6", "asga"); + + 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 = super::Config::new(); + + c.set("key_1", "121"); + c.set("key_2", "1"); + c.set("key_3", "0"); + c.set("key_4", "true"); + c.set("key_5", ""); + c.set("key_6", 51); + c.set("key_7", 0); + c.set("key_8", 12.12); + c.set("key_9", 1.0); + c.set("key_10", 0.0); + c.set("key_11", "asga"); + + assert_eq!(c.get_bool("key_1"), Some(false)); + 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"), Some(false)); + 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"), Some(false)); + } +} diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..295c7c0 --- /dev/null +++ b/src/value.rs @@ -0,0 +1,143 @@ +use std::convert::{From, TryFrom}; + +// Variant for a configuration Value +// The additional Option is used for the textual representation of the +// underlying type (to cache the string generation) but only if requested. +pub enum Value { + String(String), + Integer(i64, Option), + Float(f64, Option), + Boolean(bool, Option), +} + +// Conversion from type into variant +impl From for Value { + fn from(value: String) -> Value { + Value::String(value) + } +} + +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, None) + } +} + +impl From for Value { + fn from(value: f64) -> Value { + Value::Float(value, None) + } +} + +impl From for Value { + fn from(value: bool) -> Value { + Value::Boolean(value, None) + } +} + +// Conversion from variant into type +impl<'a> TryFrom<&'a mut Value> for &'a str { + type Err = (); + + fn try_from(value: &mut Value) -> Result<&str, ()> { + // When converting a non-string value into a string; + // cache the conversion and return a reference + + if let Value::String(ref value) = *value { + Ok(value) + } else if let Value::Integer(value, ref mut text) = *value { + if let Some(ref text) = *text { + Ok(text) + } else { + *text = Some(value.to_string()); + + Ok(text.as_ref().unwrap()) + } + } else if let Value::Float(value, ref mut text) = *value { + if let Some(ref text) = *text { + Ok(text) + } else { + *text = Some(value.to_string()); + + Ok(text.as_ref().unwrap()) + } + } else if let Value::Boolean(value, ref mut text) = *value { + if let Some(ref text) = *text { + Ok(text) + } else { + *text = Some(value.to_string()); + + Ok(text.as_ref().unwrap()) + } + } else { + Err(()) + } + } +} + +impl<'a> TryFrom<&'a mut Value> for i64 { + type Err = (); + + fn try_from(value: &mut Value) -> Result { + if let Value::Integer(value, ..) = *value { + Ok(value) + } else if let Value::String(ref value) = *value { + value.parse().map_err(|_| { + // Drop specific error + }) + } else if let Value::Boolean(value, ..) = *value { + Ok(if value { 1 } else { 0 }) + } else if let Value::Float(value, ..) = *value { + Ok(value.round() as i64) + } else { + Err(()) + } + } +} + +impl<'a> TryFrom<&'a mut Value> for f64 { + type Err = (); + + fn try_from(value: &mut Value) -> Result { + if let Value::Float(value, ..) = *value { + Ok(value) + } else if let Value::String(ref value) = *value { + value.parse().map_err(|_| { + // Drop specific error + }) + } else if let Value::Integer(value, ..) = *value { + Ok(value as f64) + } else if let Value::Boolean(value, ..) = *value { + Ok(if value { 1.0 } else { 0.0 }) + } else { + Err(()) + } + } +} + +impl<'a> TryFrom<&'a mut Value> for bool { + type Err = (); + + fn try_from(value: &mut Value) -> Result { + if let Value::Boolean(value, ..) = *value { + Ok(value) + } else if let Value::String(ref value) = *value { + Ok(match value.to_lowercase().as_ref() { + "1" | "true" | "on" | "yes" => true, + _ => false, + }) + } else if let Value::Integer(value, ..) = *value { + Ok(value != 0) + } else if let Value::Float(value, ..) = *value { + Ok(value != 0.0) + } else { + Err(()) + } + } +} -- cgit v1.2.3