summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2021-04-20 09:39:59 +0200
committerGitHub <noreply@github.com>2021-04-20 09:39:59 +0200
commit86f87764fed802f876a7b8bf1fc7f824c28d28c8 (patch)
tree413c436d2143733a14eccd21f73a980fb3484d0d
parentfd5c87a78e335097a1f714ec5a5a76ee79dd83cf (diff)
parentba883ca731ea5222b0cadbad53b144fd4908f5f1 (diff)
Merge pull request #202 from skreborn/master
Add support for RON format
-rw-r--r--Cargo.toml3
-rw-r--r--README.md4
-rw-r--r--src/file/format/mod.rs13
-rw-r--r--src/file/format/ron.rs68
-rw-r--r--src/lib.rs5
-rw-r--r--tests/Settings-invalid.ron4
-rw-r--r--tests/Settings.ron18
-rw-r--r--tests/datetime.rs20
-rw-r--r--tests/file_ron.rs83
9 files changed, 215 insertions, 3 deletions
diff --git a/Cargo.toml b/Cargo.toml
index bb265bb..040124f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,7 @@ license = "MIT/Apache-2.0"
maintenance = { status = "actively-developed" }
[features]
-default = ["toml", "json", "yaml", "hjson", "ini"]
+default = ["toml", "json", "yaml", "hjson", "ini", "ron"]
json = ["serde_json"]
yaml = ["yaml-rust"]
hjson = ["serde-hjson"]
@@ -30,6 +30,7 @@ serde_json = { version = "1.0.2", optional = true }
yaml-rust = { version = "0.4", optional = true }
serde-hjson = { version = "0.9", default-features = false, optional = true }
rust-ini = { version = "0.17", optional = true }
+ron = { version = "0.6", optional = true }
[dev-dependencies]
serde_derive = "1.0.8"
diff --git a/README.md b/README.md
index 243b42c..7f43c62 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
- Set defaults
- Set explicit values (to programmatically override)
- - Read from [JSON], [TOML], [YAML], [HJSON], [INI] files
+ - Read from [JSON], [TOML], [YAML], [HJSON], [INI], [RON] files
- Read from environment
- Loosely typed — Configuration values may be read in any supported type, as long as there exists a reasonable conversion
- Access nested fields using a formatted path — Uses a subset of JSONPath; currently supports the child ( `redis.port` ) and subscript operators ( `databases[0].name` )
@@ -20,6 +20,7 @@
[YAML]: https://github.com/chyh1990/yaml-rust
[HJSON]: https://github.com/hjson/hjson-rust
[INI]: https://github.com/zonyitoo/rust-ini
+[RON]: https://github.com/ron-rs/ron
## Usage
@@ -33,6 +34,7 @@ config = "0.11"
- `hjson` - Adds support for reading HJSON files
- `yaml` - Adds support for reading YAML files
- `toml` - Adds support for reading TOML files
+ - `ron` - Adds support for reading RON files
See the [documentation](https://docs.rs/config) or [examples](https://github.com/mehcode/config-rs/tree/master/examples) for
more usage information.
diff --git a/src/file/format/mod.rs b/src/file/format/mod.rs
index 0ceeaf4..bbd62a2 100644
--- a/src/file/format/mod.rs
+++ b/src/file/format/mod.rs
@@ -22,6 +22,9 @@ mod hjson;
#[cfg(feature = "ini")]
mod ini;
+#[cfg(feature = "ron")]
+mod ron;
+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum FileFormat {
/// TOML (parsed with toml)
@@ -42,6 +45,10 @@ pub enum FileFormat {
/// INI (parsed with rust_ini)
#[cfg(feature = "ini")]
Ini,
+
+ /// RON (parsed with ron)
+ #[cfg(feature = "ron")]
+ Ron,
}
lazy_static! {
@@ -65,6 +72,9 @@ lazy_static! {
#[cfg(feature = "ini")]
formats.insert(FileFormat::Ini, vec!["ini"]);
+ #[cfg(feature = "ron")]
+ formats.insert(FileFormat::Ron, vec!["ron"]);
+
formats
};
}
@@ -102,6 +112,9 @@ impl FileFormat {
#[cfg(feature = "ini")]
FileFormat::Ini => ini::parse(uri, text),
+
+ #[cfg(feature = "ron")]
+ FileFormat::Ron => ron::parse(uri, text),
}
}
}
diff --git a/src/file/format/ron.rs b/src/file/format/ron.rs
new file mode 100644
index 0000000..d3a97cb
--- /dev/null
+++ b/src/file/format/ron.rs
@@ -0,0 +1,68 @@
+use std::collections::HashMap;
+use std::error::Error;
+
+use ron;
+
+use crate::value::{Value, ValueKind};
+
+pub fn parse(
+ uri: Option<&String>,
+ text: &str,
+) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
+ let value = from_ron_value(uri, ron::from_str(text)?)?;
+ match value.kind {
+ ValueKind::Table(map) => Ok(map),
+
+ _ => Ok(HashMap::new()),
+ }
+}
+
+fn from_ron_value(
+ uri: Option<&String>,
+ value: ron::Value,
+) -> Result<Value, Box<dyn Error + Send + Sync>> {
+ let kind = match value {
+ ron::Value::Option(value) => match value {
+ Some(value) => from_ron_value(uri, *value)?.kind,
+ None => ValueKind::Nil,
+ },
+
+ ron::Value::Unit => ValueKind::Nil,
+
+ ron::Value::Bool(value) => ValueKind::Boolean(value),
+
+ ron::Value::Number(value) => match value {
+ ron::Number::Float(value) => ValueKind::Float(value.get()),
+ ron::Number::Integer(value) => ValueKind::Integer(value),
+ },
+
+ ron::Value::Char(value) => ValueKind::String(value.to_string()),
+
+ ron::Value::String(value) => ValueKind::String(value),
+
+ ron::Value::Seq(values) => {
+ let array = values
+ .into_iter()
+ .map(|value| from_ron_value(uri, value))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ ValueKind::Array(array)
+ }
+
+ ron::Value::Map(values) => {
+ let map = values
+ .iter()
+ .map(|(key, value)| -> Result<_, Box<dyn Error + Send + Sync>> {
+ let key = key.clone().into_rust::<String>()?;
+ let value = from_ron_value(uri, value.clone())?;
+
+ Ok((key, value))
+ })
+ .collect::<Result<HashMap<_, _>, _>>()?;
+
+ ValueKind::Table(map)
+ }
+ };
+
+ Ok(Value::new(uri, kind))
+}
diff --git a/src/lib.rs b/src/lib.rs
index 9603118..03a82c1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,7 +6,7 @@
//! - Environment variables
//! - Another Config instance
//! - Remote configuration: etcd, Consul
-//! - Files: JSON, YAML, TOML, HJSON
+//! - Files: TOML, JSON, YAML, HJSON, INI, RON
//! - Manual, programmatic override (via a `.set` method on the Config instance)
//!
//! Additionally, Config supports:
@@ -48,6 +48,9 @@ extern crate serde_hjson;
#[cfg(feature = "ini")]
extern crate ini;
+#[cfg(feature = "ron")]
+extern crate ron;
+
mod config;
mod de;
mod env;
diff --git a/tests/Settings-invalid.ron b/tests/Settings-invalid.ron
new file mode 100644
index 0000000..0f41c5c
--- /dev/null
+++ b/tests/Settings-invalid.ron
@@ -0,0 +1,4 @@
+(
+ ok: true,
+ error
+)
diff --git a/tests/Settings.ron b/tests/Settings.ron
new file mode 100644
index 0000000..528fd61
--- /dev/null
+++ b/tests/Settings.ron
@@ -0,0 +1,18 @@
+(
+ debug: true,
+ production: false,
+ arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+ place: (
+ initials: ('T', 'P'),
+ name: "Torre di Pisa",
+ longitude: 43.7224985,
+ latitude: 10.3970522,
+ favorite: false,
+ reviews: 3866,
+ rating: Some(4.5),
+ telephone: None,
+ creator: {
+ "name": "John Smith"
+ }
+ )
+)
diff --git a/tests/datetime.rs b/tests/datetime.rs
index 6c1e620..471da47 100644
--- a/tests/datetime.rs
+++ b/tests/datetime.rs
@@ -4,6 +4,7 @@
feature = "hjson",
feature = "yaml",
feature = "ini",
+ feature = "ron",
))]
extern crate chrono;
@@ -53,6 +54,15 @@ fn make() -> Config {
FileFormat::Ini,
))
.unwrap()
+ .merge(File::from_str(
+ r#"
+ (
+ ron_datetime: "2021-04-19T11:33:02Z"
+ )
+ "#,
+ FileFormat::Ron,
+ ))
+ .unwrap()
.clone()
}
@@ -84,6 +94,11 @@ fn test_datetime_string() {
let date: String = s.get("ini_datetime").unwrap();
assert_eq!(&date, "2017-05-10T02:14:53Z");
+
+ // RON
+ let date: String = s.get("ron_datetime").unwrap();
+
+ assert_eq!(&date, "2021-04-19T11:33:02Z");
}
#[test]
@@ -114,4 +129,9 @@ fn test_datetime() {
let date: DateTime<Utc> = s.get("ini_datetime").unwrap();
assert_eq!(date, Utc.ymd(2017, 5, 10).and_hms(2, 14, 53));
+
+ // RON
+ let date: DateTime<Utc> = s.get("ron_datetime").unwrap();
+
+ assert_eq!(date, Utc.ymd(2021, 4, 19).and_hms(11, 33, 2));
}
diff --git a/tests/file_ron.rs b/tests/file_ron.rs
new file mode 100644
index 0000000..1f1ede2
--- /dev/null
+++ b/tests/file_ron.rs
@@ -0,0 +1,83 @@
+#![cfg(feature = "ron")]
+
+extern crate config;
+extern crate float_cmp;
+extern crate serde;
+
+#[macro_use]
+extern crate serde_derive;
+
+use std::collections::HashMap;
+use std::path::PathBuf;
+
+use config::*;
+use float_cmp::ApproxEqUlps;
+
+#[derive(Debug, Deserialize)]
+struct Place {
+ initials: (char, char),
+ name: String,
+ longitude: f64,
+ latitude: f64,
+ favorite: bool,
+ telephone: Option<String>,
+ reviews: u64,
+ creator: HashMap<String, Value>,
+ rating: Option<f32>,
+}
+
+#[derive(Debug, Deserialize)]
+struct Settings {
+ debug: f64,
+ production: Option<String>,
+ place: Place,
+ #[serde(rename = "arr")]
+ elements: Vec<String>,
+}
+
+fn make() -> Config {
+ let mut c = Config::default();
+ c.merge(File::new("tests/Settings", FileFormat::Ron))
+ .unwrap();
+
+ c
+}
+
+#[test]
+fn test_file() {
+ let c = make();
+
+ // Deserialize the entire file as single struct
+ let s: Settings = c.try_into().unwrap();
+
+ assert!(s.debug.approx_eq_ulps(&1.0, 2));
+ assert_eq!(s.production, Some("false".to_string()));
+ assert_eq!(s.place.initials, ('T', 'P'));
+ assert_eq!(s.place.name, "Torre di Pisa");
+ assert!(s.place.longitude.approx_eq_ulps(&43.7224985, 2));
+ assert!(s.place.latitude.approx_eq_ulps(&10.3970522, 2));
+ assert_eq!(s.place.favorite, false);
+ assert_eq!(s.place.reviews, 3866);
+ assert_eq!(s.place.rating, Some(4.5));
+ assert_eq!(s.place.telephone, None);
+ assert_eq!(s.elements.len(), 10);
+ assert_eq!(s.elements[3], "4".to_string());
+ assert_eq!(
+ s.place.creator["name"].clone().into_string().unwrap(),
+ "John Smith".to_string()
+ );
+}
+
+#[test]
+fn test_error_parse() {
+ let mut c = Config::default();
+ let res = c.merge(File::new("tests/Settings-invalid", FileFormat::Ron));
+
+ let path_with_extension: PathBuf = ["tests", "Settings-invalid.ron"].iter().collect();
+
+ assert!(res.is_err());
+ assert_eq!(
+ res.unwrap_err().to_string(),
+ format!("4:1: Expected colon in {}", path_with_extension.display())
+ );
+}