aboutsummaryrefslogtreecommitdiff
path: root/src/ser.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/ser.rs')
-rw-r--r--src/ser.rs468
1 files changed, 432 insertions, 36 deletions
diff --git a/src/ser.rs b/src/ser.rs
index 77db867..58c0b42 100644
--- a/src/ser.rs
+++ b/src/ser.rs
@@ -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(())
}
}