Skip to content

Commit 86f8776

Browse files
Merge pull request #202 from skreborn/master
Add support for RON format
2 parents fd5c87a + ba883ca commit 86f8776

File tree

9 files changed

+215
-3
lines changed

9 files changed

+215
-3
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ license = "MIT/Apache-2.0"
1414
maintenance = { status = "actively-developed" }
1515

1616
[features]
17-
default = ["toml", "json", "yaml", "hjson", "ini"]
17+
default = ["toml", "json", "yaml", "hjson", "ini", "ron"]
1818
json = ["serde_json"]
1919
yaml = ["yaml-rust"]
2020
hjson = ["serde-hjson"]
@@ -30,6 +30,7 @@ serde_json = { version = "1.0.2", optional = true }
3030
yaml-rust = { version = "0.4", optional = true }
3131
serde-hjson = { version = "0.9", default-features = false, optional = true }
3232
rust-ini = { version = "0.17", optional = true }
33+
ron = { version = "0.6", optional = true }
3334

3435
[dev-dependencies]
3536
serde_derive = "1.0.8"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
- Set defaults
1212
- Set explicit values (to programmatically override)
13-
- Read from [JSON], [TOML], [YAML], [HJSON], [INI] files
13+
- Read from [JSON], [TOML], [YAML], [HJSON], [INI], [RON] files
1414
- Read from environment
1515
- Loosely typed — Configuration values may be read in any supported type, as long as there exists a reasonable conversion
1616
- 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 @@
2020
[YAML]: https://github.com/chyh1990/yaml-rust
2121
[HJSON]: https://github.com/hjson/hjson-rust
2222
[INI]: https://github.com/zonyitoo/rust-ini
23+
[RON]: https://github.com/ron-rs/ron
2324

2425
## Usage
2526

@@ -33,6 +34,7 @@ config = "0.11"
3334
- `hjson` - Adds support for reading HJSON files
3435
- `yaml` - Adds support for reading YAML files
3536
- `toml` - Adds support for reading TOML files
37+
- `ron` - Adds support for reading RON files
3638

3739
See the [documentation](https://docs.rs/config) or [examples](https://github.com/mehcode/config-rs/tree/master/examples) for
3840
more usage information.

src/file/format/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ mod hjson;
2222
#[cfg(feature = "ini")]
2323
mod ini;
2424

25+
#[cfg(feature = "ron")]
26+
mod ron;
27+
2528
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
2629
pub enum FileFormat {
2730
/// TOML (parsed with toml)
@@ -42,6 +45,10 @@ pub enum FileFormat {
4245
/// INI (parsed with rust_ini)
4346
#[cfg(feature = "ini")]
4447
Ini,
48+
49+
/// RON (parsed with ron)
50+
#[cfg(feature = "ron")]
51+
Ron,
4552
}
4653

4754
lazy_static! {
@@ -65,6 +72,9 @@ lazy_static! {
6572
#[cfg(feature = "ini")]
6673
formats.insert(FileFormat::Ini, vec!["ini"]);
6774

75+
#[cfg(feature = "ron")]
76+
formats.insert(FileFormat::Ron, vec!["ron"]);
77+
6878
formats
6979
};
7080
}
@@ -102,6 +112,9 @@ impl FileFormat {
102112

103113
#[cfg(feature = "ini")]
104114
FileFormat::Ini => ini::parse(uri, text),
115+
116+
#[cfg(feature = "ron")]
117+
FileFormat::Ron => ron::parse(uri, text),
105118
}
106119
}
107120
}

src/file/format/ron.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use std::collections::HashMap;
2+
use std::error::Error;
3+
4+
use ron;
5+
6+
use crate::value::{Value, ValueKind};
7+
8+
pub fn parse(
9+
uri: Option<&String>,
10+
text: &str,
11+
) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
12+
let value = from_ron_value(uri, ron::from_str(text)?)?;
13+
match value.kind {
14+
ValueKind::Table(map) => Ok(map),
15+
16+
_ => Ok(HashMap::new()),
17+
}
18+
}
19+
20+
fn from_ron_value(
21+
uri: Option<&String>,
22+
value: ron::Value,
23+
) -> Result<Value, Box<dyn Error + Send + Sync>> {
24+
let kind = match value {
25+
ron::Value::Option(value) => match value {
26+
Some(value) => from_ron_value(uri, *value)?.kind,
27+
None => ValueKind::Nil,
28+
},
29+
30+
ron::Value::Unit => ValueKind::Nil,
31+
32+
ron::Value::Bool(value) => ValueKind::Boolean(value),
33+
34+
ron::Value::Number(value) => match value {
35+
ron::Number::Float(value) => ValueKind::Float(value.get()),
36+
ron::Number::Integer(value) => ValueKind::Integer(value),
37+
},
38+
39+
ron::Value::Char(value) => ValueKind::String(value.to_string()),
40+
41+
ron::Value::String(value) => ValueKind::String(value),
42+
43+
ron::Value::Seq(values) => {
44+
let array = values
45+
.into_iter()
46+
.map(|value| from_ron_value(uri, value))
47+
.collect::<Result<Vec<_>, _>>()?;
48+
49+
ValueKind::Array(array)
50+
}
51+
52+
ron::Value::Map(values) => {
53+
let map = values
54+
.iter()
55+
.map(|(key, value)| -> Result<_, Box<dyn Error + Send + Sync>> {
56+
let key = key.clone().into_rust::<String>()?;
57+
let value = from_ron_value(uri, value.clone())?;
58+
59+
Ok((key, value))
60+
})
61+
.collect::<Result<HashMap<_, _>, _>>()?;
62+
63+
ValueKind::Table(map)
64+
}
65+
};
66+
67+
Ok(Value::new(uri, kind))
68+
}

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//! - Environment variables
77
//! - Another Config instance
88
//! - Remote configuration: etcd, Consul
9-
//! - Files: JSON, YAML, TOML, HJSON
9+
//! - Files: TOML, JSON, YAML, HJSON, INI, RON
1010
//! - Manual, programmatic override (via a `.set` method on the Config instance)
1111
//!
1212
//! Additionally, Config supports:
@@ -48,6 +48,9 @@ extern crate serde_hjson;
4848
#[cfg(feature = "ini")]
4949
extern crate ini;
5050

51+
#[cfg(feature = "ron")]
52+
extern crate ron;
53+
5154
mod config;
5255
mod de;
5356
mod env;

tests/Settings-invalid.ron

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(
2+
ok: true,
3+
error
4+
)

tests/Settings.ron

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
(
2+
debug: true,
3+
production: false,
4+
arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
5+
place: (
6+
initials: ('T', 'P'),
7+
name: "Torre di Pisa",
8+
longitude: 43.7224985,
9+
latitude: 10.3970522,
10+
favorite: false,
11+
reviews: 3866,
12+
rating: Some(4.5),
13+
telephone: None,
14+
creator: {
15+
"name": "John Smith"
16+
}
17+
)
18+
)

tests/datetime.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
feature = "hjson",
55
feature = "yaml",
66
feature = "ini",
7+
feature = "ron",
78
))]
89

910
extern crate chrono;
@@ -53,6 +54,15 @@ fn make() -> Config {
5354
FileFormat::Ini,
5455
))
5556
.unwrap()
57+
.merge(File::from_str(
58+
r#"
59+
(
60+
ron_datetime: "2021-04-19T11:33:02Z"
61+
)
62+
"#,
63+
FileFormat::Ron,
64+
))
65+
.unwrap()
5666
.clone()
5767
}
5868

@@ -84,6 +94,11 @@ fn test_datetime_string() {
8494
let date: String = s.get("ini_datetime").unwrap();
8595

8696
assert_eq!(&date, "2017-05-10T02:14:53Z");
97+
98+
// RON
99+
let date: String = s.get("ron_datetime").unwrap();
100+
101+
assert_eq!(&date, "2021-04-19T11:33:02Z");
87102
}
88103

89104
#[test]
@@ -114,4 +129,9 @@ fn test_datetime() {
114129
let date: DateTime<Utc> = s.get("ini_datetime").unwrap();
115130

116131
assert_eq!(date, Utc.ymd(2017, 5, 10).and_hms(2, 14, 53));
132+
133+
// RON
134+
let date: DateTime<Utc> = s.get("ron_datetime").unwrap();
135+
136+
assert_eq!(date, Utc.ymd(2021, 4, 19).and_hms(11, 33, 2));
117137
}

tests/file_ron.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#![cfg(feature = "ron")]
2+
3+
extern crate config;
4+
extern crate float_cmp;
5+
extern crate serde;
6+
7+
#[macro_use]
8+
extern crate serde_derive;
9+
10+
use std::collections::HashMap;
11+
use std::path::PathBuf;
12+
13+
use config::*;
14+
use float_cmp::ApproxEqUlps;
15+
16+
#[derive(Debug, Deserialize)]
17+
struct Place {
18+
initials: (char, char),
19+
name: String,
20+
longitude: f64,
21+
latitude: f64,
22+
favorite: bool,
23+
telephone: Option<String>,
24+
reviews: u64,
25+
creator: HashMap<String, Value>,
26+
rating: Option<f32>,
27+
}
28+
29+
#[derive(Debug, Deserialize)]
30+
struct Settings {
31+
debug: f64,
32+
production: Option<String>,
33+
place: Place,
34+
#[serde(rename = "arr")]
35+
elements: Vec<String>,
36+
}
37+
38+
fn make() -> Config {
39+
let mut c = Config::default();
40+
c.merge(File::new("tests/Settings", FileFormat::Ron))
41+
.unwrap();
42+
43+
c
44+
}
45+
46+
#[test]
47+
fn test_file() {
48+
let c = make();
49+
50+
// Deserialize the entire file as single struct
51+
let s: Settings = c.try_into().unwrap();
52+
53+
assert!(s.debug.approx_eq_ulps(&1.0, 2));
54+
assert_eq!(s.production, Some("false".to_string()));
55+
assert_eq!(s.place.initials, ('T', 'P'));
56+
assert_eq!(s.place.name, "Torre di Pisa");
57+
assert!(s.place.longitude.approx_eq_ulps(&43.7224985, 2));
58+
assert!(s.place.latitude.approx_eq_ulps(&10.3970522, 2));
59+
assert_eq!(s.place.favorite, false);
60+
assert_eq!(s.place.reviews, 3866);
61+
assert_eq!(s.place.rating, Some(4.5));
62+
assert_eq!(s.place.telephone, None);
63+
assert_eq!(s.elements.len(), 10);
64+
assert_eq!(s.elements[3], "4".to_string());
65+
assert_eq!(
66+
s.place.creator["name"].clone().into_string().unwrap(),
67+
"John Smith".to_string()
68+
);
69+
}
70+
71+
#[test]
72+
fn test_error_parse() {
73+
let mut c = Config::default();
74+
let res = c.merge(File::new("tests/Settings-invalid", FileFormat::Ron));
75+
76+
let path_with_extension: PathBuf = ["tests", "Settings-invalid.ron"].iter().collect();
77+
78+
assert!(res.is_err());
79+
assert_eq!(
80+
res.unwrap_err().to_string(),
81+
format!("4:1: Expected colon in {}", path_with_extension.display())
82+
);
83+
}

0 commit comments

Comments
 (0)