#[derive(Debug, PartialEq, Eq)]
enum Token {
Num(u64),
Text(String),
}
fn tokenize(name: &str) -> Vec<Token> {
let mut out = Vec::new();
let mut chars = name.chars().peekable();
while let Some(c) = chars.peek().copied() {
if c.is_ascii_digit() {
let mut n: u64 = 0;
while let Some(d) = chars.peek().and_then(|c| c.to_digit(10)) {
n = n.saturating_mul(10).saturating_add(d as u64);
chars.next();
}
out.push(Token::Num(n));
} else {
let mut s = String::new();
while let Some(&c) = chars.peek() {
if c.is_ascii_digit() {
break;
}
if c.is_ascii_alphanumeric() {
s.push(c.to_ascii_lowercase());
}
chars.next();
}
if !s.is_empty() {
out.push(Token::Text(s));
}
}
}
out
}
fn natural_cmp(a: &str, b: &str) -> std::cmp::Ordering {
use std::cmp::Ordering;
let (ta, tb) = (tokenize(a), tokenize(b));
for (x, y) in ta.iter().zip(tb.iter()) {
let ord = match (x, y) {
(Token::Num(m), Token::Num(n)) => m.cmp(n),
(Token::Text(m), Token::Text(n)) => m.cmp(n),
(Token::Num(_), Token::Text(_)) => Ordering::Greater,
(Token::Text(_), Token::Num(_)) => Ordering::Less,
};
if ord != Ordering::Equal {
return ord;
}
}
ta.len().cmp(&tb.len())
}
pub(crate) fn sort_newest_first(models: &mut [&str]) {
models.sort_by(|a, b| natural_cmp(b, a));
}