#[rustfmt::skip]
const HIGH: [char; 128] = [
'Ä','Å','Ç','É','Ñ','Ö','Ü','á','à','â','ä','ã','å','ç','é','è',
'ê','ë','í','ì','î','ï','ñ','ó','ò','ô','ö','õ','ú','ù','û','ü',
'†','°','¢','£','§','•','¶','ß','®','©','™','´','¨','≠','Æ','Ø',
'∞','±','≤','≥','¥','µ','∂','∑','∏','π','∫','ª','º','Ω','æ','ø',
'¿','¡','¬','√','ƒ','≈','∆','«','»','…','\u{00A0}','À','Ã','Õ','Œ','œ',
'–','—','“','”','‘','’','÷','◊','ÿ','Ÿ','⁄','€','‹','›','fi','fl',
'‡','·','‚','„','‰','Â','Ê','Á','Ë','È','Í','Î','Ï','Ì','Ó','Ô',
'\u{F8FF}','Ò','Ú','Û','Ù','ı','ˆ','˜','¯','˘','˙','˚','¸','˝','˛','ˇ',
];
pub fn decode(bytes: &[u8]) -> String {
bytes
.iter()
.map(|&b| {
if b < 0x80 {
b as char
} else {
HIGH[(b - 0x80) as usize]
}
})
.collect()
}
pub fn eq_ignore_case(a: &str, b: &str) -> bool {
let fold = |c: char| {
if c.is_ascii_uppercase() {
c.to_ascii_lowercase()
} else {
c
}
};
a.chars().map(fold).eq(b.chars().map(fold))
}
pub fn encode(s: &str) -> crate::Result<Vec<u8>> {
let mut out = Vec::with_capacity(s.len());
for c in s.chars() {
if (c as u32) < 0x80 {
out.push(c as u8);
} else if let Some(i) = HIGH.iter().position(|&h| h == c) {
out.push(0x80 + i as u8);
} else {
return Err(crate::Error::InvalidArgument(format!(
"macroman: character {c:?} is not representable in MacRoman"
)));
}
}
Ok(out)
}
#[inline]
fn fold(b: u8) -> u8 {
if b.is_ascii_lowercase() { b - 0x20 } else { b }
}
pub fn cmp_ci(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
use std::cmp::Ordering;
for (&x, &y) in a.iter().zip(b.iter()) {
match fold(x).cmp(&fold(y)) {
Ordering::Equal => {}
other => return other,
}
}
a.len().cmp(&b.len())
}
#[cfg(test)]
mod tests {
use super::*;
use std::cmp::Ordering;
#[test]
fn encode_decode_round_trip() {
for s in [
"hello.txt",
"System Folder",
"A/ROSE Includes",
"TokenTalk™ Prep",
] {
let bytes = encode(s).unwrap();
assert_eq!(decode(&bytes), s, "round-trip {s:?}");
}
assert_eq!(encode("™").unwrap(), vec![0xAA]);
assert_eq!(encode("©").unwrap(), vec![0xA9]);
assert!(encode("emoji 😀").is_err());
}
#[test]
fn cmp_ci_is_case_insensitive_and_ordered() {
assert_eq!(cmp_ci(b"Apple", b"apple"), Ordering::Equal);
assert_eq!(cmp_ci(b"Apple", b"BOB"), Ordering::Less);
assert_eq!(cmp_ci(b"bob", b"Apple"), Ordering::Greater);
assert_eq!(cmp_ci(b"file", b"file.txt"), Ordering::Less);
assert_eq!(cmp_ci(b"", b"x"), Ordering::Less);
assert_eq!(cmp_ci(b"1file", b"afile"), Ordering::Less);
}
}