Skip to content

Commit

Permalink
Merge pull request #202 from skreborn/master
Browse files Browse the repository at this point in the history
Add support for RON format
  • Loading branch information
matthiasbeyer authored Apr 20, 2021
2 parents fd5c87a + ba883ca commit 86f8776
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 3 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` )
Expand All @@ -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

Expand All @@ -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.
Expand Down
13 changes: 13 additions & 0 deletions src/file/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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! {
Expand All @@ -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
};
}
Expand Down Expand Up @@ -102,6 +112,9 @@ impl FileFormat {

#[cfg(feature = "ini")]
FileFormat::Ini => ini::parse(uri, text),

#[cfg(feature = "ron")]
FileFormat::Ron => ron::parse(uri, text),
}
}
}
68 changes: 68 additions & 0 deletions src/file/format/ron.rs
Original file line number Diff line number Diff line change
@@ -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))
}
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions tests/Settings-invalid.ron
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(
ok: true,
error
)
18 changes: 18 additions & 0 deletions tests/Settings.ron
Original file line number Diff line number Diff line change
@@ -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"
}
)
)
20 changes: 20 additions & 0 deletions tests/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
feature = "hjson",
feature = "yaml",
feature = "ini",
feature = "ron",
))]

extern crate chrono;
Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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));
}
83 changes: 83 additions & 0 deletions tests/file_ron.rs
Original file line number Diff line number Diff line change
@@ -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())
);
}

0 comments on commit 86f8776

Please sign in to comment.