From 67132245640a0d2e95b696163b504553eb55efa5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 8 Apr 2015 08:01:49 -0700 Subject: [PATCH 01/17] Tweak doc url in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 259846e..09b7152 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.org/alexcrichton/toml-rs.svg?branch=master)](https://travis-ci.org/alexcrichton/toml-rs) -[Documentation](http://alexcrichton.com/toml-rs/toml/index.html) +[Documentation](http://alexcrichton.com/toml-rs) A [TOML][toml] decoder and encoder for Rust. This library is currently compliant with the v0.4.0 version of TOML. This library will also likely continue to stay up to From 321afe620530bde9875c42489211f891c2ac0646 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 15 May 2015 11:50:24 -0700 Subject: [PATCH 02/17] Try out a new travis config --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 145528e..d6669c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,7 @@ language: rust +rust: + - 1.0.0 + - nightly sudo: false script: - cargo build --verbose @@ -10,6 +13,7 @@ script: after_success: | [ $TRAVIS_BRANCH = master ] && [ $TRAVIS_PULL_REQUEST = false ] && + [ $TRAVIS_RUST_VERSION = nightly ] && echo '' > target/doc/index.html && pip install ghp-import --user $USER && $HOME/.local/bin/ghp-import -n target/doc && From 971700148b58df921f070015febd8706c66edded Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 15 May 2015 11:52:49 -0700 Subject: [PATCH 03/17] Test on beta as well --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d6669c7..76e3afe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: rust rust: - 1.0.0 + - beta - nightly sudo: false script: From 2fcd829b1d9c70d0981411b4f4adca9124985b54 Mon Sep 17 00:00:00 2001 From: Andrzej Janik Date: Thu, 4 Jun 2015 20:23:46 +0200 Subject: [PATCH 04/17] Disallow table redefinitions --- src/decoder/rustc_serialize.rs | 14 +++++----- src/display.rs | 7 +++-- src/encoder/mod.rs | 8 +++--- src/encoder/rustc_serialize.rs | 11 +++++--- src/lib.rs | 20 +++++++++++-- src/parser.rs | 51 +++++++++++++++++++++------------- tests/valid.rs | 4 +-- 7 files changed, 74 insertions(+), 41 deletions(-) diff --git a/src/decoder/rustc_serialize.rs b/src/decoder/rustc_serialize.rs index 6e8fe59..af38f9b 100644 --- a/src/decoder/rustc_serialize.rs +++ b/src/decoder/rustc_serialize.rs @@ -3,7 +3,7 @@ use std::mem; use super::{Decoder, DecodeError}; use super::DecodeErrorKind::*; -use Value; +use {Value, Table}; impl rustc_serialize::Decoder for Decoder { type Error = DecodeError; @@ -141,7 +141,7 @@ impl rustc_serialize::Decoder for Decoder { Some(Value::Table(..)) => { let ret = try!(f(self)); match self.toml { - Some(Value::Table(ref t)) if t.len() == 0 => {} + Some(Value::Table(Table(ref t, _,))) if t.len() == 0 => {} _ => return Ok(ret) } self.toml.take(); @@ -156,7 +156,7 @@ impl rustc_serialize::Decoder for Decoder { { let field = format!("{}", f_name); let toml = match self.toml { - Some(Value::Table(ref mut table)) => { + Some(Value::Table(Table(ref mut table, _))) => { table.remove(&field) .or_else(|| table.remove(&f_name.replace("_", "-"))) }, @@ -165,7 +165,7 @@ impl rustc_serialize::Decoder for Decoder { let mut d = self.sub_decoder(toml, f_name); let ret = try!(f(&mut d)); if let Some(value) = d.toml { - if let Some(Value::Table(ref mut table)) = self.toml { + if let Some(Value::Table(Table(ref mut table, _))) = self.toml { table.insert(field, value); } } @@ -260,7 +260,7 @@ impl rustc_serialize::Decoder for Decoder { where F: FnOnce(&mut Decoder, usize) -> Result { let len = match self.toml { - Some(Value::Table(ref table)) => table.len(), + Some(Value::Table(Table(ref table, _))) => table.len(), ref found => return Err(self.mismatch("table", found)), }; let ret = try!(f(self, len)); @@ -273,7 +273,7 @@ impl rustc_serialize::Decoder for Decoder { { match self.toml { Some(Value::Table(ref table)) => { - match table.iter().skip(idx).next() { + match table.0.iter().skip(idx).next() { Some((key, _)) => { let val = Value::String(format!("{}", key)); f(&mut self.sub_decoder(Some(val), &**key)) @@ -290,7 +290,7 @@ impl rustc_serialize::Decoder for Decoder { { match self.toml { Some(Value::Table(ref table)) => { - match table.iter().skip(idx).next() { + match table.0.iter().skip(idx).next() { Some((_, value)) => { // XXX: this shouldn't clone f(&mut self.sub_decoder(Some(value.clone()), "")) diff --git a/src/display.rs b/src/display.rs index 0c561e8..74ec424 100644 --- a/src/display.rs +++ b/src/display.rs @@ -57,7 +57,7 @@ fn write_str(f: &mut fmt::Formatter, s: &str) -> fmt::Result { impl<'a, 'b> Printer<'a, 'b> { fn print(&mut self, table: &'a TomlTable) -> fmt::Result { - for (k, v) in table.iter() { + for (k, v) in table.0.iter() { match *v { Table(..) => continue, Array(ref a) => { @@ -70,7 +70,7 @@ impl<'a, 'b> Printer<'a, 'b> { } try!(writeln!(self.output, "{} = {}", Key(&[k]), v)); } - for (k, v) in table.iter() { + for (k, v) in table.0.iter() { match *v { Table(ref inner) => { self.stack.push(k); @@ -127,13 +127,14 @@ impl<'a> fmt::Display for Key<'a> { #[allow(warnings)] mod tests { use Value; + use Table as TomlTable; use Value::{String, Integer, Float, Boolean, Datetime, Array, Table}; use std::collections::BTreeMap; macro_rules! map( ($($k:expr => $v:expr),*) => ({ let mut _m = BTreeMap::new(); $(_m.insert($k.to_string(), $v);)* - _m + TomlTable::new(_m) }) ); #[test] diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 21185f4..ea8ef6a 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -31,7 +31,7 @@ use {Value, Table}; /// let mut e = Encoder::new(); /// my_struct.encode(&mut e).unwrap(); /// -/// assert_eq!(e.toml.get(&"foo".to_string()), Some(&Value::Integer(4))) +/// assert_eq!(e.toml.0.get(&"foo".to_string()), Some(&Value::Integer(4))) /// # } /// ``` pub struct Encoder { @@ -73,12 +73,12 @@ enum State { impl Encoder { /// Constructs a new encoder which will emit to the given output stream. pub fn new() -> Encoder { - Encoder { state: State::Start, toml: BTreeMap::new() } + Encoder { state: State::Start, toml: Table(BTreeMap::new(), false) } } fn emit_value(&mut self, v: Value) -> Result<(), Error> { match mem::replace(&mut self.state, State::Start) { - State::NextKey(key) => { self.toml.insert(key, v); Ok(()) } + State::NextKey(key) => { self.toml.0.insert(key, v); Ok(()) } State::NextArray(mut vec) => { // TODO: validate types vec.push(v); @@ -122,7 +122,7 @@ impl Encoder { State::NextKey(key) => { let mut nested = Encoder::new(); try!(f(&mut nested)); - self.toml.insert(key, Value::Table(nested.toml)); + self.toml.0.insert(key, Value::Table(nested.toml)); Ok(()) } State::NextArray(mut arr) => { diff --git a/src/encoder/rustc_serialize.rs b/src/encoder/rustc_serialize.rs index ab5e90f..830eb5e 100644 --- a/src/encoder/rustc_serialize.rs +++ b/src/encoder/rustc_serialize.rs @@ -193,8 +193,8 @@ impl rustc_serialize::Encodable for Value { }) } Value::Table(ref t) => { - e.emit_map(t.len(), |e| { - for (i, (key, value)) in t.iter().enumerate() { + e.emit_map(t.0.len(), |e| { + for (i, (key, value)) in t.0.iter().enumerate() { try!(e.emit_map_elt_key(i, |e| e.emit_str(key))); try!(e.emit_map_elt_val(i, |e| value.encode(e))); } @@ -212,6 +212,7 @@ mod tests { use {Encoder, Decoder, DecodeError}; use Value; + use Table as TomlTable; use Value::{Table, Integer, Array, Float}; macro_rules! encode( ($t:expr) => ({ @@ -228,7 +229,7 @@ mod tests { macro_rules! map( ($($k:ident, $v:expr),*) => ({ let mut _m = BTreeMap::new(); $(_m.insert(stringify!($k).to_string(), $v);)* - _m + TomlTable::new(_m) }) ); #[test] @@ -577,7 +578,9 @@ mod tests { #[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)] struct Foo { a: BTreeMap } - let v = Foo { a: map! { a, "foo".to_string() } }; + let mut v = Foo { a: BTreeMap::new() }; + v.a.insert("a".to_string(), "foo".to_string()); + let mut d = Decoder::new(Table(map! { a, Table(map! { a, Value::String("foo".to_string()) diff --git a/src/lib.rs b/src/lib.rs index 0196fbc..547c407 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,8 +75,24 @@ pub enum Value { /// Type representing a TOML array, payload of the Value::Array variant pub type Array = Vec; +// The bool field flag is used during parsing and construction. +// Is true if the given table was explicitly defined, false otherwise +// e.g. in a toml document: `[a.b] foo = "bar"`, Table `a` would be false, +// where table `b` (contained inside `a`) would be true. /// Type representing a TOML table, payload of the Value::Table variant -pub type Table = BTreeMap; +#[derive(Debug, Clone)] +pub struct Table (pub BTreeMap, bool); +impl Table { + /// Creates new TOML table + pub fn new(map: BTreeMap) -> Table { + Table(map, false) + } +} +impl PartialEq for Table { + fn eq(&self, other: &Table) -> bool { + self.0.eq(&other.0) + } +} impl Value { /// Tests whether this and another value have the same type. @@ -182,7 +198,7 @@ impl Value { let mut cur_value = self; for key in path.split('.') { match cur_value { - &Value::Table(ref hm) => { + &Value::Table(Table(ref hm, _)) => { match hm.get(key) { Some(v) => cur_value = v, None => return None diff --git a/src/parser.rs b/src/parser.rs index 9a15de8..ccf0d3a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -162,7 +162,7 @@ impl<'a> Parser<'a> { /// If an error occurs, the `errors` field of this parser can be consulted /// to determine the cause of the parse failure. pub fn parse(&mut self) -> Option { - let mut ret = BTreeMap::new(); + let mut ret = TomlTable(BTreeMap::new(), false); while self.peek(0).is_some() { self.ws(); if self.newline() { continue } @@ -189,7 +189,7 @@ impl<'a> Parser<'a> { if keys.len() == 0 { return None } // Build the section table - let mut table = BTreeMap::new(); + let mut table = TomlTable(BTreeMap::new(), false); if !self.values(&mut table) { return None } if array { self.insert_array(&mut ret, &*keys, Table(table), start) @@ -715,7 +715,7 @@ impl<'a> Parser<'a> { fn inline_table(&mut self, _start: usize) -> Option { if !self.expect('{') { return None } self.ws(); - let mut ret = BTreeMap::new(); + let mut ret = TomlTable(BTreeMap::new(), true); if self.eat('}') { return Some(Table(ret)) } loop { let lo = self.next_pos(); @@ -734,14 +734,14 @@ impl<'a> Parser<'a> { fn insert(&mut self, into: &mut TomlTable, key: String, value: Value, key_lo: usize) { - if into.contains_key(&key) { + if into.0.contains_key(&key) { self.errors.push(ParserError { lo: key_lo, hi: key_lo + key.len(), desc: format!("duplicate key: `{}`", key), }) } else { - into.insert(key, value); + into.0.insert(key, value); } } @@ -751,8 +751,8 @@ impl<'a> Parser<'a> { for part in keys[..keys.len() - 1].iter() { let tmp = cur; - if tmp.contains_key(part) { - match *tmp.get_mut(part).unwrap() { + if tmp.0.contains_key(part) { + match *tmp.0.get_mut(part).unwrap() { Table(ref mut table) => { cur = table; continue @@ -785,8 +785,8 @@ impl<'a> Parser<'a> { } // Initialize an empty table as part of this sub-key - tmp.insert(part.clone(), Table(BTreeMap::new())); - match *tmp.get_mut(part).unwrap() { + tmp.0.insert(part.clone(), Table(TomlTable(BTreeMap::new(), false))); + match *tmp.0.get_mut(part).unwrap() { Table(ref mut inner) => cur = inner, _ => unreachable!(), } @@ -802,22 +802,22 @@ impl<'a> Parser<'a> { }; let key = format!("{}", key); let mut added = false; - if !into.contains_key(&key) { - into.insert(key.clone(), Table(BTreeMap::new())); + if !into.0.contains_key(&key) { + into.0.insert(key.clone(), Table(TomlTable(BTreeMap::new(), true))); added = true; } - match into.get_mut(&key) { + match into.0.get_mut(&key) { Some(&mut Table(ref mut table)) => { - let any_tables = table.values().any(|v| v.as_table().is_some()); - if !any_tables && !added { + let any_tables = table.0.values().any(|v| v.as_table().is_some()); + if !added && (!any_tables || table.1) { self.errors.push(ParserError { lo: key_lo, hi: key_lo + key.len(), desc: format!("redefinition of table `{}`", key), }); } - for (k, v) in value.into_iter() { - if table.insert(k.clone(), v).is_some() { + for (k, v) in value.0.into_iter() { + if table.0.insert(k.clone(), v).is_some() { self.errors.push(ParserError { lo: key_lo, hi: key_lo + key.len(), @@ -844,10 +844,10 @@ impl<'a> Parser<'a> { None => return, }; let key = format!("{}", key); - if !into.contains_key(&key) { - into.insert(key.clone(), Array(Vec::new())); + if !into.0.contains_key(&key) { + into.0.insert(key.clone(), Array(Vec::new())); } - match *into.get_mut(&key).unwrap() { + match *into.0.get_mut(&key).unwrap() { Array(ref mut vec) => { match vec.first() { Some(ref v) if !v.same_type(&value) => { @@ -1333,4 +1333,17 @@ trimmed in raw strings. c = 2 ", "duplicate key `c` in table"); } + + #[test] + fn bad_table_redefine() { + let mut p = Parser::new(" + [a] + foo=\"bar\" + [a.b] + foo=\"bar\" + [a] + baz=\"bar\" + "); + assert!(p.parse().is_none()); + } } diff --git a/tests/valid.rs b/tests/valid.rs index 568518b..18c21d6 100644 --- a/tests/valid.rs +++ b/tests/valid.rs @@ -32,7 +32,7 @@ fn to_json(toml: Value) -> Json { let json = Json::Array(arr.into_iter().map(to_json).collect()); if is_table {json} else {doit("array", json)} } - Table(table) => Json::Object(table.into_iter().map(|(k, v)| { + Table(table) => Json::Object(table.0.into_iter().map(|(k, v)| { (k, to_json(v)) }).collect()), } @@ -58,7 +58,7 @@ fn run(toml: &str, json: &str) { let table2 = Parser::new(&toml_string).parse().unwrap(); // floats are a little lossy - if table2.values().any(|v| v.as_float().is_some()) { return } + if table2.0.values().any(|v| v.as_float().is_some()) { return } assert_eq!(toml, Table(table2)); } From 8487b63c97080296269242c31f36a557a90da0cf Mon Sep 17 00:00:00 2001 From: Andrzej Janik Date: Sat, 6 Jun 2015 18:11:48 +0200 Subject: [PATCH 05/17] Rework fix for table redefinition to avoid breaking AST-compatiblity --- src/decoder/rustc_serialize.rs | 14 ++-- src/display.rs | 7 +- src/encoder/mod.rs | 8 +-- src/encoder/rustc_serialize.rs | 11 ++- src/lib.rs | 20 +----- src/parser.rs | 127 ++++++++++++++++++++++++++------- tests/valid.rs | 4 +- 7 files changed, 125 insertions(+), 66 deletions(-) diff --git a/src/decoder/rustc_serialize.rs b/src/decoder/rustc_serialize.rs index af38f9b..6e8fe59 100644 --- a/src/decoder/rustc_serialize.rs +++ b/src/decoder/rustc_serialize.rs @@ -3,7 +3,7 @@ use std::mem; use super::{Decoder, DecodeError}; use super::DecodeErrorKind::*; -use {Value, Table}; +use Value; impl rustc_serialize::Decoder for Decoder { type Error = DecodeError; @@ -141,7 +141,7 @@ impl rustc_serialize::Decoder for Decoder { Some(Value::Table(..)) => { let ret = try!(f(self)); match self.toml { - Some(Value::Table(Table(ref t, _,))) if t.len() == 0 => {} + Some(Value::Table(ref t)) if t.len() == 0 => {} _ => return Ok(ret) } self.toml.take(); @@ -156,7 +156,7 @@ impl rustc_serialize::Decoder for Decoder { { let field = format!("{}", f_name); let toml = match self.toml { - Some(Value::Table(Table(ref mut table, _))) => { + Some(Value::Table(ref mut table)) => { table.remove(&field) .or_else(|| table.remove(&f_name.replace("_", "-"))) }, @@ -165,7 +165,7 @@ impl rustc_serialize::Decoder for Decoder { let mut d = self.sub_decoder(toml, f_name); let ret = try!(f(&mut d)); if let Some(value) = d.toml { - if let Some(Value::Table(Table(ref mut table, _))) = self.toml { + if let Some(Value::Table(ref mut table)) = self.toml { table.insert(field, value); } } @@ -260,7 +260,7 @@ impl rustc_serialize::Decoder for Decoder { where F: FnOnce(&mut Decoder, usize) -> Result { let len = match self.toml { - Some(Value::Table(Table(ref table, _))) => table.len(), + Some(Value::Table(ref table)) => table.len(), ref found => return Err(self.mismatch("table", found)), }; let ret = try!(f(self, len)); @@ -273,7 +273,7 @@ impl rustc_serialize::Decoder for Decoder { { match self.toml { Some(Value::Table(ref table)) => { - match table.0.iter().skip(idx).next() { + match table.iter().skip(idx).next() { Some((key, _)) => { let val = Value::String(format!("{}", key)); f(&mut self.sub_decoder(Some(val), &**key)) @@ -290,7 +290,7 @@ impl rustc_serialize::Decoder for Decoder { { match self.toml { Some(Value::Table(ref table)) => { - match table.0.iter().skip(idx).next() { + match table.iter().skip(idx).next() { Some((_, value)) => { // XXX: this shouldn't clone f(&mut self.sub_decoder(Some(value.clone()), "")) diff --git a/src/display.rs b/src/display.rs index 74ec424..0c561e8 100644 --- a/src/display.rs +++ b/src/display.rs @@ -57,7 +57,7 @@ fn write_str(f: &mut fmt::Formatter, s: &str) -> fmt::Result { impl<'a, 'b> Printer<'a, 'b> { fn print(&mut self, table: &'a TomlTable) -> fmt::Result { - for (k, v) in table.0.iter() { + for (k, v) in table.iter() { match *v { Table(..) => continue, Array(ref a) => { @@ -70,7 +70,7 @@ impl<'a, 'b> Printer<'a, 'b> { } try!(writeln!(self.output, "{} = {}", Key(&[k]), v)); } - for (k, v) in table.0.iter() { + for (k, v) in table.iter() { match *v { Table(ref inner) => { self.stack.push(k); @@ -127,14 +127,13 @@ impl<'a> fmt::Display for Key<'a> { #[allow(warnings)] mod tests { use Value; - use Table as TomlTable; use Value::{String, Integer, Float, Boolean, Datetime, Array, Table}; use std::collections::BTreeMap; macro_rules! map( ($($k:expr => $v:expr),*) => ({ let mut _m = BTreeMap::new(); $(_m.insert($k.to_string(), $v);)* - TomlTable::new(_m) + _m }) ); #[test] diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index ea8ef6a..21185f4 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -31,7 +31,7 @@ use {Value, Table}; /// let mut e = Encoder::new(); /// my_struct.encode(&mut e).unwrap(); /// -/// assert_eq!(e.toml.0.get(&"foo".to_string()), Some(&Value::Integer(4))) +/// assert_eq!(e.toml.get(&"foo".to_string()), Some(&Value::Integer(4))) /// # } /// ``` pub struct Encoder { @@ -73,12 +73,12 @@ enum State { impl Encoder { /// Constructs a new encoder which will emit to the given output stream. pub fn new() -> Encoder { - Encoder { state: State::Start, toml: Table(BTreeMap::new(), false) } + Encoder { state: State::Start, toml: BTreeMap::new() } } fn emit_value(&mut self, v: Value) -> Result<(), Error> { match mem::replace(&mut self.state, State::Start) { - State::NextKey(key) => { self.toml.0.insert(key, v); Ok(()) } + State::NextKey(key) => { self.toml.insert(key, v); Ok(()) } State::NextArray(mut vec) => { // TODO: validate types vec.push(v); @@ -122,7 +122,7 @@ impl Encoder { State::NextKey(key) => { let mut nested = Encoder::new(); try!(f(&mut nested)); - self.toml.0.insert(key, Value::Table(nested.toml)); + self.toml.insert(key, Value::Table(nested.toml)); Ok(()) } State::NextArray(mut arr) => { diff --git a/src/encoder/rustc_serialize.rs b/src/encoder/rustc_serialize.rs index 830eb5e..ab5e90f 100644 --- a/src/encoder/rustc_serialize.rs +++ b/src/encoder/rustc_serialize.rs @@ -193,8 +193,8 @@ impl rustc_serialize::Encodable for Value { }) } Value::Table(ref t) => { - e.emit_map(t.0.len(), |e| { - for (i, (key, value)) in t.0.iter().enumerate() { + e.emit_map(t.len(), |e| { + for (i, (key, value)) in t.iter().enumerate() { try!(e.emit_map_elt_key(i, |e| e.emit_str(key))); try!(e.emit_map_elt_val(i, |e| value.encode(e))); } @@ -212,7 +212,6 @@ mod tests { use {Encoder, Decoder, DecodeError}; use Value; - use Table as TomlTable; use Value::{Table, Integer, Array, Float}; macro_rules! encode( ($t:expr) => ({ @@ -229,7 +228,7 @@ mod tests { macro_rules! map( ($($k:ident, $v:expr),*) => ({ let mut _m = BTreeMap::new(); $(_m.insert(stringify!($k).to_string(), $v);)* - TomlTable::new(_m) + _m }) ); #[test] @@ -578,9 +577,7 @@ mod tests { #[derive(RustcEncodable, RustcDecodable, PartialEq, Debug)] struct Foo { a: BTreeMap } - let mut v = Foo { a: BTreeMap::new() }; - v.a.insert("a".to_string(), "foo".to_string()); - + let v = Foo { a: map! { a, "foo".to_string() } }; let mut d = Decoder::new(Table(map! { a, Table(map! { a, Value::String("foo".to_string()) diff --git a/src/lib.rs b/src/lib.rs index 547c407..0196fbc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,24 +75,8 @@ pub enum Value { /// Type representing a TOML array, payload of the Value::Array variant pub type Array = Vec; -// The bool field flag is used during parsing and construction. -// Is true if the given table was explicitly defined, false otherwise -// e.g. in a toml document: `[a.b] foo = "bar"`, Table `a` would be false, -// where table `b` (contained inside `a`) would be true. /// Type representing a TOML table, payload of the Value::Table variant -#[derive(Debug, Clone)] -pub struct Table (pub BTreeMap, bool); -impl Table { - /// Creates new TOML table - pub fn new(map: BTreeMap) -> Table { - Table(map, false) - } -} -impl PartialEq for Table { - fn eq(&self, other: &Table) -> bool { - self.0.eq(&other.0) - } -} +pub type Table = BTreeMap; impl Value { /// Tests whether this and another value have the same type. @@ -198,7 +182,7 @@ impl Value { let mut cur_value = self; for key in path.split('.') { match cur_value { - &Value::Table(Table(ref hm, _)) => { + &Value::Table(ref hm) => { match hm.get(key) { Some(v) => cur_value = v, None => return None diff --git a/src/parser.rs b/src/parser.rs index ccf0d3a..068bf1d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,13 +5,92 @@ use std::error::Error; use std::fmt; use std::str; -use Table as TomlTable; -use Value::{self, Array, Table, Float, Integer, Boolean, Datetime}; - macro_rules! try { ($e:expr) => (match $e { Some(s) => s, None => return None }) } +/* + * We redefine Array, Table and Value, because we need to keep track of + * encountered table definitions, eg when parsing: + * [a] + * [a.b] + * [a] + * we have to error out on redefinition of [a]. + * This bit of data is impossible to represent in the user-consumed table + * without breaking compatibility, so we use one AST structure during parsing + * and expose another (after running convert(...) on it) to the user. + */ +type Array = Vec; + +#[derive(PartialEq, Clone, Debug)] +// If the bool flag is true, the table was explicitly defined +// e.g. in a toml document: `[a.b] foo = "bar"`, Table `a` would be false, +// where table `b` (contained inside `a`) would be true. +struct TomlTable(BTreeMap, bool); +impl TomlTable { + fn convert(self) -> super::Table { + self.0.into_iter().map(|(k,v)| (k,v.convert())).collect() + } +} + +#[derive(PartialEq, Clone, Debug)] +enum Value { + String(String), + Integer(i64), + Float(f64), + Boolean(bool), + Datetime(String), + Array(Array), + Table(TomlTable), +} + +impl Value { + fn type_str(&self) -> &'static str { + match *self { + Value::String(..) => "string", + Value::Integer(..) => "integer", + Value::Float(..) => "float", + Value::Boolean(..) => "boolean", + Value::Datetime(..) => "datetime", + Value::Array(..) => "array", + Value::Table(..) => "table", + } + } + + fn as_table<'a>(&'a self) -> Option<&'a TomlTable> { + match *self { Value::Table(ref s) => Some(s), _ => None } + } + + fn same_type(&self, other: &Value) -> bool { + match (self, other) { + (&Value::String(..), &Value::String(..)) | + (&Value::Integer(..), &Value::Integer(..)) | + (&Value::Float(..), &Value::Float(..)) | + (&Value::Boolean(..), &Value::Boolean(..)) | + (&Value::Datetime(..), &Value::Datetime(..)) | + (&Value::Array(..), &Value::Array(..)) | + (&Value::Table(..), &Value::Table(..)) => true, + + _ => false, + } + } + + fn convert(self) -> super::Value { + match self { + Value::String(x) => super::Value::String(x), + Value::Integer(x) => super::Value::Integer(x), + Value::Float(x) => super::Value::Float(x), + Value::Boolean(x) => super::Value::Boolean(x), + Value::Datetime(x) => super::Value::Datetime(x), + Value::Array(v) => + super::Value::Array( + v.into_iter().map(|x| x.convert()).collect() + ), + Value::Table(t) => super::Value::Table(t.convert()) + } + } +} + /// Parser for converting a string to a TOML `Value` instance. /// /// This parser contains the string slice that is being parsed, and exports the @@ -161,7 +240,7 @@ impl<'a> Parser<'a> { /// /// If an error occurs, the `errors` field of this parser can be consulted /// to determine the cause of the parse failure. - pub fn parse(&mut self) -> Option { + pub fn parse(&mut self) -> Option { let mut ret = TomlTable(BTreeMap::new(), false); while self.peek(0).is_some() { self.ws(); @@ -192,7 +271,7 @@ impl<'a> Parser<'a> { let mut table = TomlTable(BTreeMap::new(), false); if !self.values(&mut table) { return None } if array { - self.insert_array(&mut ret, &*keys, Table(table), start) + self.insert_array(&mut ret, &*keys, Value::Table(table), start) } else { self.insert_table(&mut ret, &*keys, table, start) } @@ -203,7 +282,7 @@ impl<'a> Parser<'a> { if self.errors.len() > 0 { None } else { - Some(ret) + Some(ret.convert()) } } @@ -519,9 +598,9 @@ impl<'a> Parser<'a> { }; let input = input.trim_left_matches('+'); if is_float { - input.parse().ok().map(Float) + input.parse().ok().map(Value::Float) } else { - input.parse().ok().map(Integer) + input.parse().ok().map(Value::Integer) } }; if ret.is_none() { @@ -603,12 +682,12 @@ impl<'a> Parser<'a> { for _ in 0..4 { self.cur.next(); } - Some(Boolean(true)) + Some(Value::Boolean(true)) } else if rest.starts_with("false") { for _ in 0..5 { self.cur.next(); } - Some(Boolean(false)) + Some(Value::Boolean(false)) } else { let next = self.next_pos(); self.errors.push(ParserError { @@ -659,7 +738,7 @@ impl<'a> Parser<'a> { valid = valid && it.next().map(is_digit).unwrap_or(false); valid = valid && it.next().map(|c| c == 'Z').unwrap_or(false); if valid { - Some(Datetime(date.clone())) + Some(Value::Datetime(date.clone())) } else { self.errors.push(ParserError { lo: start, @@ -683,7 +762,7 @@ impl<'a> Parser<'a> { loop { // Break out early if we see the closing bracket consume(self); - if self.eat(']') { return Some(Array(ret)) } + if self.eat(']') { return Some(Value::Array(ret)) } // Attempt to parse a value, triggering an error if it's the wrong // type. @@ -709,14 +788,14 @@ impl<'a> Parser<'a> { } consume(self); if !self.expect(']') { return None } - return Some(Array(ret)) + return Some(Value::Array(ret)) } fn inline_table(&mut self, _start: usize) -> Option { if !self.expect('{') { return None } self.ws(); let mut ret = TomlTable(BTreeMap::new(), true); - if self.eat('}') { return Some(Table(ret)) } + if self.eat('}') { return Some(Value::Table(ret)) } loop { let lo = self.next_pos(); let key = try!(self.key_name()); @@ -729,7 +808,7 @@ impl<'a> Parser<'a> { if !self.expect(',') { return None } self.ws(); } - return Some(Table(ret)) + return Some(Value::Table(ret)) } fn insert(&mut self, into: &mut TomlTable, key: String, value: Value, @@ -753,13 +832,13 @@ impl<'a> Parser<'a> { if tmp.0.contains_key(part) { match *tmp.0.get_mut(part).unwrap() { - Table(ref mut table) => { + Value::Table(ref mut table) => { cur = table; continue } - Array(ref mut array) => { + Value::Array(ref mut array) => { match array.last_mut() { - Some(&mut Table(ref mut table)) => cur = table, + Some(&mut Value::Table(ref mut table)) => cur = table, _ => { self.errors.push(ParserError { lo: key_lo, @@ -785,9 +864,9 @@ impl<'a> Parser<'a> { } // Initialize an empty table as part of this sub-key - tmp.0.insert(part.clone(), Table(TomlTable(BTreeMap::new(), false))); + tmp.0.insert(part.clone(), Value::Table(TomlTable(BTreeMap::new(), false))); match *tmp.0.get_mut(part).unwrap() { - Table(ref mut inner) => cur = inner, + Value::Table(ref mut inner) => cur = inner, _ => unreachable!(), } } @@ -803,11 +882,11 @@ impl<'a> Parser<'a> { let key = format!("{}", key); let mut added = false; if !into.0.contains_key(&key) { - into.0.insert(key.clone(), Table(TomlTable(BTreeMap::new(), true))); + into.0.insert(key.clone(), Value::Table(TomlTable(BTreeMap::new(), true))); added = true; } match into.0.get_mut(&key) { - Some(&mut Table(ref mut table)) => { + Some(&mut Value::Table(ref mut table)) => { let any_tables = table.0.values().any(|v| v.as_table().is_some()); if !added && (!any_tables || table.1) { self.errors.push(ParserError { @@ -845,10 +924,10 @@ impl<'a> Parser<'a> { }; let key = format!("{}", key); if !into.0.contains_key(&key) { - into.0.insert(key.clone(), Array(Vec::new())); + into.0.insert(key.clone(), Value::Array(Vec::new())); } match *into.0.get_mut(&key).unwrap() { - Array(ref mut vec) => { + Value::Array(ref mut vec) => { match vec.first() { Some(ref v) if !v.same_type(&value) => { self.errors.push(ParserError { diff --git a/tests/valid.rs b/tests/valid.rs index 18c21d6..568518b 100644 --- a/tests/valid.rs +++ b/tests/valid.rs @@ -32,7 +32,7 @@ fn to_json(toml: Value) -> Json { let json = Json::Array(arr.into_iter().map(to_json).collect()); if is_table {json} else {doit("array", json)} } - Table(table) => Json::Object(table.0.into_iter().map(|(k, v)| { + Table(table) => Json::Object(table.into_iter().map(|(k, v)| { (k, to_json(v)) }).collect()), } @@ -58,7 +58,7 @@ fn run(toml: &str, json: &str) { let table2 = Parser::new(&toml_string).parse().unwrap(); // floats are a little lossy - if table2.0.values().any(|v| v.as_float().is_some()) { return } + if table2.values().any(|v| v.as_float().is_some()) { return } assert_eq!(toml, Table(table2)); } From 6580b77a203f360cf9f519e773b1d89de4d1336b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 7 Jun 2015 22:38:36 -0700 Subject: [PATCH 06/17] Re-structure control flow a bit + modernization --- src/parser.rs | 87 +++++++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 52 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 068bf1d..d9c04cb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -9,7 +9,7 @@ macro_rules! try { ($e:expr) => (match $e { Some(s) => s, None => return None }) } -/* +/* * We redefine Array, Table and Value, because we need to keep track of * encountered table definitions, eg when parsing: * [a] @@ -56,7 +56,7 @@ impl Value { Value::Table(..) => "table", } } - + fn as_table<'a>(&'a self) -> Option<&'a TomlTable> { match *self { Value::Table(ref s) => Some(s), _ => None } } @@ -254,9 +254,8 @@ impl<'a> Parser<'a> { let mut keys = Vec::new(); loop { self.ws(); - match self.key_name() { - Some(s) => keys.push(s), - None => {} + if let Some(s) = self.key_name() { + keys.push(s); } self.ws(); if self.eat(']') { @@ -293,18 +292,13 @@ impl<'a> Parser<'a> { self.finish_string(start, false) } else { let mut ret = String::new(); - loop { - match self.cur.clone().next() { - Some((_, ch)) => { - match ch { - 'a' ... 'z' | - 'A' ... 'Z' | - '0' ... '9' | - '_' | '-' => { self.cur.next(); ret.push(ch) } - _ => break, - } - } - None => break + while let Some((_, ch)) = self.cur.clone().next() { + match ch { + 'a' ... 'z' | + 'A' ... 'Z' | + '0' ... '9' | + '_' | '-' => { self.cur.next(); ret.push(ch) } + _ => break, } } Some(ret) @@ -423,9 +417,8 @@ impl<'a> Parser<'a> { return Some(ret) } Some((pos, '\\')) => { - match escape(self, pos, multiline) { - Some(c) => ret.push(c), - None => {} + if let Some(c) = escape(self, pos, multiline) { + ret.push(c); } } Some((pos, ch)) if ch < '\u{1f}' => { @@ -470,32 +463,26 @@ impl<'a> Parser<'a> { } else { "invalid" }; - match u32::from_str_radix(num, 16).ok() { - Some(n) => { - match char::from_u32(n) { - Some(c) => { - me.cur.by_ref().skip(len - 1).next(); - return Some(c) - } - None => { - me.errors.push(ParserError { - lo: pos + 1, - hi: pos + 5, - desc: format!("codepoint `{:x}` is \ - not a valid unicode \ - codepoint", n), - }) - } - } - } - None => { + if let Some(n) = u32::from_str_radix(num, 16).ok() { + if let Some(c) = char::from_u32(n) { + me.cur.by_ref().skip(len - 1).next(); + return Some(c) + } else { me.errors.push(ParserError { - lo: pos, - hi: pos + 1, - desc: format!("expected {} hex digits \ - after a `{}` escape", len, c), + lo: pos + 1, + hi: pos + 5, + desc: format!("codepoint `{:x}` is \ + not a valid unicode \ + codepoint", n), }) } + } else { + me.errors.push(ParserError { + lo: pos, + hi: pos + 1, + desc: format!("expected {} hex digits \ + after a `{}` escape", len, c), + }) } None } @@ -832,10 +819,7 @@ impl<'a> Parser<'a> { if tmp.0.contains_key(part) { match *tmp.0.get_mut(part).unwrap() { - Value::Table(ref mut table) => { - cur = table; - continue - } + Value::Table(ref mut table) => cur = table, Value::Array(ref mut array) => { match array.last_mut() { Some(&mut Value::Table(ref mut table)) => cur = table, @@ -849,7 +833,6 @@ impl<'a> Parser<'a> { return None } } - continue } _ => { self.errors.push(ParserError { @@ -861,6 +844,7 @@ impl<'a> Parser<'a> { return None } } + continue } // Initialize an empty table as part of this sub-key @@ -922,11 +906,10 @@ impl<'a> Parser<'a> { Some(pair) => pair, None => return, }; - let key = format!("{}", key); - if !into.0.contains_key(&key) { - into.0.insert(key.clone(), Value::Array(Vec::new())); + if !into.0.contains_key(key) { + into.0.insert(key.to_owned(), Value::Array(Vec::new())); } - match *into.0.get_mut(&key).unwrap() { + match *into.0.get_mut(key).unwrap() { Value::Array(ref mut vec) => { match vec.first() { Some(ref v) if !v.same_type(&value) => { From e96e25bec74e9dccbf56275913894bda561a3154 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 7 Jun 2015 23:12:57 -0700 Subject: [PATCH 07/17] Add a toml to json example --- examples/toml2json.rs | 57 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 examples/toml2json.rs diff --git a/examples/toml2json.rs b/examples/toml2json.rs new file mode 100644 index 0000000..0d40680 --- /dev/null +++ b/examples/toml2json.rs @@ -0,0 +1,57 @@ +#![deny(warnings)] + +extern crate toml; +extern crate rustc_serialize; + +use std::fs::File; +use std::env; +use std::io; +use std::io::prelude::*; + +use toml::Value; +use rustc_serialize::json::Json; + +fn main() { + let mut args = env::args(); + let mut input = String::new(); + let filename = if args.len() > 1 { + let name = args.nth(1).unwrap(); + File::open(&name).and_then(|mut f| { + f.read_to_string(&mut input) + }).unwrap(); + name + } else { + io::stdin().read_to_string(&mut input).unwrap(); + "".to_string() + }; + + let mut parser = toml::Parser::new(&input); + let toml = match parser.parse() { + Some(toml) => toml, + None => { + for err in &parser.errors { + let (loline, locol) = parser.to_linecol(err.lo); + let (hiline, hicol) = parser.to_linecol(err.hi); + println!("{}:{}:{}-{}:{} error: {}", + filename, loline, locol, hiline, hicol, err.desc); + } + return + } + }; + let json = convert(Value::Table(toml)); + println!("{}", json.pretty()); +} + +fn convert(toml: Value) -> Json { + match toml { + Value::String(s) => Json::String(s), + Value::Integer(i) => Json::I64(i), + Value::Float(f) => Json::F64(f), + Value::Boolean(b) => Json::Boolean(b), + Value::Array(arr) => Json::Array(arr.into_iter().map(convert).collect()), + Value::Table(table) => Json::Object(table.into_iter().map(|(k, v)| { + (k, convert(v)) + }).collect()), + Value::Datetime(dt) => Json::String(dt), + } +} From 68924534e271fe5ce534e9d274af081ce2579803 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 7 Jun 2015 23:13:14 -0700 Subject: [PATCH 08/17] Use deref coercions --- src/parser.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index d9c04cb..5224804 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -270,9 +270,10 @@ impl<'a> Parser<'a> { let mut table = TomlTable(BTreeMap::new(), false); if !self.values(&mut table) { return None } if array { - self.insert_array(&mut ret, &*keys, Value::Table(table), start) + self.insert_array(&mut ret, &keys, Value::Table(table), + start) } else { - self.insert_table(&mut ret, &*keys, table, start) + self.insert_table(&mut ret, &keys, table, start) } } else { if !self.values(&mut ret) { return None } From 88461157f267cbc4b4ee1352ed225667d4acc466 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 7 Jun 2015 23:16:56 -0700 Subject: [PATCH 09/17] Clean up more style --- src/parser.rs | 86 ++++++++++++++++++++------------------------------- 1 file changed, 34 insertions(+), 52 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 5224804..b37621a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -9,38 +9,31 @@ macro_rules! try { ($e:expr) => (match $e { Some(s) => s, None => return None }) } -/* - * We redefine Array, Table and Value, because we need to keep track of - * encountered table definitions, eg when parsing: - * [a] - * [a.b] - * [a] - * we have to error out on redefinition of [a]. - * This bit of data is impossible to represent in the user-consumed table - * without breaking compatibility, so we use one AST structure during parsing - * and expose another (after running convert(...) on it) to the user. - */ -type Array = Vec; - -#[derive(PartialEq, Clone, Debug)] -// If the bool flag is true, the table was explicitly defined -// e.g. in a toml document: `[a.b] foo = "bar"`, Table `a` would be false, -// where table `b` (contained inside `a`) would be true. +// We redefine Array, Table and Value, because we need to keep track of +// encountered table definitions, eg when parsing: +// +// [a] +// [a.b] +// [a] +// +// we have to error out on redefinition of [a]. This bit of data is difficult to +// track in a side table so we just have a "stripped down" AST to work with +// which has the relevant metadata fields in it. struct TomlTable(BTreeMap, bool); + impl TomlTable { fn convert(self) -> super::Table { self.0.into_iter().map(|(k,v)| (k,v.convert())).collect() } } -#[derive(PartialEq, Clone, Debug)] enum Value { String(String), Integer(i64), Float(f64), Boolean(bool), Datetime(String), - Array(Array), + Array(Vec), Table(TomlTable), } @@ -57,10 +50,6 @@ impl Value { } } - fn as_table<'a>(&'a self) -> Option<&'a TomlTable> { - match *self { Value::Table(ref s) => Some(s), _ => None } - } - fn same_type(&self, other: &Value) -> bool { match (self, other) { (&Value::String(..), &Value::String(..)) | @@ -267,7 +256,7 @@ impl<'a> Parser<'a> { if keys.len() == 0 { return None } // Build the section table - let mut table = TomlTable(BTreeMap::new(), false); + let mut table = TomlTable(BTreeMap::new(), true); if !self.values(&mut table) { return None } if array { self.insert_array(&mut ret, &keys, Value::Table(table), @@ -864,40 +853,33 @@ impl<'a> Parser<'a> { Some(pair) => pair, None => return, }; - let key = format!("{}", key); - let mut added = false; - if !into.0.contains_key(&key) { - into.0.insert(key.clone(), Value::Table(TomlTable(BTreeMap::new(), true))); - added = true; + if !into.0.contains_key(key) { + into.0.insert(key.to_owned(), Value::Table(value)); + return } - match into.0.get_mut(&key) { - Some(&mut Value::Table(ref mut table)) => { - let any_tables = table.0.values().any(|v| v.as_table().is_some()); - if !added && (!any_tables || table.1) { - self.errors.push(ParserError { - lo: key_lo, - hi: key_lo + key.len(), - desc: format!("redefinition of table `{}`", key), - }); - } - for (k, v) in value.0.into_iter() { - if table.0.insert(k.clone(), v).is_some() { - self.errors.push(ParserError { - lo: key_lo, - hi: key_lo + key.len(), - desc: format!("duplicate key `{}` in table", k), - }); - } - } - } - Some(_) => { + if let Value::Table(ref mut table) = *into.0.get_mut(key).unwrap() { + if table.1 { self.errors.push(ParserError { lo: key_lo, hi: key_lo + key.len(), - desc: format!("duplicate key `{}` in table", key), + desc: format!("redefinition of table `{}`", key), }); } - None => {} + for (k, v) in value.0 { + if table.0.insert(k.clone(), v).is_some() { + self.errors.push(ParserError { + lo: key_lo, + hi: key_lo + key.len(), + desc: format!("duplicate key `{}` in table", k), + }); + } + } + } else { + self.errors.push(ParserError { + lo: key_lo, + hi: key_lo + key.len(), + desc: format!("duplicate key `{}` in table", key), + }); } } From 00baf76107ed8c40c3b96dcc191277dfaab82ca1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 7 Jun 2015 23:46:02 -0700 Subject: [PATCH 10/17] Add a few more tests for redefining tables --- src/parser.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index b37621a..68fa546 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1381,14 +1381,29 @@ trimmed in raw strings. #[test] fn bad_table_redefine() { - let mut p = Parser::new(" + bad!(" [a] foo=\"bar\" [a.b] foo=\"bar\" [a] - baz=\"bar\" - "); - assert!(p.parse().is_none()); + ", "redefinition of table `a`"); + bad!(" + [a] + foo=\"bar\" + b = { foo = \"bar\" } + [a] + ", "redefinition of table `a`"); + bad!(" + [a] + b = {} + [a.b] + ", "redefinition of table `b`"); + + bad!(" + [a] + b = {} + [a] + ", "redefinition of table `a`"); } } From 27a70d4024bcc769e6ff031a302fff9c7a2a2c70 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 8 Jun 2015 00:03:36 -0700 Subject: [PATCH 11/17] Name the fields of the custom table AST --- src/parser.rs | 55 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 68fa546..b7a810f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -9,8 +9,8 @@ macro_rules! try { ($e:expr) => (match $e { Some(s) => s, None => return None }) } -// We redefine Array, Table and Value, because we need to keep track of -// encountered table definitions, eg when parsing: +// We redefine Value because we need to keep track of encountered table +// definitions, eg when parsing: // // [a] // [a.b] @@ -19,11 +19,14 @@ macro_rules! try { // we have to error out on redefinition of [a]. This bit of data is difficult to // track in a side table so we just have a "stripped down" AST to work with // which has the relevant metadata fields in it. -struct TomlTable(BTreeMap, bool); +struct TomlTable { + values: BTreeMap, + defined: bool, +} impl TomlTable { fn convert(self) -> super::Table { - self.0.into_iter().map(|(k,v)| (k,v.convert())).collect() + self.values.into_iter().map(|(k,v)| (k, v.convert())).collect() } } @@ -230,7 +233,7 @@ impl<'a> Parser<'a> { /// If an error occurs, the `errors` field of this parser can be consulted /// to determine the cause of the parse failure. pub fn parse(&mut self) -> Option { - let mut ret = TomlTable(BTreeMap::new(), false); + let mut ret = TomlTable { values: BTreeMap::new(), defined: false }; while self.peek(0).is_some() { self.ws(); if self.newline() { continue } @@ -256,7 +259,10 @@ impl<'a> Parser<'a> { if keys.len() == 0 { return None } // Build the section table - let mut table = TomlTable(BTreeMap::new(), true); + let mut table = TomlTable { + values: BTreeMap::new(), + defined: true, + }; if !self.values(&mut table) { return None } if array { self.insert_array(&mut ret, &keys, Value::Table(table), @@ -771,7 +777,7 @@ impl<'a> Parser<'a> { fn inline_table(&mut self, _start: usize) -> Option { if !self.expect('{') { return None } self.ws(); - let mut ret = TomlTable(BTreeMap::new(), true); + let mut ret = TomlTable { values: BTreeMap::new(), defined: true }; if self.eat('}') { return Some(Value::Table(ret)) } loop { let lo = self.next_pos(); @@ -790,14 +796,14 @@ impl<'a> Parser<'a> { fn insert(&mut self, into: &mut TomlTable, key: String, value: Value, key_lo: usize) { - if into.0.contains_key(&key) { + if into.values.contains_key(&key) { self.errors.push(ParserError { lo: key_lo, hi: key_lo + key.len(), desc: format!("duplicate key: `{}`", key), }) } else { - into.0.insert(key, value); + into.values.insert(key, value); } } @@ -807,8 +813,8 @@ impl<'a> Parser<'a> { for part in keys[..keys.len() - 1].iter() { let tmp = cur; - if tmp.0.contains_key(part) { - match *tmp.0.get_mut(part).unwrap() { + if tmp.values.contains_key(part) { + match *tmp.values.get_mut(part).unwrap() { Value::Table(ref mut table) => cur = table, Value::Array(ref mut array) => { match array.last_mut() { @@ -838,8 +844,11 @@ impl<'a> Parser<'a> { } // Initialize an empty table as part of this sub-key - tmp.0.insert(part.clone(), Value::Table(TomlTable(BTreeMap::new(), false))); - match *tmp.0.get_mut(part).unwrap() { + tmp.values.insert(part.clone(), Value::Table(TomlTable { + values: BTreeMap::new(), + defined: false, + })); + match *tmp.values.get_mut(part).unwrap() { Value::Table(ref mut inner) => cur = inner, _ => unreachable!(), } @@ -848,25 +857,25 @@ impl<'a> Parser<'a> { } fn insert_table(&mut self, into: &mut TomlTable, keys: &[String], - value: TomlTable, key_lo: usize) { + table: TomlTable, key_lo: usize) { let (into, key) = match self.recurse(into, keys, key_lo) { Some(pair) => pair, None => return, }; - if !into.0.contains_key(key) { - into.0.insert(key.to_owned(), Value::Table(value)); + if !into.values.contains_key(key) { + into.values.insert(key.to_owned(), Value::Table(table)); return } - if let Value::Table(ref mut table) = *into.0.get_mut(key).unwrap() { - if table.1 { + if let Value::Table(ref mut into) = *into.values.get_mut(key).unwrap() { + if into.defined { self.errors.push(ParserError { lo: key_lo, hi: key_lo + key.len(), desc: format!("redefinition of table `{}`", key), }); } - for (k, v) in value.0 { - if table.0.insert(k.clone(), v).is_some() { + for (k, v) in table.values { + if into.values.insert(k.clone(), v).is_some() { self.errors.push(ParserError { lo: key_lo, hi: key_lo + key.len(), @@ -889,10 +898,10 @@ impl<'a> Parser<'a> { Some(pair) => pair, None => return, }; - if !into.0.contains_key(key) { - into.0.insert(key.to_owned(), Value::Array(Vec::new())); + if !into.values.contains_key(key) { + into.values.insert(key.to_owned(), Value::Array(Vec::new())); } - match *into.0.get_mut(key).unwrap() { + match *into.values.get_mut(key).unwrap() { Value::Array(ref mut vec) => { match vec.first() { Some(ref v) if !v.same_type(&value) => { From 89332806c5cab1cca39b0a676aa69aad599b3de7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 23 Jun 2015 17:43:52 -0700 Subject: [PATCH 12/17] Improve the error message in failing enums --- src/decoder/rustc_serialize.rs | 75 ++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/src/decoder/rustc_serialize.rs b/src/decoder/rustc_serialize.rs index 6e8fe59..534154d 100644 --- a/src/decoder/rustc_serialize.rs +++ b/src/decoder/rustc_serialize.rs @@ -96,15 +96,31 @@ impl rustc_serialize::Decoder for Decoder { -> Result where F: FnMut(&mut Decoder, usize) -> Result { - let mut first_error = None; + // When decoding enums, this crate takes the strategy of trying to + // decode the current TOML as all of the possible variants, returning + // success on the first one that succeeds. + // + // Note that fidelity of the errors returned here is a little nebulous, + // but we try to return the error that had the relevant field as the + // longest field. This way we hopefully match an error against what was + // most likely being written down without losing too much info. + let mut first_error = None::; for i in 0..names.len() { let mut d = self.sub_decoder(self.toml.clone(), ""); match f(&mut d, i) { - Ok(t) => { self.toml = d.toml; return Ok(t) } + Ok(t) => { + self.toml = d.toml; + return Ok(t) + } Err(e) => { - if first_error.is_none() { - first_error = Some(e); + if let Some(ref first) = first_error { + let my_len = e.field.as_ref().map(|s| s.len()); + let first_len = first.field.as_ref().map(|s| s.len()); + if my_len <= first_len { + continue + } } + first_error = Some(e); } } } @@ -158,7 +174,7 @@ impl rustc_serialize::Decoder for Decoder { let toml = match self.toml { Some(Value::Table(ref mut table)) => { table.remove(&field) - .or_else(|| table.remove(&f_name.replace("_", "-"))) + .or_else(|| table.remove(&f_name.replace("_", "-"))) }, ref found => return Err(self.mismatch("table", found)), }; @@ -275,8 +291,8 @@ impl rustc_serialize::Decoder for Decoder { Some(Value::Table(ref table)) => { match table.iter().skip(idx).next() { Some((key, _)) => { - let val = Value::String(format!("{}", key)); - f(&mut self.sub_decoder(Some(val), &**key)) + let val = Value::String(key.to_string()); + f(&mut self.sub_decoder(Some(val), key)) } None => Err(self.err(ExpectedMapKey(idx))), } @@ -291,9 +307,9 @@ impl rustc_serialize::Decoder for Decoder { match self.toml { Some(Value::Table(ref table)) => { match table.iter().skip(idx).next() { - Some((_, value)) => { + Some((key, value)) => { // XXX: this shouldn't clone - f(&mut self.sub_decoder(Some(value.clone()), "")) + f(&mut self.sub_decoder(Some(value.clone()), key)) } None => Err(self.err(ExpectedMapElement(idx))), } @@ -309,3 +325,44 @@ impl rustc_serialize::Decoder for Decoder { } } } + +#[cfg(test)] +mod tests { + use rustc_serialize::Decodable; + use std::collections::HashMap; + + use {Parser, Decoder, Value}; + + #[test] + fn bad_enum_chooses_longest_error() { + #[derive(RustcDecodable)] + #[allow(dead_code)] + struct Foo { + wut: HashMap, + } + + #[derive(RustcDecodable)] + enum Bar { + Simple(String), + Detailed(Baz), + } + + #[derive(RustcDecodable, Debug)] + struct Baz { + features: Vec, + } + + let s = r#" + [wut] + a = { features = "" } + "#; + let v = Parser::new(s).parse().unwrap(); + let mut d = Decoder::new(Value::Table(v)); + let err = match Foo::decode(&mut d) { + Ok(_) => panic!("expected error"), + Err(e) => e, + }; + assert_eq!(err.field.as_ref().unwrap(), "wut.a.features"); + + } +} From c5119e743ca2a4d83b9f9113d785565ebbbb229e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 23 Jun 2015 17:45:55 -0700 Subject: [PATCH 13/17] Bump to 0.1.21 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 102f63d..c3ce3b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "toml" -version = "0.1.20" +version = "0.1.21" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" From 5cf475fc4070276b46f5620366fe93275d955b91 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 10 Jul 2015 15:03:01 -0700 Subject: [PATCH 14/17] Use travis-cargo for coverage + doc upload --- .travis.yml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 76e3afe..30aebbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ rust: - beta - nightly sudo: false +before_script: + - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH script: - cargo build --verbose - cargo build --verbose --no-default-features @@ -11,18 +13,18 @@ script: - cargo test --verbose --no-default-features - rustdoc --test README.md -L target - cargo doc --no-deps -after_success: | - [ $TRAVIS_BRANCH = master ] && - [ $TRAVIS_PULL_REQUEST = false ] && - [ $TRAVIS_RUST_VERSION = nightly ] && - echo '' > target/doc/index.html && - pip install ghp-import --user $USER && - $HOME/.local/bin/ghp-import -n target/doc && - git push -qf https://${TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages +after_success: + - travis-cargo --only stable doc-upload + - travis-cargo coveralls --no-sudo env: global: - - secure: FO8GVrtHAn5GTj4LOz2BApC3tAEsMbNzvH5UVmCIeNKPuVcKcI3oWNJC/KMCvuJZhu96J3okfRLBxBJrhxsp/YT4fS4kibhZDm6AzbCqxz6AmvHJo2d0jztoRyuLwLSkhwW8vM4VeHH+Tf4PeC56YmnpUGkccHMMidxytJzx8qI= - - secure: WVCzGVsthRub6ezJU15xzo+ahlUoZEwvZDeMPmjIMf1G28ObE9Y4BeUNW0j9CxCjyQ+5S0mrp1l0TESN326XTDosigabDiGnKyr5wfncnreN3PCUi3gx7NI+bRTy9B3eV318BhuCDgLgRWLWufCyPtkgAdT6cl+u6p+bEh+vyxo= + secure: LZMkQQJT5LqLQQ8JyakjvHNqqMPy8lm/SyC+H5cKUVI/xk7xRuti4eKY937N8uSmbff2m9ZYlG6cNwIOfk/nWn8YsqxA8Wg/xugubWzqGuqu+NQ4IZVa7INT2Fiqyk5SPCh8B5fo2x7OBJ24SCkWb2p8bEWAuW8XdZZOdmi3H2I= notifications: email: on_success: never +addons: + apt: + packages: + - libcurl4-openssl-dev + - libelf-dev + - libdw-dev From 2683e1968267648b8798ebd6d4c444d46ca74f1e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 10 Jul 2015 15:09:04 -0700 Subject: [PATCH 15/17] Upload docs only on nightly --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 30aebbd..94e4a6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ script: - rustdoc --test README.md -L target - cargo doc --no-deps after_success: - - travis-cargo --only stable doc-upload + - travis-cargo --only nightly doc-upload - travis-cargo coveralls --no-sudo env: global: From baf77e44c3f645421ded4c583118821a474cce5d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 10 Jul 2015 15:10:57 -0700 Subject: [PATCH 16/17] Add coverage badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 09b7152..6933498 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # toml-rs [![Build Status](https://travis-ci.org/alexcrichton/toml-rs.svg?branch=master)](https://travis-ci.org/alexcrichton/toml-rs) +[![Coverage Status](https://coveralls.io/repos/alexcrichton/toml-rs/badge.svg?branch=master&service=github)](https://coveralls.io/github/alexcrichton/toml-rs?branch=master) [Documentation](http://alexcrichton.com/toml-rs) From b70f6e53b20c8f90f525219b1db7e99f6ef417dc Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Aug 2015 09:18:52 -0700 Subject: [PATCH 17/17] Don't need to qualify String --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0196fbc..c974cd0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,6 @@ use std::collections::BTreeMap; use std::str::FromStr; -use std::string; pub use parser::{Parser, ParserError}; @@ -76,7 +75,7 @@ pub enum Value { pub type Array = Vec; /// Type representing a TOML table, payload of the Value::Table variant -pub type Table = BTreeMap; +pub type Table = BTreeMap; impl Value { /// Tests whether this and another value have the same type.