Merge pull request #252 from ehuss/dotted-keys

0.5: Support dotted keys.
This commit is contained in:
Alex Crichton 2018-07-28 15:15:01 -07:00 committed by GitHub
commit 22f00d37bf
5 changed files with 113 additions and 7 deletions

View file

@ -159,6 +159,9 @@ enum ErrorKind {
/// A struct was expected but something else was found /// A struct was expected but something else was found
ExpectedString, ExpectedString,
/// Dotted key attempted to extend something that is not a table.
DottedKeyInvalidType,
#[doc(hidden)] #[doc(hidden)]
__Nonexhaustive, __Nonexhaustive,
} }
@ -210,7 +213,7 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> {
if cur_table.values.is_none() { if cur_table.values.is_none() {
cur_table.values = Some(Vec::new()); cur_table.values = Some(Vec::new());
} }
cur_table.values.as_mut().unwrap().push((key, value)); self.add_dotted_key(key, value, cur_table.values.as_mut().unwrap())?;
} }
} }
} }
@ -523,7 +526,7 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> {
s.end()?; s.end()?;
Ok(ret) Ok(ret)
} }
E::InlineTable(values) => { E::InlineTable(values) | E::DottedTable(values) => {
visitor.visit_map(InlineTableDeserializer { visitor.visit_map(InlineTableDeserializer {
values: values.into_iter(), values: values.into_iter(),
next_value: None, next_value: None,
@ -796,7 +799,7 @@ impl<'a> Deserializer<'a> {
} }
fn key_value(&mut self) -> Result<Line<'a>, Error> { fn key_value(&mut self) -> Result<Line<'a>, Error> {
let key = self.table_key()?; let key = self.dotted_key()?;
self.eat_whitespace()?; self.eat_whitespace()?;
self.expect(Token::Equals)?; self.expect(Token::Equals)?;
self.eat_whitespace()?; self.eat_whitespace()?;
@ -1095,11 +1098,12 @@ impl<'a> Deserializer<'a> {
return Ok((span, ret)) return Ok((span, ret))
} }
loop { loop {
let key = self.table_key()?; let key = self.dotted_key()?;
self.eat_whitespace()?; self.eat_whitespace()?;
self.expect(Token::Equals)?; self.expect(Token::Equals)?;
self.eat_whitespace()?; self.eat_whitespace()?;
ret.push((key, self.value()?)); let value = self.value()?;
self.add_dotted_key(key, value, &mut ret)?;
self.eat_whitespace()?; self.eat_whitespace()?;
if let Some(span) = self.eat_spanned(Token::RightBrace)? { if let Some(span) = self.eat_spanned(Token::RightBrace)? {
@ -1152,6 +1156,52 @@ impl<'a> Deserializer<'a> {
self.tokens.table_key().map(|t| t.1).map_err(|e| self.token_error(e)) self.tokens.table_key().map(|t| t.1).map_err(|e| self.token_error(e))
} }
fn dotted_key(&mut self) -> Result<Vec<Cow<'a, str>>, Error> {
let mut result = Vec::new();
result.push(self.table_key()?);
self.eat_whitespace()?;
while self.eat(Token::Period)? {
self.eat_whitespace()?;
result.push(self.table_key()?);
self.eat_whitespace()?;
}
Ok(result)
}
fn add_dotted_key(
&self,
mut key_parts: Vec<Cow<'a, str>>,
value: Value<'a>,
values: &mut Vec<(Cow<'a, str>, Value<'a>)>,
) -> Result<(), Error> {
let key = key_parts.remove(0);
if key_parts.is_empty() {
values.push((key, value));
return Ok(());
}
match values.iter_mut().find(|&&mut (ref k, _)| *k == key) {
Some(&mut (_, Value { e: E::DottedTable(ref mut v), .. })) => {
return self.add_dotted_key(key_parts, value, v);
}
Some(&mut (_, Value { start, .. })) => {
return Err(self.error(start, ErrorKind::DottedKeyInvalidType));
}
None => {}
}
// The start/end value is somewhat misleading here.
let inline_table = Value {
e: E::DottedTable(Vec::new()),
start: value.start,
end: value.end,
};
values.push((key, inline_table));
let last_i = values.len() - 1;
if let (_, Value { e: E::DottedTable(ref mut v), .. }) = values[last_i] {
self.add_dotted_key(key_parts, value, v)?;
}
Ok(())
}
fn eat_whitespace(&mut self) -> Result<(), Error> { fn eat_whitespace(&mut self) -> Result<(), Error> {
self.tokens.eat_whitespace().map_err(|e| self.token_error(e)) self.tokens.eat_whitespace().map_err(|e| self.token_error(e))
} }
@ -1329,6 +1379,7 @@ impl fmt::Display for Error {
ErrorKind::EmptyTableKey => "empty table key found".fmt(f)?, ErrorKind::EmptyTableKey => "empty table key found".fmt(f)?,
ErrorKind::Custom => self.inner.message.fmt(f)?, ErrorKind::Custom => self.inner.message.fmt(f)?,
ErrorKind::ExpectedString => "expected string".fmt(f)?, ErrorKind::ExpectedString => "expected string".fmt(f)?,
ErrorKind::DottedKeyInvalidType => "dotted key attempted to extend non-table type".fmt(f)?,
ErrorKind::__Nonexhaustive => panic!(), ErrorKind::__Nonexhaustive => panic!(),
} }
@ -1372,6 +1423,7 @@ impl error::Error for Error {
ErrorKind::EmptyTableKey => "empty table key found", ErrorKind::EmptyTableKey => "empty table key found",
ErrorKind::Custom => "a custom error", ErrorKind::Custom => "a custom error",
ErrorKind::ExpectedString => "expected string", ErrorKind::ExpectedString => "expected string",
ErrorKind::DottedKeyInvalidType => "dotted key invalid type",
ErrorKind::__Nonexhaustive => panic!(), ErrorKind::__Nonexhaustive => panic!(),
} }
} }
@ -1385,7 +1437,7 @@ impl de::Error for Error {
enum Line<'a> { enum Line<'a> {
Table { at: usize, header: Header<'a>, array: bool }, Table { at: usize, header: Header<'a>, array: bool },
KeyValue(Cow<'a, str>, Value<'a>), KeyValue(Vec<Cow<'a, str>>, Value<'a>),
} }
struct Header<'a> { struct Header<'a> {
@ -1447,6 +1499,7 @@ enum E<'a> {
Datetime(&'a str), Datetime(&'a str),
Array(Vec<Value<'a>>), Array(Vec<Value<'a>>),
InlineTable(Vec<(Cow<'a, str>, Value<'a>)>), InlineTable(Vec<(Cow<'a, str>, Value<'a>)>),
DottedTable(Vec<(Cow<'a, str>, Value<'a>)>),
} }
impl<'a> Value<'a> { impl<'a> Value<'a> {
@ -1459,6 +1512,7 @@ impl<'a> Value<'a> {
(&E::Datetime(..), &E::Datetime(..)) | (&E::Datetime(..), &E::Datetime(..)) |
(&E::Array(..), &E::Array(..)) | (&E::Array(..), &E::Array(..)) |
(&E::InlineTable(..), &E::InlineTable(..)) => true, (&E::InlineTable(..), &E::InlineTable(..)) => true,
(&E::DottedTable(..), &E::DottedTable(..)) => true,
_ => false, _ => false,
} }

View file

@ -17,4 +17,12 @@ fn bad() {
bad("a = -0x1"); bad("a = -0x1");
bad("a = 0x-1"); bad("a = 0x-1");
// Dotted keys.
bad("a.b.c = 1
a.b = 2
");
bad("a = 1
a.b = 2");
bad("a = {k1 = 1, k1.name = \"joe\"}")
} }

View file

@ -45,7 +45,7 @@ fn to_json(toml: toml::Value) -> Json {
fn run_pretty(toml: Toml) { fn run_pretty(toml: Toml) {
// Assert toml == json // Assert toml == json
println!("### pretty round trip parse."); println!("### pretty round trip parse.");
// standard pretty // standard pretty
let toml_raw = to_string_pretty(&toml).expect("to string"); let toml_raw = to_string_pretty(&toml).expect("to string");
let toml2 = toml_raw.parse().expect("from string"); let toml2 = toml_raw.parse().expect("from string");
@ -247,3 +247,6 @@ test!(key_quote_newline,
test!(table_array_nest_no_keys, test!(table_array_nest_no_keys,
include_str!("valid/table-array-nest-no-keys.toml"), include_str!("valid/table-array-nest-no-keys.toml"),
include_str!("valid/table-array-nest-no-keys.json")); include_str!("valid/table-array-nest-no-keys.json"));
test!(dotted_keys,
include_str!("valid/dotted-keys.toml"),
include_str!("valid/dotted-keys.json"));

View file

@ -0,0 +1,34 @@
{
"a": {
"b": {
"type": "integer",
"value": "123"
}
},
"table": {
"a": {
"b": {
"c": {
"type": "integer",
"value": "1"
},
"d": {
"type": "integer",
"value": "2"
}
}
},
"in": {
"type": {
"color": {
"type": "string",
"value": "blue"
},
"name": {
"type": "string",
"value": "cat"
}
}
}
}
}

View file

@ -0,0 +1,7 @@
a.b = 123
[table]
a.b.c = 1
a . b . d = 2
in = { type.name = "cat", type.color = "blue" }