use std::borrow::Cow;
use charcode::{Bom, DecodeOptions, EUC_KR};
pub fn cp949_to_utf8(bytes: &[u8]) -> Cow<'_, str> {
let (decoded, _, _tally) = EUC_KR.decode_with(bytes, DecodeOptions::new().bom(Bom::Ignore));
decoded
}
pub fn utf8_to_cp949(s: &str) -> crate::Result<Vec<u8>> {
match EUC_KR.encode(s) {
Ok((encoded, _, _)) => Ok(encoded.into_owned()),
Err(e) => Err(crate::Error::InvalidArgument(format!(
"grf: filename {s:?} cannot be stored — {e}"
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ascii_round_trip() {
let plain = b"data/info.txt";
let utf = cp949_to_utf8(plain);
assert_eq!(utf, "data/info.txt");
let back = utf8_to_cp949(&utf).unwrap();
assert_eq!(back, plain);
}
#[test]
fn unrepresentable_names_are_refused() {
for name in ["café.txt", "😀.txt", "Ω\u{303}.txt"] {
let err = utf8_to_cp949(name).unwrap_err();
assert!(
matches!(err, crate::Error::InvalidArgument(_)),
"{name:?}: {err}"
);
let msg = err.to_string();
assert!(
msg.contains(&format!("{name:?}")),
"should name the file: {msg}"
);
assert!(
msg.contains("cannot be represented"),
"should say why: {msg}"
);
}
}
#[test]
fn cp949_covers_hangul_and_hanja() {
for name in ["한국어.txt", "日本.txt", "Ω.txt", "plain.txt"] {
let bytes = utf8_to_cp949(name).expect("representable");
assert_eq!(cp949_to_utf8(&bytes), name, "round-trip {name:?}");
}
}
#[test]
fn every_two_byte_sequence_round_trips() {
let mut checked = 0u32;
for hi in 0x81u8..=0xFE {
for lo in 0x41u8..=0xFE {
let src = [hi, lo];
let text = cp949_to_utf8(&src);
if text.contains('\u{FFFD}') {
continue; }
let back = utf8_to_cp949(&text).expect("decoded, so representable");
assert_eq!(back, &src[..], "round-trip {src:02x?}");
checked += 1;
}
}
assert!(
checked > 17_000,
"expected the full CP949 table, saw {checked}"
);
}
}