Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/kasper9n/ferrum. Music library app for Mac, Linux and Windows ferrum.kasper.space
electron linux macos music music-library music-player napi windows
Something went wrong. Try again.
39 kB · 1207 lines
Rust
at sqlite
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208use crate::db::TrackIDNew;use crate::library::TrackField;use anyhow::{Context, Result};use rayon::prelude::*;use rusqlite::Connection;use serde::Deserialize;use specta::Type;use std::time::Instant;use std::{collections::HashMap, str::Chars};use unicode_normalization::{Recompositions, UnicodeNormalization};
fn match_at_start(mut text: Recompositions<Chars>, target: Chars) -> bool { for target_char in target { let text_char = match text.next() { Some(x) => x, None => return false, }; match check(target_char, text_char) { Eq::True => {} Eq::False => return false, Eq::Skip => { // skip only once. would it be useful to skip multiple times? let new_text_char = match text.next() { Some(x) => x, None => return false, }; match check(target_char, new_text_char) { Eq::True => {} Eq::False => return false, Eq::Skip => return false, } } } } return true;}
fn find_match(text: &str, target: &str) -> bool { let mut target_chars = target.chars(); let first_target_char = match target_chars.next() { Some(x) => x, None => return true, // match if target is empty string }; let mut text_chars = text.nfc(); while let Some(text_char) = text_chars.next() { match check(first_target_char, text_char) { Eq::True => { if match_at_start(text_chars.clone(), target_chars.clone()) { return true; } } _ => {} } } return false;}
fn find_match_opt(text: &Option<String>, target: &str) -> bool { match text { Some(text) => find_match(text, target), None => return false, }}
fn strip_prefix_ignore_case<'a>(text: &'a str, prefix: &str) -> Option<&'a str> { if text.len() >= prefix.len() && text.get(..prefix.len())?.eq_ignore_ascii_case(prefix) { Some(&text[prefix.len()..]) } else { None }}
fn strip_suffix_ignore_case<'a>(text: &'a str, suffix: &str) -> Option<&'a str> { if text.len() >= suffix.len() && text .get(text.len() - suffix.len()..)? .eq_ignore_ascii_case(suffix) { Some(&text[..text.len() - suffix.len()]) } else { None }}
fn find_ignore_case(haystack: &str, needle: &str) -> Option<usize> { let needle_len = needle.len(); if needle_len == 0 || needle_len > haystack.len() { return None; } for start in 0..=(haystack.len() - needle_len) { if !haystack.is_char_boundary(start) { continue; } let end = start + needle_len; if !haystack.is_char_boundary(end) { continue; } if haystack[start..end].eq_ignore_ascii_case(needle) { return Some(start); } } None}
fn feat_artists_match(track_name: &str, target: &str) -> bool { let prefixes = [ "feat.", "feat ", "ft.", "ft ", "featuring ", "w/", "prod. by ", "prod by ", "prod.", "prod ", ]; let suffixes = [ " remix", " flip", " bootleg", " edit", " cover", " rework", " refix", " vip", " mashup", ];
// Look for unbracketed artists, like `Moonlight feat. Aloma Steele` for prefix in prefixes { if let Some(pos) = find_ignore_case(track_name, prefix) { let after = &track_name[pos + prefix.len()..]; // Stop at bracket let end = after.find(['(', '[']).unwrap_or(after.len()); let artist_text = after[..end].trim(); if find_match(artist_text, target) { return true; } } }
// Look inside brackets for (open, close) in &[('(', ')'), ('[', ']')] { let mut rest = track_name; while let Some(start) = rest.find(*open) { if let Some(end) = rest[start..].find(*close) { let inside = &rest[start + 1..start + end]; for prefix in ["feat.", "feat ", "ft.", "ft ", "featuring "] { let artist_text = strip_prefix_ignore_case(inside, prefix); if let Some(artist_text) = artist_text { return find_match(artist_text, target); } } for suffix in suffixes { let artist_text = strip_suffix_ignore_case(inside, suffix); if let Some(artist_text) = artist_text { return find_match(artist_text, target); } } rest = &rest[start + end + 1..]; } else { break; } } } false}
fn filter_term(ids: Vec<TrackIDNew>, term: FilterTerm, cache: &TracksCache) -> Vec<TrackIDNew> { let filtered_tracks: Vec<_> = ids .into_par_iter() .with_min_len(2000) .filter(|track_id| { let track = match cache.get_track(track_id) { Ok(track) => track, Err(_) => panic!("Track ID {} not found", track_id), }; if term.field.is_none() { let is_match = find_match(&track.title, &term.literal) || find_match(&track.artist, &term.literal) || find_match_opt(&track.album_title, &term.literal) || find_match_opt(&track.comments, &term.literal) || find_match_opt(&track.genre, &&term.literal); return is_match; }; let is_match = match term.field.as_ref().unwrap() { Field::Title => find_match(&track.title, &term.literal), Field::Artist => { find_match(&track.artist, &term.literal) || feat_artists_match(&track.title, &term.literal) } Field::Album => find_match_opt(&track.album_title, &term.literal), Field::AlbumArtist => find_match_opt(&track.album_artist, &term.literal), Field::Comments => find_match_opt(&track.comments, &term.literal), Field::Genre => find_match_opt(&track.genre, &term.literal), Field::Composer => find_match_opt(&track.composer, &term.literal), Field::Group => find_match_opt(&track.grouping, &term.literal), Field::Year => { todo!(); // track.year.map(|n| n.to_string()).unwrap_or("".to_string()) == term.literal } Field::Plays => { todo!(); // track // .plays // .as_ref() // .map(|n| n.len().to_string()) // .unwrap_or("".to_string()) // == term.literal } Field::Skips => { todo!(); // track // .skips // .as_ref() // .map(|n| n.len().to_string()) // .unwrap_or("".to_string()) // == term.literal } Field::Bpm => { todo!(); // track.bpm.map(|n| n.to_string()).unwrap_or("".to_string()) == term.literal } }; is_match }) .map(|id| id.clone()) .collect(); filtered_tracks}
#[derive(Clone, Debug, Deserialize, Type)]#[cfg_attr(feature = "napi", napi)]pub enum Field { Title, Artist, Album, AlbumArtist, Comments, Genre, Composer, Group, Year, Plays, Skips, Bpm,}
#[derive(Clone, Debug, Deserialize, Type)]#[cfg_attr(feature = "napi", napi)]pub enum NgramField { Title = 0, Artist = 1, Album = 2, AlbumArtist = 3, Composer = 6, Comments = 4, Genre = 5, Group = 7,}impl NgramField { pub fn as_id(&self) -> u8 { self.clone() as u8 }}
#[derive(Default, Clone, Debug, Deserialize, Type)]#[cfg_attr(feature = "napi", napi(object))]pub struct FilterTerm { pub field: Option<Field>, pub literal: String,}impl FilterTerm { pub fn is_whitespace(&self) -> bool { self.field.is_none() && self.literal == "" }}
pub struct CachedTrack { pub id: i64, pub added_at: i64, pub album_artist: Option<String>, pub album_title: Option<String>, pub artist: String, pub bpm: Option<f64>, pub comments: Option<String>, pub composer: Option<String>, pub disc_count: Option<u32>, pub disc_num: Option<u32>, pub duration_s: f64, pub genre: Option<String>, pub grouping: Option<String>, pub play_count: u32, pub skip_count: u32, pub title: String, pub track_count: Option<u32>, pub track_num: Option<u32>, pub year: Option<i64>,}impl CachedTrack { pub fn has_album(&self) -> bool { self.album_title.is_some() && self.album_artist.is_some() } pub fn is_same_album(&self, other: &CachedTrack) -> bool { self.has_album() && self.album_title == other.album_title && self.album_artist == other.album_artist }}
#[derive(Debug, Clone, Copy)]pub enum SortKey { AddedAt, AlbumArtist, AlbumTitle, Artist, Bpm, Comments, Composer, DiscCount, DiscNum, Duration, Genre, Grouping, PlayCount, SkipCount, Title, TrackCount, TrackNum, Year,}impl SortKey { /// returns (column_name, is_text) pub fn from_col_view_key(sort_key: &str) -> Self { match sort_key { "dateAdded" => SortKey::AddedAt, // is_text: false "albumArtist" => SortKey::AlbumArtist, // is_text: true "albumName" => SortKey::AlbumTitle, // is_text: true "artist" => SortKey::Artist, // is_text: true "bpm" => SortKey::Bpm, // is_text: false "comments" => SortKey::Comments, // is_text: true "composer" => SortKey::Composer, // is_text: true "discCount" => SortKey::DiscCount, // is_text: false "discNum" => SortKey::DiscNum, // is_text: false "duration" => SortKey::Duration, // is_text: false "genre" => SortKey::Genre, // is_text: true "grouping" => SortKey::Grouping, // is_text: true "playCount" => SortKey::PlayCount, // is_text: false "skipCount" => SortKey::SkipCount, // is_text: false "name" => SortKey::Title, // is_text: true "trackCount" => SortKey::TrackCount, // is_text: false "trackNum" => SortKey::TrackNum, // is_text: false "year" => SortKey::Year, // is_text: false sort_key => panic!("Invalid sort key {sort_key}"), } }
pub fn field_type(&self) -> TrackField { match self { SortKey::AddedAt => TrackField::I64, SortKey::AlbumArtist => TrackField::String, SortKey::AlbumTitle => TrackField::String, SortKey::Artist => TrackField::String, SortKey::Bpm => TrackField::F64, SortKey::Comments => TrackField::String, SortKey::Composer => TrackField::String, SortKey::DiscCount => TrackField::U32, SortKey::DiscNum => TrackField::U32, SortKey::Duration => TrackField::F64, SortKey::Genre => TrackField::String, SortKey::Grouping => TrackField::String, SortKey::PlayCount => TrackField::U32, SortKey::SkipCount => TrackField::U32, SortKey::Title => TrackField::String, SortKey::TrackCount => TrackField::U32, SortKey::TrackNum => TrackField::U32, SortKey::Year => TrackField::I64, } }}
pub struct TracksCache { cached_revision_n: i64, tracks: HashMap<i64, CachedTrack>,}impl TracksCache { pub fn get_track(&self, id: &i64) -> Result<&CachedTrack> { self.tracks .get(id) .with_context(|| format!("Track with ID {id} not found in cache")) } pub fn load_all(db: &mut Connection) -> Result<Self> { let tx = db.transaction().context("Failed to begin transaction")?;
let max_revision_n: i64 = tx .prepare_cached("SELECT COALESCE(MAX(revision_n), 0) FROM track_updates")? .query_one([], |row| row.get(0)) .context("Failed to get latest revision_n")?;
let tracks: Vec<CachedTrack> = tx .prepare_cached( "SELECT id, added_at, album_artist, album_title, artist, bpm, comments, composer, disc_count, disc_num, duration_s, genre, grouping, play_count, skip_count, title, track_count, track_num, year FROM tracks", )? .query_map([], |row| { Ok(CachedTrack { id: row.get(0)?, added_at: row.get(1)?, album_artist: row.get(2)?, album_title: row.get(3)?, artist: row.get(4)?, bpm: row.get(5)?, comments: row.get(6)?, composer: row.get(7)?, disc_count: row.get(8)?, disc_num: row.get(9)?, duration_s: row.get(10)?, genre: row.get(11)?, grouping: row.get(12)?, play_count: row.get(13)?, skip_count: row.get(14)?, title: row.get(15)?, track_count: row.get(16)?, track_num: row.get(17)?, year: row.get(18)?, }) })? .collect::<rusqlite::Result<_>>() .context("Failed to fetch all tracks")?;
tx.commit()?;
let tracks: HashMap<i64, CachedTrack> = tracks.into_iter().map(|track| (track.id, track)).collect();
let cache = TracksCache { cached_revision_n: max_revision_n, tracks, }; Ok(cache) }
pub fn refresh(&mut self, db: &mut Connection) -> Result<()> { let tx = db.transaction().context("Failed to begin transaction")?;
let (min_revision_n, max_revision_n): (i64, i64) = tx .prepare_cached( " SELECT COALESCE(MIN(revision_n), 0), COALESCE(MAX(revision_n), 0) FROM track_updates ", )? .query_one([], |row| Ok((row.get(0)?, row.get(1)?))) .context("Failed to check track revision_n")?; if self.cached_revision_n + 1 < min_revision_n { // If track_update rows were deleted (the cache is too old), refresh everything drop(tx); let new_cache = Self::load_all(db)?; *self = new_cache; return Ok(()); }
let deletions: Vec<i64> = tx .prepare_cached( " SELECT id FROM track_updates WHERE revision_n > ? AND is_delete = true ", )? .query_map([self.cached_revision_n], |row| row.get(0))? .collect::<rusqlite::Result<_>>() .context("Failed to check track deletions")?;
for deletion in deletions { self.tracks.remove(&deletion); }
let tracks: Vec<CachedTrack> = tx .prepare_cached( " SELECT u.id, t.added_at, t.album_artist, t.album_title, t.artist, t.bpm, t.comments, t.composer, t.disc_count, t.disc_num, t.duration_s, t.genre, t.grouping, t.play_count, t.skip_count, t.title, t.track_count, t.track_num, t.year FROM track_updates u LEFT JOIN tracks t ON t.id = u.id WHERE u.revision_n > ? AND u.is_delete = false ", )? .query_map([self.cached_revision_n], |row| { Ok(CachedTrack { id: row.get(0)?, added_at: row.get(1)?, album_artist: row.get(2)?, album_title: row.get(3)?, artist: row.get(4)?, bpm: row.get(5)?, comments: row.get(6)?, composer: row.get(7)?, disc_count: row.get(8)?, disc_num: row.get(9)?, duration_s: row.get(10)?, genre: row.get(11)?, grouping: row.get(12)?, play_count: row.get(13)?, skip_count: row.get(14)?, title: row.get(15)?, track_count: row.get(16)?, track_num: row.get(17)?, year: row.get(18)?, }) })? .collect::<rusqlite::Result<_>>() .context("Failed to get track updates")?;
for track in tracks { self.tracks.insert(track.id, track); } self.cached_revision_n = max_revision_n;
tx.commit()?; Ok(()) }}
pub fn filter( mut ids: Vec<TrackIDNew>, terms: Vec<FilterTerm>, library: &TracksCache,) -> Vec<TrackIDNew> { let now = Instant::now(); let terms: Vec<_> = terms .into_iter() .filter(|term| !term.is_whitespace()) .map(|term| FilterTerm { field: term.field, literal: term.literal.to_lowercase().nfc().collect(), }) .collect(); if terms.len() == 0 { return ids; }
for term in terms { ids = filter_term(ids, term, &library); } println!("Filter: {}ms", now.elapsed().as_millis()); ids}
enum Eq { True, False, Skip,}fn check(user_char: char, data_char: char) -> Eq { if user_char == data_char { return Eq::True; } // unicode list: https://en.wikipedia.org/wiki/List_of_Unicode_characters // double char matches todo: æ ae, ß ss, œ oe let is_skip = match data_char { // ascii symbols, \u{0021} to \u{002F} '!' | '"' | '#' | '$' | '%' | '&' | '\'' | '(' | ')' => true, '*' | '+' | ',' | '-' | '.' | '/' => true, // ascii symbols, \u{003A} to \u{0040} ':' | ';' | '<' | '=' | '>' | '?' | '@' => true, // ascii symbols, \u{005B} to \u{0060} '[' | '\\' | ']' | '^' | '_' | '`' => true, // ascii symbols, \u{007B} to \u{007E} '{' | '|' | '}' | '~' => true,
// latin-1 puncutation/symbols, \u{00A1} to \u{00BF} '¡' | '¢' | '£' | '¤' | '¥' | '¦' | '§' | '¨' | '©' | 'ª' | '«' | '¬' => true, '\u{00AD}' | '®' | '¯' | '°' | '±' | '²' | '³' | '´' | 'µ' | '¶' | '·' => true, '¸' | '¹' | 'º' | '»' | '¼' | '½' | '¾' | '¿' => true,
// math, \u{00D7} and \u{00F7} '×' | '÷' => true,
// unicode symbols \u{2012} to \u{204A} '–' | '—' | '―' | '‗' | '‘' | '’' | '‚' | '‛' | '“' | '”' | '„' | '†' | '‡' | '•' | '…' | '‰' | '′' | '″' | '‹' | '›' | '‼' | '‾' | '⁄' | '⁊' => true,
// fullwidth ascii symbols, \u{FF01} to \u{FF0F} '!' | '"' | '#' | '$' | '%' | '&' | ''' => true, '(' | ')' | '*' | '+' | ',' | '-' | '.' | '/' => true, // fullwidth ascii symbols, \u{FF1A} to \u{FF20} ':' | ';' | '<' | '=' | '>' | '?' | '@' => true, // fullwidth ascii symbols, \u{FF3B} to \u{FF40} '[' | '\' | ']' | '^' | '_' | '`' => true, // fullwidth ascii symbols, \u{FF5B} to \u{FF5E} '{' | '|' | '}' | '~' => true, // fullwidth double brackets, \u{FF5F} and \u{FF60} '⦅' | '⦆' => true,
// figure dash '‒' => true,
// other currencies '֏' | '؋' | '৲' | '৳' | '৻' | '૱' | '௹' | '฿' | '៛' | '₠' | '₡' | '₢' | '₣' | '₤' | '₥' | '₦' | '₧' | '₨' | '₩' | '₪' | '₫' | '€' | '₭' | '₮' | '₯' | '₰' | '₱' | '₲' | '₳' | '₴' | '₵' | '₶' | '₷' | '₸' | '₹' | '₺' | '₻' | '₼' | '₽' | '₾' | '₿' | '﹩' | '¢' | '£' | '¥' | '₩' => true,
_ => false, }; if is_skip { return Eq::Skip; } fn one_of(c: &char, a: char, b: char) -> bool { return c == &a || c == &b; } let is_match = match user_char { // dash '–' | '—' | '―' => match data_char { '–' | '—' | '―' | '-' => true, _ => false, }, // Basic Latin, Latin-1 Supplement, Latin Extended-A 'A' | 'a' => match data_char { 'A' | 'a' | 'A' | 'a' | 'À' | 'à' | 'Á' | 'á' | 'Â' | 'â' | 'Ã' | 'ã' => { true } 'Ä' | 'ä' | 'Å' | 'å' | 'Ā' | 'ā' | 'Ă' | 'ă' | 'Ą' | 'ą' => true, _ => false, }, 'A' | 'a' => one_of(&data_char, 'A', 'a'), 'À' | 'à' => one_of(&data_char, 'À', 'à'), 'Á' | 'á' => one_of(&data_char, 'Á', 'á'), 'Â' | 'â' => one_of(&data_char, 'Â', 'â'), 'Ã' | 'ã' => one_of(&data_char, 'Ã', 'ã'), 'Ä' | 'ä' => one_of(&data_char, 'Ä', 'ä'), 'Å' | 'å' => one_of(&data_char, 'Å', 'å'), 'Ā' | 'ā' => one_of(&data_char, 'Ā', 'ā'), 'Ă' | 'ă' => one_of(&data_char, 'Ă', 'ă'), 'Ą' | 'ą' => one_of(&data_char, 'Ą', 'ą'),
'B' | 'b' => match data_char { 'B' | 'b' | 'B' | 'b' => true, _ => false, }, 'B' | 'b' => one_of(&data_char, 'B', 'b'),
'C' | 'c' => match data_char { 'C' | 'c' | 'C' | 'c' | 'Ç' | 'ç' | 'Ć' | 'ć' | 'Ĉ' | 'ĉ' | 'Ċ' | 'ċ' | 'Č' | 'č' => { true } _ => false, }, 'C' | 'c' => one_of(&data_char, 'C', 'c'), 'Ç' | 'ç' => one_of(&data_char, 'Ç', 'ç'), 'Ć' | 'ć' => one_of(&data_char, 'Ć', 'ć'), 'Ĉ' | 'ĉ' => one_of(&data_char, 'Ĉ', 'ĉ'), 'Ċ' | 'ċ' => one_of(&data_char, 'Ċ', 'ċ'), 'Č' | 'č' => one_of(&data_char, 'Č', 'č'),
'D' | 'd' => match data_char { 'D' | 'd' | 'D' | 'd' | 'Ð' | 'ð' | 'Ď' | 'ď' | 'Đ' | 'đ' => true, _ => false, }, 'D' | 'd' => one_of(&data_char, 'D', 'd'), 'Ð' | 'ð' => one_of(&data_char, 'Ð', 'ð'), 'Ď' | 'ď' => one_of(&data_char, 'Ď', 'ď'), 'Đ' | 'đ' => one_of(&data_char, 'Đ', 'đ'),
'E' | 'e' => match data_char { 'E' | 'e' | 'E' | 'e' | 'È' | 'è' | 'É' | 'é' | 'Ê' | 'ê' | 'Ë' | 'ë' => { true } 'Ē' | 'ē' | 'Ĕ' | 'ĕ' | 'Ė' | 'ė' | 'Ę' | 'ę' | 'Ě' | 'ě' => true, _ => false, }, 'E' | 'e' => one_of(&data_char, 'E', 'e'), 'È' | 'è' => one_of(&data_char, 'È', 'è'), 'É' | 'é' => one_of(&data_char, 'É', 'é'), 'Ê' | 'ê' => one_of(&data_char, 'Ê', 'ê'), 'Ë' | 'ë' => one_of(&data_char, 'Ë', 'ë'), 'Ē' | 'ē' => one_of(&data_char, 'Ē', 'ē'), 'Ĕ' | 'ĕ' => one_of(&data_char, 'Ĕ', 'ĕ'), 'Ė' | 'ė' => one_of(&data_char, 'Ė', 'ė'), 'Ę' | 'ę' => one_of(&data_char, 'Ę', 'ę'), 'Ě' | 'ě' => one_of(&data_char, 'Ě', 'ě'),
'F' | 'f' => match data_char { 'F' | 'f' | 'F' | 'f' => true, _ => false, }, 'F' | 'f' => one_of(&data_char, 'F', 'f'),
'G' | 'g' => match data_char { 'G' | 'g' | 'G' | 'g' | 'Ĝ' | 'ĝ' | 'Ğ' | 'ğ' | 'Ġ' | 'ġ' | 'Ģ' | 'ģ' => { true } _ => false, }, 'G' | 'g' => one_of(&data_char, 'G', 'g'), 'Ĝ' | 'ĝ' => one_of(&data_char, 'Ĝ', 'ĝ'), 'Ğ' | 'ğ' => one_of(&data_char, 'Ğ', 'ğ'), 'Ġ' | 'ġ' => one_of(&data_char, 'Ġ', 'ġ'), 'Ģ' | 'ģ' => one_of(&data_char, 'Ģ', 'ģ'),
'H' | 'h' => match data_char { 'H' | 'h' | 'H' | 'h' | 'Ĥ' | 'ĥ' | 'Ħ' | 'ħ' => true, _ => false, }, 'H' | 'h' => one_of(&data_char, 'H', 'h'), 'Ĥ' | 'ĥ' => one_of(&data_char, 'Ĥ', 'ĥ'), 'Ħ' | 'ħ' => one_of(&data_char, 'Ħ', 'ħ'),
'I' | 'i' => match data_char { 'I' | 'i' | 'I' | 'i' | 'Ì' | 'ì' | 'Í' | 'í' | 'Î' | 'î' | 'Ï' | 'ï' => { true } 'Ĩ' | 'ĩ' | 'Ī' | 'ī' | 'Ĭ' | 'ĭ' | 'Į' | 'į' => true, 'İ' | 'ı' => true, // dotted I and dotless i _ => false, }, 'I' | 'i' => one_of(&data_char, 'I', 'i'), 'Ĩ' | 'ĩ' => one_of(&data_char, 'Ĩ', 'ĩ'), 'Ī' | 'ī' => one_of(&data_char, 'Ī', 'ī'), 'Ĭ' | 'ĭ' => one_of(&data_char, 'Ĭ', 'ĭ'), 'Į' | 'į' => one_of(&data_char, 'Į', 'į'),
'J' | 'j' => match data_char { 'J' | 'j' | 'J' | 'j' | 'Ĵ' | 'ĵ' => true, _ => false, }, 'J' | 'j' => one_of(&data_char, 'J', 'j'), 'Ĵ' | 'ĵ' => one_of(&data_char, 'Ĵ', 'ĵ'),
'K' | 'k' => match data_char { 'K' | 'k' | 'K' | 'k' | 'Ķ' | 'ķ' => true, 'ĸ' => true, // only lowercase _ => false, }, 'K' | 'k' => one_of(&data_char, 'K', 'k'), 'Ķ' | 'ķ' => one_of(&data_char, 'Ķ', 'ķ'),
'L' | 'l' => match data_char { 'L' | 'l' | 'L' | 'l' | 'Ĺ' | 'ĺ' => true, 'Ļ' | 'ļ' | 'Ľ' | 'ľ' | 'Ŀ' | 'ŀ' | 'Ł' | 'ł' => true, _ => false, }, 'L' | 'l' => one_of(&data_char, 'L', 'l'), 'Ĺ' | 'ĺ' => one_of(&data_char, 'Ĺ', 'ĺ'), 'Ļ' | 'ļ' => one_of(&data_char, 'Ļ', 'ļ'), 'Ľ' | 'ľ' => one_of(&data_char, 'Ľ', 'ľ'), 'Ŀ' | 'ŀ' => one_of(&data_char, 'Ŀ', 'ŀ'), 'Ł' | 'ł' => one_of(&data_char, 'Ł', 'ł'),
'M' | 'm' => match data_char { 'M' | 'm' | 'M' | 'm' => true, _ => false, }, 'M' | 'm' => one_of(&data_char, 'M', 'm'),
'N' | 'n' => match data_char { 'N' | 'n' | 'N' | 'n' | 'Ñ' | 'ñ' | 'Ń' | 'ń' | 'Ņ' | 'ņ' | 'Ň' | 'ň' => { true } 'ʼn' => true, // only lowercase, deprecated _ => false, }, 'N' | 'n' => one_of(&data_char, 'N', 'n'), 'Ñ' | 'ñ' => one_of(&data_char, 'Ñ', 'ñ'), 'Ń' | 'ń' => one_of(&data_char, 'Ń', 'ń'), 'Ņ' | 'ņ' => one_of(&data_char, 'Ņ', 'ņ'), 'Ň' | 'ň' => one_of(&data_char, 'Ň', 'ň'),
'O' | 'o' => match data_char { 'O' | 'o' | 'O' | 'o' | 'Ò' | 'ò' | 'Ó' | 'ó' | 'Ô' | 'ô' | 'Õ' | 'õ' => { true } 'Ö' | 'ö' | 'Ø' | 'ø' | 'Ō' | 'ō' | 'Ŏ' | 'ŏ' | 'Ő' | 'ő' | 'Ơ' | 'ơ' => { true } _ => false, }, 'O' | 'o' => one_of(&data_char, 'O', 'o'), 'Ò' | 'ò' => one_of(&data_char, 'Ò', 'ò'), 'Ó' | 'ó' => one_of(&data_char, 'Ó', 'ó'), 'Ô' | 'ô' => one_of(&data_char, 'Ô', 'ô'), 'Õ' | 'õ' => one_of(&data_char, 'Õ', 'õ'), 'Ö' | 'ö' => one_of(&data_char, 'Ö', 'ö'), 'Ø' | 'ø' => one_of(&data_char, 'Ø', 'ø'), 'Ō' | 'ō' => one_of(&data_char, 'Ō', 'ō'), 'Ŏ' | 'ŏ' => one_of(&data_char, 'Ŏ', 'ŏ'), 'Ő' | 'ő' => one_of(&data_char, 'Ő', 'ő'), 'Ơ' | 'ơ' => one_of(&data_char, 'Ơ', 'ơ'),
'P' | 'p' => match data_char { 'P' | 'p' | 'P' | 'p' => true, _ => false, }, 'P' | 'p' => one_of(&data_char, 'P', 'p'),
'Q' | 'q' => match data_char { 'Q' | 'q' | 'Q' | 'q' => true, _ => false, }, 'Q' | 'q' => one_of(&data_char, 'Q', 'q'),
'R' | 'r' => match data_char { 'R' | 'r' | 'R' | 'r' | 'Ŕ' | 'ŕ' | 'Ŗ' | 'ŗ' | 'Ř' | 'ř' => true, _ => false, }, 'R' | 'r' => one_of(&data_char, 'R', 'r'), 'Ŕ' | 'ŕ' => one_of(&data_char, 'Ŕ', 'ŕ'), 'Ŗ' | 'ŗ' => one_of(&data_char, 'Ŗ', 'ŗ'), 'Ř' | 'ř' => one_of(&data_char, 'Ř', 'ř'),
'S' | 's' => match data_char { 'S' | 's' | 'S' | 's' | 'Ś' | 'ś' | 'Ŝ' | 'ŝ' | 'Ş' | 'ş' | 'Š' | 'š' => { true } 'ſ' => true, // only lowercase _ => false, }, 'S' | 's' => one_of(&data_char, 'S', 's'), 'Ś' | 'ś' => one_of(&data_char, 'Ś', 'ś'), 'Ŝ' | 'ŝ' => one_of(&data_char, 'Ŝ', 'ŝ'), 'Ş' | 'ş' => one_of(&data_char, 'Ş', 'ş'), 'Š' | 'š' => one_of(&data_char, 'Š', 'š'),
'T' | 't' => match data_char { 'T' | 't' | 'T' | 't' | 'Ţ' | 'ţ' | 'Ť' | 'ť' | 'Ŧ' | 'ŧ' => true, _ => false, }, 'T' | 't' => one_of(&data_char, 'T', 't'), 'Ţ' | 'ţ' => one_of(&data_char, 'Ţ', 'ţ'), 'Ť' | 'ť' => one_of(&data_char, 'Ť', 'ť'), 'Ŧ' | 'ŧ' => one_of(&data_char, 'Ŧ', 'ŧ'),
'U' | 'u' => match data_char { 'U' | 'u' | 'U' | 'u' | 'Ù' | 'ù' | 'Ú' | 'ú' | 'Û' | 'û' | 'Ü' | 'ü' => { true } 'Ũ' | 'ũ' | 'Ū' | 'ū' | 'Ŭ' | 'ŭ' | 'Ů' | 'ů' | 'Ű' | 'ű' | 'Ų' | 'ų' => { true } _ => false, }, 'U' | 'u' => one_of(&data_char, 'U', 'u'), 'Ù' | 'ù' => one_of(&data_char, 'Ù', 'ù'), 'Ú' | 'ú' => one_of(&data_char, 'Ú', 'ú'), 'Û' | 'û' => one_of(&data_char, 'Û', 'û'), 'Ü' | 'ü' => one_of(&data_char, 'Ü', 'ü'), 'Ũ' | 'ũ' => one_of(&data_char, 'Ũ', 'ũ'), 'Ū' | 'ū' => one_of(&data_char, 'Ū', 'ū'), 'Ŭ' | 'ŭ' => one_of(&data_char, 'Ŭ', 'ŭ'), 'Ů' | 'ů' => one_of(&data_char, 'Ů', 'ů'), 'Ű' | 'ű' => one_of(&data_char, 'Ű', 'ű'), 'Ų' | 'ų' => one_of(&data_char, 'Ų', 'ų'),
'V' | 'v' => match data_char { 'V' | 'v' | 'V' | 'v' => true, _ => false, }, 'V' | 'v' => one_of(&data_char, 'V', 'v'),
'W' | 'w' => match data_char { 'W' | 'w' | 'W' | 'w' | 'Ŵ' | 'ŵ' => true, _ => false, }, 'W' | 'w' => one_of(&data_char, 'W', 'w'), 'Ŵ' | 'ŵ' => one_of(&data_char, 'Ŵ', 'ŵ'),
'X' | 'x' => match data_char { 'X' | 'x' | 'X' | 'x' => true, _ => false, }, 'X' | 'x' => one_of(&data_char, 'X', 'x'), 'Y' | 'y' => match data_char { 'Y' | 'y' | 'Y' | 'y' | 'Ý' | 'ý' | 'Ÿ' | 'ÿ' | 'Ŷ' | 'ŷ' => true, _ => false, }, 'Y' | 'y' => one_of(&data_char, 'Y', 'y'), 'Ý' | 'ý' => one_of(&data_char, 'Ý', 'ý'), 'Ÿ' | 'ÿ' => one_of(&data_char, 'Ÿ', 'ÿ'), 'Ŷ' | 'ŷ' => one_of(&data_char, 'Ŷ', 'ŷ'),
'Z' | 'z' => match data_char { 'Z' | 'z' | 'Z' | 'z' | 'Ź' | 'ź' | 'Ż' | 'ż' | 'Ž' | 'ž' => true, _ => false, }, 'Z' | 'z' => one_of(&data_char, 'Z', 'z'), 'Ź' | 'ź' => one_of(&data_char, 'Ź', 'ź'), 'Ż' | 'ż' => one_of(&data_char, 'Ż', 'ż'), 'Ž' | 'ž' => one_of(&data_char, 'Ž', 'ž'),
'Æ' | 'æ' => one_of(&data_char, 'Æ', 'æ'), 'Þ' | 'þ' => one_of(&data_char, 'Þ', 'þ'), 'ẞ' | 'ß' => one_of(&data_char, 'ẞ', 'ß'), 'IJ' | 'ij' => one_of(&data_char, 'IJ', 'ij'), 'Ŋ' | 'ŋ' => one_of(&data_char, 'Ŋ', 'ŋ'), 'Œ' | 'œ' => one_of(&data_char, 'Œ', 'œ'),
// fullwidth characters, \u{FF01} to \u{FF20} '!' => data_char == '!', '"' => data_char == '"', '#' => data_char == '#', '$' => data_char == '$', '%' => data_char == '%', '&' => data_char == '&', '\'' => data_char == ''', '(' => data_char == '(', ')' => data_char == ')', '*' => data_char == '*', '+' => data_char == '+', ',' => data_char == ',', '-' => data_char == '-', '.' => data_char == '.', '/' => data_char == '/', '0' => data_char == '0', '1' => data_char == '1', '2' => data_char == '2', '3' => data_char == '3', '4' => data_char == '4', '5' => data_char == '5', '6' => data_char == '6', '7' => data_char == '7', '8' => data_char == '8', '9' => data_char == '9', ':' => data_char == ':', ';' => data_char == ';', '<' => data_char == '<', '=' => data_char == '=', '>' => data_char == '>', '?' => data_char == '?', '@' => data_char == '@',
// cyrillic // - only case-insensitive matching (é won't match e) 'Ѐ' | 'ѐ' => one_of(&data_char, 'Ѐ', 'ѐ'), 'Ё' | 'ё' => one_of(&data_char, 'Ё', 'ё'), 'Ђ' | 'ђ' => one_of(&data_char, 'Ђ', 'ђ'), 'Ѓ' | 'ѓ' => one_of(&data_char, 'Ѓ', 'ѓ'), 'Є' | 'є' => one_of(&data_char, 'Є', 'є'), 'Ѕ' | 'ѕ' => one_of(&data_char, 'Ѕ', 'ѕ'), 'І' | 'і' => one_of(&data_char, 'І', 'і'), 'Ї' | 'ї' => one_of(&data_char, 'Ї', 'ї'), 'Ј' | 'ј' => one_of(&data_char, 'Ј', 'ј'), 'Љ' | 'љ' => one_of(&data_char, 'Љ', 'љ'), 'Њ' | 'њ' => one_of(&data_char, 'Њ', 'њ'), 'Ћ' | 'ћ' => one_of(&data_char, 'Ћ', 'ћ'), 'Ќ' | 'ќ' => one_of(&data_char, 'Ќ', 'ќ'), 'Ѝ' | 'ѝ' => one_of(&data_char, 'Ѝ', 'ѝ'), 'Ў' | 'ў' => one_of(&data_char, 'Ў', 'ў'), 'Џ' | 'џ' => one_of(&data_char, 'Џ', 'џ'), 'А' | 'а' => one_of(&data_char, 'А', 'а'), 'Б' | 'б' => one_of(&data_char, 'Б', 'б'), 'В' | 'в' => one_of(&data_char, 'В', 'в'), 'Г' | 'г' => one_of(&data_char, 'Г', 'г'), 'Д' | 'д' => one_of(&data_char, 'Д', 'д'), 'Е' | 'е' => one_of(&data_char, 'Е', 'е'), 'Ж' | 'ж' => one_of(&data_char, 'Ж', 'ж'), 'З' | 'з' => one_of(&data_char, 'З', 'з'), 'И' | 'и' => one_of(&data_char, 'И', 'и'), 'Й' | 'й' => one_of(&data_char, 'Й', 'й'), 'К' | 'к' => one_of(&data_char, 'К', 'к'), 'Л' | 'л' => one_of(&data_char, 'Л', 'л'), 'М' | 'м' => one_of(&data_char, 'М', 'м'), 'Н' | 'н' => one_of(&data_char, 'Н', 'н'), 'О' | 'о' => one_of(&data_char, 'О', 'о'), 'П' | 'п' => one_of(&data_char, 'П', 'п'), 'Р' | 'р' => one_of(&data_char, 'Р', 'р'), 'С' | 'с' => one_of(&data_char, 'С', 'с'), 'Т' | 'т' => one_of(&data_char, 'Т', 'т'), 'У' | 'у' => one_of(&data_char, 'У', 'у'), 'Ф' | 'ф' => one_of(&data_char, 'Ф', 'ф'), 'Х' | 'х' => one_of(&data_char, 'Х', 'х'), 'Ц' | 'ц' => one_of(&data_char, 'Ц', 'ц'), 'Ч' | 'ч' => one_of(&data_char, 'Ч', 'ч'), 'Ш' | 'ш' => one_of(&data_char, 'Ш', 'ш'), 'Щ' | 'щ' => one_of(&data_char, 'Щ', 'щ'), 'Ъ' | 'ъ' => one_of(&data_char, 'Ъ', 'ъ'), 'Ы' | 'ы' => one_of(&data_char, 'Ы', 'ы'), 'Ь' | 'ь' => one_of(&data_char, 'Ь', 'ь'), 'Э' | 'э' => one_of(&data_char, 'Э', 'э'), 'Ю' | 'ю' => one_of(&data_char, 'Ю', 'ю'), 'Я' | 'я' => one_of(&data_char, 'Я', 'я'), 'Ѡ' | 'ѡ' => one_of(&data_char, 'Ѡ', 'ѡ'), 'Ѣ' | 'ѣ' => one_of(&data_char, 'Ѣ', 'ѣ'), 'Ѥ' | 'ѥ' => one_of(&data_char, 'Ѥ', 'ѥ'), 'Ѧ' | 'ѧ' => one_of(&data_char, 'Ѧ', 'ѧ'), 'Ѩ' | 'ѩ' => one_of(&data_char, 'Ѩ', 'ѩ'), 'Ѫ' | 'ѫ' => one_of(&data_char, 'Ѫ', 'ѫ'), 'Ѭ' | 'ѭ' => one_of(&data_char, 'Ѭ', 'ѭ'), 'Ѯ' | 'ѯ' => one_of(&data_char, 'Ѯ', 'ѯ'), 'Ѱ' | 'ѱ' => one_of(&data_char, 'Ѱ', 'ѱ'), 'Ѳ' | 'ѳ' => one_of(&data_char, 'Ѳ', 'ѳ'), 'Ѵ' | 'ѵ' => one_of(&data_char, 'Ѵ', 'ѵ'), 'Ѷ' | 'ѷ' => one_of(&data_char, 'Ѷ', 'ѷ'), 'Ѹ' | 'ѹ' => one_of(&data_char, 'Ѹ', 'ѹ'), 'Ѻ' | 'ѻ' => one_of(&data_char, 'Ѻ', 'ѻ'), 'Ѽ' | 'ѽ' => one_of(&data_char, 'Ѽ', 'ѽ'), 'Ѿ' | 'ѿ' => one_of(&data_char, 'Ѿ', 'ѿ'), 'Ҁ' | 'ҁ' => one_of(&data_char, 'Ҁ', 'ҁ'), 'Ҋ' | 'ҋ' => one_of(&data_char, 'Ҋ', 'ҋ'), 'Ҍ' | 'ҍ' => one_of(&data_char, 'Ҍ', 'ҍ'), 'Ҏ' | 'ҏ' => one_of(&data_char, 'Ҏ', 'ҏ'), 'Ґ' | 'ґ' => one_of(&data_char, 'Ґ', 'ґ'), 'Ғ' | 'ғ' => one_of(&data_char, 'Ғ', 'ғ'), 'Ҕ' | 'ҕ' => one_of(&data_char, 'Ҕ', 'ҕ'), 'Җ' | 'җ' => one_of(&data_char, 'Җ', 'җ'), 'Ҙ' | 'ҙ' => one_of(&data_char, 'Ҙ', 'ҙ'), 'Қ' | 'қ' => one_of(&data_char, 'Қ', 'қ'), 'Ҝ' | 'ҝ' => one_of(&data_char, 'Ҝ', 'ҝ'), 'Ҟ' | 'ҟ' => one_of(&data_char, 'Ҟ', 'ҟ'), 'Ҡ' | 'ҡ' => one_of(&data_char, 'Ҡ', 'ҡ'), 'Ң' | 'ң' => one_of(&data_char, 'Ң', 'ң'), 'Ҥ' | 'ҥ' => one_of(&data_char, 'Ҥ', 'ҥ'), 'Ҧ' | 'ҧ' => one_of(&data_char, 'Ҧ', 'ҧ'), 'Ҩ' | 'ҩ' => one_of(&data_char, 'Ҩ', 'ҩ'), 'Ҫ' | 'ҫ' => one_of(&data_char, 'Ҫ', 'ҫ'), 'Ҭ' | 'ҭ' => one_of(&data_char, 'Ҭ', 'ҭ'), 'Ү' | 'ү' => one_of(&data_char, 'Ү', 'ү'), 'Ұ' | 'ұ' => one_of(&data_char, 'Ұ', 'ұ'), 'Ҳ' | 'ҳ' => one_of(&data_char, 'Ҳ', 'ҳ'), 'Ҵ' | 'ҵ' => one_of(&data_char, 'Ҵ', 'ҵ'), 'Ҷ' | 'ҷ' => one_of(&data_char, 'Ҷ', 'ҷ'), 'Ҹ' | 'ҹ' => one_of(&data_char, 'Ҹ', 'ҹ'), 'Һ' | 'һ' => one_of(&data_char, 'Һ', 'һ'), 'Ҽ' | 'ҽ' => one_of(&data_char, 'Ҽ', 'ҽ'), 'Ҿ' | 'ҿ' => one_of(&data_char, 'Ҿ', 'ҿ'), 'Ӏ' | 'Ӂ' => one_of(&data_char, 'Ӏ', 'Ӂ'), 'ӂ' | 'Ӄ' => one_of(&data_char, 'ӂ', 'Ӄ'), 'ӄ' | 'Ӆ' => one_of(&data_char, 'ӄ', 'Ӆ'), 'ӆ' | 'Ӈ' => one_of(&data_char, 'ӆ', 'Ӈ'), 'ӈ' | 'Ӊ' => one_of(&data_char, 'ӈ', 'Ӊ'), 'ӊ' | 'Ӌ' => one_of(&data_char, 'ӊ', 'Ӌ'), 'ӌ' | 'Ӎ' => one_of(&data_char, 'ӌ', 'Ӎ'), 'ӎ' | 'ӏ' => one_of(&data_char, 'ӎ', 'ӏ'), 'Ӑ' | 'ӑ' => one_of(&data_char, 'Ӑ', 'ӑ'), 'Ӓ' | 'ӓ' => one_of(&data_char, 'Ӓ', 'ӓ'), 'Ӕ' | 'ӕ' => one_of(&data_char, 'Ӕ', 'ӕ'), 'Ӗ' | 'ӗ' => one_of(&data_char, 'Ӗ', 'ӗ'), 'Ә' | 'ә' => one_of(&data_char, 'Ә', 'ә'), 'Ӛ' | 'ӛ' => one_of(&data_char, 'Ӛ', 'ӛ'), 'Ӝ' | 'ӝ' => one_of(&data_char, 'Ӝ', 'ӝ'), 'Ӟ' | 'ӟ' => one_of(&data_char, 'Ӟ', 'ӟ'), 'Ӡ' | 'ӡ' => one_of(&data_char, 'Ӡ', 'ӡ'), 'Ӣ' | 'ӣ' => one_of(&data_char, 'Ӣ', 'ӣ'), 'Ӥ' | 'ӥ' => one_of(&data_char, 'Ӥ', 'ӥ'), 'Ӧ' | 'ӧ' => one_of(&data_char, 'Ӧ', 'ӧ'), 'Ө' | 'ө' => one_of(&data_char, 'Ө', 'ө'), 'Ӫ' | 'ӫ' => one_of(&data_char, 'Ӫ', 'ӫ'), 'Ӭ' | 'ӭ' => one_of(&data_char, 'Ӭ', 'ӭ'), 'Ӯ' | 'ӯ' => one_of(&data_char, 'Ӯ', 'ӯ'), 'Ӱ' | 'ӱ' => one_of(&data_char, 'Ӱ', 'ӱ'), 'Ӳ' | 'ӳ' => one_of(&data_char, 'Ӳ', 'ӳ'), 'Ӵ' | 'ӵ' => one_of(&data_char, 'Ӵ', 'ӵ'), 'Ӷ' | 'ӷ' => one_of(&data_char, 'Ӷ', 'ӷ'), 'Ӹ' | 'ӹ' => one_of(&data_char, 'Ӹ', 'ӹ'), 'Ӻ' | 'ӻ' => one_of(&data_char, 'Ӻ', 'ӻ'), 'Ӽ' | 'ӽ' => one_of(&data_char, 'Ӽ', 'ӽ'), 'Ӿ' | 'ӿ' => one_of(&data_char, 'Ӿ', 'ӿ'),
// greek letters/symbols // - only case-insensitive matching (é won't match e) // - incomplete 'Α' | 'α' => one_of(&data_char, 'Α', 'α'), 'Β' | 'β' => one_of(&data_char, 'Β', 'β'), 'Γ' | 'γ' => one_of(&data_char, 'Γ', 'γ'), 'Δ' | 'δ' => one_of(&data_char, 'Δ', 'δ'), 'Ε' | 'ε' => one_of(&data_char, 'Ε', 'ε'), 'Ζ' | 'ζ' => one_of(&data_char, 'Ζ', 'ζ'), 'Η' | 'η' => one_of(&data_char, 'Η', 'η'), 'Θ' | 'θ' => one_of(&data_char, 'Θ', 'θ'), 'Ι' | 'ι' => one_of(&data_char, 'Ι', 'ι'), 'Κ' | 'κ' => one_of(&data_char, 'Κ', 'κ'), 'Λ' | 'λ' => one_of(&data_char, 'Λ', 'λ'), 'Μ' | 'μ' => one_of(&data_char, 'Μ', 'μ'), 'Ν' | 'ν' => one_of(&data_char, 'Ν', 'ν'), 'Ξ' | 'ξ' => one_of(&data_char, 'Ξ', 'ξ'), 'Ο' | 'ο' => one_of(&data_char, 'Ο', 'ο'), 'Π' | 'π' => one_of(&data_char, 'Π', 'π'), 'Ρ' | 'ρ' => one_of(&data_char, 'Ρ', 'ρ'), 'Σ' | 'σ' => one_of(&data_char, 'Σ', 'σ'), 'Τ' | 'τ' => one_of(&data_char, 'Τ', 'τ'), 'Υ' | 'υ' => one_of(&data_char, 'Υ', 'υ'), 'Φ' | 'φ' => one_of(&data_char, 'Φ', 'φ'), 'Χ' | 'χ' => one_of(&data_char, 'Χ', 'χ'), 'Ψ' | 'ψ' => one_of(&data_char, 'Ψ', 'ψ'), 'Ω' | 'ω' => one_of(&data_char, 'Ω', 'ω'), 'Ϊ' | 'ϊ' => one_of(&data_char, 'Ϊ', 'ϊ'), 'Ϋ' | 'ϋ' => one_of(&data_char, 'Ϋ', 'ϋ'), 'Ͱ' | 'ͱ' => one_of(&data_char, 'Ͱ', 'ͱ'), 'Ͳ' | 'ͳ' => one_of(&data_char, 'Ͳ', 'ͳ'), 'Ͷ' | 'ͷ' => one_of(&data_char, 'Ͷ', 'ͷ'), 'Ϳ' | 'ϳ' => one_of(&data_char, 'Ϳ', 'ϳ'), 'Ϙ' | 'ϙ' => one_of(&data_char, 'Ϙ', 'ϙ'), 'Ϛ' | 'ϛ' => one_of(&data_char, 'Ϛ', 'ϛ'), 'Ϝ' | 'ϝ' => one_of(&data_char, 'Ϝ', 'ϝ'), 'Ϟ' | 'ϟ' => one_of(&data_char, 'Ϟ', 'ϟ'), 'Ϡ' | 'ϡ' => one_of(&data_char, 'Ϡ', 'ϡ'), 'Ά' | 'ά' => one_of(&data_char, 'Ά', 'ά'), 'Έ' | 'έ' => one_of(&data_char, 'Έ', 'έ'), 'Ή' | 'ή' => one_of(&data_char, 'Ή', 'ή'), 'Ί' | 'ί' => one_of(&data_char, 'Ί', 'ί'), 'Ό' | 'ό' => one_of(&data_char, 'Ό', 'ό'), 'Ύ' | 'ύ' => one_of(&data_char, 'Ύ', 'ύ'), 'Ώ' | 'ώ' => one_of(&data_char, 'Ώ', 'ώ'), 'Ϸ' | 'ϸ' => one_of(&data_char, 'Ϸ', 'ϸ'), 'Ϻ' | 'ϻ' => one_of(&data_char, 'Ϻ', 'ϻ'), 'Ͻ' | 'ͻ' => one_of(&data_char, 'Ͻ', 'ͻ'), 'Ͼ' | 'ͼ' => one_of(&data_char, 'Ͼ', 'ͼ'), 'Ͽ' | 'ͽ' => one_of(&data_char, 'Ͽ', 'ͽ'), 'Ϗ' | 'ϗ' => one_of(&data_char, 'Ϗ', 'ϗ'), 'ϴ' | 'ϑ' => one_of(&data_char, 'ϴ', 'ϑ'), 'Ϲ' | 'ϲ' => one_of(&data_char, 'Ϲ', 'ϲ'),
// coptic 'Ϣ' | 'ϣ' => one_of(&data_char, 'Ϣ', 'ϣ'), 'Ϥ' | 'ϥ' => one_of(&data_char, 'Ϥ', 'ϥ'), 'Ϧ' | 'ϧ' => one_of(&data_char, 'Ϧ', 'ϧ'), 'Ϩ' | 'ϩ' => one_of(&data_char, 'Ϩ', 'ϩ'), 'Ϫ' | 'ϫ' => one_of(&data_char, 'Ϫ', 'ϫ'), 'Ϭ' | 'ϭ' => one_of(&data_char, 'Ϭ', 'ϭ'), 'Ϯ' | 'ϯ' => one_of(&data_char, 'Ϯ', 'ϯ'),
// armenian 'Ա' | 'ա' => one_of(&data_char, 'Ա', 'ա'), 'Բ' | 'բ' => one_of(&data_char, 'Բ', 'բ'), 'Գ' | 'գ' => one_of(&data_char, 'Գ', 'գ'), 'Դ' | 'դ' => one_of(&data_char, 'Դ', 'դ'), 'Ե' | 'ե' => one_of(&data_char, 'Ե', 'ե'), 'Զ' | 'զ' => one_of(&data_char, 'Զ', 'զ'), 'Է' | 'է' => one_of(&data_char, 'Է', 'է'), 'Ը' | 'ը' => one_of(&data_char, 'Ը', 'ը'), 'Թ' | 'թ' => one_of(&data_char, 'Թ', 'թ'), 'Ժ' | 'ժ' => one_of(&data_char, 'Ժ', 'ժ'), 'Ի' | 'ի' => one_of(&data_char, 'Ի', 'ի'), 'Լ' | 'լ' => one_of(&data_char, 'Լ', 'լ'), 'Խ' | 'խ' => one_of(&data_char, 'Խ', 'խ'), 'Ծ' | 'ծ' => one_of(&data_char, 'Ծ', 'ծ'), 'Կ' | 'կ' => one_of(&data_char, 'Կ', 'կ'), 'Հ' | 'հ' => one_of(&data_char, 'Հ', 'հ'), 'Ձ' | 'ձ' => one_of(&data_char, 'Ձ', 'ձ'), 'Ղ' | 'ղ' => one_of(&data_char, 'Ղ', 'ղ'), 'Ճ' | 'ճ' => one_of(&data_char, 'Ճ', 'ճ'), 'Մ' | 'մ' => one_of(&data_char, 'Մ', 'մ'), 'Յ' | 'յ' => one_of(&data_char, 'Յ', 'յ'), 'Ն' | 'ն' => one_of(&data_char, 'Ն', 'ն'), 'Շ' | 'շ' => one_of(&data_char, 'Շ', 'շ'), 'Ո' | 'ո' => one_of(&data_char, 'Ո', 'ո'), 'Չ' | 'չ' => one_of(&data_char, 'Չ', 'չ'), 'Պ' | 'պ' => one_of(&data_char, 'Պ', 'պ'), 'Ջ' | 'ջ' => one_of(&data_char, 'Ջ', 'ջ'), 'Ռ' | 'ռ' => one_of(&data_char, 'Ռ', 'ռ'), 'Ս' | 'ս' => one_of(&data_char, 'Ս', 'ս'), 'Վ' | 'վ' => one_of(&data_char, 'Վ', 'վ'), 'Տ' | 'տ' => one_of(&data_char, 'Տ', 'տ'), 'Ր' | 'ր' => one_of(&data_char, 'Ր', 'ր'), 'Ց' | 'ց' => one_of(&data_char, 'Ց', 'ց'), 'Ւ' | 'ւ' => one_of(&data_char, 'Ւ', 'ւ'), 'Փ' | 'փ' => one_of(&data_char, 'Փ', 'փ'), 'Ք' | 'ք' => one_of(&data_char, 'Ք', 'ք'), 'Օ' | 'օ' => one_of(&data_char, 'Օ', 'օ'), 'Ֆ' | 'ֆ' => one_of(&data_char, 'Ֆ', 'ֆ'),
// cherokee 'Ᏸ' | 'ᏸ' => one_of(&data_char, 'Ᏸ', 'ᏸ'), 'Ᏹ' | 'ᏹ' => one_of(&data_char, 'Ᏹ', 'ᏹ'), 'Ᏺ' | 'ᏺ' => one_of(&data_char, 'Ᏺ', 'ᏺ'), 'Ᏻ' | 'ᏻ' => one_of(&data_char, 'Ᏻ', 'ᏻ'), 'Ᏼ' | 'ᏼ' => one_of(&data_char, 'Ᏼ', 'ᏼ'),
_ => false, }; return match is_match { true => Eq::True, false => Eq::False, };}