1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
use std::cmp::Ordering;
#[cfg(test)]
use std::collections::BTreeSet;
fn cmp_ignore_ascii_case(a: &str, b: &str) -> Ordering {
let (mut a, mut b) = (a.chars(), b.chars());
loop {
match (a.next(), b.next()) {
(Some(a), Some(b)) => match a.to_ascii_uppercase().cmp(&b.to_ascii_uppercase()) {
Ordering::Greater => return Ordering::Greater,
Ordering::Less => return Ordering::Less,
Ordering::Equal => (),
}
(None, Some(_)) => return Ordering::Less,
(Some(_), None) => return Ordering::Greater,
(None, None) => return Ordering::Equal,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Caseless<'a>(pub &'a str);
impl PartialEq<Caseless<'_>> for Caseless<'_> {
fn eq(&self, other: &Caseless<'_>) -> bool {
self.0.eq_ignore_ascii_case(other.0)
}
}
impl Eq for Caseless<'_> {}
impl Ord for Caseless<'_> {
fn cmp(&self, other: &Self) -> Ordering {
cmp_ignore_ascii_case(self.0, other.0)
}
}
impl PartialOrd<Caseless<'_>> for Caseless<'_> {
fn partial_cmp(&self, other: &Caseless<'_>) -> Option<Ordering> {
Some(cmp_ignore_ascii_case(self.0, other.0))
}
}
#[test]
fn test_caseless() {
assert_eq!(Caseless("hello"), Caseless("hello"));
assert_eq!(Caseless("HeLLo"), Caseless("hello"));
assert_ne!(Caseless("hello"), Caseless("hellod"));
assert_ne!(Caseless("HeLLo"), Caseless("hellod"));
assert!(Caseless("abc") < Caseless("BCD"));
assert!(Caseless("ABC") < Caseless("bcd"));
assert!(Caseless("ABC") < Caseless("abcd"));
assert!(Caseless("abc") < Caseless("ABCD"));
assert!(Caseless("ABCDE") > Caseless("abcd"));
assert!(Caseless("abcde") > Caseless("ABCD"));
assert!(Caseless("DQ") != Caseless("DX"));
assert!(Caseless("DQ") < Caseless("DX"));
{
let mut s = BTreeSet::new();
s.insert(Caseless("hello"));
assert!(s.contains(&Caseless("HellO")));
assert!(!s.contains(&Caseless("HellfO")));
assert!(!s.insert(Caseless("hEllo")));
}
}