#![deny(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Finding {
pub original: char,
pub ascii_equivalent: char,
pub byte_pos: usize,
}
pub fn find_homoglyphs(s: &str) -> Vec<Finding> {
let mut out = Vec::new();
for (byte_pos, c) in s.char_indices() {
if let Some(eq) = ascii_equivalent(c) {
out.push(Finding {
original: c,
ascii_equivalent: eq,
byte_pos,
});
}
}
out
}
pub fn has_homoglyphs(s: &str) -> bool {
s.chars().any(|c| ascii_equivalent(c).is_some())
}
pub fn normalize_to_ascii(s: &str) -> String {
s.chars()
.map(|c| ascii_equivalent(c).unwrap_or(c))
.collect()
}
pub fn ascii_equivalent(c: char) -> Option<char> {
match c {
'\u{0430}' => Some('a'),
'\u{0435}' => Some('e'),
'\u{043E}' => Some('o'),
'\u{0440}' => Some('p'),
'\u{0441}' => Some('c'),
'\u{0445}' => Some('x'),
'\u{0443}' => Some('y'),
'\u{04CF}' => Some('l'),
'\u{0410}' => Some('A'),
'\u{0412}' => Some('B'),
'\u{0415}' => Some('E'),
'\u{041A}' => Some('K'),
'\u{041C}' => Some('M'),
'\u{041D}' => Some('H'),
'\u{041E}' => Some('O'),
'\u{0420}' => Some('P'),
'\u{0421}' => Some('C'),
'\u{0422}' => Some('T'),
'\u{0425}' => Some('X'),
'\u{03B1}' => Some('a'),
'\u{03BF}' => Some('o'),
'\u{03C1}' => Some('p'),
'\u{0391}' => Some('A'),
'\u{0392}' => Some('B'),
'\u{0395}' => Some('E'),
'\u{0396}' => Some('Z'),
'\u{0397}' => Some('H'),
'\u{0399}' => Some('I'),
'\u{039A}' => Some('K'),
'\u{039C}' => Some('M'),
'\u{039D}' => Some('N'),
'\u{039F}' => Some('O'),
'\u{03A1}' => Some('P'),
'\u{03A4}' => Some('T'),
'\u{03A7}' => Some('X'),
c if ('\u{FF21}'..='\u{FF3A}').contains(&c) => {
Some(('A' as u32 + (c as u32 - 0xFF21)) as u8 as char)
}
c if ('\u{FF41}'..='\u{FF5A}').contains(&c) => {
Some(('a' as u32 + (c as u32 - 0xFF41)) as u8 as char)
}
_ => None,
}
}