use crate::string::{NoNul, Word};
use std::num::NonZeroU8;
pub fn unescape_byte(byte: &u8) -> u8 {
match byte {
b':' => b';',
b's' => b' ',
b'r' => b'\r',
b'n' => b'\n',
b => *b,
}
}
pub fn escape_byte(byte: &u8) -> Option<NonZeroU8> {
match byte {
b';' => Some(unsafe { NonZeroU8::new_unchecked(b':') }),
b' ' => Some(unsafe { NonZeroU8::new_unchecked(b's') }),
b'\r' => Some(unsafe { NonZeroU8::new_unchecked(b'r') }),
b'\n' => Some(unsafe { NonZeroU8::new_unchecked(b'n') }),
b'\\' => Some(unsafe { NonZeroU8::new_unchecked(b'\\') }),
_ => None,
}
}
pub fn escape<'a>(tag_value: impl Into<NoNul<'a>>) -> Word<'a> {
let tag_value = tag_value.into();
let Some(first_idx) = tag_value.iter().position(|c| escape_byte(c).is_some()) else {
return unsafe { Word::from_unchecked(tag_value.into_bytes()) };
};
let (mut new_bytes, rest) = unsafe {
let (no_escape, rest) = tag_value.as_bytes_unsafe().split_at(first_idx);
let (first, rest) = rest.split_first().unwrap_unchecked();
let count = 1 + rest.iter().filter(|c| escape_byte(c).is_some()).count();
let mut new_bytes = Vec::with_capacity(tag_value.len() + count);
new_bytes.extend_from_slice(no_escape);
new_bytes.push(b'\\');
new_bytes.push(escape_byte(first).unwrap().get());
(new_bytes, rest)
};
for byte in rest {
if let Some(esc) = escape_byte(byte) {
new_bytes.push(b'\\');
new_bytes.push(esc.get());
} else {
new_bytes.push(*byte);
}
}
unsafe { Word::from_unchecked(new_bytes.into()) }
}
pub fn unescape<'a>(tag_value: impl Into<NoNul<'a>>) -> NoNul<'a> {
let tag_value = tag_value.into();
let Some(first_idx) = tag_value.iter().position(|c| *c == b'\\') else {
return tag_value;
};
let (mut new_bytes, rest) = unsafe {
let (no_escape, rest) = tag_value.as_bytes_unsafe().split_at(first_idx);
let (first, rest) = rest.split_first().unwrap_unchecked();
let mut new_bytes = Vec::with_capacity(tag_value.len() - 1);
new_bytes.extend_from_slice(no_escape);
new_bytes.push(unescape_byte(first));
(new_bytes, rest)
};
let mut esc = false;
for byte in rest {
if esc {
new_bytes.push(unescape_byte(byte));
esc = false;
} else if *byte == b'\\' {
esc = true;
} else {
new_bytes.push(*byte);
}
}
unsafe { NoNul::from_unchecked(new_bytes.into()) }
}