From 67132245640a0d2e95b696163b504553eb55efa5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 8 Apr 2015 08:01:49 -0700 Subject: 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 -- cgit v1.2.3 From 321afe620530bde9875c42489211f891c2ac0646 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 15 May 2015 11:50:24 -0700 Subject: 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 && -- cgit v1.2.3 From 971700148b58df921f070015febd8706c66edded Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 15 May 2015 11:52:49 -0700 Subject: 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: -- cgit v1.2.3 From 2fcd829b1d9c70d0981411b4f4adca9124985b54 Mon Sep 17 00:00:00 2001 From: Andrzej Janik Date: Thu, 4 Jun 2015 20:23:46 +0200 Subject: 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)); } -- cgit v1.2.3 From 8487b63c97080296269242c31f36a557a90da0cf Mon Sep 17 00:00:00 2001 From: Andrzej Janik Date: Sat, 6 Jun 2015 18:11:48 +0200 Subject: 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)); } -- cgit v1.2.3 From 6580b77a203f360cf9f519e773b1d89de4d1336b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 7 Jun 2015 22:38:36 -0700 Subject: 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) => { -- cgit v1.2.3 From e96e25bec74e9dccbf56275913894bda561a3154 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 7 Jun 2015 23:12:57 -0700 Subject: 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), + } +} -- cgit v1.2.3 From 68924534e271fe5ce534e9d274af081ce2579803 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 7 Jun 2015 23:13:14 -0700 Subject: 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 } -- cgit v1.2.3 From 88461157f267cbc4b4ee1352ed225667d4acc466 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 7 Jun 2015 23:16:56 -0700 Subject: Clean up more style --- src/parser.rs | 84 +++++++++++++++++++++++------------------------------------ 1 file changed, 33 insertions(+), 51 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) { + 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!("redefinition of table `{}`", key), + }); + } + 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!("redefinition of table `{}`", key), + desc: format!("duplicate key `{}` in table", k), }); } - 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(_) => { - self.errors.push(ParserError { - lo: key_lo, - hi: key_lo + key.len(), - desc: format!("duplicate key `{}` in table", key), - }); } - None => {} + } else { + self.errors.push(ParserError { + lo: key_lo, + hi: key_lo + key.len(), + desc: format!("duplicate key `{}` in table", key), + }); } } -- cgit v1.2.3 From 00baf76107ed8c40c3b96dcc191277dfaab82ca1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 7 Jun 2015 23:46:02 -0700 Subject: 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`"); } } -- cgit v1.2.3 From 27a70d4024bcc769e6ff031a302fff9c7a2a2c70 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 8 Jun 2015 00:03:36 -0700 Subject: 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) => { -- cgit v1.2.3 From 89332806c5cab1cca39b0a676aa69aad599b3de7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 23 Jun 2015 17:43:52 -0700 Subject: 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"); + + } +} -- cgit v1.2.3 From c5119e743ca2a4d83b9f9113d785565ebbbb229e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 23 Jun 2015 17:45:55 -0700 Subject: 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" -- cgit v1.2.3 From 5cf475fc4070276b46f5620366fe93275d955b91 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 10 Jul 2015 15:03:01 -0700 Subject: 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 -- cgit v1.2.3 From 2683e1968267648b8798ebd6d4c444d46ca74f1e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 10 Jul 2015 15:09:04 -0700 Subject: 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: -- cgit v1.2.3 From baf77e44c3f645421ded4c583118821a474cce5d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 10 Jul 2015 15:10:57 -0700 Subject: 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) -- cgit v1.2.3 From b70f6e53b20c8f90f525219b1db7e99f6ef417dc Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Aug 2015 09:18:52 -0700 Subject: 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. -- cgit v1.2.3