Skip to content

Commit

Permalink
Add escape_strings option to PrettyConfig (#426)
Browse files Browse the repository at this point in the history
  • Loading branch information
juntyr committed Oct 21, 2022
1 parent 27d2de4 commit e06dc75
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Error instead of panic when deserializing non-identifiers as field names ([#415](https://github.com/ron-rs/ron/pull/415))
- Breaking: Fix issue [#307](https://github.com/ron-rs/ron/issues/307) stack overflow with explicit recursion limits in serialising and deserialising ([#420](https://github.com/ron-rs/ron/pull/420))
- Fix issue [#423](https://github.com/ron-rs/ron/issues/423) deserialising an identifier into a borrowed str ([#424](https://github.com/ron-rs/ron/pull/424))
- Add `escape_strings` option to `PrettyConfig` to allow serialising with or without escaping ([#426](https://github.com/ron-rs/ron/pull/426))

## [0.8.0] - 2022-08-17

Expand Down
66 changes: 62 additions & 4 deletions src/ser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ pub struct PrettyConfig {
pub extensions: Extensions,
/// Enable compact arrays
pub compact_arrays: bool,
/// Whether to serialize strings as escaped strings,
/// or fall back onto raw strings if necessary.
pub escape_strings: bool,
}

impl PrettyConfig {
Expand Down Expand Up @@ -170,15 +173,17 @@ impl PrettyConfig {
self
}

/// Configures whether every array should be a single line (true) or a multi line one (false)
/// When false, `["a","b"]` (as well as any array) will serialize to
/// Configures whether every array should be a single line (`true`)
/// or a multi line one (`false`).
///
/// When `false`, `["a","b"]` (as well as any array) will serialize to
/// `
/// [
/// "a",
/// "b",
/// ]
/// `
/// When true, `["a","b"]` (as well as any array) will serialize to `["a","b"]`
/// When `true`, `["a","b"]` (as well as any array) will serialize to `["a","b"]`
///
/// Default: `false`
pub fn compact_arrays(mut self, compact_arrays: bool) -> Self {
Expand All @@ -195,6 +200,26 @@ impl PrettyConfig {

self
}

/// Configures whether strings should be serialized using escapes (true)
/// or fall back to raw strings if the string contains a `"` (false).
///
/// When `true`, "a\nb" will serialize to
/// `
/// "a\nb"
/// `
/// When `false`, "a\nb" will instead serialize to
/// `
/// "a
/// b"
/// `
///
/// Default: `true`
pub fn escape_strings(mut self, escape_strings: bool) -> Self {
self.escape_strings = escape_strings;

self
}
}

impl Default for PrettyConfig {
Expand All @@ -213,6 +238,7 @@ impl Default for PrettyConfig {
enumerate_arrays: false,
extensions: Extensions::empty(),
compact_arrays: false,
escape_strings: true,
}
}
}
Expand Down Expand Up @@ -302,6 +328,12 @@ impl<W: io::Write> Serializer<W> {
.map_or(Extensions::empty(), |&(ref config, _)| config.extensions)
}

fn escape_strings(&self) -> bool {
self.pretty
.as_ref()
.map_or(true, |&(ref config, _)| config.escape_strings)
}

fn start_indent(&mut self) -> Result<()> {
if let Some((ref config, ref mut pretty)) = self.pretty {
pretty.indent += 1;
Expand Down Expand Up @@ -356,6 +388,28 @@ impl<W: io::Write> Serializer<W> {
Ok(())
}

fn serialize_unescaped_or_raw_str(&mut self, value: &str) -> io::Result<()> {
if value.contains('"') {
let (_, num_consecutive_hashes) =
value.chars().fold((0, 0), |(count, max), c| match c {
'#' => (count + 1, max.max(count + 1)),
_ => (0_usize, max),
});
let hashes = vec![b'#'; num_consecutive_hashes + 1];
self.output.write_all(b"r")?;
self.output.write_all(&hashes)?;
self.output.write_all(b"\"")?;
self.output.write_all(value.as_bytes())?;
self.output.write_all(b"\"")?;
self.output.write_all(&hashes)?;
} else {
self.output.write_all(b"\"")?;
self.output.write_all(value.as_bytes())?;
self.output.write_all(b"\"")?;
}
Ok(())
}

fn serialize_sint(&mut self, value: impl Into<LargeSInt>) -> Result<()> {
// TODO optimize
write!(self.output, "{}", value.into())?;
Expand Down Expand Up @@ -495,7 +549,11 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer<W> {
}

fn serialize_str(self, v: &str) -> Result<()> {
self.serialize_escaped_str(v)?;
if self.escape_strings() {
self.serialize_escaped_str(v)?;
} else {
self.serialize_unescaped_or_raw_str(v)?;
}

Ok(())
}
Expand Down
60 changes: 60 additions & 0 deletions tests/425_escape_strings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use ron::{
de::from_str,
ser::{to_string, to_string_pretty, PrettyConfig},
};

fn test_string_roundtrip(s: &str, config: Option<PrettyConfig>) -> String {
let ser = match config {
Some(config) => to_string_pretty(s, config),
None => to_string(s),
}
.unwrap();

let de: String = from_str(&ser).unwrap();

assert_eq!(s, de);

ser
}

#[test]
fn test_escaped_string() {
let config = Some(PrettyConfig::default());

assert_eq!(test_string_roundtrip("a\nb", None), r#""a\nb""#);
assert_eq!(test_string_roundtrip("a\nb", config.clone()), r#""a\nb""#);

assert_eq!(test_string_roundtrip("", None), "\"\"");
assert_eq!(test_string_roundtrip("", config.clone()), "\"\"");

assert_eq!(test_string_roundtrip("\"", None), r#""\"""#);
assert_eq!(test_string_roundtrip("\"", config.clone()), r#""\"""#);

assert_eq!(test_string_roundtrip("#", None), "\"#\"");
assert_eq!(test_string_roundtrip("#", config.clone()), "\"#\"");

assert_eq!(test_string_roundtrip("\"#", None), r##""\"#""##);
assert_eq!(test_string_roundtrip("\"#", config.clone()), r##""\"#""##);

assert_eq!(test_string_roundtrip("#\"#", None), r##""#\"#""##);
assert_eq!(test_string_roundtrip("#\"#", config.clone()), r##""#\"#""##);

assert_eq!(test_string_roundtrip("#\"##", None), r###""#\"##""###);
assert_eq!(test_string_roundtrip("#\"##", config), r###""#\"##""###);
}

#[test]
fn test_unescaped_string() {
let config = Some(PrettyConfig::default().escape_strings(false));

assert_eq!(test_string_roundtrip("a\nb", config.clone()), "\"a\nb\"");
assert_eq!(test_string_roundtrip("", config.clone()), "\"\"");
assert_eq!(test_string_roundtrip("\"", config.clone()), "r#\"\"\"#");
assert_eq!(test_string_roundtrip("#", config.clone()), "\"#\"");
assert_eq!(test_string_roundtrip("\"#", config.clone()), "r##\"\"#\"##");
assert_eq!(
test_string_roundtrip("#\"#", config.clone()),
"r##\"#\"#\"##"
);
assert_eq!(test_string_roundtrip("#\"##", config), "r###\"#\"##\"###");
}

0 comments on commit e06dc75

Please sign in to comment.