//! Accent- and case-insensitive text matching. /// Lowercase `s` and strip accents ("Lim-Dûl's Cohort" → "lim-dul's cohort"), /// so typed text matches card names however they're accented. Uses the same /// per-character table as the fuzzy name search's own normalization, so both /// search paths agree on what counts as a match. pub fn fold(s: &str) -> String { s.chars() .map(|c| nucleo::chars::normalize(nucleo::chars::to_lower_case(c))) .collect() } /// Register [`fold`] as the SQL function `fold(text)` on `conn`, so queries /// can compare `fold(oc.name)` against a folded pattern. pub(crate) fn register_sql_fold(conn: &rusqlite::Connection) -> rusqlite::Result<()> { use rusqlite::functions::FunctionFlags; conn.create_scalar_function( "fold", 1, FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC, |ctx| { Ok(match ctx.get::>(0)? { Some(s) => Some(fold(&s)), None => None, }) }, ) } #[cfg(test)] mod tests { use super::fold; #[test] fn folds_case_and_accents_and_leaves_ascii_alone() { assert_eq!(fold("Lim-Dûl's Cohort"), "lim-dul's cohort"); assert_eq!(fold("Éowyn, Lady of Rohan"), "eowyn, lady of rohan"); assert_eq!(fold("Hamato Ninpō"), "hamato ninpo"); assert_eq!(fold("Jakub Šlemr"), "jakub slemr"); assert_eq!(fold("Lightning Bolt"), "lightning bolt"); } }