From f66d8bcf33530c858a502bfa170f2383a8cbc204 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sun, 29 Jan 2017 16:53:20 -0800 Subject: Rewrite crate with serde support from ground up This commit completely rewrites this crate from the ground up, supporting serde at the lowest levels as I believe serde support was intended to do. This is a major change from the previous versions of this crate, with a summary of changes being: * Serialization directly to TOML is now supported without going through a `Value` first. * Deserialization directly from TOML is now supported without going through a `Value`. Note that due to the TOML format some values still are buffered in intermediate memory, but overall this should be at a minimum now. * The API of `Value` was overhauled to match the API of `serde_json::Value`. The changes here were to: * Add `is_*` accessors * Add `get` and `get_mut` for one-field lookups. * Implement panicking lookups through `Index` The old `index` methods are now gone in favor of `get` and `Index` implementations. * A `Datetime` type has been added to represent a TOML datetime in a first-class fashion. Currently this type provides no accessors other than a `Display` implementation, but the idea is that this will grow support over time for decomposing the date. * Support for the `rustc-serialize` crate has been dropped, that'll stay on the 0.2 and 0.1 release trains. * This crate no longer supports the detection of unused fields, for that though you can use the `serde_ignored` crate on crates.io --- src/lib.rs | 640 +++++++++++++++---------------------------------------------- 1 file changed, 151 insertions(+), 489 deletions(-) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index 51d85b4..d2034e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,507 +1,169 @@ -//! A TOML-parsing library +//! A [TOML]-parsing library //! -//! This library is an implementation in Rust of a parser for TOML configuration -//! files [1]. It is focused around high quality errors including specific spans -//! and detailed error messages when things go wrong. +//! [TOML]: https://github.com/toml-lang/toml //! -//! This implementation currently passes the language agnostic [test suite][2]. +//! This library implements a [TOML] v0.4.0 compatible parser. This crate also +//! primarily supports the [`serde`] library for encoding/decoding support to +//! various types in Rust. //! -//! # Example +//! TOML itself is a simple, ergonomic, and readable configuration format: //! +//! ```toml +//! [package] +//! name = "toml" +//! version = "0.2.1" +//! authors = ["Alex Crichton "] +//! +//! [dependencies] +//! serde = "0.9" +//! ``` +//! +//! The TOML format tends to be relatively common throughout the Rust community +//! for configuration, notably being used by [Cargo], Rust's package manager. +//! +//! ## TOML values +//! +//! A value in TOML is represented with the `Value` enum in this crate: +//! +//! ```rust,ignore +//! pub enum Value { +//! String(String), +//! Integer(i64), +//! Float(f64), +//! Boolean(bool), +//! Datetime(Datetime), +//! Array(Array), +//! Table(Table), +//! } //! ``` -//! let toml = r#" -//! [test] -//! foo = "bar" -//! "#; //! -//! let value = toml::Parser::new(toml).parse().unwrap(); -//! println!("{:?}", value); +//! You'll note that TOML is very similar to JSON with the notable addition of a +//! `Datetime` type. In general TOML and JSON are interchangeable in terms of +//! formats. +//! +//! ## Parsing TOML +//! +//! The easiest way to parse a TOML document is via the `Value` type: +//! +//! ```rust +//! use toml::Value; +//! +//! let value = "foo = 'bar'".parse::().unwrap(); +//! +//! assert_eq!(value["foo"].as_str(), Some("bar")); //! ``` //! -//! # Conversions +//! The `Value` type implements a number of convenience methods and traits, +//! where the example above is using `FromStr` to parse a `str` into a `Value`. +//! +//! ## Deserialization and Serialization +//! +//! This crate currently supports [`serde`] 0.9 with a number of +//! implementations of the `Deserialize`, `Serialize`, `Deserializer`, and +//! `Serializer` traits. Namely, you'll find in this crate: +//! +//! * `Deserialize for Value` +//! * `Serialize for Value` +//! * `Deserialize for Datetime` +//! * `Serialize for Datetime` +//! +//! * `Deserializer for de::Deserializer` +//! * `Serializer for ser::Serializer` +//! * `Deserializer for Value` +//! +//! This notably means that you can use Serde to deserialize/serialize the +//! `Value` type as well as the `Datetime` type in this crate. Similarly you can +//! use the `Deserializer`, `Serializer`, or `Value` type itself to act as +//! a deserializer/serializer for arbitrary types. +//! +//! An example of deserializing with TOML is: +//! +//! ```rust +//! #[macro_use] +//! extern crate serde_derive; +//! extern crate toml; +//! +//! #[derive(Deserialize)] +//! struct Config { +//! ip: String, +//! port: Option, +//! keys: Keys, +//! } +//! +//! #[derive(Deserialize)] +//! struct Keys { +//! github: String, +//! travis: Option, +//! } +//! +//! fn main() { +//! let config: Config = toml::from_str(r#" +//! ip = '127.0.0.1' +//! +//! [keys] +//! github = 'xxxxxxxxxxxxxxxxx' +//! travis = 'yyyyyyyyyyyyyyyyy' +//! "#).unwrap(); +//! +//! assert_eq!(config.ip, "127.0.0.1"); +//! assert_eq!(config.port, None); +//! assert_eq!(config.keys.github, "xxxxxxxxxxxxxxxxx"); +//! assert_eq!(config.keys.travis.as_ref().unwrap(), "yyyyyyyyyyyyyyyyy"); +//! } +//! ``` //! -//! This library also supports using the standard `Encodable` and `Decodable` -//! traits with TOML values. This library provides the following conversion -//! capabilities: +//! Similarly you can serialize types in a similar fashion: //! -//! * `String` => `toml::Value` - via `Parser` -//! * `toml::Value` => `String` - via `Display` -//! * `toml::Value` => rust object - via `Decoder` -//! * rust object => `toml::Value` - via `Encoder` +//! ```rust +//! #[macro_use] +//! extern crate serde_derive; +//! extern crate toml; //! -//! Convenience functions for performing multiple conversions at a time are also -//! provided. +//! #[derive(Serialize)] +//! struct Config { +//! ip: String, +//! port: Option, +//! keys: Keys, +//! } //! -//! [1]: https://github.com/mojombo/toml -//! [2]: https://github.com/BurntSushi/toml-test +//! #[derive(Serialize)] +//! struct Keys { +//! github: String, +//! travis: Option, +//! } //! -//! # Encoding support +//! fn main() { +//! let config = Config { +//! ip: "127.0.0.1".to_string(), +//! port: None, +//! keys: Keys { +//! github: "xxxxxxxxxxxxxxxxx".to_string(), +//! travis: Some("yyyyyyyyyyyyyyyyy".to_string()), +//! }, +//! }; //! -//! This crate optionally supports the [`rustc-serialize`] crate and also the -//! [`serde`] crate through respective feature names. The `rustc-serialize` -//! feature is enabled by default. +//! let toml = toml::to_string(&config).unwrap(); +//! } +//! ``` //! -//! [`rustc-serialize`]: http://github.com/rust-lang/rustc-serialize -//! [`serde`]: http://github.com/serde-rs/serde +//! [Cargo]: https://crates.io/ +//! [`serde`]: https://serde.rs/ -#![doc(html_root_url = "http://alexcrichton.com/toml-rs")] +#![doc(html_root_url = "https://docs.rs/toml/0.3")] #![deny(missing_docs)] -#![cfg_attr(test, deny(warnings))] - -#[cfg(feature = "rustc-serialize")] extern crate rustc_serialize; -#[cfg(feature = "serde")] extern crate serde; - -use std::collections::BTreeMap; -use std::str::FromStr; - -pub use parser::{Parser, ParserError}; - -#[cfg(any(feature = "rustc-serialize", feature = "serde"))] -pub use self::encoder::{Encoder, Error, EncoderState, encode, encode_str}; -#[cfg(any(feature = "rustc-serialize", feature = "serde"))] -pub use self::decoder::{Decoder, DecodeError, DecodeErrorKind, decode, decode_str}; - -mod parser; -mod display; -#[cfg(any(feature = "rustc-serialize", feature = "serde"))] -mod encoder; -#[cfg(any(feature = "rustc-serialize", feature = "serde"))] -mod decoder; - -/// Representation of a TOML value. -#[derive(PartialEq, Clone, Debug)] -#[allow(missing_docs)] -pub enum Value { - String(String), - Integer(i64), - Float(f64), - Boolean(bool), - Datetime(String), - Array(Array), - Table(Table), -} - -/// Type representing a TOML array, payload of the `Value::Array` variant -pub type Array = Vec; - -/// Type representing a TOML table, payload of the `Value::Table` variant -pub type Table = BTreeMap; - -impl Value { - /// Tests whether this and another value have the same type. - pub 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, - } - } - - /// Returns a human-readable representation of the type of this value. - pub 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", - } - } - - /// Extracts the string of this value if it is a string. - pub fn as_str(&self) -> Option<&str> { - match *self { Value::String(ref s) => Some(&**s), _ => None } - } - - /// Extracts the integer value if it is an integer. - pub fn as_integer(&self) -> Option { - match *self { Value::Integer(i) => Some(i), _ => None } - } - - /// Extracts the float value if it is a float. - pub fn as_float(&self) -> Option { - match *self { Value::Float(f) => Some(f), _ => None } - } - - /// Extracts the boolean value if it is a boolean. - pub fn as_bool(&self) -> Option { - match *self { Value::Boolean(b) => Some(b), _ => None } - } - - /// Extracts the datetime value if it is a datetime. - /// - /// Note that a parsed TOML value will only contain ISO 8601 dates. An - /// example date is: - /// - /// ```notrust - /// 1979-05-27T07:32:00Z - /// ``` - pub fn as_datetime(&self) -> Option<&str> { - match *self { Value::Datetime(ref s) => Some(&**s), _ => None } - } - - /// Extracts the array value if it is an array. - pub fn as_slice(&self) -> Option<&[Value]> { - match *self { Value::Array(ref s) => Some(&**s), _ => None } - } - - /// Extracts the table value if it is a table. - pub fn as_table(&self) -> Option<&Table> { - match *self { Value::Table(ref s) => Some(s), _ => None } - } - - /// Lookups for value at specified path. - /// - /// Uses '.' as a path separator. - /// - /// Note: arrays have zero-based indexes. - /// - /// Note: empty path returns self. - /// - /// ``` - /// # #![allow(unstable)] - /// let toml = r#" - /// [test] - /// foo = "bar" - /// - /// [[values]] - /// foo = "baz" - /// - /// [[values]] - /// foo = "qux" - /// "#; - /// let value: toml::Value = toml.parse().unwrap(); - /// - /// let foo = value.lookup("test.foo").unwrap(); - /// assert_eq!(foo.as_str().unwrap(), "bar"); - /// - /// let foo = value.lookup("values.1.foo").unwrap(); - /// assert_eq!(foo.as_str().unwrap(), "qux"); - /// - /// let no_bar = value.lookup("test.bar"); - /// assert_eq!(no_bar.is_none(), true); - /// ``` - pub fn lookup(&self, path: &str) -> Option<&Value> { - let ref path = match Parser::new(path).lookup() { - Some(path) => path, - None => return None, - }; - let mut cur_value = self; - if path.is_empty() { - return Some(cur_value) - } - - for key in path { - match *cur_value { - Value::Table(ref hm) => { - match hm.get(key) { - Some(v) => cur_value = v, - None => return None - } - }, - Value::Array(ref v) => { - match key.parse::().ok() { - Some(idx) if idx < v.len() => cur_value = &v[idx], - _ => return None - } - }, - _ => return None - } - }; - - Some(cur_value) - - } - /// Lookups for mutable value at specified path. - /// - /// Uses '.' as a path separator. - /// - /// Note: arrays have zero-based indexes. - /// - /// Note: empty path returns self. - /// - /// ``` - /// # #![allow(unstable)] - /// let toml = r#" - /// [test] - /// foo = "bar" - /// - /// [[values]] - /// foo = "baz" - /// - /// [[values]] - /// foo = "qux" - /// "#; - /// let mut value: toml::Value = toml.parse().unwrap(); - /// { - /// let string = value.lookup_mut("test.foo").unwrap(); - /// assert_eq!(string, &mut toml::Value::String(String::from("bar"))); - /// *string = toml::Value::String(String::from("foo")); - /// } - /// let result = value.lookup_mut("test.foo").unwrap(); - /// assert_eq!(result.as_str().unwrap(), "foo"); - /// ``` - pub fn lookup_mut(&mut self, path: &str) -> Option<&mut Value> { - let ref path = match Parser::new(path).lookup() { - Some(path) => path, - None => return None, - }; - - let mut cur = self; - if path.is_empty() { - return Some(cur) - } - - for key in path { - let tmp = cur; - match *tmp { - Value::Table(ref mut hm) => { - match hm.get_mut(key) { - Some(v) => cur = v, - None => return None - } - } - Value::Array(ref mut v) => { - match key.parse::().ok() { - Some(idx) if idx < v.len() => cur = &mut v[idx], - _ => return None - } - } - _ => return None - } - } - Some(cur) - } -} - -impl FromStr for Value { - type Err = Vec; - fn from_str(s: &str) -> Result> { - let mut p = Parser::new(s); - match p.parse().map(Value::Table) { - Some(n) => Ok(n), - None => Err(p.errors), - } - } -} - -#[cfg(test)] -mod tests { - use super::Value; - - #[test] - fn lookup_mut_change() { - let toml = r#" - [test] - foo = "bar" - - [[values]] - foo = "baz" - - [[values]] - foo = "qux" - "#; - - let mut value: Value = toml.parse().unwrap(); - { - let foo = value.lookup_mut("values.0.foo").unwrap(); - *foo = Value::String(String::from("bar")); - } - let foo = value.lookup("values.0.foo").unwrap(); - assert_eq!(foo.as_str().unwrap(), "bar"); - } - - #[test] - fn lookup_mut_valid() { - let toml = r#" - [test] - foo = "bar" - - [[values]] - foo = "baz" - - [[values]] - foo = "qux" - "#; - - let mut value: Value = toml.parse().unwrap(); - - { - let test_foo = value.lookup_mut("test.foo").unwrap(); - assert_eq!(test_foo.as_str().unwrap(), "bar"); - } - - { - let foo1 = value.lookup_mut("values.1.foo").unwrap(); - assert_eq!(foo1.as_str().unwrap(), "qux"); - } - - assert!(value.lookup_mut("test.bar").is_none()); - assert!(value.lookup_mut("test.foo.bar").is_none()); - } - - #[test] - fn lookup_mut_invalid_index() { - let toml = r#" - [[values]] - foo = "baz" - "#; - - let mut value: Value = toml.parse().unwrap(); - - { - let foo = value.lookup_mut("test.foo"); - assert!(foo.is_none()); - } - - { - let foo = value.lookup_mut("values.100.foo"); - assert!(foo.is_none()); - } - - { - let foo = value.lookup_mut("values.str.foo"); - assert!(foo.is_none()); - } - } - - #[test] - fn lookup_mut_self() { - let mut value: Value = r#"foo = "bar""#.parse().unwrap(); - - { - let foo = value.lookup_mut("foo").unwrap(); - assert_eq!(foo.as_str().unwrap(), "bar"); - } - - let foo = value.lookup_mut("").unwrap(); - assert!(foo.as_table().is_some()); - - let baz = foo.lookup_mut("foo").unwrap(); - assert_eq!(baz.as_str().unwrap(), "bar"); - } - - #[test] - fn lookup_valid() { - let toml = r#" - [test] - foo = "bar" - - [[values]] - foo = "baz" - - [[values]] - foo = "qux" - "#; - - let value: Value = toml.parse().unwrap(); - - let test_foo = value.lookup("test.foo").unwrap(); - assert_eq!(test_foo.as_str().unwrap(), "bar"); - - let foo1 = value.lookup("values.1.foo").unwrap(); - assert_eq!(foo1.as_str().unwrap(), "qux"); - - assert!(value.lookup("test.bar").is_none()); - assert!(value.lookup("test.foo.bar").is_none()); - } - - #[test] - fn lookup_invalid_index() { - let toml = r#" - [[values]] - foo = "baz" - "#; - - let value: Value = toml.parse().unwrap(); - - let foo = value.lookup("test.foo"); - assert!(foo.is_none()); - - let foo = value.lookup("values.100.foo"); - assert!(foo.is_none()); - - let foo = value.lookup("values.str.foo"); - assert!(foo.is_none()); - } - - #[test] - fn lookup_self() { - let value: Value = r#"foo = "bar""#.parse().unwrap(); - - let foo = value.lookup("foo").unwrap(); - assert_eq!(foo.as_str().unwrap(), "bar"); - - let foo = value.lookup("").unwrap(); - assert!(foo.as_table().is_some()); - - let baz = foo.lookup("foo").unwrap(); - assert_eq!(baz.as_str().unwrap(), "bar"); - } - - #[test] - fn lookup_advanced() { - let value: Value = "[table]\n\"value\" = 0".parse().unwrap(); - let looked = value.lookup("table.\"value\"").unwrap(); - assert_eq!(*looked, Value::Integer(0)); - } - - #[test] - fn lookup_advanced_table() { - let value: Value = "[table.\"name.other\"]\nvalue = \"my value\"".parse().unwrap(); - let looked = value.lookup(r#"table."name.other".value"#).unwrap(); - assert_eq!(*looked, Value::String(String::from("my value"))); - } - - #[test] - fn lookup_mut_advanced() { - let mut value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap(); - let looked = value.lookup_mut("table.\"value\".1").unwrap(); - assert_eq!(*looked, Value::Integer(1)); - } - - #[test] - fn single_dot() { - let value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap(); - assert_eq!(None, value.lookup(".")); - } - - #[test] - fn array_dot() { - let value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap(); - assert_eq!(None, value.lookup("0.")); - } - - #[test] - fn dot_inside() { - let value: Value = "[table]\n\"value\" = [0, 1, 2]".parse().unwrap(); - assert_eq!(None, value.lookup("table.\"value.0\"")); - } - - #[test] - fn table_with_quotes() { - let value: Value = "[table.\"element\"]\n\"value\" = [0, 1, 2]".parse().unwrap(); - assert_eq!(None, value.lookup("\"table.element\".\"value\".0")); - } - - #[test] - fn table_with_quotes_2() { - let value: Value = "[table.\"element\"]\n\"value\" = [0, 1, 2]".parse().unwrap(); - assert_eq!(Value::Integer(0), *value.lookup("table.\"element\".\"value\".0").unwrap()); - } - - #[test] - fn control_characters() { - let value = Value::String("\x05".to_string()); - assert_eq!(value.to_string(), r#""\u0005""#); - } -} +#[macro_use] +extern crate serde; + +pub mod value; +mod datetime; +#[doc(no_inline)] +pub use value::Value; + +pub mod ser; +#[doc(no_inline)] +pub use ser::{to_string, to_vec, Serializer}; +pub mod de; +#[doc(no_inline)] +pub use de::{from_slice, from_str, Deserializer}; +mod tokens; -- cgit v1.2.3