use unicode_normalization::UnicodeNormalization;
use unicode_script::{Script, UnicodeScript};
pub fn letter_latin(cp: u32) -> Option<char> {
let c = char::from_u32(cp)?;
if c.is_alphabetic() && c.script() == Script::Latin {
c.to_lowercase().next()
} else {
None
}
}
pub fn skel(c: char) -> String {
unicode_security::confusable_detection::skeleton(&c.to_string()).collect()
}
pub fn legitimately_identical(a: char, b: char) -> bool {
if a == b {
return true;
}
if a.script() != b.script() {
return true;
}
if a.to_string().nfkd().eq(b.to_string().nfkd()) {
return true;
}
if let (Some(x), Some(y)) = (deunicode::deunicode_char(a), deunicode::deunicode_char(b)) {
if !x.is_empty() && x == y {
return true;
}
}
let mut p = [a, b];
p.sort_unstable();
if matches!((p[0], p[1]), ('i', 'l')) {
return true;
}
skel(a) == skel(b)
}
pub fn legitimately_identical_ci(a: char, b: char) -> bool {
let la = a.to_lowercase().next().unwrap_or(a);
let lb = b.to_lowercase().next().unwrap_or(b);
la == lb || legitimately_identical(la, lb)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn latin_filter() {
assert_eq!(letter_latin('A' as u32), Some('a'));
assert_eq!(letter_latin('é' as u32), Some('é'));
assert_eq!(letter_latin('Α' as u32), None); assert_eq!(letter_latin('1' as u32), None); }
#[test]
fn homoglyphs_and_real_swaps() {
assert!(legitimately_identical('a', 'a'));
assert!(legitimately_identical('a', '\u{0430}')); assert!(legitimately_identical('b', '\u{03B2}')); assert!(legitimately_identical('\u{00F0}', '\u{0111}')); assert!(legitimately_identical('i', 'l'));
assert!(!legitimately_identical('m', 'd')); assert!(!legitimately_identical('r', 'n'));
}
#[test]
fn case_insensitive_variant() {
assert!(legitimately_identical_ci('M', 'm'));
assert!(!legitimately_identical_ci('M', 'D'));
}
}