Merge pull request #197 from vitiral/pretty

close #119: allow "pretty" configuration
This commit is contained in:
Alex Crichton 2017-07-24 18:22:33 -05:00 committed by GitHub
commit 05a8c73439
3 changed files with 391 additions and 10 deletions

View file

@ -161,7 +161,7 @@ mod datetime;
pub mod ser; pub mod ser;
#[doc(no_inline)] #[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; pub mod de;
#[doc(no_inline)] #[doc(no_inline)]
pub use de::{from_slice, from_str, Deserializer}; pub use de::{from_slice, from_str, Deserializer};

View file

@ -93,6 +93,18 @@ pub fn to_string<T: ?Sized>(value: &T) -> Result<String, Error>
Ok(dst) 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. /// Errors that can occur when serializing a type.
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum Error { pub enum Error {
@ -137,6 +149,31 @@ pub enum Error {
__Nonexhaustive, __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,
}
}
}
#[derive(Debug, Default, Clone)]
#[doc(hidden)]
/// Internal struct for holding serialization settings
struct Settings {
array: Option<ArraySettings>,
pretty_string: bool,
}
/// Serialization implementation for TOML. /// Serialization implementation for TOML.
/// ///
/// This structure implements serialization support for TOML to serialize an /// This structure implements serialization support for TOML to serialize an
@ -149,6 +186,7 @@ pub enum Error {
pub struct Serializer<'a> { pub struct Serializer<'a> {
dst: &'a mut String, dst: &'a mut String,
state: State<'a>, state: State<'a>,
settings: Settings,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -194,9 +232,130 @@ impl<'a> Serializer<'a> {
Serializer { Serializer {
dst: dst, dst: dst,
state: State::End, state: State::End,
settings: 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: Settings {
array: Some(ArraySettings::pretty()),
pretty_string: true,
},
}
}
/// Enable or Disable pretty strings
///
/// If enabled, strings with one or more newline character will use the `'''` syntax.
///
/// # Examples
///
/// Instead of:
///
/// ```ignore
/// single = "no newlines"
/// text = "\nfoo\nbar\n"
/// ```
///
/// You will have:
///
/// ```ignore
/// single = "no newlines"
/// text = '''
/// foo
/// bar
/// '''
/// ```
pub fn pretty_string(&mut self, value: bool) -> &mut Self {
self.settings.pretty_string = value;
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:
///
/// ```ignore
/// array = ["foo", "bar"]
/// ```
///
/// You will have:
///
/// ```ignore
/// array = [
/// "foo",
/// "bar",
/// ]
/// ```
pub fn pretty_array(&mut self, value: bool) -> &mut Self {
self.settings.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 self.settings.array {
a.indent = value;
false
} else {
true
};
if use_default {
let mut array = ArraySettings::pretty();
array.indent = value;
self.settings.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 self.settings.array {
a.trailing_comma = value;
false
} else {
true
};
if use_default {
let mut array = ArraySettings::pretty();
array.trailing_comma = value;
self.settings.array = Some(array);
}
self
}
fn display<T: fmt::Display>(&mut self, fn display<T: fmt::Display>(&mut self,
t: T, t: T,
type_: &'static str) -> Result<(), Error> { type_: &'static str) -> Result<(), Error> {
@ -241,10 +400,24 @@ impl<'a> Serializer<'a> {
} }
fn emit_array(&mut self, first: &Cell<bool>) -> Result<(), Error> { fn emit_array(&mut self, first: &Cell<bool>) -> Result<(), Error> {
if first.get() { match self.settings.array {
self.dst.push_str("["); Some(ref a) => {
} else { if first.get() {
self.dst.push_str(", "); self.dst.push_str("[\n")
} else {
self.dst.push_str(",\n")
}
for _ in 0..a.indent {
self.dst.push_str(" ");
}
},
None => {
if first.get() {
self.dst.push_str("[")
} else {
self.dst.push_str(", ")
}
},
} }
Ok(()) Ok(())
} }
@ -283,15 +456,36 @@ impl<'a> Serializer<'a> {
} }
fn emit_str(&mut self, value: &str) -> Result<(), Error> { fn emit_str(&mut self, value: &str) -> Result<(), Error> {
drop(write!(self.dst, "\"")); let do_pretty = if self.settings.pretty_string {
value.contains('\n')
} else {
false
};
if do_pretty {
drop(write!(self.dst, "'''\n"));
} else {
drop(write!(self.dst, "\""));
}
for ch in value.chars() { for ch in value.chars() {
match ch { match ch {
'\u{8}' => drop(write!(self.dst, "\\b")), '\u{8}' => drop(write!(self.dst, "\\b")),
'\u{9}' => drop(write!(self.dst, "\\t")), '\u{9}' => drop(write!(self.dst, "\\t")),
'\u{a}' => drop(write!(self.dst, "\\n")), '\u{a}' => {
if do_pretty {
drop(write!(self.dst, "\n"));
} else {
drop(write!(self.dst, "\\n"));
}
},
'\u{c}' => drop(write!(self.dst, "\\f")), '\u{c}' => drop(write!(self.dst, "\\f")),
'\u{d}' => drop(write!(self.dst, "\\r")), '\u{d}' => drop(write!(self.dst, "\\r")),
'\u{22}' => drop(write!(self.dst, "\\\"")), '\u{22}' => {
if do_pretty {
drop(write!(self.dst, "\""))
} else {
drop(write!(self.dst, "\\\""))
}
},
'\u{5c}' => drop(write!(self.dst, "\\\\")), '\u{5c}' => drop(write!(self.dst, "\\\\")),
c if c < '\u{1f}' => { c if c < '\u{1f}' => {
drop(write!(self.dst, "\\u{:04X}", ch as u32)) drop(write!(self.dst, "\\u{:04X}", ch as u32))
@ -299,7 +493,11 @@ impl<'a> Serializer<'a> {
ch => drop(write!(self.dst, "{}", ch)), ch => drop(write!(self.dst, "{}", ch)),
} }
} }
drop(write!(self.dst, "\"")); if do_pretty {
drop(write!(self.dst, "'''"));
} else {
drop(write!(self.dst, "\""));
}
Ok(()) Ok(())
} }
@ -591,6 +789,7 @@ impl<'a, 'b> ser::SerializeSeq for SerializeSeq<'a, 'b> {
first: &self.first, first: &self.first,
type_: &self.type_, type_: &self.type_,
}, },
settings: self.ser.settings.clone(),
})?; })?;
self.first.set(false); self.first.set(false);
Ok(()) Ok(())
@ -599,7 +798,19 @@ impl<'a, 'b> ser::SerializeSeq for SerializeSeq<'a, 'b> {
fn end(self) -> Result<(), Error> { fn end(self) -> Result<(), Error> {
match self.type_.get() { match self.type_.get() {
Some("table") => return Ok(()), Some("table") => return Ok(()),
Some(_) => self.ser.dst.push_str("]"), Some(_) => {
match self.ser.settings.array {
Some(ref a) => {
if a.trailing_comma {
self.ser.dst.push_str(",");
}
self.ser.dst.push_str("\n]");
},
None => {
self.ser.dst.push_str("]");
}
}
}
None => { None => {
assert!(self.first.get()); assert!(self.first.get());
self.ser.emit_key("array")?; self.ser.emit_key("array")?;
@ -650,6 +861,7 @@ impl<'a, 'b> ser::SerializeMap for SerializeTable<'a, 'b> {
first: first, first: first,
table_emitted: table_emitted, table_emitted: table_emitted,
}, },
settings: ser.settings.clone(),
}); });
match res { match res {
Ok(()) => first.set(false), Ok(()) => first.set(false),
@ -705,6 +917,7 @@ impl<'a, 'b> ser::SerializeStruct for SerializeTable<'a, 'b> {
first: first, first: first,
table_emitted: table_emitted, table_emitted: table_emitted,
}, },
settings: ser.settings.clone(),
}); });
match res { match res {
Ok(()) => first.set(false), Ok(()) => first.set(false),

168
tests/pretty.rs Normal file
View file

@ -0,0 +1,168 @@
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 = []
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 = []
oneline = \"this has no newlines.\"
text = '''
this is the first line
this is the second line
'''
";
#[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();
}
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);
}