Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add escape_strings option to PrettyConfig #426

Merged
merged 1 commit into from
Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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###\"#\"##\"###");
}