Repository: toml-rs/toml-rs (Rust)

Issue:
Can't round trip a pretty-printed string ending in a single quote
Given a string that ends in a single quote character (`'`), `to_string_pretty` (or `toml::Serializer::pretty` in the end) generates output that can't be read back. Here's a test program for 0.4.7:

```rust
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate toml;

#[derive(Deserialize, Serialize, Debug, PartialEq)]
struct Something {
    foo: String,
}

fn main() {
    let foo = Something {
        foo: "here's a single quote at the end: '".to_owned(),
    };
    let foo_toml = toml::to_string_pretty(&foo).unwrap();
    println!("{}", foo_toml);

    let foo_roundtrip: Something = toml::from_str(&foo_toml).unwrap();
    assert_eq!(foo, foo_roundtrip);
}
```

It first outputs the following:

```toml
foo = '''here's a single quote at the end: ''''
```

And then blows up like so on the second `unwrap`:

```rust
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error { inner: ErrorInner { kind: NewlineInString, line: Some(0), col: 47, message: "", key: [] } }', libcore/result.rs:945:5
stack backtrace:
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
             at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::print
             at libstd/sys_common/backtrace.rs:71
             at libstd/sys_common/backtrace.rs:59
   2: std::panicking::default_hook::{{closure}}
             at libstd/panicking.rs:211
   3: std::panicking::default_hook
             at libstd/panicking.rs:227
   4: std::panicking::rust_panic_with_hook
             at libstd/panicking.rs:475
   5: std::panicking::continue_panic_fmt
             at libstd/panicking.rs:390
   6: rust_begin_unwind
             at libstd/panicking.rs:325
   7: core::panicking::panic_fmt
             at libcore/panicking.rs:77
   8: core::result::unwrap_failed
             at /checkout/src/libcore/macros.rs:26
   9: <core::result::Result<T, E>>::unwrap
             at /checkout/src/libcore/result.rs:782
  10: foo::main
             at src/main.rs:18
  11: std::rt::lang_start::{{closure}}
             at /checkout/src/libstd/rt.rs:74
  12: std::panicking::try::do_call
             at libstd/rt.rs:59
             at libstd/panicking.rs:310
  13: __rust_maybe_catch_panic
             at libpanic_unwind/lib.rs:105
  14: std::rt::lang_start_internal
             at libstd/panicking.rs:289
             at libstd/panic.rs:392
             at libstd/rt.rs:58
  15: std::rt::lang_start
             at /checkout/src/libstd/rt.rs:74
  16: main
  17: __libc_start_main
  18: _start
```

From a quick reading of the TOML 0.5.0 spec, that `''''` is probably invalid, and this should be emitted using `"` and escaping instead of `'`. (I see that GitHub's syntax highlighter also doesn't like the `''''`, which maybe counts for something.)


Discussion:
Just FYI, single quotes can end a multiline literal string as of the TOML v1.0.0 spec. An output of `foo = '''here's a single quote at the end: ''''` is now correct, and it ought to round-trip.



Patch:
diff --git a/src/ser.rs b/src/ser.rs
index 9d989db6..9f36f591 100644
--- a/src/ser.rs
+++ b/src/ser.rs
@@ -585,6 +585,10 @@ impl<'a> Serializer<'a> {
                     }
                 }
             }
+            if can_be_pretty && found_singles > 0 && value.ends_with('\'') {
+                // We cannot escape the ending quote so we must use """
+                can_be_pretty = false;
+            }
             if !can_be_pretty {
                 debug_assert!(ty != Type::OnelineTripple);
                 return Repr::Std(ty);


Test patch:
diff --git a/test-suite/tests/valid.rs b/test-suite/tests/valid.rs
index d032ba5e..7c1691c0 100644
--- a/test-suite/tests/valid.rs
+++ b/test-suite/tests/valid.rs
@@ -250,3 +250,7 @@ test!(table_array_nest_no_keys,
 test!(dotted_keys,
       include_str!("valid/dotted-keys.toml"),
       include_str!("valid/dotted-keys.json"));
+
+test!(quote_surrounded_value,
+      include_str!("valid/quote-surrounded-value.toml"),
+      include_str!("valid/quote-surrounded-value.json"));
diff --git a/test-suite/tests/valid/quote-surrounded-value.json b/test-suite/tests/valid/quote-surrounded-value.json
new file mode 100644
index 00000000..84495cf2
--- /dev/null
+++ b/test-suite/tests/valid/quote-surrounded-value.json
@@ -0,0 +1,10 @@
+{
+  "double": {
+    "type": "string",
+    "value": "\"double quotes here\""
+  },
+  "single": {
+    "type": "string",
+    "value": "'single quotes here'"
+  }
+}
diff --git a/test-suite/tests/valid/quote-surrounded-value.toml b/test-suite/tests/valid/quote-surrounded-value.toml
new file mode 100644
index 00000000..dc8697e2
--- /dev/null
+++ b/test-suite/tests/valid/quote-surrounded-value.toml
@@ -0,0 +1,2 @@
+double = '"double quotes here"'
+single = "'single quotes here'"