A fold() (lowercase + strip accents, using the same table as nucleo's own normalization) is registered as a SQLite function and used by the name, oracle-text and location filters, so "khazad-dum" finds Troll of Khazad-dûm. The fuzzy name search folds the query too (accented queries used to match nothing) and its exact-name promotion; decklist/ManaBox import falls back to a folded name match; the deck quick filter and tag suggestions fold client-side. Closes #17
43 lines
1.5 KiB
Rust
43 lines
1.5 KiB
Rust
//! 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::<Option<String>>(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");
|
|
}
|
|
}
|