Skip to content

Commit c730bdf

Browse files
committed
Make 'env' a default feature: remove 'pear' dep.
This commit removes the dependency on `pear` for parsing which improves compile times and allows making `env` a default feature, in-turn making the `Env` provider available by default. The `env` feature is no longer used or documented but continues to exist for backwards compatibility.
1 parent ceb65c8 commit c730bdf

File tree

8 files changed

+184
-93
lines changed

8 files changed

+184
-93
lines changed

Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,20 @@ keywords = ["config", "configuration", "toml", "json", "yaml"]
1111
license = "MIT OR Apache-2.0"
1212
categories = ["config"]
1313

14+
[lints.rust]
15+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(nightly)'] }
16+
1417
[features]
15-
env = ["parse-value"]
18+
default = ["env"]
1619
json = ["serde_json"]
17-
parse-value = ["pear"]
1820
test = ["tempfile", "parking_lot"]
1921
toml = ["toml_edit"]
2022
yaml = ["serde_yaml"]
23+
env = [] # does nothing; here for backwards compat
2124

2225
[dependencies]
2326
serde = "1.0"
2427
uncased = "0.9.3"
25-
pear = { version = "0.2", optional = true }
2628
toml_edit = { version = "0.22", optional = true, default-features = false, features = ["parse", "serde"] }
2729
serde_json = { version = "1.0", optional = true }
2830
serde_yaml = { version = "0.9", optional = true }

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Add the following to your `Cargo.toml`, enabling the desired built-in providers:
4747

4848
```toml
4949
[dependencies]
50-
figment = { version = "0.10", features = ["toml", "env"] }
50+
figment = { version = "0.10", features = ["toml"] }
5151
```
5252

5353
#### Third-Party Providers

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,6 @@
295295
//! | feature | gated namespace | description |
296296
//! |---------|-----------------------------|-------------------------------------------|
297297
//! | `test` | [`Jail`] | Semi-sandboxed environment for testing. |
298-
//! | `env` | [`providers::Env`] | Environment variable [`Provider`]. |
299298
//! | `toml` | [`providers::Toml`] | TOML file/string [`Provider`]. |
300299
//! | `json` | [`providers::Json`] | JSON file/string [`Provider`]. |
301300
//! | `yaml` | [`providers::Yaml`] | YAML file/string [`Provider`]. |
@@ -310,6 +309,7 @@
310309
//!
311310
//! | provider | description |
312311
//! |---------------------------------------|----------------------------------------|
312+
//! | [`providers::Env`] | Environment variable [`Provider`]. |
313313
//! | [`providers::Serialized`] | Source from any [`Serialize`] type. |
314314
//! | [`(impl AsRef<str>, impl Serialize)`] | Global source from a `("key", value)`. |
315315
//! | [`&T` _where_ `T: Provider`] | Source from `T` as a reference. |

src/providers/env.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ crate::util::cloneable_fn_trait!(
101101
/// inherent methods. The dictionary is emitted to the profile
102102
/// [`profile`](#structfield.profile), configurable via [`Env::profile()`].
103103
#[derive(Clone)]
104-
#[cfg_attr(nightly, doc(cfg(feature = "env")))]
105104
pub struct Env {
106105
filter_map: Box<dyn FilterMap>,
107106
/// The profile config data will be emitted to. Defaults to

src/providers/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
66
mod serialized;
77
mod data;
8+
mod env;
89

9-
#[cfg(feature = "env")] mod env;
10-
#[cfg(feature = "env")] pub use self::env::Env;
11-
10+
pub use self::env::Env;
1211
pub use self::serialized::Serialized;
1312
pub use self::data::*;

src/value/mod.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ mod value;
44
mod ser;
55
mod de;
66
mod tag;
7-
8-
#[cfg(feature = "parse-value")]
97
mod parse;
10-
11-
#[cfg(feature = "parse-value")]
128
mod escape;
139

1410
pub mod magic;

src/value/parse.rs

Lines changed: 173 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,198 @@
1-
use pear::{parse_error, parsers::*};
2-
use pear::combinators::*;
3-
use pear::macros::{parse, parser, switch};
4-
use pear::input::{Pear, Text};
1+
use std::borrow::Cow;
52

63
use crate::value::{Value, Dict, escape::escape};
74

8-
type Input<'a> = Pear<Text<'a>>;
9-
type Result<'a, T> = pear::input::Result<T, Input<'a>>;
5+
use super::Empty;
106

11-
#[inline(always)]
12-
fn is_whitespace(&byte: &char) -> bool {
13-
byte.is_ascii_whitespace()
7+
struct Parser<'a> {
8+
cursor: &'a str,
149
}
1510

16-
#[inline(always)]
17-
fn is_not_separator(&byte: &char) -> bool {
18-
!matches!(byte, ',' | '{' | '}' | '[' | ']')
11+
#[derive(Debug)]
12+
#[allow(dead_code)]
13+
pub enum Error {
14+
Expected(Value),
15+
Escape(super::escape::Error),
16+
Eof,
1917
}
2018

21-
// TODO: Be more permissive here?
22-
#[inline(always)]
23-
fn is_ident_char(&byte: &char) -> bool {
24-
byte.is_ascii_alphanumeric() || byte == '_' || byte == '-'
25-
}
19+
type Result<'a, T> = std::result::Result<T, Error>;
2620

27-
#[parser]
28-
fn string<'a>(input: &mut Input<'a>) -> Result<'a, String> {
29-
let mut is_escaped = false;
30-
let str_char = |&c: &char| -> bool {
31-
if is_escaped { is_escaped = false; return true; }
32-
if c == '\\' { is_escaped = true; return true; }
33-
c != '"'
34-
};
21+
impl<'a> Parser<'a> {
22+
fn new(cursor: &'a str) -> Self {
23+
Self { cursor }
24+
}
3525

36-
let inner = (eat('"')?, take_while(str_char)?, eat('"')?).1;
37-
match escape(inner) {
38-
Ok(string) => string.into_owned(),
39-
Err(e) => parse_error!("invalid string: {}", e)?,
26+
fn peek(&self, c: char) -> bool {
27+
self.cursor.chars().next() == Some(c)
4028
}
41-
}
4229

43-
#[parser]
44-
fn key<'a>(input: &mut Input<'a>) -> Result<'a, String> {
45-
switch! {
46-
peek('"') => Ok(string()?),
47-
_ => Ok(take_some_while(is_ident_char)?.to_string())
30+
fn peek_next(&self) -> Option<char> {
31+
self.cursor.chars().next()
4832
}
49-
}
5033

51-
#[parser]
52-
fn key_value<'a>(input: &mut Input<'a>) -> Result<'a, (String, Value)> {
53-
let key = (surrounded(key, is_whitespace)?, eat('=')?).0;
54-
(key, surrounded(value, is_whitespace)?)
55-
}
34+
#[inline]
35+
fn eat_if<F>(&mut self, f: F) -> Option<char>
36+
where F: FnOnce(&char) -> bool,
37+
{
38+
match self.cursor.chars().next() {
39+
Some(ch) if f(&ch) => {
40+
self.cursor = &self.cursor[ch.len_utf8()..];
41+
Some(ch)
42+
}
43+
_ => None,
44+
}
45+
}
5646

57-
#[parser]
58-
fn array<'a>(input: &mut Input<'a>) -> Result<'a, Vec<Value>> {
59-
Ok(delimited_collect('[', value, ',', ']')?)
60-
}
47+
fn eat(&mut self, c: char) -> Result<char> {
48+
self.eat_if(|&ch| ch == c)
49+
.ok_or_else(|| Error::Expected(Value::from(c)))
50+
}
6151

62-
#[parser]
63-
fn dict<'a>(input: &mut Input<'a>) -> Result<'a, Dict> {
64-
Ok(delimited_collect('{', key_value, ',', '}')?)
65-
}
52+
fn eat_any(&mut self) -> Result<char> {
53+
self.eat_if(|_| true).ok_or(Error::Eof)
54+
}
6655

67-
#[parser]
68-
fn value<'a>(input: &mut Input<'a>) -> Result<'a, Value> {
69-
skip_while(is_whitespace)?;
70-
let val = switch! {
71-
eat_slice("true") => Value::from(true),
72-
eat_slice("false") => Value::from(false),
73-
peek('{') => Value::from(dict()?),
74-
peek('[') => Value::from(array()?),
75-
peek('"') => Value::from(string()?),
76-
peek('\'') => Value::from((eat('\'')?, eat_any()?, eat('\'')?).1),
77-
_ => {
78-
let value = take_while(is_not_separator)?.trim();
79-
if value.contains('.') {
80-
if let Ok(float) = value.parse::<f64>() {
81-
return Ok(Value::from(float));
82-
}
56+
fn skip_whitespace(&mut self) {
57+
self.cursor = self.cursor.trim_start();
58+
}
59+
60+
fn substr<F>(&mut self, f: F) -> Result<&'a str>
61+
where F: FnMut(&char) -> bool,
62+
{
63+
let len = self.cursor.chars()
64+
.take_while(f)
65+
.map(|c| c.len_utf8())
66+
.sum();
67+
68+
let (substring, rest) = self.cursor.split_at(len);
69+
self.cursor = rest;
70+
Ok(substring)
71+
}
72+
73+
fn quoted_char(&mut self) -> Result<char> {
74+
self.eat('\'')?;
75+
let ch = self.eat_any()?;
76+
self.eat('\'')?;
77+
Ok(ch)
78+
}
79+
80+
fn quoted_str(&mut self) -> Result<Cow<'a, str>> {
81+
self.eat('"')?;
82+
83+
let mut is_escaped = false;
84+
let inner = self.substr(|&c: &char| -> bool {
85+
if is_escaped { is_escaped = false; return true; }
86+
if c == '\\' { is_escaped = true; return true; }
87+
c != '"'
88+
})?;
89+
90+
self.eat('"')?;
91+
escape(inner).map_err(Error::Escape)
92+
}
93+
94+
fn key(&mut self) -> Result<Cow<'a, str>> {
95+
#[inline(always)]
96+
fn is_ident_char(&byte: &char) -> bool {
97+
byte.is_ascii_alphanumeric() || byte == '_' || byte == '-'
98+
}
99+
100+
if self.peek('"') {
101+
self.quoted_str()
102+
} else {
103+
self.substr(is_ident_char).map(Cow::Borrowed)
104+
}
105+
}
106+
107+
fn dict(&mut self) -> Result<Dict> {
108+
self.eat('{')?;
109+
110+
let mut dict = Dict::new();
111+
loop {
112+
self.skip_whitespace();
113+
if self.eat('}').is_ok() {
114+
break;
83115
}
84116

85-
if let Ok(int) = value.parse::<usize>() {
86-
Value::from(int)
87-
} else if let Ok(int) = value.parse::<isize>() {
88-
Value::from(int)
89-
} else {
90-
Value::from(value.to_string())
117+
let key = self.key()?;
118+
self.skip_whitespace();
119+
self.eat('=')?;
120+
self.skip_whitespace();
121+
let value = self.value()?;
122+
dict.insert(key.to_string(), value);
123+
124+
self.skip_whitespace();
125+
let _ = self.eat(',');
126+
}
127+
128+
Ok(dict)
129+
}
130+
131+
fn array(&mut self) -> Result<Vec<Value>> {
132+
self.eat('[')?;
133+
let mut values = Vec::new();
134+
135+
loop {
136+
self.skip_whitespace();
137+
if self.eat(']').is_ok() {
138+
break;
91139
}
140+
141+
values.push(self.value()?);
142+
self.skip_whitespace();
143+
let _ = self.eat(',');
144+
}
145+
146+
Ok(values)
147+
}
148+
149+
fn value(&mut self) -> Result<Value> {
150+
#[inline(always)]
151+
fn is_not_separator(&byte: &char) -> bool {
152+
!matches!(byte, ',' | '{' | '}' | '[' | ']')
92153
}
93-
};
94154

95-
skip_while(is_whitespace)?;
96-
val
155+
let value = match self.peek_next() {
156+
Some('"') => Value::from(self.quoted_str()?.to_string()),
157+
Some('\'') => Value::from(self.quoted_char()?),
158+
Some('[') => Value::from(self.array()?),
159+
Some('{') => Value::from(self.dict()?),
160+
Some(_) => match self.substr(is_not_separator)?.trim() {
161+
"true" => Value::from(true),
162+
"false" => Value::from(false),
163+
value => {
164+
if value.contains('.') {
165+
if let Ok(float) = value.parse::<f64>() {
166+
Value::from(float)
167+
} else {
168+
Value::from(value.to_string())
169+
}
170+
} else if let Ok(int) = value.parse::<usize>() {
171+
Value::from(int)
172+
} else if let Ok(int) = value.parse::<isize>() {
173+
Value::from(int)
174+
} else {
175+
Value::from(value.to_string())
176+
}
177+
}
178+
},
179+
None => Value::from(Empty::Unit),
180+
};
181+
182+
self.skip_whitespace();
183+
Ok(value)
184+
}
97185
}
98186

99187
impl std::str::FromStr for Value {
100188
type Err = std::convert::Infallible;
101189

102190
fn from_str(s: &str) -> std::result::Result<Self, std::convert::Infallible> {
103-
Ok(parse!(value: Text::from(s))
104-
.unwrap_or_else(|_| Value::from(s.to_string())))
191+
let mut parser = Parser::new(s.trim());
192+
match parser.value() {
193+
Ok(value) => Ok(value),
194+
Err(_) => Ok(Value::from(s)),
195+
}
105196
}
106197
}
107198

@@ -113,7 +204,9 @@ mod tests {
113204
macro_rules! assert_parse_eq {
114205
($string:expr => $expected:expr) => ({
115206
let expected = Value::from($expected);
116-
let actual: Value = $string.parse().unwrap();
207+
let actual: Value = $string.parse()
208+
.unwrap_or_else(|e| panic!("failed to parse {:?}: {:?}", $string, e));
209+
117210
assert_eq!(actual, expected, "got {:?}, expected {:?}", actual, expected);
118211
});
119212

@@ -168,7 +261,10 @@ mod tests {
168261

169262
assert_parse_eq! {
170263
"[1,2,3]" => vec![1u8, 2u8, 3u8],
264+
"[ 1 , 2 ,3]" => vec![1u8, 2u8, 3u8],
265+
" [ 1 , 2 , 3 ] " => vec![1u8, 2u8, 3u8],
171266
"{a=b}" => map!["a" => "b"],
267+
" { a = b } " => map!["a" => "b"],
172268
"{\"a\"=b}" => map!["a" => "b"],
173269
"{\"a.b.c\"=b}" => map!["a.b.c" => "b"],
174270
"{a=1,b=3}" => map!["a" => 1u8, "b" => 3u8],

src/value/tag.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@ use crate::profile::{Profile, ProfileTag};
1616
#[derive(Copy, Clone)]
1717
pub struct Tag(u64);
1818

19-
#[cfg(any(target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32"))]
19+
#[cfg(not(target_has_atomic = "64"))]
2020
static COUNTER: atomic::Atomic<u64> = atomic::Atomic::new(1);
2121

22-
#[cfg(not(any(target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32")))]
22+
#[cfg(target_has_atomic = "64")]
2323
static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
2424

25-
2625
impl Tag {
2726
/// The default `Tag`. Such a tag will never have associated metadata and
2827
/// is associated with a profile of `Default`.

0 commit comments

Comments
 (0)