use encoding_rs::Encoding;
use std::sync::atomic::{AtomicPtr, Ordering};
pub use encoding_rs::{
ISO_8859_2, UTF_8, WINDOWS_1250, WINDOWS_1251, WINDOWS_1252, WINDOWS_1253, WINDOWS_1254,
WINDOWS_1256, WINDOWS_1257,
};
static DEFAULT_ENCODING: AtomicPtr<Encoding> =
AtomicPtr::new(std::ptr::from_ref::<Encoding>(WINDOWS_1252).cast_mut());
pub fn set_default_encoding(encoding: &'static Encoding) {
DEFAULT_ENCODING.store(
std::ptr::from_ref::<Encoding>(encoding).cast_mut(),
Ordering::Release,
);
}
pub fn set_default_encoding_by_label(label: &str) -> Option<&'static Encoding> {
let encoding = Encoding::for_label(label.as_bytes())?;
set_default_encoding(encoding);
Some(encoding)
}
#[must_use]
pub fn encode_checked(text: &str) -> (std::borrow::Cow<'_, [u8]>, bool) {
let (bytes, _, had_unmappable) = get().encode(text);
(bytes, had_unmappable)
}
#[must_use]
pub fn unmappable_chars(text: &str) -> Vec<char> {
let encoding = get();
let mut lost = Vec::new();
let mut buffer = [0u8; 4];
for ch in text.chars() {
let (_, _, had_unmappable) = encoding.encode(ch.encode_utf8(&mut buffer));
if had_unmappable && !lost.contains(&ch) {
lost.push(ch);
}
}
lost
}
pub(crate) fn get() -> &'static Encoding {
unsafe { &*DEFAULT_ENCODING.load(Ordering::Acquire) }
}
#[must_use]
pub fn current() -> &'static Encoding {
get()
}
#[cfg(test)]
pub(crate) fn tests_lock() -> std::sync::MutexGuard<'static, ()> {
static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
TEST_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_encoding_is_windows_1252() {
let _g = tests_lock();
let enc = get();
assert_eq!(enc.name(), WINDOWS_1252.name());
}
#[test]
fn set_and_get_encoding() {
let _g = tests_lock();
set_default_encoding(WINDOWS_1251);
let enc = get();
assert_eq!(enc.name(), WINDOWS_1251.name());
set_default_encoding(WINDOWS_1252);
let enc = get();
assert_eq!(enc.name(), WINDOWS_1252.name());
}
#[test]
fn label_resolves_and_sets() {
let _g = tests_lock();
let chosen = set_default_encoding_by_label("windows-1254");
assert_eq!(chosen.map(Encoding::name), Some("windows-1254"));
assert_eq!(get().name(), "windows-1254");
set_default_encoding(WINDOWS_1252);
}
#[test]
fn label_matching_follows_the_whatwg_aliases() {
let _g = tests_lock();
for label in ["cp1251", "WINDOWS-1251", " x-cp1251 "] {
let chosen = set_default_encoding_by_label(label);
assert_eq!(
chosen.map(Encoding::name),
Some("windows-1251"),
"label {label:?} should resolve to windows-1251"
);
}
set_default_encoding(WINDOWS_1252);
}
#[test]
fn an_unknown_label_changes_nothing() {
let _g = tests_lock();
set_default_encoding(WINDOWS_1251);
assert!(set_default_encoding_by_label("not-an-encoding").is_none());
assert_eq!(get().name(), WINDOWS_1251.name(), "the encoding must stay");
set_default_encoding(WINDOWS_1252);
}
#[test]
fn encode_checked_flags_what_the_encoding_cannot_represent() {
let _g = tests_lock();
set_default_encoding(WINDOWS_1252);
let (bytes, lost) = encode_checked("cafe");
assert!(!lost);
assert_eq!(bytes.as_ref(), b"cafe");
let (_, lost) = encode_checked("Привет");
assert!(lost, "Cyrillic does not fit in Windows-1252");
set_default_encoding(WINDOWS_1251);
let (_, lost) = encode_checked("Привет");
assert!(!lost);
set_default_encoding(WINDOWS_1252);
}
#[test]
fn unmappable_chars_names_them_once_and_in_order() {
let _g = tests_lock();
set_default_encoding(WINDOWS_1252);
assert_eq!(unmappable_chars("ok, tudo cabe: áéç"), Vec::<char>::new());
assert_eq!(unmappable_chars("aжbжc😀"), vec!['ж', '😀']);
set_default_encoding(WINDOWS_1252);
}
#[test]
fn the_re_exports_are_the_encodings_they_claim() {
assert_eq!(WINDOWS_1250.name(), "windows-1250");
assert_eq!(WINDOWS_1253.name(), "windows-1253");
assert_eq!(WINDOWS_1254.name(), "windows-1254");
assert_eq!(WINDOWS_1256.name(), "windows-1256");
assert_eq!(WINDOWS_1257.name(), "windows-1257");
assert_eq!(ISO_8859_2.name(), "ISO-8859-2");
assert_eq!(UTF_8.name(), "UTF-8");
}
}