pub(super) fn strip_locale_quotes(content: &str) -> String {
content
.strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
.map_or_else(|| content.to_string(), ToString::to_string)
}
pub(super) fn ansi_c_decode(raw: &str) -> String {
let chars: Vec<char> = raw.chars().collect();
let mut out = String::with_capacity(raw.len());
let mut i = 0;
while i < chars.len() {
let c = chars[i];
if c != '\\' || i + 1 >= chars.len() {
out.push(c);
i += 1;
continue;
}
let next = chars[i + 1];
if let Some(simple) = decode_simple_escape(next) {
out.push(simple);
i += 2;
continue;
}
if let Some((ch, consumed)) = decode_numeric_escape(&chars, i + 1) {
out.push(ch);
i += 1 + consumed;
continue;
}
out.push('\\');
out.push(next);
i += 2;
}
out
}
const fn decode_simple_escape(next: char) -> Option<char> {
Some(match next {
'a' => '\u{07}',
'b' => '\u{08}',
'e' | 'E' => '\u{1B}',
'f' => '\u{0C}',
'n' => '\n',
'r' => '\r',
't' => '\t',
'v' => '\u{0B}',
'\\' => '\\',
'\'' => '\'',
'"' => '"',
'?' => '?',
_ => return None,
})
}
fn decode_numeric_escape(chars: &[char], start: usize) -> Option<(char, usize)> {
let first = *chars.get(start)?;
match first {
'x' => take_radix_escape(chars, start + 1, 16, 2).map(|(ch, n)| (ch, n + 1)),
'u' => take_radix_escape(chars, start + 1, 16, 4).map(|(ch, n)| (ch, n + 1)),
'U' => take_radix_escape(chars, start + 1, 16, 8).map(|(ch, n)| (ch, n + 1)),
'c' => take_control_escape(chars, start + 1).map(|(ch, n)| (ch, n + 1)),
'0'..='7' => take_radix_escape(chars, start, 8, 3),
_ => None,
}
}
fn take_radix_escape(
chars: &[char],
start: usize,
radix: u32,
max_digits: usize,
) -> Option<(char, usize)> {
let mut value: u32 = 0;
let mut consumed = 0;
while consumed < max_digits {
let Some(c) = chars.get(start + consumed) else {
break;
};
let Some(digit) = c.to_digit(radix) else {
break;
};
value = value * radix + digit;
consumed += 1;
}
if consumed == 0 {
return None;
}
let ch = char::from_u32(value)?;
Some((ch, consumed))
}
fn take_control_escape(chars: &[char], start: usize) -> Option<(char, usize)> {
let c = *chars.get(start)?;
if !c.is_ascii() {
return None;
}
#[allow(clippy::cast_possible_truncation)]
let byte = (c as u32) & 0x1F;
let ch = char::from_u32(byte)?;
Some((ch, 1))
}