Move NumericPrefixWithSuffix into utils

This commit is contained in:
Kirill Bulatov 2024-02-28 11:03:34 +02:00 committed by Kirill Bulatov
parent 96d9df073e
commit ca092fb694
6 changed files with 62 additions and 31 deletions

View file

@ -31,6 +31,7 @@ serde_json.workspace = true
smol.workspace = true
take-until = "0.2.0"
tempfile = { workspace = true, optional = true }
unicase.workspace = true
url.workspace = true
[target.'cfg(windows)'.dependencies]

View file

@ -22,6 +22,7 @@ use std::{
task::{Context, Poll},
time::Instant,
};
use unicase::UniCase;
pub use take_until::*;
@ -487,6 +488,43 @@ impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
}
}
/// A way to sort strings with starting numbers numerically first, falling back to alphanumeric one,
/// case-insensitive.
///
/// This is useful for turning regular alphanumerically sorted sequences as `1-abc, 10, 11-def, .., 2, 21-abc`
/// into `1-abc, 2, 10, 11-def, .., 21-abc`
#[derive(Debug, PartialEq, Eq)]
pub struct NumericPrefixWithSuffix<'a>(i32, &'a str);
impl<'a> NumericPrefixWithSuffix<'a> {
pub fn from_str(str: &'a str) -> Option<Self> {
let mut chars = str.chars();
let prefix: String = chars.by_ref().take_while(|c| c.is_digit(10)).collect();
let remainder = chars.as_str();
match prefix.parse::<i32>() {
Ok(prefix) => Some(NumericPrefixWithSuffix(prefix, remainder)),
Err(_) => None,
}
}
}
impl Ord for NumericPrefixWithSuffix<'_> {
fn cmp(&self, other: &Self) -> Ordering {
let NumericPrefixWithSuffix(num_a, remainder_a) = self;
let NumericPrefixWithSuffix(num_b, remainder_b) = other;
num_a
.cmp(&num_b)
.then_with(|| UniCase::new(remainder_a).cmp(&UniCase::new(remainder_b)))
}
}
impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -526,4 +564,23 @@ mod tests {
assert_eq!(truncate_and_trailoff("èèèèèè", 6), "èèèèèè");
assert_eq!(truncate_and_trailoff("èèèèèè", 5), "èèèèè…");
}
#[test]
fn test_numeric_prefix_with_suffix() {
let mut sorted = vec!["1-abc", "10", "11def", "2", "21-abc"];
sorted.sort_by_key(|s| {
NumericPrefixWithSuffix::from_str(s).unwrap_or_else(|| {
panic!("Cannot convert string `{s}` into NumericPrefixWithSuffix")
})
});
assert_eq!(sorted, ["1-abc", "2", "10", "11def", "21-abc"]);
for numeric_prefix_less in ["numeric_prefix_less", "aaa", "~™£"] {
assert_eq!(
NumericPrefixWithSuffix::from_str(numeric_prefix_less),
None,
"String without numeric prefix `{numeric_prefix_less}` should not be converted into NumericPrefixWithSuffix"
)
}
}
}