diff --git a/src/lib.rs b/src/lib.rs index c4a7e9d..1c4842b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -166,3 +166,6 @@ pub mod de; #[doc(no_inline)] pub use de::{from_slice, from_str, Deserializer}; mod tokens; + +#[doc(hidden)] +pub mod macros; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..7a2bcd3 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,373 @@ +pub use serde::de::{Deserialize, IntoDeserializer}; + +use value::{Value, Table, Array}; + +/// Construct a [`toml::Value`] from TOML syntax. +/// +/// [`toml::Value`]: value/enum.Value.html +/// +/// ```rust +/// #[macro_use] +/// extern crate toml; +/// +/// fn main() { +/// let cargo_toml = toml! { +/// [package] +/// name = "toml" +/// version = "0.4.5" +/// authors = ["Alex Crichton "] +/// +/// [badges] +/// travis-ci = { repository = "alexcrichton/toml-rs" } +/// +/// [dependencies] +/// serde = "1.0" +/// +/// [dev-dependencies] +/// serde_derive = "1.0" +/// serde_json = "1.0" +/// }; +/// +/// println!("{:#?}", cargo_toml); +/// } +/// ``` +#[macro_export] +macro_rules! toml { + ($($toml:tt)+) => {{ + let table = $crate::value::Table::new(); + let mut root = $crate::Value::Table(table); + toml_internal!(@toplevel root [] $($toml)+); + root + }}; +} + +// TT-muncher to parse TOML syntax into a toml::Value. +// +// @toplevel -- Parse tokens outside of an inline table or inline array. In +// this state, `[table headers]` and `[[array headers]]` are +// allowed and `key = value` pairs are not separated by commas. +// +// @topleveldatetime -- Helper to parse a Datetime from string and insert it +// into a table, continuing in the @toplevel state. +// +// @path -- Turn a path segment into a string. Segments that look like idents +// are stringified, while quoted segments like `"cfg(windows)"` +// are not. +// +// @value -- Parse the value part of a `key = value` pair, which may be a +// primitive or inline table or inline array. +// +// @table -- Parse the contents of an inline table, returning them as a +// toml::Value::Table. +// +// @tabledatetime -- Helper to parse a Datetime from string and insert it +// into a table, continuing in the @table state. +// +// @array -- Parse the contents of an inline array, returning them as a +// toml::Value::Array. +// +// @arraydatetime -- Helper to parse a Datetime from string and push it into +// an array, continuing in the @array state. +// +// @trailingcomma -- Helper to append a comma to a sequence of tokens if the +// sequence is non-empty and does not already end in a trailing +// comma. +// +#[macro_export] +#[doc(hidden)] +macro_rules! toml_internal { + // Base case, no elements remaining. + (@toplevel $root:ident [$($path:tt)*]) => {}; + + // Parse negative number `key = -value`. + (@toplevel $root:ident [$($path:tt)*] $($k:tt)-+ = - $v:tt $($rest:tt)*) => { + toml_internal!(@toplevel $root [$($path)*] $($k)-+ = (-$v) $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T00:32:00.999999-07:00`. + (@toplevel $root:ident [$($path:tt)*] $($k:tt)-+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt $($rest:tt)*) => { + toml_internal!(@topleveldatetime $root [$($path)*] $($k)-+ = ($yr - $mo - $dhr : $min : $sec . $frac - $tzh : $tzm) $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T00:32:00-07:00`. + (@toplevel $root:ident [$($path:tt)*] $($k:tt)-+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt $($rest:tt)*) => { + toml_internal!(@topleveldatetime $root [$($path)*] $($k)-+ = ($yr - $mo - $dhr : $min : $sec - $tzh : $tzm) $($rest)*); + }; + + // Parse local datetime `key = 1979-05-27T00:32:00.999999`. + (@toplevel $root:ident [$($path:tt)*] $($k:tt)-+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt $($rest:tt)*) => { + toml_internal!(@topleveldatetime $root [$($path)*] $($k)-+ = ($yr - $mo - $dhr : $min : $sec . $frac) $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T07:32:00Z` and local datetime `key = 1979-05-27T07:32:00`. + (@toplevel $root:ident [$($path:tt)*] $($k:tt)-+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt $($rest:tt)*) => { + toml_internal!(@topleveldatetime $root [$($path)*] $($k)-+ = ($yr - $mo - $dhr : $min : $sec) $($rest)*); + }; + + // Parse local date `key = 1979-05-27`. + (@toplevel $root:ident [$($path:tt)*] $($k:tt)-+ = $yr:tt - $mo:tt - $day:tt $($rest:tt)*) => { + toml_internal!(@topleveldatetime $root [$($path)*] $($k)-+ = ($yr - $mo - $day) $($rest)*); + }; + + // Parse local time `key = 00:32:00.999999`. + (@toplevel $root:ident [$($path:tt)*] $($k:tt)-+ = $hr:tt : $min:tt : $sec:tt . $frac:tt $($rest:tt)*) => { + toml_internal!(@topleveldatetime $root [$($path)*] $($k)-+ = ($hr : $min : $sec . $frac) $($rest)*); + }; + + // Parse local time `key = 07:32:00`. + (@toplevel $root:ident [$($path:tt)*] $($k:tt)-+ = $hr:tt : $min:tt : $sec:tt $($rest:tt)*) => { + toml_internal!(@topleveldatetime $root [$($path)*] $($k)-+ = ($hr : $min : $sec) $($rest)*); + }; + + // Parse any other `key = value` including string, inline array, inline + // table, number, and boolean. + (@toplevel $root:ident [$($path:tt)*] $($k:tt)-+ = $v:tt $($rest:tt)*) => { + $crate::macros::insert_toml( + &mut $root, + &[$($path)* &concat!($("-", toml_internal!(@path $k),)+)[1..]], + toml_internal!(@value $v)); + toml_internal!(@toplevel $root [$($path)*] $($rest)*); + }; + + // Parse array header `[[bin]]`. + (@toplevel $root:ident $oldpath:tt [[$($($path:tt)-+).+]] $($rest:tt)*) => { + $crate::macros::push_toml( + &mut $root, + &[$(&concat!($("-", toml_internal!(@path $path),)+)[1..],)+]); + toml_internal!(@toplevel $root [$(&concat!($("-", toml_internal!(@path $path),)+)[1..],)+] $($rest)*); + }; + + // Parse table header `[patch.crates-io]`. + (@toplevel $root:ident $oldpath:tt [$($($path:tt)-+).+] $($rest:tt)*) => { + $crate::macros::insert_toml( + &mut $root, + &[$(&concat!($("-", toml_internal!(@path $path),)+)[1..],)+], + $crate::Value::Table($crate::value::Table::new())); + toml_internal!(@toplevel $root [$(&concat!($("-", toml_internal!(@path $path),)+)[1..],)+] $($rest)*); + }; + + // Parse datetime from string and insert into table. + (@topleveldatetime $root:ident [$($path:tt)*] $($k:tt)-+ = ($($datetime:tt)+) $($rest:tt)*) => { + $crate::macros::insert_toml( + &mut $root, + &[$($path)* &concat!($("-", toml_internal!(@path $k),)+)[1..]], + $crate::Value::Datetime(concat!($(stringify!($datetime)),+).parse().unwrap())); + toml_internal!(@toplevel $root [$($path)*] $($rest)*); + }; + + // Turn a path segment into a string. + (@path $ident:ident) => { + stringify!($ident) + }; + + // For a path segment that is not an ident, expect that it is already a + // quoted string, like in `[target."cfg(windows)".dependencies]`. + (@path $quoted:tt) => { + $quoted + }; + + // Construct a Value from an inline table. + (@value { $($inline:tt)* }) => {{ + let mut table = $crate::value::Table::new(); + toml_internal!(@trailingcomma (@table table) $($inline)*); + $crate::Value::Table(table) + }}; + + // Construct a Value from an inline array. + (@value [ $($inline:tt)* ]) => {{ + let mut array = $crate::value::Array::new(); + toml_internal!(@trailingcomma (@array array) $($inline)*); + $crate::Value::Array(array) + }}; + + // Construct a Value from any other type, probably string or boolean or number. + (@value $v:tt) => {{ + // TODO: Implement this with something like serde_json::to_value instead. + let de = $crate::macros::IntoDeserializer::<$crate::de::Error>::into_deserializer($v); + <$crate::Value as $crate::macros::Deserialize>::deserialize(de).unwrap() + }}; + + // Base case of inline table. + (@table $root:ident) => {}; + + // Parse negative number `key = -value`. + (@table $root:ident $($k:tt)-+ = - $v:tt , $($rest:tt)*) => { + toml_internal!(@table $root $($k)-+ = (-$v) , $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T00:32:00.999999-07:00`. + (@table $root:ident $($k:tt)-+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + toml_internal!(@tabledatetime $root $($k)-+ = ($yr - $mo - $dhr : $min : $sec . $frac - $tzh : $tzm) $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T00:32:00-07:00`. + (@table $root:ident $($k:tt)-+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + toml_internal!(@tabledatetime $root $($k)-+ = ($yr - $mo - $dhr : $min : $sec - $tzh : $tzm) $($rest)*); + }; + + // Parse local datetime `key = 1979-05-27T00:32:00.999999`. + (@table $root:ident $($k:tt)-+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => { + toml_internal!(@tabledatetime $root $($k)-+ = ($yr - $mo - $dhr : $min : $sec . $frac) $($rest)*); + }; + + // Parse offset datetime `key = 1979-05-27T07:32:00Z` and local datetime `key = 1979-05-27T07:32:00`. + (@table $root:ident $($k:tt)-+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt , $($rest:tt)*) => { + toml_internal!(@tabledatetime $root $($k)-+ = ($yr - $mo - $dhr : $min : $sec) $($rest)*); + }; + + // Parse local date `key = 1979-05-27`. + (@table $root:ident $($k:tt)-+ = $yr:tt - $mo:tt - $day:tt , $($rest:tt)*) => { + toml_internal!(@tabledatetime $root $($k)-+ = ($yr - $mo - $day) $($rest)*); + }; + + // Parse local time `key = 00:32:00.999999`. + (@table $root:ident $($k:tt)-+ = $hr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => { + toml_internal!(@tabledatetime $root $($k)-+ = ($hr : $min : $sec . $frac) $($rest)*); + }; + + // Parse local time `key = 07:32:00`. + (@table $root:ident $($k:tt)-+ = $hr:tt : $min:tt : $sec:tt , $($rest:tt)*) => { + toml_internal!(@tabledatetime $root $($k)-+ = ($hr : $min : $sec) $($rest)*); + }; + + // Parse any other type, probably string or boolean or number. + (@table $root:ident $($k:tt)-+ = $v:tt , $($rest:tt)*) => { + $root.insert( + concat!($("-", toml_internal!(@path $k),)+)[1..].to_owned(), + toml_internal!(@value $v)); + toml_internal!(@table $root $($rest)*); + }; + + // Parse a Datetime from string and continue in @table state. + (@tabledatetime $root:ident $($k:tt)-+ = ($($datetime:tt)*) $($rest:tt)*) => { + $root.insert( + concat!($("-", toml_internal!(@path $k),)+)[1..].to_owned(), + $crate::Value::Datetime(concat!($(stringify!($datetime)),+).parse().unwrap())); + toml_internal!(@table $root $($rest)*); + }; + + // Base case of inline array. + (@array $root:ident) => {}; + + // Parse negative number `-value`. + (@array $root:ident - $v:tt , $($rest:tt)*) => { + toml_internal!(@array $root (-$v) , $($rest)*); + }; + + // Parse offset datetime `1979-05-27T00:32:00.999999-07:00`. + (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec . $frac - $tzh : $tzm) $($rest)*); + }; + + // Parse offset datetime `1979-05-27T00:32:00-07:00`. + (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => { + toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec - $tzh : $tzm) $($rest)*); + }; + + // Parse local datetime `1979-05-27T00:32:00.999999`. + (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => { + toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec . $frac) $($rest)*); + }; + + // Parse offset datetime `1979-05-27T07:32:00Z` and local datetime `1979-05-27T07:32:00`. + (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt , $($rest:tt)*) => { + toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec) $($rest)*); + }; + + // Parse local date `1979-05-27`. + (@array $root:ident $yr:tt - $mo:tt - $day:tt , $($rest:tt)*) => { + toml_internal!(@arraydatetime $root ($yr - $mo - $day) $($rest)*); + }; + + // Parse local time `00:32:00.999999`. + (@array $root:ident $hr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => { + toml_internal!(@arraydatetime $root ($hr : $min : $sec . $frac) $($rest)*); + }; + + // Parse local time `07:32:00`. + (@array $root:ident $hr:tt : $min:tt : $sec:tt , $($rest:tt)*) => { + toml_internal!(@arraydatetime $root ($hr : $min : $sec) $($rest)*); + }; + + // Parse any other type, probably string or boolean or number. + (@array $root:ident $v:tt , $($rest:tt)*) => { + $root.push(toml_internal!(@value $v)); + toml_internal!(@array $root $($rest)*); + }; + + // Parse a Datetime from string and continue in @array state. + (@arraydatetime $root:ident ($($datetime:tt)*) $($rest:tt)*) => { + $root.push($crate::Value::Datetime(concat!($(stringify!($datetime)),+).parse().unwrap())); + toml_internal!(@array $root $($rest)*); + }; + + // No trailing comma required if the tokens are empty. + (@trailingcomma ($($args:tt)*)) => { + toml_internal!($($args)*); + }; + + // Tokens end with a trailing comma, do not append another one. + (@trailingcomma ($($args:tt)*) ,) => { + toml_internal!($($args)* ,); + }; + + // Tokens end with something other than comma, append a trailing comma. + (@trailingcomma ($($args:tt)*) $last:tt) => { + toml_internal!($($args)* $last ,); + }; + + // Not yet at the last token. + (@trailingcomma ($($args:tt)*) $first:tt $($rest:tt)+) => { + toml_internal!(@trailingcomma ($($args)* $first) $($rest)+); + }; +} + +// Called when parsing a `key = value` pair. +// Inserts an entry into the table at the given path. +pub fn insert_toml(root: &mut Value, path: &[&str], value: Value) { + *traverse(root, path) = value; +} + +// Called when parsing an `[[array header]]`. +// Pushes an empty table onto the array at the given path. +pub fn push_toml(root: &mut Value, path: &[&str]) { + let target = traverse(root, path); + if !target.is_array() { + *target = Value::Array(Array::new()); + } + target.as_array_mut().unwrap().push(Value::Table(Table::new())); +} + +fn traverse<'a>(root: &'a mut Value, path: &[&str]) -> &'a mut Value { + let mut cur = root; + for &key in path { + // Lexical lifetimes :D + let cur1 = cur; + let cur2; + + // From the TOML spec: + // + // > Each double-bracketed sub-table will belong to the most recently + // > defined table element above it. + if cur1.is_array() { + cur2 = cur1.as_array_mut().unwrap().last_mut().unwrap(); + } else { + cur2 = cur1; + }; + + // We are about to index into this value, so it better be a table. + if !cur2.is_table() { + *cur2 = Value::Table(Table::new()); + } + + if !cur2.as_table().unwrap().contains_key(key) { + // Insert an empty table for the next loop iteration to point to. + let empty = Value::Table(Table::new()); + cur2.as_table_mut().unwrap().insert(key.to_owned(), empty); + } + + // Step into the current table. + cur = cur2.as_table_mut().unwrap().get_mut(key).unwrap(); + } + cur +} diff --git a/test-suite/Cargo.toml b/test-suite/Cargo.toml index 7c91787..10ffbcb 100644 --- a/test-suite/Cargo.toml +++ b/test-suite/Cargo.toml @@ -2,6 +2,7 @@ name = "toml_test_suite" version = "0.0.0" authors = ["Alex Crichton "] +build = "build.rs" publish = false [build-dependencies] diff --git a/test-suite/build.rs b/test-suite/build.rs new file mode 100644 index 0000000..ca63946 --- /dev/null +++ b/test-suite/build.rs @@ -0,0 +1,8 @@ +extern crate rustc_version; +use rustc_version::{version, Version}; + +fn main() { + if version().unwrap() >= Version::parse("1.20.0").unwrap() { + println!(r#"cargo:rustc-cfg=feature="test-quoted-keys-in-macro""#); + } +} diff --git a/test-suite/tests/macros.rs b/test-suite/tests/macros.rs new file mode 100644 index 0000000..439420d --- /dev/null +++ b/test-suite/tests/macros.rs @@ -0,0 +1,286 @@ +#![recursion_limit = "128"] + +#[macro_use] +extern crate toml; + +macro_rules! table { + ($($key:expr => $value:expr,)*) => {{ + let mut table = toml::value::Table::new(); + $( + table.insert($key.to_string(), $value.into()); + )* + toml::Value::Table(table) + }}; +} + +macro_rules! array { + ($($element:expr,)*) => {{ + let mut array = toml::value::Array::new(); + $( + array.push($element.into()); + )* + toml::Value::Array(array) + }}; +} + +macro_rules! datetime { + ($s:tt) => { + $s.parse::().unwrap() + }; +} + +#[test] +fn test_cargo_toml() { + // Simple sanity check of: + // + // - Ordinary tables + // - Inline tables + // - Inline arrays + // - String values + // - Table keys containing hyphen + // - Table headers containing hyphen + let actual = toml! { + [package] + name = "toml" + version = "0.4.5" + authors = ["Alex Crichton "] + + [badges] + travis-ci = { repository = "alexcrichton/toml-rs" } + + [dependencies] + serde = "1.0" + + [dev-dependencies] + serde_derive = "1.0" + serde_json = "1.0" + }; + + let expected = table! { + "package" => table! { + "name" => "toml".to_owned(), + "version" => "0.4.5".to_owned(), + "authors" => array! { + "Alex Crichton ".to_owned(), + }, + }, + "badges" => table! { + "travis-ci" => table! { + "repository" => "alexcrichton/toml-rs".to_owned(), + }, + }, + "dependencies" => table! { + "serde" => "1.0".to_owned(), + }, + "dev-dependencies" => table! { + "serde_derive" => "1.0".to_owned(), + "serde_json" => "1.0".to_owned(), + }, + }; + + assert_eq!(actual, expected); +} + +#[test] +fn test_array() { + // Copied from the TOML spec. + let actual = toml! { + [[fruit]] + name = "apple" + + [fruit.physical] + color = "red" + shape = "round" + + [[fruit.variety]] + name = "red delicious" + + [[fruit.variety]] + name = "granny smith" + + [[fruit]] + name = "banana" + + [[fruit.variety]] + name = "plantain" + }; + + let expected = table! { + "fruit" => array! { + table! { + "name" => "apple", + "physical" => table! { + "color" => "red", + "shape" => "round", + }, + "variety" => array! { + table! { + "name" => "red delicious", + }, + table! { + "name" => "granny smith", + }, + }, + }, + table! { + "name" => "banana", + "variety" => array! { + table! { + "name" => "plantain", + }, + }, + }, + }, + }; + + assert_eq!(actual, expected); +} + +#[test] +fn test_number() { + let actual = toml! { + positive = 1 + negative = -1 + table = { positive = 1, negative = -1 } + array = [ 1, -1 ] + }; + + let expected = table! { + "positive" => 1, + "negative" => -1, + "table" => table! { + "positive" => 1, + "negative" => -1, + }, + "array" => array! { + 1, + -1, + }, + }; + + assert_eq!(actual, expected); +} + +#[test] +fn test_datetime() { + let actual = toml! { + // Copied from the TOML spec. + odt1 = 1979-05-27T07:32:00Z + odt2 = 1979-05-27T00:32:00-07:00 + odt3 = 1979-05-27T00:32:00.999999-07:00 + ldt1 = 1979-05-27T07:32:00 + ldt2 = 1979-05-27T00:32:00.999999 + ld1 = 1979-05-27 + lt1 = 07:32:00 + lt2 = 00:32:00.999999 + + table = { + odt1 = 1979-05-27T07:32:00Z, + odt2 = 1979-05-27T00:32:00-07:00, + odt3 = 1979-05-27T00:32:00.999999-07:00, + ldt1 = 1979-05-27T07:32:00, + ldt2 = 1979-05-27T00:32:00.999999, + ld1 = 1979-05-27, + lt1 = 07:32:00, + lt2 = 00:32:00.999999, + } + + array = [ + 1979-05-27T07:32:00Z, + 1979-05-27T00:32:00-07:00, + 1979-05-27T00:32:00.999999-07:00, + 1979-05-27T07:32:00, + 1979-05-27T00:32:00.999999, + 1979-05-27, + 07:32:00, + 00:32:00.999999, + ] + }; + + let expected = table! { + "odt1" => datetime!("1979-05-27T07:32:00Z"), + "odt2" => datetime!("1979-05-27T00:32:00-07:00"), + "odt3" => datetime!("1979-05-27T00:32:00.999999-07:00"), + "ldt1" => datetime!("1979-05-27T07:32:00"), + "ldt2" => datetime!("1979-05-27T00:32:00.999999"), + "ld1" => datetime!("1979-05-27"), + "lt1" => datetime!("07:32:00"), + "lt2" => datetime!("00:32:00.999999"), + + "table" => table! { + "odt1" => datetime!("1979-05-27T07:32:00Z"), + "odt2" => datetime!("1979-05-27T00:32:00-07:00"), + "odt3" => datetime!("1979-05-27T00:32:00.999999-07:00"), + "ldt1" => datetime!("1979-05-27T07:32:00"), + "ldt2" => datetime!("1979-05-27T00:32:00.999999"), + "ld1" => datetime!("1979-05-27"), + "lt1" => datetime!("07:32:00"), + "lt2" => datetime!("00:32:00.999999"), + }, + + "array" => array! { + datetime!("1979-05-27T07:32:00Z"), + datetime!("1979-05-27T00:32:00-07:00"), + datetime!("1979-05-27T00:32:00.999999-07:00"), + datetime!("1979-05-27T07:32:00"), + datetime!("1979-05-27T00:32:00.999999"), + datetime!("1979-05-27"), + datetime!("07:32:00"), + datetime!("00:32:00.999999"), + }, + }; + + assert_eq!(actual, expected); +} + +// This test requires rustc >= 1.20. +#[test] +#[cfg(feature = "test-quoted-keys-in-macro")] +fn test_quoted_key() { + let actual = toml! { + "quoted" = true + table = { "quoted" = true } + + [target."cfg(windows)".dependencies] + winapi = "0.2.8" + }; + + let expected = table! { + "quoted" => true, + "table" => table! { + "quoted" => true, + }, + "target" => table! { + "cfg(windows)" => table! { + "dependencies" => table! { + "winapi" => "0.2.8", + }, + }, + }, + }; + + assert_eq!(actual, expected); +} + +#[test] +fn test_empty() { + let actual = toml! { + empty_inline_table = {} + empty_inline_array = [] + + [empty_table] + + [[empty_array]] + }; + + let expected = table! { + "empty_inline_table" => table! {}, + "empty_inline_array" => array! {}, + "empty_table" => table! {}, + "empty_array" => array! { + table! {}, + }, + }; + + assert_eq!(actual, expected); +}