pub fn decode_output(escaped: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(escaped.len());
let mut i = 0;
while i < escaped.len() {
let escape = escaped[i] == b'\\' && i + 3 < escaped.len();
let Some(byte) = escape
.then(|| parse_octal(&escaped[i + 1..i + 4]))
.flatten()
else {
out.push(escaped[i]);
i += 1;
continue;
};
out.push(byte);
i += 4;
}
out
}
fn parse_octal(digits: &[u8]) -> Option<u8> {
let mut value: u16 = 0;
for &d in digits {
if !d.is_ascii_digit() || d > b'7' {
return None;
}
value = value * 8 + u16::from(d - b'0');
}
u8::try_from(value).ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_ascii_passes_through() {
assert_eq!(decode_output(b"hello world"), b"hello world");
}
#[test]
fn decodes_escaped_backslash() {
assert_eq!(decode_output(br"a\134b"), b"a\\b");
}
#[test]
fn decodes_control_bytes() {
assert_eq!(decode_output(br"\033[0m"), vec![0x1b, b'[', b'0', b'm']);
}
#[test]
fn trailing_backslash_without_digits_stays_literal() {
assert_eq!(decode_output(br"ab\"), b"ab\\");
}
#[test]
fn non_octal_after_backslash_stays_literal() {
assert_eq!(decode_output(br"\\89"), b"\\\\89");
}
#[test]
fn trailing_escape_at_end_of_input_decodes() {
assert_eq!(decode_output(br"\033"), vec![0x1b]);
assert_eq!(decode_output(br"x\000"), vec![b'x', 0x00]);
}
#[test]
fn high_bytes_pass_through_verbatim() {
assert_eq!(
decode_output(&[0xff, b'a', 0xc3, 0xa9]),
vec![0xff, b'a', 0xc3, 0xa9]
);
}
}