aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2017-01-29 16:53:20 -0800
committerAlex Crichton <alex@alexcrichton.com>2017-02-08 21:21:18 -0800
commitf66d8bcf33530c858a502bfa170f2383a8cbc204 (patch)
tree76498b837fc5f1f6ba0a5f53e1b2d85c6638da4d /src/lib.rs
parent473908c9722eeedeec1777237a135f582faa78d8 (diff)
downloadmilf-rs-f66d8bcf33530c858a502bfa170f2383a8cbc204.tar.gz
milf-rs-f66d8bcf33530c858a502bfa170f2383a8cbc204.zip
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
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs640
1 files changed, 151 insertions, 489 deletions
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 <alex@alexcrichton.com>"]
+//!
+//! [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::<Value>().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<u16>,
+//! keys: Keys,
+//! }
+//!
+//! #[derive(Deserialize)]
+//! struct Keys {
+//! github: String,
+//! travis: Option<String>,
+//! }
+//!
+//! 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<u16>,
+//! keys: Keys,
+//! }
//!
-//! [1]: https://github.com/mojombo/toml
-//! [2]: https://github.com/BurntSushi/toml-test
+//! #[derive(Serialize)]
+//! struct Keys {
+//! github: String,
+//! travis: Option<String>,
+//! }
//!
-//! # 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<Value>;
-
-/// Type representing a TOML table, payload of the `Value::Table` variant
-pub type Table = BTreeMap<String, Value>;
-
-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<i64> {
- match *self { Value::Integer(i) => Some(i), _ => None }
- }
-
- /// Extracts the float value if it is a float.
- pub fn as_float(&self) -> Option<f64> {
- match *self { Value::Float(f) => Some(f), _ => None }
- }
-
- /// Extracts the boolean value if it is a boolean.
- pub fn as_bool(&self) -> Option<bool> {
- 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::<usize>().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::<usize>().ok() {
- Some(idx) if idx < v.len() => cur = &mut v[idx],
- _ => return None
- }
- }
- _ => return None
- }
- }
- Some(cur)
- }
-}
-
-impl FromStr for Value {
- type Err = Vec<ParserError>;
- fn from_str(s: &str) -> Result<Value, Vec<ParserError>> {
- 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;