summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Leckey <leckey.ryan@gmail.com>2017-01-28 20:46:29 -0800
committerRyan Leckey <leckey.ryan@gmail.com>2017-01-28 20:47:26 -0800
commit7d870758cbd9ad4181471ad40184d1bac1204e1e (patch)
tree5f84b003c3dfe4c65b5ed97cbce8de6b8f984c49
parent0b3087a8cbe6c0b9428d33c3a2c92155a3bbc558 (diff)
Use 'Cow' to remove unnecessary allocations
-rw-r--r--examples/basic/src/main.rs8
-rw-r--r--src/config.rs263
-rw-r--r--src/file/json.rs3
-rw-r--r--src/file/toml.rs3
-rw-r--r--src/lib.rs42
-rw-r--r--src/value.rs86
6 files changed, 263 insertions, 142 deletions
diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs
index a0e40bf..6690f0e 100644
--- a/examples/basic/src/main.rs
+++ b/examples/basic/src/main.rs
@@ -2,10 +2,10 @@ extern crate config;
fn main() {
// Set defaults for `window.width` and `window.height`
- config::set_default("window.title", "Basic");
- config::set_default("window.width", 640);
- config::set_default("window.height", 480);
- config::set_default("debug", true);
+ config::set_default("window.title", "Basic").unwrap();
+ config::set_default("window.width", 640).unwrap();
+ config::set_default("window.height", 480).unwrap();
+ config::set_default("debug", true).unwrap();
// Note that you can retrieve the stored values as any type as long
// as there exists a reasonable conversion
diff --git a/src/config.rs b/src/config.rs
index e5b25f6..a1a36cb 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -2,82 +2,199 @@ use value::Value;
use source::{Source, SourceBuilder};
use std::error::Error;
+use std::fmt;
+use std::borrow::Cow;
use std::collections::HashMap;
-#[derive(Default)]
-pub struct Config {
- defaults: HashMap<String, Value>,
- overrides: HashMap<String, Value>,
- sources: Vec<Box<Source>>,
+#[derive(Default, Debug)]
+pub struct FrozenError { }
+
+impl fmt::Display for FrozenError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "FrozenError")
+ }
}
-impl Config {
- pub fn new() -> Config {
- Default::default()
+impl Error for FrozenError {
+ fn description(&self) -> &'static str {
+ "configuration is frozen"
}
+}
- /// Merge in configuration values from the given source.
- pub fn merge<T>(&mut self, source: T) -> Result<(), Box<Error>>
- where T: SourceBuilder
- {
- self.sources.push(source.build()?);
+// Underlying storage for the configuration
+enum ConfigStore<'a> {
+ Mutable {
+ defaults: HashMap<String, Value<'a>>,
+ overrides: HashMap<String, Value<'a>>,
+ sources: Vec<Box<Source>>,
+ },
+
+ // TODO: Will be used for frozen configuratino soon
+ #[allow(dead_code)]
+ Frozen,
+}
- Ok(())
+impl<'a> Default for ConfigStore<'a> {
+ fn default() -> Self {
+ ConfigStore::Mutable {
+ defaults: HashMap::new(),
+ overrides: HashMap::new(),
+ sources: Vec::new(),
+ }
}
+}
- /// Sets the default value for this key. The default value is only used
- /// when no other value is provided.
- pub fn set_default<T>(&mut self, key: &str, value: T)
- where T: Into<Value>
+impl<'a> ConfigStore<'a> {
+ fn merge<T>(&mut self, source: T) -> Result<(), Box<Error>>
+ where T: SourceBuilder
{
- self.defaults.insert(key.to_lowercase(), value.into());
+ if let ConfigStore::Mutable { ref mut sources, .. } = *self {
+ sources.push(source.build()?);
+
+ Ok(())
+ } else {
+ Err(FrozenError::default().into())
+ }
}
- /// Sets an override for this key.
- pub fn set<T>(&mut self, key: &str, value: T)
- where T: Into<Value>
+ fn set_default<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>>
+ where T: Into<Value<'a>>
{
- self.overrides.insert(key.to_lowercase(), value.into());
+ if let ConfigStore::Mutable { ref mut defaults, .. } = *self {
+ defaults.insert(key.to_lowercase(), value.into());
+
+ Ok(())
+ } else {
+ Err(FrozenError::default().into())
+ }
}
- pub fn get(&self, key: &str) -> Option<Value> {
- // Check explicit override
+ fn set<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>>
+ where T: Into<Value<'a>>
+ {
+ if let ConfigStore::Mutable { ref mut overrides, .. } = *self {
+ overrides.insert(key.to_lowercase(), value.into());
- if let Some(value) = self.overrides.get(key) {
- return Some(value.clone());
+ Ok(())
+ } else {
+ Err(FrozenError::default().into())
}
+ }
+
+ fn get(&self, key: &str) -> Option<Value> {
+ if let ConfigStore::Mutable { ref overrides, ref sources, ref defaults } = *self {
+ // Check explicit override
+ if let Some(value) = overrides.get(key) {
+ return Some(value.clone());
+ }
- // Check sources
+ // Check sources
+ for source in &mut sources.iter().rev() {
+ if let Some(value) = source.get(key) {
+ return Some(value);
+ }
+ }
- for source in &mut self.sources.iter().rev() {
- if let Some(value) = source.get(key) {
- return Some(value);
+ // Check explicit defaults
+ if let Some(value) = defaults.get(key) {
+ return Some(value.clone());
}
}
- // Check explicit defaults
+ None
+ }
+}
- if let Some(value) = self.defaults.get(key) {
- return Some(value.clone());
- }
+#[derive(Default)]
+pub struct Config<'a> {
+ store: ConfigStore<'a>,
+}
+// TODO(@rust): There must be a way to remove this function and use Value::as_str
+#[allow(needless_lifetimes)]
+fn value_into_str<'a>(value: Value<'a>) -> Option<Cow<'a, str>> {
+ if let Value::String(value) = value {
+ Some(value)
+ } else if let Value::Integer(value) = value {
+ Some(Cow::Owned(value.to_string()))
+ } else if let Value::Float(value) = value {
+ Some(Cow::Owned(value.to_string()))
+ } else if let Value::Boolean(value) = value {
+ Some(Cow::Owned(value.to_string()))
+ } else {
None
}
+}
+
+impl<'a> Config<'a> {
+ pub fn new() -> Config<'a> {
+ Default::default()
+ }
+
+ /// Merge in configuration values from the given source.
+ pub fn merge<T>(&mut self, source: T) -> Result<(), Box<Error>>
+ where T: SourceBuilder
+ {
+ self.store.merge(source)
+ }
- pub fn get_str(&self, key: &str) -> Option<String> {
- self.get(key).and_then(Value::as_str)
+ /// Sets the default value for this key. The default value is only used
+ /// when no other value is provided.
+ pub fn set_default<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>>
+ where T: Into<Value<'a>>
+ {
+ self.store.set_default(key, value)
+ }
+
+ /// Sets an override for this key.
+ pub fn set<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>>
+ where T: Into<Value<'a>>
+ {
+ self.store.set(key, value)
+ }
+
+ pub fn get(&self, key: &str) -> Option<Cow<'a, Value>> {
+ self.store.get(key).map(Cow::Owned)
+ }
+
+ pub fn get_str(&'a self, key: &str) -> Option<Cow<'a, str>> {
+ // TODO(@rust): This is a bit nasty looking; 3x match and requires this
+ // odd into_str method
+ if let Some(value) = self.get(key) {
+ match value {
+ Cow::Borrowed(value) => {
+ match value.as_str() {
+ Some(value) => {
+ match value {
+ Cow::Borrowed(value) => Some(Cow::Borrowed(value)),
+ Cow::Owned(value) => Some(Cow::Owned(value)),
+ }
+ }
+
+ _ => None,
+ }
+ }
+
+ Cow::Owned(value) => value_into_str(value),
+ }
+ } else {
+ None
+ }
}
pub fn get_int(&self, key: &str) -> Option<i64> {
- self.get(key).and_then(Value::as_int)
+ // TODO(@rust): Why doesn't .and_then(Value::as_int) work?
+ self.get(key).and_then(|v| v.as_int())
}
pub fn get_float(&self, key: &str) -> Option<f64> {
- self.get(key).and_then(Value::as_float)
+ // TODO(@rust): Why doesn't .and_then(Value::as_float) work?
+ self.get(key).and_then(|v| v.as_float())
}
pub fn get_bool(&self, key: &str) -> Option<bool> {
- self.get(key).and_then(Value::as_bool)
+ // TODO(@rust): Why doesn't .and_then(Value::as_bool) work?
+ self.get(key).and_then(|v| v.as_bool())
}
}
@@ -99,13 +216,13 @@ mod test {
fn test_default_override() {
let mut c = Config::new();
- c.set_default("key_1", false);
- c.set_default("key_2", false);
+ 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);
+ c.set("key_2", true).unwrap();
assert!(!c.get_bool("key_1").unwrap());
assert!(c.get_bool("key_2").unwrap());
@@ -116,7 +233,7 @@ mod test {
fn test_str() {
let mut c = Config::new();
- c.set("key", "value");
+ c.set("key", "value").unwrap();
assert_eq!(c.get_str("key").unwrap(), "value");
}
@@ -126,7 +243,7 @@ mod test {
fn test_bool() {
let mut c = Config::new();
- c.set("key", true);
+ c.set("key", true).unwrap();
assert_eq!(c.get_bool("key").unwrap(), true);
}
@@ -136,7 +253,7 @@ mod test {
fn test_float() {
let mut c = Config::new();
- c.set("key", 3.14);
+ c.set("key", 3.14).unwrap();
assert_eq!(c.get_float("key").unwrap(), 3.14);
}
@@ -146,7 +263,7 @@ mod test {
fn test_int() {
let mut c = Config::new();
- c.set("key", 42);
+ c.set("key", 42).unwrap();
assert_eq!(c.get_int("key").unwrap(), 42);
}
@@ -156,9 +273,9 @@ mod test {
fn test_retrieve_str() {
let mut c = Config::new();
- c.set("key_1", 115);
- c.set("key_2", 1.23);
- c.set("key_3", false);
+ 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");
@@ -170,12 +287,12 @@ mod test {
fn test_retrieve_int() {
let mut c = 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");
+ 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));
@@ -190,12 +307,12 @@ mod test {
fn test_retrieve_float() {
let mut c = 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");
+ 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));
@@ -210,17 +327,17 @@ mod test {
fn test_retrieve_bool() {
let mut c = 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");
+ 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));
diff --git a/src/file/json.rs b/src/file/json.rs
index 05af065..165bdbe 100644
--- a/src/file/json.rs
+++ b/src/file/json.rs
@@ -2,6 +2,7 @@ use serde_json;
use source::Source;
use std::error::Error;
+use std::borrow::Cow;
use value::Value;
pub struct Content {
@@ -20,7 +21,7 @@ impl Content {
fn from_json_value(value: &serde_json::Value) -> Option<Value> {
match *value {
- serde_json::Value::String(ref value) => Some(Value::String(value.clone())),
+ serde_json::Value::String(ref value) => Some(Value::String(Cow::Borrowed(value))),
serde_json::Value::Number(ref value) => {
if let Some(value) = value.as_i64() {
diff --git a/src/file/toml.rs b/src/file/toml.rs
index 9e4b170..2fd5bd3 100644
--- a/src/file/toml.rs
+++ b/src/file/toml.rs
@@ -1,5 +1,6 @@
use toml;
use source::Source;
+use std::borrow::Cow;
use std::error::Error;
use value::Value;
@@ -21,7 +22,7 @@ impl Content {
fn from_toml_value(value: &toml::Value) -> Option<Value> {
match *value {
- toml::Value::String(ref value) => Some(Value::String(value.clone())),
+ toml::Value::String(ref value) => Some(Value::String(Cow::Borrowed(value))),
toml::Value::Float(value) => Some(Value::Float(value)),
toml::Value::Integer(value) => Some(Value::Integer(value)),
toml::Value::Boolean(value) => Some(Value::Boolean(value)),
diff --git a/src/lib.rs b/src/lib.rs
index ad527e2..25e2255 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
#![feature(drop_types_in_const)]
+
#![allow(unknown_lints)]
#[cfg(feature = "toml")]
@@ -14,7 +15,8 @@ mod env;
mod config;
use std::error::Error;
-use std::sync::{Once, ONCE_INIT, RwLock};
+use std::sync::{Once, ONCE_INIT};
+use std::borrow::Cow;
pub use source::{Source, SourceBuilder};
pub use file::{File, FileFormat};
@@ -25,13 +27,15 @@ pub use value::Value;
pub use config::Config;
// Global configuration
-static mut CONFIG: Option<RwLock<Config>> = None;
+static mut CONFIG: Option<Config<'static>> = None;
static CONFIG_INIT: Once = ONCE_INIT;
// Get the global configuration instance
-fn global() -> &'static RwLock<Config> {
+pub fn global() -> &'static mut Config<'static> {
unsafe {
- CONFIG_INIT.call_once(|| { CONFIG = Some(Default::default()); });
+ CONFIG_INIT.call_once(|| {
+ CONFIG = Some(Default::default());
+ });
CONFIG.as_mut().unwrap()
}
@@ -40,43 +44,37 @@ fn global() -> &'static RwLock<Config> {
pub fn merge<T>(source: T) -> Result<(), Box<Error>>
where T: SourceBuilder
{
- global().write()?.merge(source)
+ global().merge(source)
}
pub fn set_default<T>(key: &str, value: T) -> Result<(), Box<Error>>
- where T: Into<Value>
+ where T: Into<Value<'static>>
{
- global().write()?.set_default(key, value);
-
- // TODO: `set_default` will be able to fail soon so this will not be needed
- Ok(())
+ global().set_default(key, value)
}
pub fn set<T>(key: &str, value: T) -> Result<(), Box<Error>>
- where T: Into<Value>
+ where T: Into<Value<'static>>
{
- global().write()?.set(key, value);
-
- // TODO: `set_default` will be able to fail soon so this will not be needed
- Ok(())
+ global().set(key, value)
}
-pub fn get(key: &str) -> Option<Value> {
- global().read().unwrap().get(key)
+pub fn get<'a>(key: &str) -> Option<Cow<'a, Value>> {
+ global().get(key)
}
-pub fn get_str(key: &str) -> Option<String> {
- global().read().unwrap().get_str(key)
+pub fn get_str<'a>(key: &str) -> Option<Cow<'a, str>> {
+ global().get_str(key)
}
pub fn get_int(key: &str) -> Option<i64> {
- global().read().unwrap().get_int(key)
+ global().get_int(key)
}
pub fn get_float(key: &str) -> Option<f64> {
- global().read().unwrap().get_float(key)
+ global().get_float(key)
}
pub fn get_bool(key: &str) -> Option<bool> {
- global().read().unwrap().get_bool(key)
+ global().get_bool(key)
}
diff --git a/src/value.rs b/src/value.rs
index a887104..ad173e8 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -1,49 +1,53 @@
use std::convert::From;
use std::collections::HashMap;
+use std::borrow::Cow;
/// 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)]
-pub enum Value {
- String(String),
+pub enum Value<'a> {
+ String(Cow<'a, str>),
Integer(i64),
Float(f64),
Boolean(bool),
- Table(HashMap<String, Value>),
- Array(Vec<Value>),
+ Table(HashMap<String, Value<'a>>),
+ Array(Vec<Value<'a>>),
}
-impl Value {
- /// Gets the underyling value as a string, performing a conversion only if neccessary.
- pub fn as_str(self) -> Option<String> {
- if let Value::String(value) = self {
- Some(value)
- } else if let Value::Integer(value) = self {
- Some(value.to_string())
- } else if let Value::Float(value) = self {
- Some(value.to_string())
- } else if let Value::Boolean(value) = self {
- Some(value.to_string())
+impl<'a> Value<'a> {
+ /// Gets the underlying value as a string, performing a conversion only if neccessary.
+ pub fn as_str(&'a self) -> Option<Cow<'a, str>> {
+ if let Value::String(ref value) = *self {
+ Some(match *value {
+ Cow::Borrowed(v) => Cow::Borrowed(v),
+ Cow::Owned(ref v) => Cow::Borrowed(v),
+ })
+ } else if let Value::Integer(value) = *self {
+ Some(Cow::Owned(value.to_string()))
+ } else if let Value::Float(value) = *self {
+ Some(Cow::Owned(value.to_string()))
+ } else if let Value::Boolean(value) = *self {
+ Some(Cow::Owned(value.to_string()))
} else {
None
}
}
/// Gets the underlying type as a boolean, performing a conversion only if neccessary.
- pub fn as_bool(self) -> Option<bool> {
- if let Value::Boolean(value) = self {
+ pub fn as_bool(&self) -> Option<bool> {
+ if let Value::Boolean(value) = *self {
Some(value)
- } else if let Value::String(ref value) = self {
+ } else if let Value::String(ref value) = *self {
match value.to_lowercase().as_ref() {
"1" | "true" | "on" | "yes" => Some(true),
"0" | "false" | "off" | "no" => Some(false),
_ => None,
}
- } else if let Value::Integer(value) = self {
+ } else if let Value::Integer(value) = *self {
Some(value != 0)
- } else if let Value::Float(value) = self {
+ } else if let Value::Float(value) = *self {
Some(value != 0.0)
} else {
None
@@ -51,14 +55,14 @@ impl Value {
}
/// Gets the underlying type as an integer, performing a conversion only if neccessary.
- pub fn as_int(self) -> Option<i64> {
- if let Value::Integer(value) = self {
+ pub fn as_int(&self) -> Option<i64> {
+ if let Value::Integer(value) = *self {
Some(value)
- } else if let Value::String(ref value) = self {
+ } else if let Value::String(ref value) = *self {
value.parse().ok()
- } else if let Value::Boolean(value) = self {
+ } else if let Value::Boolean(value) = *self {
Some(if value { 1 } else { 0 })
- } else if let Value::Float(value) = self {
+ } else if let Value::Float(value) = *self {
Some(value.round() as i64)
} else {
None
@@ -66,14 +70,14 @@ impl Value {
}
/// Gets the underlying type as a floating-point, performing a conversion only if neccessary.
- pub fn as_float(self) -> Option<f64> {
- if let Value::Float(value) = self {
+ pub fn as_float(&self) -> Option<f64> {
+ if let Value::Float(value) = *self {
Some(value)
- } else if let Value::String(ref value) = self {
+ } else if let Value::String(ref value) = *self {
value.parse().ok()
- } else if let Value::Integer(value) = self {
+ } else if let Value::Integer(value) = *self {
Some(value as f64)
- } else if let Value::Boolean(value) = self {
+ } else if let Value::Boolean(value) = *self {
Some(if value { 1.0 } else { 0.0 })
} else {
None
@@ -84,32 +88,32 @@ impl Value {
// Generalized construction from type into variant is needed
// for setting configuration values
-impl From<String> for Value {
- fn from(value: String) -> Value {
- Value::String(value)
+impl<'a> From<String> for Value<'a> {
+ fn from(value: String) -> Value<'a> {
+ Value::String(value.into())
}
}
-impl<'a> From<&'a str> for Value {
- fn from(value: &'a str) -> Value {
+impl<'a> From<&'a str> for Value<'a> {
+ fn from(value: &'a str) -> Value<'a> {
Value::String(value.into())
}
}
-impl From<i64> for Value {
- fn from(value: i64) -> Value {
+impl<'a> From<i64> for Value<'a> {
+ fn from(value: i64) -> Value<'a> {
Value::Integer(value)
}
}
-impl From<f64> for Value {
- fn from(value: f64) -> Value {
+impl<'a> From<f64> for Value<'a> {
+ fn from(value: f64) -> Value<'a> {
Value::Float(value)
}
}
-impl From<bool> for Value {
- fn from(value: bool) -> Value {
+impl<'a> From<bool> for Value<'a> {
+ fn from(value: bool) -> Value<'a> {
Value::Boolean(value)
}
}