summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Leckey <leckey.ryan@gmail.com>2017-01-26 19:02:13 -0800
committerRyan Leckey <leckey.ryan@gmail.com>2017-01-26 19:02:13 -0800
commit589036c19409c7626a5bafda5af96c2fbc6a7de5 (patch)
tree9f4e09809df7c200c3d60b619c7eea9b91e6d70c
parentd7ad51c8851fe3c845569760935fae1828e859b5 (diff)
Refactor the file source to allow for N formats; implement JSON.
-rw-r--r--Cargo.toml7
-rw-r--r--examples/basic/Cargo.toml3
-rw-r--r--examples/file-json/Cargo.toml6
-rw-r--r--examples/file-json/Settings.json5
-rw-r--r--examples/file-json/src/main.rs10
-rw-r--r--examples/file-toml/Cargo.toml (renamed from examples/basic-file/Cargo.toml)3
-rw-r--r--examples/file-toml/src/main.rs (renamed from examples/basic-file/src/main.rs)2
-rw-r--r--src/config.rs46
-rw-r--r--src/file.rs143
-rw-r--r--src/file/json.rs65
-rw-r--r--src/file/mod.rs157
-rw-r--r--src/file/nil.rs11
-rw-r--r--src/file/toml.rs57
-rw-r--r--src/lib.rs19
-rw-r--r--src/source.rs7
-rw-r--r--src/value.rs53
16 files changed, 379 insertions, 215 deletions
diff --git a/Cargo.toml b/Cargo.toml
index cb18d51..7aef758 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,5 +3,10 @@ name = "config"
version = "0.1.0"
authors = ["Ryan Leckey <leckey.ryan@gmail.com>"]
+[features]
+default = ["toml"]
+json = ["serde_json"]
+
[dependencies]
-toml = "0.2.1"
+toml = { version = "0.2.1", optional = true }
+serde_json = { version = "0.9", optional = true }
diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml
index 2cb273b..7ede162 100644
--- a/examples/basic/Cargo.toml
+++ b/examples/basic/Cargo.toml
@@ -1,7 +1,6 @@
[package]
name = "basic"
version = "0.1.0"
-authors = ["Ryan Leckey <leckey.ryan@gmail.com>"]
[dependencies]
-config = { path = "../.." }
+config = { path = "../..", default-features = false }
diff --git a/examples/file-json/Cargo.toml b/examples/file-json/Cargo.toml
new file mode 100644
index 0000000..7223f35
--- /dev/null
+++ b/examples/file-json/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "file-json"
+version = "0.1.0"
+
+[dependencies]
+config = { path = "../..", default-features = false, features = ["json"] }
diff --git a/examples/file-json/Settings.json b/examples/file-json/Settings.json
new file mode 100644
index 0000000..72b28e6
--- /dev/null
+++ b/examples/file-json/Settings.json
@@ -0,0 +1,5 @@
+{
+ "debug": false,
+ "pi": 3.14159,
+ "weight": 150
+}
diff --git a/examples/file-json/src/main.rs b/examples/file-json/src/main.rs
new file mode 100644
index 0000000..9c636ac
--- /dev/null
+++ b/examples/file-json/src/main.rs
@@ -0,0 +1,10 @@
+extern crate config;
+
+fn main() {
+ // Read configuration from "Settings.json"
+ config::merge(config::File::new("Settings", config::FileFormat::Json)).unwrap();
+
+ println!("debug = {:?}", config::get("debug"));
+ println!("pi = {:?}", config::get("pi"));
+ println!("weight = {:?}", config::get("weight"));
+}
diff --git a/examples/basic-file/Cargo.toml b/examples/file-toml/Cargo.toml
index ffef864..6f45799 100644
--- a/examples/basic-file/Cargo.toml
+++ b/examples/file-toml/Cargo.toml
@@ -1,7 +1,6 @@
[package]
-name = "basic-file"
+name = "file-toml"
version = "0.1.0"
-authors = ["Ryan Leckey <leckey.ryan@gmail.com>"]
[dependencies]
config = { path = "../.." }
diff --git a/examples/basic-file/src/main.rs b/examples/file-toml/src/main.rs
index ae394f9..03ce61a 100644
--- a/examples/basic-file/src/main.rs
+++ b/examples/file-toml/src/main.rs
@@ -2,7 +2,7 @@ extern crate config;
fn main() {
// Read configuration from $(cwd)/Cargo.toml
- config::merge(config::File::with_name("Cargo")).unwrap();
+ config::merge(config::File::new("Cargo", config::FileFormat::Toml)).unwrap();
println!("package.name = {:?}", config::get_str("package.name"));
println!("package.version = {:?}", config::get_str("package.version"));
diff --git a/src/config.rs b/src/config.rs
index 22bf2c4..42e6c5c 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,10 +1,9 @@
use value::Value;
-use source::Source;
+use source::{Source, SourceBuilder};
use std::env;
use std::error::Error;
use std::collections::HashMap;
-use std::borrow::Cow;
#[derive(Default)]
pub struct Config {
@@ -12,8 +11,7 @@ pub struct Config {
defaults: HashMap<String, Value>,
overrides: HashMap<String, Value>,
- environ: HashMap<String, Value>,
- sources: Vec<HashMap<String, Value>>,
+ sources: Vec<Box<Source>>,
}
impl Config {
@@ -22,8 +20,8 @@ impl Config {
}
/// Merge in configuration values from the given source.
- pub fn merge<T>(&mut self, mut source: T) -> Result<(), Box<Error>>
- where T: Source
+ pub fn merge<T>(&mut self, source: T) -> Result<(), Box<Error>>
+ where T: SourceBuilder
{
self.sources.push(source.build()?);
@@ -54,11 +52,11 @@ impl Config {
self.overrides.insert(key.to_lowercase(), value.into());
}
- pub fn get<'a>(&'a mut self, key: &str) -> Option<&'a Value> {
+ pub fn get(&self, key: &str) -> Option<Value> {
// Check explicit override
if let Some(value) = self.overrides.get(key) {
- return Some(value);
+ return Some(value.clone());
}
// Check environment
@@ -75,9 +73,7 @@ impl Config {
env_key.push_str(&key.to_uppercase());
if let Ok(value) = env::var(env_key.clone()) {
- // TODO: Find a better way to do this?
- self.environ.insert(key.into(), value.into());
- return self.environ.get(key);
+ return Some(Value::from(value));
}
// Check sources
@@ -91,38 +87,26 @@ impl Config {
// Check explicit defaults
if let Some(value) = self.defaults.get(key) {
- return Some(value);
+ return Some(value.clone());
}
None
}
- pub fn get_str<'a>(&'a mut self, key: &str) -> Option<Cow<'a, str>> {
+ pub fn get_str(&self, key: &str) -> Option<String> {
self.get(key).and_then(Value::as_str)
}
- pub fn get_int(&mut self, key: &str) -> Option<i64> {
- if let Some(value) = self.get(key) {
- value.as_int()
- } else {
- None
- }
+ pub fn get_int(&self, key: &str) -> Option<i64> {
+ self.get(key).and_then(Value::as_int)
}
- pub fn get_float(&mut self, key: &str) -> Option<f64> {
- if let Some(value) = self.get(key) {
- value.as_float()
- } else {
- None
- }
+ pub fn get_float(&self, key: &str) -> Option<f64> {
+ self.get(key).and_then(Value::as_float)
}
- pub fn get_bool(&mut self, key: &str) -> Option<bool> {
- if let Some(value) = self.get(key) {
- value.as_bool()
- } else {
- None
- }
+ pub fn get_bool(&self, key: &str) -> Option<bool> {
+ self.get(key).and_then(Value::as_bool)
}
}
diff --git a/src/file.rs b/src/file.rs
deleted file mode 100644
index 2ffc836..0000000
--- a/src/file.rs
+++ /dev/null
@@ -1,143 +0,0 @@
-use std::fs;
-use std::env;
-use std::error::Error;
-use std::io::Read;
-use std::collections::HashMap;
-
-use toml;
-
-use value::Value;
-use source::Source;
-
-#[derive(Default)]
-pub struct File {
- /// 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>,
-
- /// Namespace to restrict configuration from the file
- namespace: Option<String>,
-
- /// A required File will error if it cannot be found
- required: bool,
-}
-
-impl File {
- pub fn with_name(name: &str) -> File {
- File {
- name: name.into(),
- required: true,
-
- ..Default::default()
- }
- }
-
- pub fn path(&mut self, path: &str) -> &mut File {
- self.path = Some(path.into());
- self
- }
-
- pub fn namespace(&mut self, namespace: &str) -> &mut File {
- self.namespace = Some(namespace.into());
- self
- }
-
- pub fn required(&mut self, required: bool) -> &mut File {
- self.required = required;
- self
- }
-}
-
-fn toml_collect(content: &mut HashMap<String, Value>,
- table: &toml::Table,
- prefix: Option<String>) {
- for (key, value) in table {
- // Construct full key from prefix
- let key = if let Some(ref prefix) = prefix {
- prefix.clone() + "." + key
- } else {
- key.clone()
- };
-
- match *value {
- // Recurse into nested table
- toml::Value::Table(ref table) => toml_collect(content, table, Some(key)),
-
- toml::Value::String(ref value) => {
- content.insert(key, value.clone().into());
- }
-
- toml::Value::Integer(value) => {
- content.insert(key, value.into());
- }
-
- toml::Value::Float(value) => {
- content.insert(key, value.into());
- }
-
- toml::Value::Boolean(value) => {
- content.insert(key, value.into());
- }
-
- _ => {
- // Unhandled
- }
- }
- }
-}
-
-impl Source for File {
- fn build(&mut self) -> Result<HashMap<String, Value>, Box<Error>> {
- let mut content = HashMap::new();
-
- // Find file
- // TODO: Use a nearest algorithm rather than strictly CWD
- let cwd = match env::current_dir() {
- Ok(cwd) => cwd,
- Err(err) => {
- if self.required {
- return Err(From::from(err));
- } else {
- return Ok(content);
- }
- }
- };
-
- let filename = cwd.join(self.name.clone() + ".toml");
-
- // Read contents from file
- let mut file = match fs::File::open(filename) {
- Ok(file) => file,
- Err(err) => {
- if self.required {
- return Err(From::from(err));
- } else {
- return Ok(content);
- }
- }
- };
-
- let mut buffer = String::new();
- let res = file.read_to_string(&mut buffer);
- if res.is_err() {
- if self.required {
- return Err(From::from(res.err().unwrap()));
- } else {
- return Ok(content);
- }
- }
-
- // Parse
- let mut parser = toml::Parser::new(&buffer);
- // TODO: Get a solution to make this return an Error-able
- let document = parser.parse().unwrap();
-
- // Iterate through document and fill content
- toml_collect(&mut content, &document, None);
-
- Ok(content)
- }
-}
diff --git a/src/file/json.rs b/src/file/json.rs
new file mode 100644
index 0000000..05af065
--- /dev/null
+++ b/src/file/json.rs
@@ -0,0 +1,65 @@
+use serde_json;
+
+use source::Source;
+use std::error::Error;
+use value::Value;
+
+pub struct Content {
+ // Root table of the TOML document
+ root: serde_json::Value,
+}
+
+impl Content {
+ pub fn parse(text: &str) -> Result<Box<Source>, Box<Error>> {
+ // Parse
+ let root = serde_json::from_str(text)?;
+
+ Ok(Box::new(Content { root: root }))
+ }
+}
+
+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::Number(ref value) => {
+ if let Some(value) = value.as_i64() {
+ Some(Value::Integer(value))
+ } else if let Some(value) = value.as_f64() {
+ Some(Value::Float(value))
+ } else {
+ None
+ }
+ }
+
+ serde_json::Value::Bool(value) => Some(Value::Boolean(value)),
+
+ _ => None,
+ }
+}
+
+impl Source for Content {
+ fn get(&self, key: &str) -> Option<Value> {
+ // TODO: Key segment iteration is not something that should be here directly
+ let key_delim = '.';
+ let key_segments = key.split(key_delim);
+ let mut json_cursor = &self.root;
+ for segment in key_segments {
+ match *json_cursor {
+ serde_json::Value::Object(ref table) => {
+ if let Some(value) = table.get(segment) {
+ json_cursor = value;
+ }
+ }
+
+ _ => {
+ // This is not a table or array
+ // Traversal is not possible
+ return None;
+ }
+ }
+ }
+
+ from_json_value(json_cursor)
+ }
+}
diff --git a/src/file/mod.rs b/src/file/mod.rs
new file mode 100644
index 0000000..2177a54
--- /dev/null
+++ b/src/file/mod.rs
@@ -0,0 +1,157 @@
+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;
+
+pub enum FileFormat {
+ /// TOML (parsed with toml)
+ #[cfg(feature = "toml")]
+ Toml,
+
+ /// JSON (parsed with serde_json)
+ #[cfg(feature = "json")]
+ Json,
+}
+
+impl FileFormat {
+ fn extensions(&self) -> Vec<&'static str> {
+ match *self {
+ #[cfg(feature = "toml")]
+ FileFormat::Toml => vec!["toml"],
+
+ #[cfg(feature = "json")]
+ FileFormat::Json => vec!["json"],
+ }
+ }
+
+ #[allow(unused_variables)]
+ fn parse(&self, text: &str) -> Result<Box<Source>, Box<Error>> {
+ match *self {
+ #[cfg(feature = "toml")]
+ FileFormat::Toml => toml::Content::parse(text),
+
+ #[cfg(feature = "json")]
+ FileFormat::Json => json::Content::parse(text),
+ }
+ }
+}
+
+pub struct File {
+ /// 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>,
+
+ /// Namespace to restrict configuration from the file
+ namespace: Option<String>,
+
+ /// 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 new(name: &str, format: FileFormat) -> File {
+ File {
+ name: name.into(),
+ format: format,
+ required: true,
+ path: None,
+ namespace: None,
+ }
+ }
+
+ pub fn path(&mut self, path: &str) -> &mut File {
+ self.path = Some(path.into());
+ self
+ }
+
+ pub fn namespace(&mut self, namespace: &str) -> &mut File {
+ self.namespace = Some(namespace.into());
+ self
+ }
+
+ pub fn required(&mut self, required: bool) -> &mut File {
+ self.required = required;
+ self
+ }
+
+ // Find configuration file
+ // Use algorithm similar to .git detection by git
+ fn find_file(&self) -> Result<PathBuf, Box<Error>> {
+ // Build expected configuration file
+ let mut basename = PathBuf::new();
+ let extensions = self.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());
+ }
+ }
+ }
+
+ // Build normally and return error on failure
+ fn try_build(&self) -> Result<Box<Source>, Box<Error>> {
+ // Find file
+ let filename = self.find_file()?;
+
+ // 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
+ self.format.parse(&text)
+ }
+}
+
+impl SourceBuilder for File {
+ // Use try_build but only pass an error through if this source
+ // is required
+ fn build(&self) -> Result<Box<Source>, Box<Error>> {
+ if self.required {
+ self.try_build().or_else(|_| Ok(Box::new(nil::Nil {})))
+ } else {
+ self.try_build()
+ }
+ }
+}
diff --git a/src/file/nil.rs b/src/file/nil.rs
new file mode 100644
index 0000000..7666f8a
--- /dev/null
+++ b/src/file/nil.rs
@@ -0,0 +1,11 @@
+use source::Source;
+use value::Value;
+
+// Nil source that does nothing for optional files
+pub struct Nil {}
+
+impl Source for Nil {
+ fn get(&self, _: &str) -> Option<Value> {
+ None
+ }
+}
diff --git a/src/file/toml.rs b/src/file/toml.rs
new file mode 100644
index 0000000..9e4b170
--- /dev/null
+++ b/src/file/toml.rs
@@ -0,0 +1,57 @@
+use toml;
+use source::Source;
+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) -> Result<Box<Source>, Box<Error>> {
+ // Parse
+ let mut parser = toml::Parser::new(text);
+ // TODO: Get a solution to make this return an Error-able
+ let root = parser.parse().unwrap();
+
+ Ok(Box::new(Content { root: toml::Value::Table(root) }))
+ }
+}
+
+fn from_toml_value(value: &toml::Value) -> Option<Value> {
+ match *value {
+ toml::Value::String(ref value) => Some(Value::String(value.clone())),
+ toml::Value::Float(value) => Some(Value::Float(value)),
+ toml::Value::Integer(value) => Some(Value::Integer(value)),
+ toml::Value::Boolean(value) => Some(Value::Boolean(value)),
+
+ _ => None,
+ }
+}
+
+impl Source for Content {
+ fn get(&self, key: &str) -> Option<Value> {
+ // TODO: Key segment iteration is not something that should be here directly
+ let key_delim = '.';
+ let key_segments = key.split(key_delim);
+ let mut toml_cursor = &self.root;
+ for segment in key_segments {
+ match *toml_cursor {
+ toml::Value::Table(ref table) => {
+ if let Some(value) = table.get(segment) {
+ toml_cursor = value;
+ }
+ }
+
+ _ => {
+ // This is not a table or array
+ // Traversal is not possible
+ return None;
+ }
+ }
+ }
+
+ from_toml_value(toml_cursor)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 426b526..99a6da3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,19 +1,22 @@
#![feature(drop_types_in_const)]
#![allow(unknown_lints)]
+#[cfg(feature = "toml")]
extern crate toml;
+#[cfg(feature = "json")]
+extern crate serde_json;
+
mod value;
mod source;
mod file;
mod config;
use std::error::Error;
-use std::borrow::Cow;
use std::sync::{Once, ONCE_INIT};
-pub use source::Source;
-pub use file::File;
+pub use source::{Source, SourceBuilder};
+pub use file::{File, FileFormat};
pub use value::Value;
@@ -26,14 +29,16 @@ static CONFIG_INIT: Once = ONCE_INIT;
// Get the global configuration instance
fn global() -> &'static mut Config {
unsafe {
- CONFIG_INIT.call_once(|| { CONFIG = Some(Default::default()); });
+ CONFIG_INIT.call_once(|| {
+ CONFIG = Some(Default::default());
+ });
CONFIG.as_mut().unwrap()
}
}
pub fn merge<T>(source: T) -> Result<(), Box<Error>>
- where T: Source
+ where T: SourceBuilder
{
global().merge(source)
}
@@ -54,11 +59,11 @@ pub fn set<T>(key: &str, value: T)
global().set(key, value)
}
-pub fn get<'a>(key: &str) -> Option<&'a Value> {
+pub fn get(key: &str) -> Option<Value> {
global().get(key)
}
-pub fn get_str<'a>(key: &str) -> Option<Cow<'a, str>> {
+pub fn get_str(key: &str) -> Option<String> {
global().get_str(key)
}
diff --git a/src/source.rs b/src/source.rs
index 95785a6..1a8d08f 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -1,8 +1,11 @@
use std::error::Error;
-use std::collections::HashMap;
use value::Value;
pub trait Source {
- fn build(&mut self) -> Result<HashMap<String, Value>, Box<Error>>;
+ fn get(&self, key: &str) -> Option<Value>;
+}
+
+pub trait SourceBuilder {
+ fn build(&self) -> Result<Box<Source>, Box<Error>>;
}
diff --git a/src/value.rs b/src/value.rs
index 52a2103..a887104 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -1,5 +1,5 @@
use std::convert::From;
-use std::borrow::Cow;
+use std::collections::HashMap;
/// A configuration value.
///
@@ -11,38 +11,39 @@ pub enum Value {
Integer(i64),
Float(f64),
Boolean(bool),
+ Table(HashMap<String, Value>),
+ Array(Vec<Value>),
}
impl Value {
/// Gets the underyling value as a string, performing a conversion only if neccessary.
- #[allow(needless_lifetimes)]
- pub fn as_str<'a>(&'a self) -> Option<Cow<'a, str>> {
- if let Value::String(ref value) = *self {
- Some(Cow::Borrowed(value))
- } 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()))
+ 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())
} 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
@@ -50,14 +51,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
@@ -65,14 +66,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