add """ for non-literals with newlines, clean up logic and add tests

This commit is contained in:
Garrett Berg 2017-07-28 09:12:21 -06:00
parent dabe477d64
commit 54a4833bc1
2 changed files with 116 additions and 62 deletions

View file

@ -458,14 +458,21 @@ impl<'a> Serializer<'a> {
} }
fn emit_str(&mut self, value: &str, is_key: bool) -> Result<(), Error> { fn emit_str(&mut self, value: &str, is_key: bool) -> Result<(), Error> {
/// Use ''' or '
#[derive(PartialEq)] #[derive(PartialEq)]
enum Multi { enum Type {
False, NewlineTripple,
True, OnelineTripple,
Single, OnelineSingle,
} }
fn do_pretty(value: &str) -> Option<(Multi, String)> {
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 // For doing pretty prints we store in a new String
// because there are too many cases where pretty cannot // because there are too many cases where pretty cannot
// work. We need to determine: // work. We need to determine:
@ -476,16 +483,18 @@ impl<'a> Serializer<'a> {
// Doing it any other way would require multiple passes // Doing it any other way would require multiple passes
// to determine if a pretty string works or not. // to determine if a pretty string works or not.
let mut out = String::with_capacity(value.len() * 2); let mut out = String::with_capacity(value.len() * 2);
let mut multi = Multi::False; let mut ty = Type::OnelineSingle;
// found consecutive single quotes // found consecutive single quotes
let mut max_found_singles = 0; let mut max_found_singles = 0;
let mut found_singles = 0; let mut found_singles = 0;
let mut can_be_pretty = true;
for ch in value.chars() { for ch in value.chars() {
if can_be_pretty {
if ch == '\'' { if ch == '\'' {
found_singles += 1; found_singles += 1;
if found_singles >= 3 { if found_singles >= 3 {
return None; can_be_pretty = false;
} }
} else { } else {
if found_singles > max_found_singles { if found_singles > max_found_singles {
@ -495,49 +504,71 @@ impl<'a> Serializer<'a> {
} }
match ch { match ch {
'\t' => {}, '\t' => {},
'\n' => multi = Multi::True, '\n' => ty = Type::NewlineTripple,
// note that the following are invalid: \b \f \r // note that the following are invalid: \b \f \r
c if c < '\u{1f}' => return None, // Invalid control character c if c < '\u{1f}' => can_be_pretty = false, // Invalid control character
_ => {} _ => {}
} }
out.push(ch); out.push(ch);
} else {
// the string cannot be represented as pretty,
// still check if it should be multiline
if ch == '\n' {
ty = Type::NewlineTripple;
}
}
}
if !can_be_pretty {
debug_assert!(ty != Type::OnelineTripple);
return Repr::Std(ty);
} }
if found_singles > max_found_singles { if found_singles > max_found_singles {
max_found_singles = found_singles; max_found_singles = found_singles;
} }
debug_assert!(max_found_singles < 3); debug_assert!(max_found_singles < 3);
if multi == Multi::False && max_found_singles >= 1 { if ty == Type::OnelineSingle && max_found_singles >= 1 {
// no newlines, but must use ''' because it has ' in it // no newlines, but must use ''' because it has ' in it
multi = Multi::Single; ty = Type::OnelineTripple;
} }
Some((multi, out)) Repr::Literal(out, ty)
} }
let pretty = if !is_key && self.settings.pretty_string { let repr = if !is_key && self.settings.pretty_string {
do_pretty(value) do_pretty(value)
} else { } else {
None Repr::Std(Type::OnelineSingle)
}; };
if let Some((multi, s)) = pretty { match repr {
Repr::Literal(literal, ty) => {
// A pretty string // A pretty string
match multi { match ty {
Multi::True => self.dst.push_str("'''\n"), Type::NewlineTripple => self.dst.push_str("'''\n"),
Multi::Single => self.dst.push_str("'''"), Type::OnelineTripple => self.dst.push_str("'''"),
Multi::False => self.dst.push('\''), Type::OnelineSingle => self.dst.push('\''),
} }
self.dst.push_str(&s); self.dst.push_str(&literal);
match multi { match ty {
Multi::False => self.dst.push('\''), Type::OnelineSingle => self.dst.push('\''),
_ => self.dst.push_str("'''"), _ => self.dst.push_str("'''"),
} }
} else { },
// Not a pretty string Repr::Std(ty) => {
drop(write!(self.dst, "\"")); match ty {
Type::NewlineTripple => self.dst.push_str("\"\"\"\n"),
Type::OnelineSingle => self.dst.push('"'),
_ => unreachable!(),
}
for ch in value.chars() { for ch in value.chars() {
match ch { match ch {
'\u{8}' => self.dst.push_str("\\b"), '\u{8}' => self.dst.push_str("\\b"),
'\u{9}' => self.dst.push_str("\\t"), '\u{9}' => self.dst.push_str("\\t"),
'\u{a}' => self.dst.push_str("\\n"), '\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{c}' => self.dst.push_str("\\f"),
'\u{d}' => self.dst.push_str("\\r"), '\u{d}' => self.dst.push_str("\\r"),
'\u{22}' => self.dst.push_str("\\\""), '\u{22}' => self.dst.push_str("\\\""),
@ -546,7 +577,12 @@ impl<'a> Serializer<'a> {
ch => self.dst.push(ch), ch => self.dst.push(ch),
} }
} }
self.dst.push_str("\""); match ty {
Type::NewlineTripple => self.dst.push_str("\"\"\""),
Type::OnelineSingle => self.dst.push('"'),
_ => unreachable!(),
}
},
} }
Ok(()) Ok(())
} }

View file

@ -175,14 +175,32 @@ fn pretty_no_string() {
assert_eq!(toml, &result); assert_eq!(toml, &result);
} }
const PRETTY_TRICKY: &'static str = r"[example] const PRETTY_TRICKY: &'static str = r##"[example]
single = '''this is a single line but has '' for no reason''' f = "\f"
text = ''' glass = '''
this is the first line Nothing too unusual, except that I can eat glass in:
This has a ''\' in it for no reason - Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα.
this is the third line - 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 third line
"""
"##;
#[test] #[test]
fn pretty_tricky() { fn pretty_tricky() {