feat!: transliteration option for natural sorting (#1053)

Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
George Nelson 2024-05-26 09:10:33 -05:00 committed by sxyazi
parent 061faea1c5
commit 1ab3df6850
No known key found for this signature in database
18 changed files with 909 additions and 59 deletions

View File

@ -1 +1 @@
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds"],"language":"en","version":"0.2","flagWords":[]}
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit"],"version":"0.2","flagWords":[],"language":"en"}

View File

@ -106,18 +106,18 @@ keymap = [
{ on = [ "N" ], run = "find_arrow --previous", desc = "Go to previous found file" },
# Sorting
{ on = [ ",", "m" ], run = "sort modified --dir-first", desc = "Sort by modified time" },
{ on = [ ",", "M" ], run = "sort modified --reverse --dir-first", desc = "Sort by modified time (reverse)" },
{ on = [ ",", "c" ], run = "sort created --dir-first", desc = "Sort by created time" },
{ on = [ ",", "C" ], run = "sort created --reverse --dir-first", desc = "Sort by created time (reverse)" },
{ on = [ ",", "e" ], run = "sort extension --dir-first", desc = "Sort by extension" },
{ on = [ ",", "E" ], run = "sort extension --reverse --dir-first", desc = "Sort by extension (reverse)" },
{ on = [ ",", "a" ], run = "sort alphabetical --dir-first", desc = "Sort alphabetically" },
{ on = [ ",", "A" ], run = "sort alphabetical --reverse --dir-first", desc = "Sort alphabetically (reverse)" },
{ on = [ ",", "n" ], run = "sort natural --dir-first", desc = "Sort naturally" },
{ on = [ ",", "N" ], run = "sort natural --reverse --dir-first", desc = "Sort naturally (reverse)" },
{ on = [ ",", "s" ], run = "sort size --dir-first", desc = "Sort by size" },
{ on = [ ",", "S" ], run = "sort size --reverse --dir-first", desc = "Sort by size (reverse)" },
{ on = [ ",", "m" ], run = "sort modified --reverse=no", desc = "Sort by modified time" },
{ on = [ ",", "M" ], run = "sort modified --reverse", desc = "Sort by modified time (reverse)" },
{ on = [ ",", "c" ], run = "sort created --reverse=no", desc = "Sort by created time" },
{ on = [ ",", "C" ], run = "sort created --reverse", desc = "Sort by created time (reverse)" },
{ on = [ ",", "e" ], run = "sort extension --reverse=no", desc = "Sort by extension" },
{ on = [ ",", "E" ], run = "sort extension --reverse", desc = "Sort by extension (reverse)" },
{ on = [ ",", "a" ], run = "sort alphabetical --reverse=no", desc = "Sort alphabetically" },
{ on = [ ",", "A" ], run = "sort alphabetical --reverse", desc = "Sort alphabetically (reverse)" },
{ on = [ ",", "n" ], run = "sort natural --reverse=no", desc = "Sort naturally" },
{ on = [ ",", "N" ], run = "sort natural --reverse", desc = "Sort naturally (reverse)" },
{ on = [ ",", "s" ], run = "sort size --reverse=no", desc = "Sort by size" },
{ on = [ ",", "S" ], run = "sort size --reverse", desc = "Sort by size (reverse)" },
# Tabs
{ on = [ "t" ], run = "tab_create --current", desc = "Create a new tab using the current path" },

View File

@ -6,8 +6,9 @@
ratio = [ 1, 4, 3 ]
sort_by = "alphabetical"
sort_sensitive = false
sort_reverse = false
sort_reverse = false
sort_dir_first = true
sort_translit = false
linemode = "none"
show_hidden = false
show_symlink = true
@ -185,9 +186,10 @@ open_origin = "hovered"
open_offset = [ 0, 1, 50, 7 ]
[which]
sort_by = "none"
sort_by = "none"
sort_sensitive = false
sort_reverse = false
sort_reverse = false
sort_translit = false
[log]
enabled = false

View File

@ -1,8 +1,8 @@
use std::{fmt, mem};
use std::{fmt, str::FromStr};
use anyhow::{bail, Result};
use anyhow::Result;
use serde::{de::{self, Visitor}, Deserializer};
use yazi_shared::event::{Cmd, Data};
use yazi_shared::event::Cmd;
pub(super) fn run_deserialize<'de, D>(deserializer: D) -> Result<Vec<Cmd>, D::Error>
where
@ -10,33 +10,6 @@ where
{
struct RunVisitor;
#[allow(clippy::explicit_counter_loop)]
fn parse(s: &str) -> Result<Cmd> {
let mut args = shell_words::split(s)?;
let mut cmd = Cmd { name: mem::take(&mut args[0]), ..Default::default() };
let mut i = 0usize;
for arg in args.into_iter().skip(1) {
let Some(arg) = arg.strip_prefix("--") else {
cmd.args.insert(i.to_string(), Data::String(arg));
i += 1;
continue;
};
let mut parts = arg.splitn(2, '=');
let Some(key) = parts.next().map(|s| s.to_owned()) else {
bail!("invalid argument: {arg}");
};
if let Some(val) = parts.next() {
cmd.args.insert(key, Data::String(val.to_owned()));
} else {
cmd.args.insert(key, Data::Boolean(true));
}
}
Ok(cmd)
}
impl<'de> Visitor<'de> for RunVisitor {
type Value = Vec<Cmd>;
@ -50,7 +23,7 @@ where
{
let mut cmds = vec![];
while let Some(value) = &seq.next_element::<String>()? {
cmds.push(parse(value).map_err(de::Error::custom)?);
cmds.push(Cmd::from_str(value).map_err(de::Error::custom)?);
}
if cmds.is_empty() {
return Err(de::Error::custom("`run` within keymap.toml cannot be empty"));
@ -62,7 +35,7 @@ where
where
E: de::Error,
{
Ok(vec![parse(value).map_err(de::Error::custom)?])
Ok(vec![Cmd::from_str(value).map_err(de::Error::custom)?])
}
}

View File

@ -13,6 +13,7 @@ pub struct Manager {
pub sort_sensitive: bool,
pub sort_reverse: bool,
pub sort_dir_first: bool,
pub sort_translit: bool,
// Display
#[validate(length(min = 1, max = 20, message = "must be between 1 and 20 characters"))]

View File

@ -10,6 +10,7 @@ pub struct Which {
pub sort_by: SortBy,
pub sort_sensitive: bool,
pub sort_reverse: bool,
pub sort_translit: bool,
}
impl Default for Which {

View File

@ -1,7 +1,7 @@
use std::{cmp::Ordering, collections::HashMap, mem};
use yazi_config::manager::SortBy;
use yazi_shared::{fs::{File, Url}, natsort};
use yazi_shared::{fs::{File, Url}, natsort, Transliterator};
#[derive(Clone, Copy, Default, PartialEq)]
pub struct FilesSorter {
@ -9,6 +9,7 @@ pub struct FilesSorter {
pub sensitive: bool,
pub reverse: bool,
pub dir_first: bool,
pub translit: bool,
}
impl FilesSorter {
@ -76,7 +77,16 @@ impl FilesSorter {
return promote;
}
let ordering = natsort(entities[a], entities[b], !self.sensitive);
let ordering = if !self.translit {
natsort(entities[a], entities[b], !self.sensitive)
} else {
natsort(
entities[a].transliterate().as_bytes(),
entities[b].transliterate().as_bytes(),
!self.sensitive,
)
};
if self.reverse { ordering.reverse() } else { ordering }
});

View File

@ -8,12 +8,15 @@ use crate::{tab::Tab, tasks::Tasks};
impl Tab {
pub fn sort(&mut self, mut c: Cmd, tasks: &Tasks) {
let conf = &mut self.conf;
if let Some(by) = c.take_first_str() {
self.conf.sort_by = SortBy::from_str(&by).unwrap_or_default();
conf.sort_by = SortBy::from_str(&by).unwrap_or_default();
}
self.conf.sort_sensitive = c.bool("sensitive");
self.conf.sort_reverse = c.bool("reverse");
self.conf.sort_dir_first = c.bool("dir-first");
conf.sort_reverse = c.maybe_bool("reverse").unwrap_or(conf.sort_reverse);
conf.sort_dir_first = c.maybe_bool("dir-first").unwrap_or(conf.sort_dir_first);
conf.sort_sensitive = c.maybe_bool("sensitive").unwrap_or(conf.sort_sensitive);
conf.sort_translit = c.maybe_bool("translit").unwrap_or(conf.sort_translit);
self.apply_files_attrs();
ManagerProxy::update_paged();

View File

@ -9,6 +9,7 @@ pub struct Config {
pub sort_sensitive: bool,
pub sort_reverse: bool,
pub sort_dir_first: bool,
pub sort_translit: bool,
// Display
pub linemode: String,
@ -23,6 +24,7 @@ impl Default for Config {
sort_sensitive: MANAGER.sort_sensitive,
sort_reverse: MANAGER.sort_reverse,
sort_dir_first: MANAGER.sort_dir_first,
sort_translit: MANAGER.sort_translit,
// Display
linemode: MANAGER.linemode.to_owned(),
@ -45,6 +47,7 @@ impl Config {
sensitive: self.sort_sensitive,
reverse: self.sort_reverse,
dir_first: self.sort_dir_first,
translit: self.sort_translit,
}
}
}

View File

@ -1,13 +1,14 @@
use std::{borrow::Cow, mem};
use yazi_config::{keymap::ControlCow, which::SortBy, WHICH};
use yazi_shared::natsort;
use yazi_shared::{natsort, Transliterator};
#[derive(Clone, Copy, PartialEq)]
pub struct WhichSorter {
pub by: SortBy,
pub sensitive: bool,
pub reverse: bool,
pub translit: bool,
}
impl Default for WhichSorter {
@ -16,6 +17,7 @@ impl Default for WhichSorter {
by: WHICH.sort_by,
sensitive: WHICH.sort_sensitive,
reverse: WHICH.sort_reverse,
translit: WHICH.sort_translit,
}
}
}
@ -38,7 +40,16 @@ impl WhichSorter {
}
indices.sort_unstable_by(|&a, &b| {
let ordering = natsort(entities[a].as_bytes(), entities[b].as_bytes(), !self.sensitive);
let ordering = if !self.translit {
natsort(entities[a].as_bytes(), entities[b].as_bytes(), !self.sensitive)
} else {
natsort(
entities[a].as_bytes().transliterate().as_bytes(),
entities[b].as_bytes().transliterate().as_bytes(),
!self.sensitive,
)
};
if self.reverse { ordering.reverse() } else { ordering }
});

View File

@ -26,6 +26,7 @@ impl Config {
reg.add_field_method_get("sort_sensitive", |_, me| Ok(me.sort_sensitive));
reg.add_field_method_get("sort_reverse", |_, me| Ok(me.sort_reverse));
reg.add_field_method_get("sort_dir_first", |_, me| Ok(me.sort_dir_first));
reg.add_field_method_get("sort_translit", |_, me| Ok(me.sort_translit));
reg.add_field_method_get("linemode", |_, me| Ok(me.linemode.to_owned()));
reg.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden));

View File

@ -60,6 +60,11 @@ impl Cmd {
self.args.get(name).and_then(Data::as_bool).unwrap_or(false)
}
#[inline]
pub fn maybe_bool(&self, name: &str) -> Option<bool> {
self.args.get(name).and_then(Data::as_bool)
}
#[inline]
pub fn first(&self) -> Option<&Data> { self.args.get("0") }

View File

@ -25,6 +25,8 @@ impl Data {
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Boolean(b) => Some(*b),
Self::String(s) if s == "no" => Some(false),
Self::String(s) if s == "yes" => Some(true),
_ => None,
}
}

View File

@ -16,6 +16,7 @@ pub mod term;
pub mod theme;
mod throttle;
mod time;
mod translit;
mod xdg;
pub use chars::*;
@ -31,6 +32,7 @@ pub use os::*;
pub use ro_cell::*;
pub use throttle::*;
pub use time::*;
pub use translit::*;
pub use xdg::*;
pub fn init() { event::Event::init(); }

View File

@ -135,12 +135,12 @@ mod tests {
#[test]
fn test_natsort() {
let dates = vec!["1999-3-3", "1999-12-25", "2000-1-2", "2000-1-10", "2000-3-23"];
let fractions = vec![
let dates = ["1999-3-3", "1999-12-25", "2000-1-2", "2000-1-10", "2000-3-23"];
let fractions = [
"1.002.01", "1.002.03", "1.002.08", "1.009.02", "1.009.10", "1.009.20", "1.010.12",
"1.011.02",
];
let words = vec![
let words = [
"1-02",
"1-2",
"1-20",

View File

@ -0,0 +1,5 @@
mod table;
mod traits;
use table::*;
pub use traits::*;

View File

@ -0,0 +1,762 @@
const TABLE_0: [&str; 496] = [
"A", // À 192
"A", // Á 193
"A", // Â 194
"A", // Ã 195
"A", // Ä 196
"A", // Å 197
"AE", // Æ 198
"C", // Ç 199
"E", // È 200
"E", // É 201
"E", // Ê 202
"E", // Ë 203
"I", // Ì 204
"I", // Í 205
"I", // Î 206
"I", // Ï 207
"D", // Ð 208
"N", // Ñ 209
"O", // Ò 210
"O", // Ó 211
"O", // Ô 212
"O", // Õ 213
"O", // Ö 214
"×", // × 215
"O", // Ø 216
"U", // Ù 217
"U", // Ú 218
"U", // Û 219
"U", // Ü 220
"Y", // Ý 221
"T", // Þ 222
"ss", // ß 223
"a", // à 224
"a", // á 225
"a", // â 226
"a", // ã 227
"a", // ä 228
"a", // å 229
"ae", // æ 230
"c", // ç 231
"e", // è 232
"e", // é 233
"e", // ê 234
"e", // ë 235
"i", // ì 236
"i", // í 237
"i", // î 238
"i", // ï 239
"d", // ð 240
"n", // ñ 241
"o", // ò 242
"o", // ó 243
"o", // ô 244
"o", // õ 245
"o", // ö 246
"÷", // ÷ 247
"o", // ø 248
"u", // ù 249
"u", // ú 250
"u", // û 251
"u", // ü 252
"y", // ý 253
"t", // þ 254
"y", // ÿ 255
"A", // Ā 256
"a", // ā 257
"A", // Ă 258
"a", // ă 259
"A", // Ą 260
"a", // ą 261
"C", // Ć 262
"c", // ć 263
"C", // Ĉ 264
"c", // ĉ 265
"C", // Ċ 266
"c", // ċ 267
"C", // Č 268
"c", // č 269
"D", // Ď 270
"d", // ď 271
"D", // Đ 272
"d", // đ 273
"E", // Ē 274
"e", // ē 275
"E", // Ĕ 276
"e", // ĕ 277
"E", // Ė 278
"e", // ė 279
"E", // Ę 280
"e", // ę 281
"E", // Ě 282
"e", // ě 283
"G", // Ĝ 284
"g", // ĝ 285
"G", // Ğ 286
"g", // ğ 287
"G", // Ġ 288
"g", // ġ 289
"G", // Ģ 290
"g", // ģ 291
"H", // Ĥ 292
"h", // ĥ 293
"H", // Ħ 294
"h", // ħ 295
"I", // Ĩ 296
"i", // ĩ 297
"I", // Ī 298
"i", // ī 299
"I", // Ĭ 300
"i", // ĭ 301
"I", // Į 302
"i", // į 303
"I", // İ 304
"i", // ı 305
"IJ", // IJ 306
"ij", // ij 307
"J", // Ĵ 308
"j", // ĵ 309
"K", // Ķ 310
"k", // ķ 311
"k", // ĸ 312
"L", // Ĺ 313
"l", // ĺ 314
"L", // Ļ 315
"l", // ļ 316
"L", // Ľ 317
"l", // ľ 318
"L", // Ŀ 319
"l", // ŀ 320
"L", // Ł 321
"l", // ł 322
"N", // Ń 323
"n", // ń 324
"N", // Ņ 325
"n", // ņ 326
"N", // Ň 327
"n", // ň 328
"n", // ʼn 329
"N", // Ŋ 330
"n", // ŋ 331
"O", // Ō 332
"o", // ō 333
"O", // Ŏ 334
"o", // ŏ 335
"O", // Ő 336
"o", // ő 337
"OE", // Π338
"oe", // œ 339
"R", // Ŕ 340
"r", // ŕ 341
"R", // Ŗ 342
"r", // ŗ 343
"R", // Ř 344
"r", // ř 345
"S", // Ś 346
"s", // ś 347
"S", // Ŝ 348
"s", // ŝ 349
"S", // Ş 350
"s", // ş 351
"S", // Š 352
"s", // š 353
"T", // Ţ 354
"t", // ţ 355
"T", // Ť 356
"t", // ť 357
"T", // Ŧ 358
"t", // ŧ 359
"U", // Ũ 360
"u", // ũ 361
"U", // Ū 362
"u", // ū 363
"U", // Ŭ 364
"u", // ŭ 365
"U", // Ů 366
"u", // ů 367
"U", // Ű 368
"u", // ű 369
"U", // Ų 370
"u", // ų 371
"W", // Ŵ 372
"w", // ŵ 373
"Y", // Ŷ 374
"y", // ŷ 375
"Y", // Ÿ 376
"Z", // Ź 377
"z", // ź 378
"Z", // Ż 379
"z", // ż 380
"Z", // Ž 381
"z", // ž 382
"s", // ſ 383
"ƀ", // ƀ 384
"B", // Ɓ 385
"Ƃ", // Ƃ 386
"ƃ", // ƃ 387
"Ƅ", // Ƅ 388
"ƅ", // ƅ 389
"O", // Ɔ 390
"Ƈ", // Ƈ 391
"ƈ", // ƈ 392
"Ɖ", // Ɖ 393
"D", // Ɗ 394
"Ƌ", // Ƌ 395
"ƌ", // ƌ 396
"ƍ", // ƍ 397
"Ǝ", // Ǝ 398
"E", // Ə 399
"E", // Ɛ 400
"F", // Ƒ 401
"f", // ƒ 402
"Ɠ", // Ɠ 403
"Ɣ", // Ɣ 404
"ƕ", // ƕ 405
"Ɩ", // Ɩ 406
"Ɨ", // Ɨ 407
"K", // Ƙ 408
"k", // ƙ 409
"l", // ƚ 410
"l", // ƛ 411
"Ɯ", // Ɯ 412
"N", // Ɲ 413
"ƞ", // ƞ 414
"O", // Ɵ 415
"O", // Ơ 416
"o", // ơ 417
"Ƣ", // Ƣ 418
"ƣ", // ƣ 419
"Ƥ", // Ƥ 420
"ƥ", // ƥ 421
"Ʀ", // Ʀ 422
"Ƨ", // Ƨ 423
"ƨ", // ƨ 424
"Ʃ", // Ʃ 425
"ƪ", // ƪ 426
"ƫ", // ƫ 427
"Ƭ", // Ƭ 428
"ƭ", // ƭ 429
"Ʈ", // Ʈ 430
"U", // Ư 431
"u", // ư 432
"Ʊ", // Ʊ 433
"Ʋ", // Ʋ 434
"Y", // Ƴ 435
"y", // ƴ 436
"Z", // Ƶ 437
"z", // ƶ 438
"Ʒ", // Ʒ 439
"Ƹ", // Ƹ 440
"ƹ", // ƹ 441
"ƺ", // ƺ 442
"ƻ", // ƻ 443
"Ƽ", // Ƽ 444
"ƽ", // ƽ 445
"ƾ", // ƾ 446
"ƿ", // ƿ 447
"ǀ", // ǀ 448
"ǁ", // ǁ 449
"ǂ", // ǂ 450
"ǃ", // ǃ 451
"DZ", // DŽ 452
"Dz", // Dž 453
"dz", // dž 454
"LJ", // LJ 455
"Lj", // Lj 456
"lj", // lj 457
"NJ", // NJ 458
"Nj", // Nj 459
"nj", // nj 460
"A", // Ǎ 461
"a", // ǎ 462
"I", // Ǐ 463
"i", // ǐ 464
"O", // Ǒ 465
"o", // ǒ 466
"U", // Ǔ 467
"u", // ǔ 468
"U", // Ǖ 469
"u", // ǖ 470
"U", // Ǘ 471
"u", // ǘ 472
"U", // Ǚ 473
"u", // ǚ 474
"U", // Ǜ 475
"u", // ǜ 476
"ǝ", // ǝ 477
"Ǟ", // Ǟ 478
"ǟ", // ǟ 479
"Ǡ", // Ǡ 480
"ǡ", // ǡ 481
"Ǣ", // Ǣ 482
"ǣ", // ǣ 483
"Ǥ", // Ǥ 484
"ǥ", // ǥ 485
"G", // Ǧ 486
"g", // ǧ 487
"Ǩ", // Ǩ 488
"ǩ", // ǩ 489
"O", // Ǫ 490
"o", // ǫ 491
"Ǭ", // Ǭ 492
"ǭ", // ǭ 493
"Ǯ", // Ǯ 494
"e", // ǯ 495
"j", // ǰ 496
"DZ", // DZ 497
"Dz", // Dz 498
"dz", // dz 499
"G", // Ǵ 500
"g", // ǵ 501
"Ƕ", // Ƕ 502
"Ƿ", // Ƿ 503
"N", // Ǹ 504
"n", // ǹ 505
"A", // Ǻ 506
"a", // ǻ 507
"AE", // Ǽ 508
"ae", // ǽ 509
"O", // Ǿ 510
"o", // ǿ 511
"Ȁ", // Ȁ 512
"ȁ", // ȁ 513
"Ȃ", // Ȃ 514
"ȃ", // ȃ 515
"Ȅ", // Ȅ 516
"ȅ", // ȅ 517
"Ȇ", // Ȇ 518
"ȇ", // ȇ 519
"Ȉ", // Ȉ 520
"ȉ", // ȉ 521
"Ȋ", // Ȋ 522
"ȋ", // ȋ 523
"Ȍ", // Ȍ 524
"ȍ", // ȍ 525
"Ȏ", // Ȏ 526
"ȏ", // ȏ 527
"Ȑ", // Ȑ 528
"ȑ", // ȑ 529
"Ȓ", // Ȓ 530
"ȓ", // ȓ 531
"Ȕ", // Ȕ 532
"ȕ", // ȕ 533
"Ȗ", // Ȗ 534
"ȗ", // ȗ 535
"S", // Ș 536
"s", // ș 537
"T", // Ț 538
"t", // ț 539
"Ȝ", // Ȝ 540
"ȝ", // ȝ 541
"Ȟ", // Ȟ 542
"ȟ", // ȟ 543
"Ƞ", // Ƞ 544
"ȡ", // ȡ 545
"Ȣ", // Ȣ 546
"ȣ", // ȣ 547
"Ȥ", // Ȥ 548
"ȥ", // ȥ 549
"Ȧ", // Ȧ 550
"ȧ", // ȧ 551
"Ȩ", // Ȩ 552
"ȩ", // ȩ 553
"Ȫ", // Ȫ 554
"ȫ", // ȫ 555
"Ȭ", // Ȭ 556
"ȭ", // ȭ 557
"Ȯ", // Ȯ 558
"ȯ", // ȯ 559
"Ȱ", // Ȱ 560
"ȱ", // ȱ 561
"Y", // Ȳ 562
"y", // ȳ 563
"ȴ", // ȴ 564
"ȵ", // ȵ 565
"ȶ", // ȶ 566
"j", // ȷ 567
"ȸ", // ȸ 568
"ȹ", // ȹ 569
"Ⱥ", // Ⱥ 570
"Ȼ", // Ȼ 571
"ȼ", // ȼ 572
"L", // Ƚ 573
"Ⱦ", // Ⱦ 574
"ȿ", // ȿ 575
"ɀ", // ɀ 576
"Ɂ", // Ɂ 577
"ɂ", // ɂ 578
"Ƀ", // Ƀ 579
"Ʉ", // Ʉ 580
"Ʌ", // Ʌ 581
"Ɇ", // Ɇ 582
"ɇ", // ɇ 583
"Ɉ", // Ɉ 584
"ɉ", // ɉ 585
"Ɋ", // Ɋ 586
"ɋ", // ɋ 587
"Ɍ", // Ɍ 588
"ɍ", // ɍ 589
"Ɏ", // Ɏ 590
"ɏ", // ɏ 591
"a", // ɐ 592
"a", // ɑ 593
"a", // ɒ 594
"b", // ɓ 595
"o", // ɔ 596
"c", // ɕ 597
"d", // ɖ 598
"d", // ɗ 599
"e", // ɘ 600
"e", // ə 601
"e", // ɚ 602
"o", // ɛ 603
"e", // ɜ 604
"e", // ɝ 605
"e", // ɞ 606
"j", // ɟ 607
"g", // ɠ 608
"g", // ɡ 609
"ɢ", // ɢ 610
"g", // ɣ 611
"ɤ", // ɤ 612
"h", // ɥ 613
"h", // ɦ 614
"h", // ɧ 615
"i", // ɨ 616
"i", // ɩ 617
"ɪ", // ɪ 618
"l", // ɫ 619
"l", // ɬ 620
"l", // ɭ 621
"le", // ɮ 622
"m", // ɯ 623
"m", // ɰ 624
"m", // ɱ 625
"n", // ɲ 626
"n", // ɳ 627
"ɴ", // ɴ 628
"o", // ɵ 629
"OE", // ɶ 630
"ɷ", // ɷ 631
"p", // ɸ 632
"r", // ɹ 633
"r", // ɺ 634
"r", // ɻ 635
"r", // ɼ 636
"r", // ɽ 637
"r", // ɾ 638
"r", // ɿ 639
"ʀ", // ʀ 640
"R", // ʁ 641
"s", // ʂ 642
"s", // ʃ 643
"j", // ʄ 644
"s", // ʅ 645
"s", // ʆ 646
"t", // ʇ 647
"t", // ʈ 648
"u", // ʉ 649
"u", // ʊ 650
"v", // ʋ 651
"v", // ʌ 652
"w", // ʍ 653
"y", // ʎ 654
"ʏ", // ʏ 655
"z", // ʐ 656
"z", // ʑ 657
"e", // ʒ 658
"e", // ʓ 659
"ʔ", // ʔ 660
"ʕ", // ʕ 661
"ʖ", // ʖ 662
"C", // ʗ 663
"o", // ʘ 664
"ʙ", // ʙ 665
"e", // ʚ 666
"G", // ʛ 667
"ʜ", // ʜ 668
"j", // ʝ 669
"k", // ʞ 670
"ʟ", // ʟ 671
"q", // ʠ 672
"ʡ", // ʡ 673
"ʢ", // ʢ 674
"dz", // ʣ 675
"de", // ʤ 676
"dz", // ʥ 677
"ts", // ʦ 678
"ts", // ʧ 679
"tc", // ʨ 680
"fn", // ʩ 681
"ls", // ʪ 682
"lz", // ʫ 683
"W", // ʬ 684
"ʭ", // ʭ 685
"h", // ʮ 686
"h", // ʯ 687
];
const TABLE_1: [&str; 246] = [
"B", // Ḅ 7684
"b", // ḅ 7685
"", // Ḇ 7686
"", // ḇ 7687
"", // Ḉ 7688
"", // ḉ 7689
"", // Ḋ 7690
"", // ḋ 7691
"D", // Ḍ 7692
"d", // ḍ 7693
"D", // Ḏ 7694
"d", // ḏ 7695
"", // Ḑ 7696
"", // ḑ 7697
"D", // Ḓ 7698
"d", // ḓ 7699
"", // Ḕ 7700
"", // ḕ 7701
"", // Ḗ 7702
"", // ḗ 7703
"", // Ḙ 7704
"", // ḙ 7705
"", // Ḛ 7706
"", // ḛ 7707
"", // Ḝ 7708
"", // ḝ 7709
"", // Ḟ 7710
"", // ḟ 7711
"G", // Ḡ 7712
"g", // ḡ 7713
"", // Ḣ 7714
"", // ḣ 7715
"H", // Ḥ 7716
"h", // ḥ 7717
"", // Ḧ 7718
"", // ḧ 7719
"", // Ḩ 7720
"", // ḩ 7721
"H", // Ḫ 7722
"h", // ḫ 7723
"", // Ḭ 7724
"", // ḭ 7725
"", // Ḯ 7726
"", // ḯ 7727
"", // Ḱ 7728
"", // ḱ 7729
"K", // Ḳ 7730
"k", // ḳ 7731
"K", // Ḵ 7732
"k", // ḵ 7733
"L", // Ḷ 7734
"l", // ḷ 7735
"L", // Ḹ 7736
"l", // ḹ 7737
"L", // Ḻ 7738
"l", // ḻ 7739
"L", // Ḽ 7740
"l", // ḽ 7741
"M", // Ḿ 7742
"m", // ḿ 7743
"M", // Ṁ 7744
"m", // ṁ 7745
"M", // Ṃ 7746
"m", // ṃ 7747
"N", // Ṅ 7748
"n", // ṅ 7749
"N", // Ṇ 7750
"n", // ṇ 7751
"N", // Ṉ 7752
"n", // ṉ 7753
"N", // Ṋ 7754
"n", // ṋ 7755
"", // Ṍ 7756
"", // ṍ 7757
"", // Ṏ 7758
"", // ṏ 7759
"", // Ṑ 7760
"", // ṑ 7761
"", // Ṓ 7762
"", // ṓ 7763
"", // Ṕ 7764
"", // ṕ 7765
"", // Ṗ 7766
"", // ṗ 7767
"R", // Ṙ 7768
"r", // ṙ 7769
"R", // Ṛ 7770
"r", // ṛ 7771
"R", // Ṝ 7772
"r", // ṝ 7773
"R", // Ṟ 7774
"r", // ṟ 7775
"S", // Ṡ 7776
"s", // ṡ 7777
"S", // Ṣ 7778
"s", // ṣ 7779
"", // Ṥ 7780
"", // ṥ 7781
"", // Ṧ 7782
"", // ṧ 7783
"", // Ṩ 7784
"", // ṩ 7785
"", // Ṫ 7786
"", // ṫ 7787
"T", // Ṭ 7788
"t", // ṭ 7789
"T", // Ṯ 7790
"t", // ṯ 7791
"T", // Ṱ 7792
"t", // ṱ 7793
"", // Ṳ 7794
"", // ṳ 7795
"", // Ṵ 7796
"", // ṵ 7797
"", // Ṷ 7798
"", // ṷ 7799
"", // Ṹ 7800
"", // ṹ 7801
"", // Ṻ 7802
"", // ṻ 7803
"", // Ṽ 7804
"", // ṽ 7805
"", // Ṿ 7806
"ṿ", // ṿ 7807
"W", // Ẁ 7808
"w", // ẁ 7809
"W", // Ẃ 7810
"w", // ẃ 7811
"W", // Ẅ 7812
"w", // ẅ 7813
"", // Ẇ 7814
"", // ẇ 7815
"", // Ẉ 7816
"", // ẉ 7817
"", // Ẋ 7818
"", // ẋ 7819
"", // Ẍ 7820
"", // ẍ 7821
"Y", // Ẏ 7822
"y", // ẏ 7823
"", // Ẑ 7824
"", // ẑ 7825
"Z", // Ẓ 7826
"z", // ẓ 7827
"Z", // Ẕ 7828
"z", // ẕ 7829
"h", // ẖ 7830
"t", // ẗ 7831
"", // ẘ 7832
"", // ẙ 7833
"", // ẚ 7834
"", // ẛ 7835
"", // ẜ 7836
"", // 7837
"SS", // ẞ 7838
"", // ẟ 7839
"A", // Ạ 7840
"a", // ạ 7841
"A", // Ả 7842
"a", // ả 7843
"A", // Ấ 7844
"a", // ấ 7845
"A", // Ầ 7846
"a", // ầ 7847
"A", // Ẩ 7848
"a", // ẩ 7849
"A", // Ẫ 7850
"a", // ẫ 7851
"A", // Ậ 7852
"a", // ậ 7853
"A", // Ắ 7854
"a", // ắ 7855
"A", // Ằ 7856
"a", // ằ 7857
"A", // Ẳ 7858
"a", // ẳ 7859
"A", // Ẵ 7860
"a", // ẵ 7861
"A", // Ặ 7862
"a", // ặ 7863
"E", // Ẹ 7864
"e", // ẹ 7865
"E", // Ẻ 7866
"e", // ẻ 7867
"E", // Ẽ 7868
"e", // ẽ 7869
"E", // Ế 7870
"e", // ế 7871
"E", // Ề 7872
"e", // ề 7873
"E", // Ể 7874
"e", // ể 7875
"E", // Ễ 7876
"e", // ễ 7877
"E", // Ệ 7878
"e", // ệ 7879
"I", // Ỉ 7880
"i", // ỉ 7881
"I", // Ị 7882
"i", // ị 7883
"O", // Ọ 7884
"o", // ọ 7885
"O", // Ỏ 7886
"o", // ỏ 7887
"O", // Ố 7888
"o", // ố 7889
"O", // Ồ 7890
"o", // ồ 7891
"O", // Ổ 7892
"o", // ổ 7893
"O", // Ỗ 7894
"o", // ỗ 7895
"O", // Ộ 7896
"o", // ộ 7897
"O", // Ớ 7898
"o", // ớ 7899
"O", // Ờ 7900
"o", // ờ 7901
"O", // Ở 7902
"o", // ở 7903
"O", // Ỡ 7904
"o", // ỡ 7905
"O", // Ợ 7906
"o", // ợ 7907
"U", // Ụ 7908
"u", // ụ 7909
"U", // Ủ 7910
"u", // ủ 7911
"U", // Ứ 7912
"u", // ứ 7913
"U", // Ừ 7914
"u", // ừ 7915
"U", // Ử 7916
"u", // ử 7917
"U", // Ữ 7918
"u", // ữ 7919
"U", // Ự 7920
"u", // ự 7921
"Y", // Ỳ 7922
"y", // ỳ 7923
"Y", // Ỵ 7924
"y", // ỵ 7925
"Y", // Ỷ 7926
"y", // ỷ 7927
"Y", // Ỹ 7928
"y", // ỹ 7929
];
const TABLE_2: [&str; 2] = [
"fi", // fi 64257
"fl", // fl 64258
];
#[inline(always)]
pub(super) fn lookup(c: char) -> Option<&'static str> {
match c as u16 {
192..=687 => unsafe { Some(TABLE_0.get_unchecked((c as u16 - 192) as usize)) },
7684..=7929 => unsafe { Some(TABLE_1.get_unchecked((c as u16 - 7684) as usize)) },
64257..=64258 => unsafe { Some(TABLE_2.get_unchecked((c as u16 - 64257) as usize)) },
_ => None,
}
}

View File

@ -0,0 +1,69 @@
use core::str;
use std::borrow::Cow;
pub trait Transliterator {
fn transliterate(&self) -> Cow<str>;
}
impl Transliterator for &[u8] {
fn transliterate(&self) -> Cow<str> {
// Fast path to skip over ASCII chars at the beginning of the string
let ascii_len = self.iter().take_while(|&&c| c < 0x7f).count();
if ascii_len >= self.len() {
return Cow::Borrowed(unsafe { str::from_utf8_unchecked(self) });
}
let (ascii, rest) = self.split_at(ascii_len);
let ascii = unsafe { str::from_utf8_unchecked(ascii) };
// Reserve a bit more space to avoid reallocations on longer transliterations
// but instead of `+ 16` uses `| 15` to stay in the smallest allocation bucket
// for short strings
let mut out = String::new();
out.try_reserve_exact(self.len() | 15).unwrap_or_else(|_| panic!());
out.push_str(ascii);
for c in String::from_utf8_lossy(rest).chars() {
if let Some(s) = super::lookup(c) {
out.push_str(s);
} else {
out.push(c);
}
}
Cow::Owned(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transliterate() {
assert_eq!("Æcœ".as_bytes().transliterate(), "AEcoe");
assert_eq!(
"ěřůøĉĝĥĵŝŭèùÿėįųāēīūļķņģőűëïąćęłńśźżõșțčďĺľňŕšťýžéíñóúüåäöçîşûğăâđêôơưáàãảạ"
.as_bytes()
.transliterate(),
"eruocghjsueuyeiuaeiulkngoueiacelnszzostcdllnrstyzeinouuaaocisugaadeoouaaaaa",
);
assert_eq!(
"áạàảãăắặằẳẵâấậầẩẫéẹèẻẽêếệềểễiíịìỉĩoóọòỏõôốộồổỗơớợờởỡúụùủũưứựừửữyýỵỳỷỹđ"
.as_bytes()
.transliterate(),
"aaaaaaaaaaaaaaaaaeeeeeeeeeeeiiiiiioooooooooooooooooouuuuuuuuuuuyyyyyyd",
);
assert_ne!(
"ěřůøĉĝĥĵŝŭèùÿėįųāēīūļķņģőűëïąćęłńśźżõșțčďĺľňŕšťýžéíñóúüåäöçîşûğăâđêôơưáàãảạfifl"
.as_bytes()
.transliterate(),
"ěřůøĉĝĥĵŝŭèùÿėįųāēīūļķņģőűëïąćęłńśźżõșțčďĺľňŕšťýžéíñóúüåäöçîşûğăâđêôơưáàãảạfifl"
);
assert_eq!(
"THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOGthequickbrownfoxjumpedoverthelazydog"
.as_bytes()
.transliterate(),
"THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOGthequickbrownfoxjumpedoverthelazydog"
);
}
}