diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/de.rs | 12 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/ser.rs | 468 |
3 files changed, 444 insertions, 38 deletions
@@ -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(()) } } |