diff options
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | examples/decode.rs | 1 | ||||
-rw-r--r-- | src/de.rs | 12 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/ser.rs | 468 | ||||
-rw-r--r-- | tests/formatting.rs | 1 | ||||
-rw-r--r-- | tests/pretty.rs | 308 | ||||
-rw-r--r-- | tests/serde.rs | 51 | ||||
-rw-r--r-- | tests/valid.rs | 58 | ||||
-rw-r--r-- | tests/valid/table-multi-empty.json | 5 | ||||
-rw-r--r-- | tests/valid/table-multi-empty.toml | 5 |
12 files changed, 868 insertions, 47 deletions
diff --git a/.travis.yml b/.travis.yml index c9e484b..e4c5407 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ before_script: script: - cargo test - rustdoc --test README.md -L target - - cargo doc --no-deps + - test "$TRAVIS_RUST_VERSION" != "1.15.0" && cargo doc --no-deps || echo "skipping cargo doc" after_success: - travis-cargo --only nightly doc-upload - travis-cargo coveralls --no-sudo @@ -1,6 +1,6 @@ [package] name = "toml" -version = "0.4.2" +version = "0.4.5" authors = ["Alex Crichton <alex@alexcrichton.com>"] license = "MIT/Apache-2.0" readme = "README.md" diff --git a/examples/decode.rs b/examples/decode.rs index e15da79..e8ce586 100644 --- a/examples/decode.rs +++ b/examples/decode.rs @@ -4,7 +4,6 @@ #![deny(warnings)] extern crate toml; -extern crate serde; #[macro_use] extern crate serde_derive; @@ -438,9 +438,19 @@ impl<'de, 'b> de::Deserializer<'de> for MapVisitor<'de, 'b> { visitor.visit_some(self) } + fn deserialize_newtype_struct<V>( + self, + _name: &'static str, + visitor: V + ) -> Result<V::Value, Error> + where V: de::Visitor<'de> + { + visitor.visit_newtype_struct(self) + } + forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq - bytes byte_buf map struct unit newtype_struct identifier + bytes byte_buf map struct unit identifier ignored_any unit_struct tuple_struct tuple enum } } @@ -161,7 +161,7 @@ mod datetime; pub mod ser; #[doc(no_inline)] -pub use ser::{to_string, to_vec, Serializer}; +pub use ser::{to_string, to_string_pretty, to_vec, Serializer}; pub mod de; #[doc(no_inline)] pub use de::{from_slice, from_str, Deserializer}; @@ -30,6 +30,7 @@ use std::cell::Cell; use std::error; use std::fmt::{self, Write}; use std::marker; +use std::rc::Rc; use serde::ser; use datetime::{SERDE_STRUCT_FIELD_NAME, SERDE_STRUCT_NAME}; @@ -93,6 +94,18 @@ pub fn to_string<T: ?Sized>(value: &T) -> Result<String, Error> Ok(dst) } +/// Serialize the given data structure as a "pretty" String of TOML. +/// +/// This is identical to `to_string` except the output string has a more +/// "pretty" output. See `Serializer::pretty` for more details. +pub fn to_string_pretty<T: ?Sized>(value: &T) -> Result<String, Error> + where T: ser::Serialize, +{ + let mut dst = String::with_capacity(128); + value.serialize(&mut Serializer::pretty(&mut dst))?; + Ok(dst) +} + /// Errors that can occur when serializing a type. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Error { @@ -137,6 +150,47 @@ pub enum Error { __Nonexhaustive, } +#[derive(Debug, Default, Clone)] +#[doc(hidden)] +/// Internal place for holding array setings +struct ArraySettings { + indent: usize, + trailing_comma: bool, +} + +impl ArraySettings { + fn pretty() -> ArraySettings { + ArraySettings { + indent: 4, + trailing_comma: true, + } + } +} + +#[doc(hidden)] +#[derive(Debug, Default, Clone)] +/// String settings +struct StringSettings { + /// Whether to use literal strings when possible + literal: bool, +} + +impl StringSettings { + fn pretty() -> StringSettings { + StringSettings { + literal: true, + } + } +} + +#[derive(Debug, Default, Clone)] +#[doc(hidden)] +/// Internal struct for holding serialization settings +struct Settings { + array: Option<ArraySettings>, + string: Option<StringSettings>, +} + /// Serialization implementation for TOML. /// /// This structure implements serialization support for TOML to serialize an @@ -149,6 +203,7 @@ pub enum Error { pub struct Serializer<'a> { dst: &'a mut String, state: State<'a>, + settings: Rc<Settings>, } #[derive(Debug, Clone)] @@ -163,6 +218,7 @@ enum State<'a> { parent: &'a State<'a>, first: &'a Cell<bool>, type_: &'a Cell<Option<&'static str>>, + len: Option<usize>, }, End, } @@ -172,6 +228,7 @@ pub struct SerializeSeq<'a: 'b, 'b> { ser: &'b mut Serializer<'a>, first: Cell<bool>, type_: Cell<Option<&'static str>>, + len: Option<usize>, } #[doc(hidden)] @@ -194,7 +251,178 @@ impl<'a> Serializer<'a> { Serializer { dst: dst, state: State::End, + settings: Rc::new(Settings::default()), + } + } + + /// Instantiate a "pretty" formatter + /// + /// By default this will use: + /// + /// - pretty strings: strings with newlines will use the `'''` syntax. See + /// `Serializer::pretty_string` + /// - pretty arrays: each item in arrays will be on a newline, have an indentation of 4 and + /// have a trailing comma. See `Serializer::pretty_array` + pub fn pretty(dst: &'a mut String) -> Serializer<'a> { + Serializer { + dst: dst, + state: State::End, + settings: Rc::new(Settings { + array: Some(ArraySettings::pretty()), + string: Some(StringSettings::pretty()), + }), + } + } + + /// Enable or Disable pretty strings + /// + /// If enabled, literal strings will be used when possible and strings with + /// one or more newlines will use triple quotes (i.e.: `'''` or `"""`) + /// + /// # Examples + /// + /// Instead of: + /// + /// ```toml,ignore + /// single = "no newlines" + /// text = "\nfoo\nbar\n" + /// ``` + /// + /// You will have: + /// + /// ```toml,ignore + /// single = 'no newlines' + /// text = ''' + /// foo + /// bar + /// ''' + /// ``` + pub fn pretty_string(&mut self, value: bool) -> &mut Self { + Rc::get_mut(&mut self.settings).unwrap().string = if value { + Some(StringSettings::pretty()) + } else { + None + }; + self + } + + /// Enable or Disable Literal strings for pretty strings + /// + /// If enabled, literal strings will be used when possible and strings with + /// one or more newlines will use triple quotes (i.e.: `'''` or `"""`) + /// + /// If disabled, literal strings will NEVER be used and strings with one or + /// more newlines will use `"""` + /// + /// # Examples + /// + /// Instead of: + /// + /// ```toml,ignore + /// single = "no newlines" + /// text = "\nfoo\nbar\n" + /// ``` + /// + /// You will have: + /// + /// ```toml,ignore + /// single = "no newlines" + /// text = """ + /// foo + /// bar + /// """ + /// ``` + pub fn pretty_string_literal(&mut self, value: bool) -> &mut Self { + let use_default = if let &mut Some(ref mut s) = &mut Rc::get_mut(&mut self.settings) + .unwrap().string { + s.literal = value; + false + } else { + true + }; + + if use_default { + let mut string = StringSettings::pretty(); + string.literal = value; + Rc::get_mut(&mut self.settings).unwrap().string = Some(string); + } + self + } + + /// Enable or Disable pretty arrays + /// + /// If enabled, arrays will always have each item on their own line. + /// + /// Some specific features can be controlled via other builder methods: + /// + /// - `Serializer::pretty_array_indent`: set the indent to a value other + /// than 4. + /// - `Serializer::pretty_array_trailing_comma`: enable/disable the trailing + /// comma on the last item. + /// + /// # Examples + /// + /// Instead of: + /// + /// ```toml,ignore + /// array = ["foo", "bar"] + /// ``` + /// + /// You will have: + /// + /// ```toml,ignore + /// array = [ + /// "foo", + /// "bar", + /// ] + /// ``` + pub fn pretty_array(&mut self, value: bool) -> &mut Self { + Rc::get_mut(&mut self.settings).unwrap().array = if value { + Some(ArraySettings::pretty()) + } else { + None + }; + self + } + + /// Set the indent for pretty arrays + /// + /// See `Serializer::pretty_array` for more details. + pub fn pretty_array_indent(&mut self, value: usize) -> &mut Self { + let use_default = if let &mut Some(ref mut a) = &mut Rc::get_mut(&mut self.settings) + .unwrap().array { + a.indent = value; + false + } else { + true + }; + + if use_default { + let mut array = ArraySettings::pretty(); + array.indent = value; + Rc::get_mut(&mut self.settings).unwrap().array = Some(array); } + self + } + + /// Specify whether to use a trailing comma when serializing pretty arrays + /// + /// See `Serializer::pretty_array` for more details. + pub fn pretty_array_trailing_comma(&mut self, value: bool) -> &mut Self { + let use_default = if let &mut Some(ref mut a) = &mut Rc::get_mut(&mut self.settings) + .unwrap().array { + a.trailing_comma = value; + false + } else { + true + }; + + if use_default { + let mut array = ArraySettings::pretty(); + array.trailing_comma = value; + Rc::get_mut(&mut self.settings).unwrap().array = Some(array); + } + self } fn display<T: fmt::Display>(&mut self, @@ -218,12 +446,12 @@ impl<'a> Serializer<'a> { fn _emit_key(&mut self, state: &State) -> Result<(), Error> { match *state { State::End => Ok(()), - State::Array { parent, first, type_ } => { + State::Array { parent, first, type_, len } => { assert!(type_.get().is_some()); if first.get() { self._emit_key(parent)?; } - self.emit_array(first) + self.emit_array(first, len) } State::Table { parent, first, table_emitted, key } => { if table_emitted.get() { @@ -240,11 +468,25 @@ impl<'a> Serializer<'a> { } } - fn emit_array(&mut self, first: &Cell<bool>) -> Result<(), Error> { - if first.get() { - self.dst.push_str("["); - } else { - self.dst.push_str(", "); + fn emit_array(&mut self, first: &Cell<bool>, len: Option<usize>) -> Result<(), Error> { + match (len, &self.settings.array) { + (Some(0...1), _) | (_, &None) => { + if first.get() { + self.dst.push_str("[") + } else { + self.dst.push_str(", ") + } + }, + (_, &Some(ref a)) => { + if first.get() { + self.dst.push_str("[\n") + } else { + self.dst.push_str(",\n") + } + for _ in 0..a.indent { + self.dst.push_str(" "); + } + }, } Ok(()) } @@ -277,29 +519,144 @@ impl<'a> Serializer<'a> { if ok { drop(write!(self.dst, "{}", key)); } else { - self.emit_str(key)?; + self.emit_str(key, true)?; } Ok(()) } - fn emit_str(&mut self, value: &str) -> Result<(), Error> { - drop(write!(self.dst, "\"")); - for ch in value.chars() { - match ch { - '\u{8}' => drop(write!(self.dst, "\\b")), - '\u{9}' => drop(write!(self.dst, "\\t")), - '\u{a}' => drop(write!(self.dst, "\\n")), - '\u{c}' => drop(write!(self.dst, "\\f")), - '\u{d}' => drop(write!(self.dst, "\\r")), - '\u{22}' => drop(write!(self.dst, "\\\"")), - '\u{5c}' => drop(write!(self.dst, "\\\\")), - c if c < '\u{1f}' => { - drop(write!(self.dst, "\\u{:04X}", ch as u32)) + fn emit_str(&mut self, value: &str, is_key: bool) -> Result<(), Error> { + #[derive(PartialEq)] + enum Type { + NewlineTripple, + OnelineTripple, + OnelineSingle, + } + + enum Repr { + /// represent as a literal string (using '') + Literal(String, Type), + /// represent the std way (using "") + Std(Type), + } + + fn do_pretty(value: &str) -> Repr { + // For doing pretty prints we store in a new String + // because there are too many cases where pretty cannot + // work. We need to determine: + // - if we are a "multi-line" pretty (if there are \n) + // - if ['''] appears if multi or ['] if single + // - if there are any invalid control characters + // + // Doing it any other way would require multiple passes + // to determine if a pretty string works or not. + let mut out = String::with_capacity(value.len() * 2); + let mut ty = Type::OnelineSingle; + // found consecutive single quotes + let mut max_found_singles = 0; + let mut found_singles = 0; + let mut can_be_pretty = true; + + for ch in value.chars() { + if can_be_pretty { + if ch == '\'' { + found_singles += 1; + if found_singles >= 3 { + can_be_pretty = false; + } + } else { + if found_singles > max_found_singles { + max_found_singles = found_singles; + } + found_singles = 0 + } + match ch { + '\t' => {}, + '\n' => ty = Type::NewlineTripple, + // note that the following are invalid: \b \f \r + c if c < '\u{1f}' => can_be_pretty = false, // Invalid control character + _ => {} + } + out.push(ch); + } else { + // the string cannot be represented as pretty, + // still check if it should be multiline + if ch == '\n' { + ty = Type::NewlineTripple; + } } - ch => drop(write!(self.dst, "{}", ch)), } + if !can_be_pretty { + debug_assert!(ty != Type::OnelineTripple); + return Repr::Std(ty); + } + if found_singles > max_found_singles { + max_found_singles = found_singles; + } + debug_assert!(max_found_singles < 3); + if ty == Type::OnelineSingle && max_found_singles >= 1 { + // no newlines, but must use ''' because it has ' in it + ty = Type::OnelineTripple; + } + Repr::Literal(out, ty) + } + + let repr = if !is_key && self.settings.string.is_some() { + match (&self.settings.string, do_pretty(value)) { + (&Some(StringSettings { literal: false, .. }), Repr::Literal(_, ty)) => + Repr::Std(ty), + (_, r @ _) => r, + } + } else { + Repr::Std(Type::OnelineSingle) + }; + match repr { + Repr::Literal(literal, ty) => { + // A pretty string + match ty { + Type::NewlineTripple => self.dst.push_str("'''\n"), + Type::OnelineTripple => self.dst.push_str("'''"), + Type::OnelineSingle => self.dst.push('\''), + } + self.dst.push_str(&literal); + match ty { + Type::OnelineSingle => self.dst.push('\''), + _ => self.dst.push_str("'''"), + } + }, + Repr::Std(ty) => { + match ty { + Type::NewlineTripple => self.dst.push_str("\"\"\"\n"), + // note: OnelineTripple can happen if do_pretty wants to do + // '''it's one line''' + // but settings.string.literal == false + Type::OnelineSingle | + Type::OnelineTripple => self.dst.push('"'), + } + for ch in value.chars() { + match ch { + '\u{8}' => self.dst.push_str("\\b"), + '\u{9}' => self.dst.push_str("\\t"), + '\u{a}' => { + match ty { + Type::NewlineTripple => self.dst.push('\n'), + Type::OnelineSingle => self.dst.push_str("\\n"), + _ => unreachable!(), + } + }, + '\u{c}' => self.dst.push_str("\\f"), + '\u{d}' => self.dst.push_str("\\r"), + '\u{22}' => self.dst.push_str("\\\""), + '\u{5c}' => self.dst.push_str("\\\\"), + c if c < '\u{1f}' => drop(write!(self.dst, "\\u{:04X}", ch as u32)), + ch => self.dst.push(ch), + } + } + match ty { + Type::NewlineTripple => self.dst.push_str("\"\"\""), + Type::OnelineSingle | Type::OnelineTripple => self.dst.push('"'), + } + }, } - drop(write!(self.dst, "\"")); Ok(()) } @@ -330,12 +687,25 @@ impl<'a> Serializer<'a> { } match *state { - State::Table { first, .. } | - State::Array { parent: &State::Table { first, .. }, .. } => { + State::Table { first, .. } => { if !first.get() { - self.dst.push_str("\n"); + // Newline if we are a table that is not the first + // table in the document. + self.dst.push('\n'); } - } + }, + State::Array { parent, first, .. } => { + if !first.get() { + // Always newline if we are not the first item in the + // table-array + self.dst.push('\n'); + } else if let State::Table { first, .. } = *parent { + if !first.get() { + // Newline if we are not the first item in the document + self.dst.push('\n'); + } + } + }, _ => {} } self.dst.push_str("["); @@ -414,7 +784,7 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { self.display(v, "integer") } - fn serialize_f32(mut self, v: f32) -> Result<(), Self::Error> { + fn serialize_f32(self, v: f32) -> Result<(), Self::Error> { if !v.is_finite() { return Err(Error::NumberInvalid); } @@ -430,7 +800,7 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { Ok(()) } - fn serialize_f64(mut self, v: f64) -> Result<(), Self::Error> { + fn serialize_f64(self, v: f64) -> Result<(), Self::Error> { if !v.is_finite() { return Err(Error::NumberInvalid); } @@ -451,9 +821,9 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { self.serialize_str(v.encode_utf8(&mut buf)) } - fn serialize_str(mut self, value: &str) -> Result<(), Self::Error> { + fn serialize_str(self, value: &str) -> Result<(), Self::Error> { self.emit_key("string")?; - self.emit_str(value)?; + self.emit_str(value, false)?; if let State::Table { .. } = self.state { self.dst.push_str("\n"); } @@ -511,13 +881,14 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { Err(Error::UnsupportedType) } - fn serialize_seq(mut self, _len: Option<usize>) + fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> { self.array_type("array")?; Ok(SerializeSeq { ser: self, first: Cell::new(true), type_: Cell::new(None), + len: len, }) } @@ -540,7 +911,7 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { Err(Error::UnsupportedType) } - fn serialize_map(mut self, _len: Option<usize>) + fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> { self.array_type("table")?; Ok(SerializeTable::Table { @@ -551,7 +922,7 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { }) } - fn serialize_struct(mut self, name: &'static str, _len: usize) + fn serialize_struct(self, name: &'static str, _len: usize) -> Result<Self::SerializeStruct, Self::Error> { if name == SERDE_STRUCT_NAME { self.array_type("datetime")?; @@ -590,7 +961,9 @@ impl<'a, 'b> ser::SerializeSeq for SerializeSeq<'a, 'b> { parent: &self.ser.state, first: &self.first, type_: &self.type_, + len: self.len, }, + settings: self.ser.settings.clone(), })?; self.first.set(false); Ok(()) @@ -599,7 +972,19 @@ impl<'a, 'b> ser::SerializeSeq for SerializeSeq<'a, 'b> { fn end(self) -> Result<(), Error> { match self.type_.get() { Some("table") => return Ok(()), - Some(_) => self.ser.dst.push_str("]"), + Some(_) => { + match (self.len, &self.ser.settings.array) { + (Some(0...1), _) | (_, &None) => { + self.ser.dst.push_str("]"); + }, + (_, &Some(ref a)) => { + if a.trailing_comma { + self.ser.dst.push_str(","); + } + self.ser.dst.push_str("\n]"); + }, + } + } None => { assert!(self.first.get()); self.ser.emit_key("array")?; @@ -650,6 +1035,7 @@ impl<'a, 'b> ser::SerializeMap for SerializeTable<'a, 'b> { first: first, table_emitted: table_emitted, }, + settings: ser.settings.clone(), }); match res { Ok(()) => first.set(false), @@ -664,7 +1050,7 @@ impl<'a, 'b> ser::SerializeMap for SerializeTable<'a, 'b> { fn end(self) -> Result<(), Error> { match self { SerializeTable::Datetime(_) => panic!(), // shouldn't be possible - SerializeTable::Table { mut ser, first, .. } => { + SerializeTable::Table { ser, first, .. } => { if first.get() { let state = ser.state.clone(); ser.emit_table_header(&state)?; @@ -705,6 +1091,7 @@ impl<'a, 'b> ser::SerializeStruct for SerializeTable<'a, 'b> { first: first, table_emitted: table_emitted, }, + settings: ser.settings.clone(), }); match res { Ok(()) => first.set(false), @@ -717,6 +1104,15 @@ impl<'a, 'b> ser::SerializeStruct for SerializeTable<'a, 'b> { } fn end(self) -> Result<(), Error> { + match self { + SerializeTable::Datetime(_) => {}, + SerializeTable::Table { ser, first, .. } => { + if first.get() { + let state = ser.state.clone(); + ser.emit_table_header(&state)?; + } + } + } Ok(()) } } diff --git a/tests/formatting.rs b/tests/formatting.rs index 10fb165..4ba1418 100644 --- a/tests/formatting.rs +++ b/tests/formatting.rs @@ -1,4 +1,3 @@ -extern crate serde; #[macro_use] extern crate serde_derive; extern crate toml; diff --git a/tests/pretty.rs b/tests/pretty.rs new file mode 100644 index 0000000..19ed22d --- /dev/null +++ b/tests/pretty.rs @@ -0,0 +1,308 @@ +extern crate toml; +extern crate serde; + +use serde::ser::Serialize; + +const NO_PRETTY: &'static str = "\ +[example] +array = [\"item 1\", \"item 2\"] +empty = [] +oneline = \"this has no newlines.\" +text = \"\\nthis is the first line\\nthis is the second line\\n\" +"; + +#[test] +fn no_pretty() { + let toml = NO_PRETTY; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + value.serialize(&mut toml::Serializer::new(&mut result)).unwrap(); + println!("EXPECTED:\n{}", toml); + println!("\nRESULT:\n{}", result); + assert_eq!(toml, &result); +} + +#[test] +fn disable_pretty() { + let toml = NO_PRETTY; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + { + let mut serializer = toml::Serializer::pretty(&mut result); + serializer.pretty_string(false); + serializer.pretty_array(false); + value.serialize(&mut serializer).unwrap(); + } + println!("EXPECTED:\n{}", toml); + println!("\nRESULT:\n{}", result); + assert_eq!(toml, &result); +} + +const PRETTY_STD: &'static str = "\ +[example] +array = [ + 'item 1', + 'item 2', +] +empty = [] +one = ['one'] +oneline = 'this has no newlines.' +text = ''' +this is the first line +this is the second line +''' +"; + +#[test] +fn pretty_std() { + let toml = PRETTY_STD; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + value.serialize(&mut toml::Serializer::pretty(&mut result)).unwrap(); + println!("EXPECTED:\n{}", toml); + println!("\nRESULT:\n{}", result); + assert_eq!(toml, &result); +} + + +const PRETTY_INDENT_2: &'static str = "\ +[example] +array = [ + 'item 1', + 'item 2', +] +empty = [] +one = ['one'] +oneline = 'this has no newlines.' +text = ''' +this is the first line +this is the second line +''' +three = [ + 'one', + 'two', + 'three', +] +"; + +#[test] +fn pretty_indent_2() { + let toml = PRETTY_INDENT_2; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + { + let mut serializer = toml::Serializer::pretty(&mut result); + serializer.pretty_array_indent(2); + value.serialize(&mut serializer).unwrap(); + } + println!(">> Result:\n{}", result); + assert_eq!(toml, &result); +} + +const PRETTY_INDENT_2_OTHER: &'static str = "\ +[example] +array = [ + \"item 1\", + \"item 2\", +] +empty = [] +oneline = \"this has no newlines.\" +text = \"\\nthis is the first line\\nthis is the second line\\n\" +"; + + +#[test] +/// Test pretty indent when gotten the other way +fn pretty_indent_2_other() { + let toml = PRETTY_INDENT_2_OTHER; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + { + let mut serializer = toml::Serializer::new(&mut result); + serializer.pretty_array_indent(2); + value.serialize(&mut serializer).unwrap(); + } + assert_eq!(toml, &result); +} + + +const PRETTY_ARRAY_NO_COMMA: &'static str = "\ +[example] +array = [ + \"item 1\", + \"item 2\" +] +empty = [] +oneline = \"this has no newlines.\" +text = \"\\nthis is the first line\\nthis is the second line\\n\" +"; +#[test] +/// Test pretty indent when gotten the other way +fn pretty_indent_array_no_comma() { + let toml = PRETTY_ARRAY_NO_COMMA; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + { + let mut serializer = toml::Serializer::new(&mut result); + serializer.pretty_array_trailing_comma(false); + value.serialize(&mut serializer).unwrap(); + } + assert_eq!(toml, &result); +} + + +const PRETTY_NO_STRING: &'static str = "\ +[example] +array = [ + \"item 1\", + \"item 2\", +] +empty = [] +oneline = \"this has no newlines.\" +text = \"\\nthis is the first line\\nthis is the second line\\n\" +"; +#[test] +/// Test pretty indent when gotten the other way +fn pretty_no_string() { + let toml = PRETTY_NO_STRING; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + { + let mut serializer = toml::Serializer::pretty(&mut result); + serializer.pretty_string(false); + value.serialize(&mut serializer).unwrap(); + } + assert_eq!(toml, &result); +} + +const PRETTY_TRICKY: &'static str = r##"[example] +f = "\f" +glass = ''' +Nothing too unusual, except that I can eat glass in: +- Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα. +- Polish: Mogę jeść szkło, i mi nie szkodzi. +- Hindi: मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती. +- Japanese: 私はガラスを食べられます。それは私を傷つけません。 +''' +r = "\r" +r_newline = """ +\r +""" +single = '''this is a single line but has '' cuz it's tricky''' +single_tricky = "single line with ''' in it" +tabs = ''' +this is pretty standard + except for some tabs right here +''' +text = """ +this is the first line. +This has a ''' in it and \"\"\" cuz it's tricky yo +Also ' and \" because why not +this is the fourth line +""" +"##; + +#[test] +fn pretty_tricky() { + let toml = PRETTY_TRICKY; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + value.serialize(&mut toml::Serializer::pretty(&mut result)).unwrap(); + println!("EXPECTED:\n{}", toml); + println!("\nRESULT:\n{}", result); + assert_eq!(toml, &result); +} + +const PRETTY_TABLE_ARRAY: &'static str = r##"[[array]] +key = 'foo' + +[[array]] +key = 'bar' + +[abc] +doc = 'this is a table' + +[example] +single = 'this is a single line string' +"##; + +#[test] +fn pretty_table_array() { + let toml = PRETTY_TABLE_ARRAY; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + value.serialize(&mut toml::Serializer::pretty(&mut result)).unwrap(); + println!("EXPECTED:\n{}", toml); + println!("\nRESULT:\n{}", result); + assert_eq!(toml, &result); +} + +const TABLE_ARRAY: &'static str = r##"[[array]] +key = "foo" + +[[array]] +key = "bar" + +[abc] +doc = "this is a table" + +[example] +single = "this is a single line string" +"##; + +#[test] +fn table_array() { + let toml = TABLE_ARRAY; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + value.serialize(&mut toml::Serializer::new(&mut result)).unwrap(); + println!("EXPECTED:\n{}", toml); + println!("\nRESULT:\n{}", result); + assert_eq!(toml, &result); +} + +const PRETTY_TRICKY_NON_LITERAL: &'static str = r##"[example] +f = "\f" +glass = """ +Nothing too unusual, except that I can eat glass in: +- Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα. +- Polish: Mogę jeść szkło, i mi nie szkodzi. +- Hindi: मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती. +- Japanese: 私はガラスを食べられます。それは私を傷つけません。 +""" +plain = """ +This has a couple of lines +Because it likes to. +""" +r = "\r" +r_newline = """ +\r +""" +single = "this is a single line but has '' cuz it's tricky" +single_tricky = "single line with ''' in it" +tabs = """ +this is pretty standard +\texcept for some \ttabs right here +""" +text = """ +this is the first line. +This has a ''' in it and \"\"\" cuz it's tricky yo +Also ' and \" because why not +this is the fourth line +""" +"##; + +#[test] +fn pretty_tricky_non_literal() { + let toml = PRETTY_TRICKY_NON_LITERAL; + let value: toml::Value = toml::from_str(toml).unwrap(); + let mut result = String::with_capacity(128); + { + let mut serializer = toml::Serializer::pretty(&mut result); + serializer.pretty_string_literal(false); + value.serialize(&mut serializer).unwrap(); + } + println!("EXPECTED:\n{}", toml); + println!("\nRESULT:\n{}", result); + assert_eq!(toml, &result); +} diff --git a/tests/serde.rs b/tests/serde.rs index 3ae2bbd..57fa5db 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -525,3 +525,54 @@ fn newtypes() { Table(map! { b: Integer(2) }), } } + +#[test] +fn newtypes2() { + #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] + struct A { + b: B + } + + #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] + struct B(Option<C>); + + #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)] + struct C { + x: u32, + y: u32, + z: u32 + } + + equivalent! { + A { b: B(Some(C { x: 0, y: 1, z: 2 })) }, + Table(map! { + b: Table(map! { + x: Integer(0), + y: Integer(1), + z: Integer(2) + }) + }), + } +} + +#[derive(Debug, Default, PartialEq, Serialize, Deserialize)] +struct CanBeEmpty { + a: Option<String>, + b: Option<String>, +} + +#[test] +fn table_structs_empty() { + let text = "[bar]\n\n[baz]\n\n[bazv]\na = \"foo\"\n\n[foo]\n"; + let value: BTreeMap<String, CanBeEmpty> = toml::from_str(text).unwrap(); + let mut expected: BTreeMap<String, CanBeEmpty> = BTreeMap::new(); + expected.insert("bar".to_string(), CanBeEmpty::default()); + expected.insert("baz".to_string(), CanBeEmpty::default()); + expected.insert( + "bazv".to_string(), + CanBeEmpty {a: Some("foo".to_string()), b: None}, + ); + expected.insert("foo".to_string(), CanBeEmpty::default()); + assert_eq!(value, expected); + assert_eq!(toml::to_string(&value).unwrap(), text); +} diff --git a/tests/valid.rs b/tests/valid.rs index e7577ad..b186800 100644 --- a/tests/valid.rs +++ b/tests/valid.rs @@ -1,7 +1,9 @@ extern crate toml; +extern crate serde; extern crate serde_json; -use toml::Value as Toml; +use toml::{Value as Toml, to_string_pretty}; +use serde::ser::Serialize; use serde_json::Value as Json; fn to_json(toml: toml::Value) -> Json { @@ -40,10 +42,52 @@ fn to_json(toml: toml::Value) -> Json { } } -fn run(toml: &str, json: &str) { - println!("parsing:\n{}", toml); - let toml: Toml = toml.parse().unwrap(); - let json: Json = json.parse().unwrap(); +fn run_pretty(toml: Toml) { + // Assert toml == json + println!("### pretty round trip parse."); + + // standard pretty + let toml_raw = to_string_pretty(&toml).expect("to string"); + let toml2 = toml_raw.parse().expect("from string"); + assert_eq!(toml, toml2); + + // pretty with indent 2 + let mut result = String::with_capacity(128); + { + let mut serializer = toml::Serializer::pretty(&mut result); + serializer.pretty_array_indent(2); + toml.serialize(&mut serializer).expect("to string"); + } + assert_eq!(toml, result.parse().expect("from str")); + result.clear(); + { + let mut serializer = toml::Serializer::new(&mut result); + serializer.pretty_array_trailing_comma(false); + toml.serialize(&mut serializer).expect("to string"); + } + assert_eq!(toml, result.parse().expect("from str")); + result.clear(); + { + let mut serializer = toml::Serializer::pretty(&mut result); + serializer.pretty_string(false); + toml.serialize(&mut serializer).expect("to string"); + assert_eq!(toml, toml2); + } + assert_eq!(toml, result.parse().expect("from str")); + result.clear(); + { + let mut serializer = toml::Serializer::pretty(&mut result); + serializer.pretty_array(false); + toml.serialize(&mut serializer).expect("to string"); + assert_eq!(toml, toml2); + } + assert_eq!(toml, result.parse().expect("from str")); +} + +fn run(toml_raw: &str, json_raw: &str) { + println!("parsing:\n{}", toml_raw); + let toml: Toml = toml_raw.parse().unwrap(); + let json: Json = json_raw.parse().unwrap(); // Assert toml == json let toml_json = to_json(toml.clone()); @@ -56,6 +100,7 @@ fn run(toml: &str, json: &str) { println!("round trip parse: {}", toml); let toml2 = toml.to_string().parse().unwrap(); assert_eq!(toml, toml2); + run_pretty(toml); } macro_rules! test( ($name:ident, $toml:expr, $json:expr) => ( @@ -162,6 +207,9 @@ test!(table_empty, test!(table_sub_empty, include_str!("valid/table-sub-empty.toml"), include_str!("valid/table-sub-empty.json")); +test!(table_multi_empty, + include_str!("valid/table-multi-empty.toml"), + include_str!("valid/table-multi-empty.json")); test!(table_whitespace, include_str!("valid/table-whitespace.toml"), include_str!("valid/table-whitespace.json")); diff --git a/tests/valid/table-multi-empty.json b/tests/valid/table-multi-empty.json new file mode 100644 index 0000000..a6e17c9 --- /dev/null +++ b/tests/valid/table-multi-empty.json @@ -0,0 +1,5 @@ +{ + "a": { "b": {} }, + "b": {}, + "c": { "a": {} } +} diff --git a/tests/valid/table-multi-empty.toml b/tests/valid/table-multi-empty.toml new file mode 100644 index 0000000..2266ed2 --- /dev/null +++ b/tests/valid/table-multi-empty.toml @@ -0,0 +1,5 @@ +[a] +[a.b] +[b] +[c] +[c.a] |