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,77 +483,106 @@ 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 ch == '\'' { if can_be_pretty {
found_singles += 1; if ch == '\'' {
if found_singles >= 3 { found_singles += 1;
return None; 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 { } else {
if found_singles > max_found_singles { // the string cannot be represented as pretty,
max_found_singles = found_singles; // still check if it should be multiline
if ch == '\n' {
ty = Type::NewlineTripple;
} }
found_singles = 0
} }
match ch { }
'\t' => {}, if !can_be_pretty {
'\n' => multi = Multi::True, debug_assert!(ty != Type::OnelineTripple);
// note that the following are invalid: \b \f \r return Repr::Std(ty);
c if c < '\u{1f}' => return None, // Invalid control character
_ => {}
}
out.push(ch);
} }
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 {
// A pretty string Repr::Literal(literal, ty) => {
match multi { // A pretty string
Multi::True => self.dst.push_str("'''\n"), match ty {
Multi::Single => self.dst.push_str("'''"), Type::NewlineTripple => self.dst.push_str("'''\n"),
Multi::False => self.dst.push('\''), Type::OnelineTripple => self.dst.push_str("'''"),
} Type::OnelineSingle => self.dst.push('\''),
self.dst.push_str(&s);
match multi {
Multi::False => self.dst.push('\''),
_ => self.dst.push_str("'''"),
}
} else {
// Not a pretty string
drop(write!(self.dst, "\""));
for ch in value.chars() {
match ch {
'\u{8}' => self.dst.push_str("\\b"),
'\u{9}' => self.dst.push_str("\\t"),
'\u{a}' => self.dst.push_str("\\n"),
'\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),
} }
} self.dst.push_str(&literal);
self.dst.push_str("\""); match ty {
Type::OnelineSingle => self.dst.push('\''),
_ => self.dst.push_str("'''"),
}
},
Repr::Std(ty) => {
match ty {
Type::NewlineTripple => self.dst.push_str("\"\"\"\n"),
Type::OnelineSingle => self.dst.push('"'),
_ => unreachable!(),
}
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 => 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() {