use std::iter::Peekable;
pub(crate) fn unescape_bytes(escaped_string: &str) -> Vec<u8> {
let mut output = Vec::with_capacity(escaped_string.len());
let mut bytes = escaped_string.bytes().peekable();
while let Some(byte) = bytes.next() {
if byte != b'\\' {
output.push(byte);
continue;
}
if let Some(octal_byte) = try_parse_octal_escape(&mut bytes) {
output.push(octal_byte);
continue;
}
if let Some(next_byte) = bytes.next() {
let escaped = match next_byte {
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
b'\\' => b'\\',
b'"' => b'"',
_ => next_byte,
};
output.push(escaped);
}
}
output
}
pub(crate) fn try_parse_octal_escape<I: Iterator<Item = u8>>(
bytes: &mut Peekable<I>,
) -> Option<u8> {
let mut value = 0u8;
let mut parsed_digits = 0;
for _ in 0..3 {
if let Some(&byte) = bytes.peek() {
if (b'0'..=b'7').contains(&byte) {
let digit = byte - b'0';
value = value.wrapping_mul(8).wrapping_add(digit);
bytes.next(); parsed_digits += 1;
} else {
break;
}
}
}
if parsed_digits > 0 { Some(value) } else { None }
}
pub(crate) fn extract_value<'a>(line: &'a str, prefix: &str) -> Option<&'a str> {
line.trim()
.strip_prefix(prefix)
.map(|rest| rest.trim_matches(|character| character == ' ' || character == '"'))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unescape_bytes_plain_and_escapes() {
assert_eq!(unescape_bytes("hello world"), b"hello world");
assert_eq!(
unescape_bytes(r#"\n\r\t\\\""#),
vec![b'\n', b'\r', b'\t', b'\\', b'\"']
);
assert_eq!(unescape_bytes(r#"\001\377\07"#), vec![1, 255, 7]);
assert_eq!(unescape_bytes(r#"\x"#), b"x");
assert_eq!(unescape_bytes(r#"abc\"#), b"abc");
}
#[test]
fn try_parse_octal_escape_digits() {
let mut bytes1 = b"5abc".iter().copied().peekable();
assert_eq!(try_parse_octal_escape(&mut bytes1), Some(5));
let mut bytes2 = b"77xyz".iter().copied().peekable();
assert_eq!(try_parse_octal_escape(&mut bytes2), Some(63));
let mut bytes3 = b"377xyz".iter().copied().peekable();
assert_eq!(try_parse_octal_escape(&mut bytes3), Some(255));
let mut non_octal = b"89".iter().copied().peekable();
assert_eq!(try_parse_octal_escape(&mut non_octal), None);
}
#[test]
fn extract_value_prefixes_and_quotes() {
assert_eq!(
extract_value(r#" name: "my_test" "#, "name: "),
Some("my_test")
);
assert_eq!(
extract_value(" group_uid: 42 ", "group_uid: "),
Some("42")
);
assert_eq!(extract_value("other_field: 1", "name: "), None);
}
}