use std::borrow::Cow;
pub fn parse_string(input: &str) -> Cow<str> {
let input = match slice_middle(input) {
Some(input) => input,
None => {
warn!("Not a 'string', returning as-is: {:?}", input);
return Cow::Borrowed(input);
}
};
if !input.contains('\\') {
trace!("No escapes, returning as-is: {:?}", input);
return Cow::Borrowed(input);
}
let mut output = String::new();
let mut wants_escape = false;
for ch in input.chars() {
if wants_escape {
match escape_char(ch) {
Some(replacement) => {
trace!("Replacing backslash escape: \\{ch}");
output.push(replacement);
}
None => {
warn!("Invalid backslash escape found, ignoring: \\{ch}");
output.push('\\');
output.push(ch);
}
}
wants_escape = false;
} else if ch == '\\' {
wants_escape = true;
} else {
output.push(ch);
}
}
Cow::Owned(output)
}
fn slice_middle(input: &str) -> Option<&str> {
if input.len() < 2 || !input.starts_with('"') || !input.ends_with('"') {
return None;
}
let last = input.len() - 1;
Some(&input[1..last])
}
fn escape_char(ch: char) -> Option<char> {
let escaped = match ch {
'\\' => '\\',
'\"' => '\"',
'\'' => '\'',
'r' => '\r',
'n' => '\n',
't' => '\t',
_ => return None,
};
Some(escaped)
}
#[test]
fn test_parse_string() {
macro_rules! test {
($input:expr, $expected:expr, $variant:tt $(,)?) => {{
let actual = parse_string($input);
assert_eq!(
&actual, $expected,
"Actual string (left) doesn't match expected (right)"
);
assert!(
matches!(actual, Cow::$variant(_)),
"Outputted string of the incorrect variant",
);
}};
}
test!(r#""""#, "", Borrowed);
test!(r#""!""#, "!", Borrowed);
test!(r#""\"""#, "\"", Owned);
test!(r#""\'""#, "\'", Owned);
test!(r#""apple banana""#, "apple banana", Borrowed);
test!(r#""abc \\""#, "abc \\", Owned);
test!(r#""\n def""#, "\n def", Owned);
test!(
r#""abc \t (\\\t) \r (\\\r) def""#,
"abc \t (\\\t) \r (\\\r) def",
Owned,
);
test!(r#""abc \t \x \y \z \n""#, "abc \t \\x \\y \\z \n", Owned);
test!("'abc'", "'abc'", Borrowed);
test!("\"abc", "\"abc", Borrowed);
test!("foo", "foo", Borrowed);
}
#[test]
fn test_slice_middle() {
macro_rules! test {
($input:expr, $expected:expr $(,)?) => {{
let actual = slice_middle($input).expect("Invalid string input");
assert_eq!(
actual, $expected,
"Actual (left) doesn't match expected (right)",
);
}};
($input:expr $(,)?) => {{
assert!(
slice_middle($input).is_none(),
"Invalid string was accepted",
);
}};
}
test!(r#""""#, "");
test!(r#""!""#, "!");
test!(r#""abc""#, "abc");
test!(r#""apple banana cherry""#, "apple banana cherry");
test!("");
test!("\"");
test!("\"'");
test!("''");
test!("[]");
}