pub struct StringEscapeDecoder<'a> {
data: std::iter::Peekable<std::str::Chars<'a>>,
min_size: usize,
max_size: usize,
}
impl<'a> From<&'a str> for StringEscapeDecoder<'a> {
fn from(buffer: &'a str) -> Self {
let max_escapes = buffer.matches('\\').count();
StringEscapeDecoder {
data: buffer.chars().peekable(),
min_size: buffer.len() - max_escapes,
max_size: buffer.len(),
}
}
}
impl<'a> std::iter::Iterator for StringEscapeDecoder<'a> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
match self.data.next() {
Some('\\') => {
let (consume_next, char_to_emit) = match self.data.peek() {
Some('0') => (true, '\x00'),
Some('a') => (true, '\x07'),
Some('b') => (true, '\x08'),
Some('e') => (true, '\x1B'),
Some('f') => (true, '\x0C'),
Some('n') => (true, '\n'),
Some('r') => (true, '\r'),
Some('t') => (true, '\t'),
Some('v') => (true, '\x0B'),
Some('\\') => (true, '\\'),
Some('\'') => (true, '\''),
Some('"') => (true, '"'),
Some('?') => (true, '\x3F'),
_ => (false, '\\'),
};
if consume_next {
self.data.next();
self.min_size -= 2;
self.max_size -= 2;
} else {
self.min_size -= 1;
self.max_size -= 1;
};
Some(char_to_emit)
},
Some(c) => Some(c),
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.min_size, Some(self.max_size))
}
}
#[cfg(test)]
mod tests {
use super::StringEscapeDecoder as SED;
#[test]
fn no_effect_on_empty_string() {
let decoded = SED::from("").collect::<String>();
assert_eq!("", decoded);
}
#[test]
fn no_effect_when_there_are_no_escapes() {
let decoded = SED::from("definitely no escapes to decode").collect::<String>();
assert_eq!("definitely no escapes to decode", decoded);
}
#[test]
fn decodes_ascii_byte_sequence() {
let escaped_control_chars = vec![
"\\0", "\\a", "\\b", "\\e", "\\f", "\\n", "\\r", "\\t", "\\v", "\\", "\'", "\\\"",
"\\?",
];
let expected_control_chars = vec![
'\x00', '\x07', '\x08', '\x1B', '\x0C', '\n', '\r', '\t', '\x0B', '\\', '\'', '"', '?',
];
for (idx, escaped_char) in escaped_control_chars.iter().enumerate() {
let control_char = expected_control_chars[idx];
let input = format!("delim{}seperated{}list", escaped_char, escaped_char);
let expected = format!("delim{}seperated{}list", control_char, control_char);
let output: String = SED::from(input.as_str()).collect();
assert_eq!(expected, output, "Failed to decode: {}", input);
}
let decoded = SED::from("delim\\0seperated\\0list").collect::<String>();
assert_eq!("delim\0seperated\0list", decoded);
}
}