Coverage Report

Created: 2026-05-30 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/unicase-2.9.0/src/lib.rs
Line
Count
Source
1
#![cfg_attr(test, deny(missing_docs))]
2
#![cfg_attr(test, deny(warnings))]
3
#![cfg_attr(feature = "nightly", feature(test))]
4
#![no_std]
5
6
//! # UniCase
7
//!
8
//! UniCase provides a way of specifying strings that are case-insensitive.
9
//!
10
//! UniCase supports full [Unicode case
11
//! folding](https://www.w3.org/International/wiki/Case_folding). It can also
12
//! utilize faster ASCII case comparisons, if both strings are ASCII.
13
//!
14
//! Using the `UniCase::new()` constructor will check the string to see if it
15
//! is all ASCII. When a `UniCase` is compared against another, if both are
16
//! ASCII, it will use the faster comparison.
17
//!
18
//! There also exists the `Ascii` type in this crate, which will always assume
19
//! to use the ASCII case comparisons, if the encoding is already known.
20
//!
21
//! ## Example
22
//!
23
//! ```rust
24
//! use unicase::UniCase;
25
//!
26
//! let a = UniCase::new("Maße");
27
//! let b = UniCase::new("MASSE");
28
//! let c = UniCase::new("mase");
29
//!
30
//! assert_eq!(a, b);
31
//! assert!(b != c);
32
//! ```
33
//!
34
//! ## Ascii
35
//!
36
//! ```rust
37
//! use unicase::Ascii;
38
//!
39
//! let a = Ascii::new("foobar");
40
//! let b = Ascii::new("FoObAr");
41
//!
42
//! assert_eq!(a, b);
43
//! ```
44
45
#[cfg(test)]
46
extern crate std;
47
#[cfg(feature = "nightly")]
48
extern crate test;
49
50
extern crate alloc;
51
use alloc::string::String;
52
53
use alloc::borrow::Cow;
54
use core::cmp::Ordering;
55
use core::fmt;
56
use core::hash::{Hash, Hasher};
57
use core::ops::{Deref, DerefMut};
58
use core::str::FromStr;
59
60
use self::unicode::Unicode;
61
62
mod ascii;
63
mod unicode;
64
65
/// Case Insensitive wrapper of strings.
66
#[derive(Clone, Copy)]
67
pub struct UniCase<S>(Encoding<S>);
68
69
/// Case Insensitive wrapper of Ascii strings.
70
#[derive(Clone, Copy, Debug, Default)]
71
pub struct Ascii<S>(S);
72
73
/// Compare two string-like types for case-less equality, using unicode folding.
74
///
75
/// Equivalent to `UniCase::new(left) == UniCase::new(right)`.
76
///
77
/// Note: This will perform a scan for ASCII characters before doing the
78
/// the comparison. See `UniCase` for more information.
79
#[inline]
80
0
pub fn eq<S: AsRef<str> + ?Sized>(left: &S, right: &S) -> bool {
81
0
    UniCase::new(left) == UniCase::new(right)
82
0
}
83
84
/// Compare two string-like types for case-less equality, ignoring ASCII case.
85
///
86
/// Equivalent to `Ascii::new(left) == Ascii::new(right)`.
87
#[inline]
88
0
pub fn eq_ascii<S: AsRef<str> + ?Sized>(left: &S, right: &S) -> bool {
89
0
    Ascii(left) == Ascii(right)
90
0
}
91
92
#[derive(Clone, Copy, Debug)]
93
enum Encoding<S> {
94
    Ascii(Ascii<S>),
95
    Unicode(Unicode<S>),
96
}
97
98
macro_rules! inner {
99
    (mut $e:expr) => {{
100
        match &mut $e {
101
            &mut Encoding::Ascii(ref mut s) => &mut s.0,
102
            &mut Encoding::Unicode(ref mut s) => &mut s.0,
103
        }
104
    }};
105
    ($e:expr) => {{
106
        match &$e {
107
            &Encoding::Ascii(ref s) => &s.0,
108
            &Encoding::Unicode(ref s) => &s.0,
109
        }
110
    }};
111
}
112
113
impl<S: AsRef<str> + Default> Default for UniCase<S> {
114
0
    fn default() -> Self {
115
0
        Self::new(Default::default())
116
0
    }
117
}
118
119
impl<S: AsRef<str>> UniCase<S> {
120
    /// Creates a new `UniCase`.
121
    ///
122
    /// Note: This scans the text to determine if it is all ASCII or not.
123
1.45M
    pub fn new(s: S) -> UniCase<S> {
124
1.45M
        if s.as_ref().is_ascii() {
125
1.37M
            UniCase(Encoding::Ascii(Ascii(s)))
126
        } else {
127
75.8k
            UniCase(Encoding::Unicode(Unicode(s)))
128
        }
129
1.45M
    }
<unicase::UniCase<pulldown_cmark::strings::CowStr>>::new
Line
Count
Source
123
1.45M
    pub fn new(s: S) -> UniCase<S> {
124
1.45M
        if s.as_ref().is_ascii() {
125
1.37M
            UniCase(Encoding::Ascii(Ascii(s)))
126
        } else {
127
75.8k
            UniCase(Encoding::Unicode(Unicode(s)))
128
        }
129
1.45M
    }
Unexecuted instantiation: <unicase::UniCase<_>>::new
130
131
    /// Returns a copy of this string where each character is mapped to its
132
    /// Unicode CaseFolding equivalent.
133
    ///
134
    /// # Note
135
    ///
136
    /// Unicode Case Folding is meant for string storage and matching, not for
137
    /// display.
138
0
    pub fn to_folded_case(&self) -> String {
139
0
        match self.0 {
140
0
            Encoding::Ascii(ref s) => s.0.as_ref().to_ascii_lowercase(),
141
0
            Encoding::Unicode(ref s) => s.to_folded_case(),
142
        }
143
0
    }
144
}
145
146
impl<S> UniCase<S> {
147
    /// Creates a new `UniCase`, skipping the ASCII check.
148
0
    pub const fn unicode(s: S) -> UniCase<S> {
149
0
        UniCase(Encoding::Unicode(Unicode(s)))
150
0
    }
Unexecuted instantiation: <unicase::UniCase<alloc::borrow::Cow<str>>>::unicode
Unexecuted instantiation: <unicase::UniCase<alloc::string::String>>::unicode
Unexecuted instantiation: <unicase::UniCase<&str>>::unicode
151
152
    /// Creates a new `UniCase` which performs only ASCII case folding.
153
0
    pub const fn ascii(s: S) -> UniCase<S> {
154
0
        UniCase(Encoding::Ascii(Ascii(s)))
155
0
    }
156
157
    /// Return `true` if this instance will only perform ASCII case folding.
158
0
    pub fn is_ascii(&self) -> bool {
159
0
        match self.0 {
160
0
            Encoding::Ascii(_) => true,
161
0
            Encoding::Unicode(_) => false,
162
        }
163
0
    }
164
165
    /// Unwraps the inner value held by this `UniCase`.
166
    #[inline]
167
0
    pub fn into_inner(self) -> S {
168
0
        match self.0 {
169
0
            Encoding::Ascii(s) => s.0,
170
0
            Encoding::Unicode(s) => s.0,
171
        }
172
0
    }
Unexecuted instantiation: <unicase::UniCase<alloc::borrow::Cow<str>>>::into_inner
Unexecuted instantiation: <unicase::UniCase<alloc::string::String>>::into_inner
Unexecuted instantiation: <unicase::UniCase<&str>>::into_inner
173
}
174
175
impl<S> Deref for UniCase<S> {
176
    type Target = S;
177
    #[inline]
178
0
    fn deref<'a>(&'a self) -> &'a S {
179
0
        inner!(self.0)
180
0
    }
181
}
182
183
impl<S> DerefMut for UniCase<S> {
184
    #[inline]
185
0
    fn deref_mut<'a>(&'a mut self) -> &'a mut S {
186
0
        inner!(mut self.0)
187
0
    }
188
}
189
190
impl<S: AsRef<str>> AsRef<str> for UniCase<S> {
191
    #[inline]
192
0
    fn as_ref(&self) -> &str {
193
0
        inner!(self.0).as_ref()
194
0
    }
195
}
196
197
impl<S: fmt::Debug> fmt::Debug for UniCase<S> {
198
    #[inline]
199
0
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
200
0
        fmt::Debug::fmt(inner!(self.0), fmt)
201
0
    }
202
}
203
204
impl<S: fmt::Display> fmt::Display for UniCase<S> {
205
    #[inline]
206
0
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
207
0
        fmt::Display::fmt(inner!(self.0), fmt)
208
0
    }
209
}
210
211
impl<S1: AsRef<str>, S2: AsRef<str>> PartialEq<UniCase<S2>> for UniCase<S1> {
212
    #[inline]
213
519k
    fn eq(&self, other: &UniCase<S2>) -> bool {
214
519k
        match (&self.0, &other.0) {
215
480k
            (&Encoding::Ascii(ref x), &Encoding::Ascii(ref y)) => x == y,
216
32.4k
            (&Encoding::Unicode(ref x), &Encoding::Unicode(ref y)) => x == y,
217
4.41k
            (&Encoding::Ascii(ref x), &Encoding::Unicode(ref y)) => &Unicode(x.as_ref()) == y,
218
1.79k
            (&Encoding::Unicode(ref x), &Encoding::Ascii(ref y)) => x == &Unicode(y.as_ref()),
219
        }
220
519k
    }
<unicase::UniCase<pulldown_cmark::strings::CowStr> as core::cmp::PartialEq>::eq
Line
Count
Source
213
519k
    fn eq(&self, other: &UniCase<S2>) -> bool {
214
519k
        match (&self.0, &other.0) {
215
480k
            (&Encoding::Ascii(ref x), &Encoding::Ascii(ref y)) => x == y,
216
32.4k
            (&Encoding::Unicode(ref x), &Encoding::Unicode(ref y)) => x == y,
217
4.41k
            (&Encoding::Ascii(ref x), &Encoding::Unicode(ref y)) => &Unicode(x.as_ref()) == y,
218
1.79k
            (&Encoding::Unicode(ref x), &Encoding::Ascii(ref y)) => x == &Unicode(y.as_ref()),
219
        }
220
519k
    }
Unexecuted instantiation: <unicase::UniCase<_> as core::cmp::PartialEq<unicase::UniCase<_>>>::eq
221
}
222
223
impl<S: AsRef<str>> Eq for UniCase<S> {}
224
225
impl<S: AsRef<str>> Hash for UniCase<S> {
226
    #[inline]
227
716k
    fn hash<H: Hasher>(&self, hasher: &mut H) {
228
716k
        match self.0 {
229
641k
            Encoding::Ascii(ref s) => s.hash(hasher),
230
74.5k
            Encoding::Unicode(ref s) => s.hash(hasher),
231
        }
232
716k
    }
<unicase::UniCase<pulldown_cmark::strings::CowStr> as core::hash::Hash>::hash::<std::hash::random::DefaultHasher>
Line
Count
Source
227
716k
    fn hash<H: Hasher>(&self, hasher: &mut H) {
228
716k
        match self.0 {
229
641k
            Encoding::Ascii(ref s) => s.hash(hasher),
230
74.5k
            Encoding::Unicode(ref s) => s.hash(hasher),
231
        }
232
716k
    }
Unexecuted instantiation: <unicase::UniCase<_> as core::hash::Hash>::hash::<_>
233
}
234
235
impl<S> From<Ascii<S>> for UniCase<S> {
236
0
    fn from(ascii: Ascii<S>) -> Self {
237
0
        UniCase(Encoding::Ascii(ascii))
238
0
    }
239
}
240
241
macro_rules! from_impl {
242
    ($from:ty => $to:ty; $by:ident) => (
243
        impl<'a> From<$from> for UniCase<$to> {
244
0
            fn from(s: $from) -> Self {
245
0
                UniCase::unicode(s.$by())
246
0
            }
Unexecuted instantiation: <unicase::UniCase<alloc::borrow::Cow<str>> as core::convert::From<&str>>::from
Unexecuted instantiation: <unicase::UniCase<alloc::borrow::Cow<str>> as core::convert::From<alloc::string::String>>::from
Unexecuted instantiation: <unicase::UniCase<alloc::string::String> as core::convert::From<&str>>::from
Unexecuted instantiation: <unicase::UniCase<alloc::string::String> as core::convert::From<alloc::borrow::Cow<str>>>::from
Unexecuted instantiation: <unicase::UniCase<&str> as core::convert::From<&alloc::string::String>>::from
247
        }
248
    );
249
    ($from:ty => $to:ty) => ( from_impl!($from => $to; into); )
250
}
251
252
macro_rules! into_impl {
253
    ($to:ty) => {
254
        impl<'a> Into<$to> for UniCase<$to> {
255
0
            fn into(self) -> $to {
256
0
                self.into_inner()
257
0
            }
Unexecuted instantiation: <unicase::UniCase<&str> as core::convert::Into<&str>>::into
Unexecuted instantiation: <unicase::UniCase<alloc::string::String> as core::convert::Into<alloc::string::String>>::into
Unexecuted instantiation: <unicase::UniCase<alloc::borrow::Cow<str>> as core::convert::Into<alloc::borrow::Cow<str>>>::into
258
        }
259
    };
260
}
261
262
impl<S: AsRef<str>> From<S> for UniCase<S> {
263
0
    fn from(s: S) -> Self {
264
0
        UniCase::new(s)
265
0
    }
266
}
267
268
from_impl!(&'a str => Cow<'a, str>);
269
from_impl!(String => Cow<'a, str>);
270
from_impl!(&'a str => String);
271
from_impl!(Cow<'a, str> => String; into_owned);
272
from_impl!(&'a String => &'a str; as_ref);
273
274
into_impl!(&'a str);
275
into_impl!(String);
276
into_impl!(Cow<'a, str>);
277
278
impl<T: AsRef<str>> PartialOrd for UniCase<T> {
279
    #[inline]
280
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
281
0
        Some(self.cmp(other))
282
0
    }
283
}
284
285
impl<T: AsRef<str>> Ord for UniCase<T> {
286
    #[inline]
287
0
    fn cmp(&self, other: &Self) -> Ordering {
288
0
        match (&self.0, &other.0) {
289
0
            (&Encoding::Ascii(ref x), &Encoding::Ascii(ref y)) => x.cmp(y),
290
0
            (&Encoding::Unicode(ref x), &Encoding::Unicode(ref y)) => x.cmp(y),
291
0
            (&Encoding::Ascii(ref x), &Encoding::Unicode(ref y)) => {
292
0
                Unicode(x.as_ref()).cmp(&Unicode(y.0.as_ref()))
293
            }
294
0
            (&Encoding::Unicode(ref x), &Encoding::Ascii(ref y)) => {
295
0
                Unicode(x.0.as_ref()).cmp(&Unicode(y.as_ref()))
296
            }
297
        }
298
0
    }
299
}
300
301
impl<S: FromStr + AsRef<str>> FromStr for UniCase<S> {
302
    type Err = <S as FromStr>::Err;
303
0
    fn from_str(s: &str) -> Result<UniCase<S>, Self::Err> {
304
0
        s.parse().map(UniCase::new)
305
0
    }
306
}
307
308
#[cfg(test)]
309
mod tests {
310
    use super::UniCase;
311
    use alloc::borrow::Cow;
312
    use std::borrow::ToOwned;
313
    use std::collections::hash_map::DefaultHasher;
314
    use std::hash::{Hash, Hasher};
315
    use std::string::String;
316
317
    fn hash<T: Hash>(t: &T) -> u64 {
318
        let mut s = DefaultHasher::new();
319
        t.hash(&mut s);
320
        s.finish()
321
    }
322
323
    #[test]
324
    fn test_copy_for_refs() {
325
        fn foo<T>(_: UniCase<T>) {}
326
327
        let a = UniCase::new("foobar");
328
        foo(a);
329
        foo(a);
330
    }
331
332
    #[test]
333
    fn test_eq_ascii() {
334
        let a = UniCase::new("foobar");
335
        let b = UniCase::new("FOOBAR");
336
        let c = UniCase::ascii("FoObAr");
337
        let d = UniCase::<&str>::from("foobar");
338
339
        assert_eq!(a, b);
340
        assert_eq!(a, c);
341
        assert_eq!(a, d);
342
343
        assert_eq!(b, a);
344
        assert_eq!(b, c);
345
        assert_eq!(b, d);
346
347
        assert_eq!(c, a);
348
        assert_eq!(c, b);
349
        assert_eq!(c, d);
350
351
        assert_eq!(d, a);
352
        assert_eq!(d, b);
353
        assert_eq!(d, c);
354
355
        assert_eq!(hash(&a), hash(&b));
356
        assert_eq!(hash(&a), hash(&c));
357
        assert_eq!(hash(&a), hash(&d));
358
359
        assert!(a.is_ascii());
360
        assert!(b.is_ascii());
361
        assert!(c.is_ascii());
362
        assert!(d.is_ascii());
363
    }
364
365
    #[test]
366
    fn test_str_ascii() {
367
        // https://github.com/seanmonstar/unicase/issues/76
368
369
        let a = UniCase::new("foobar");
370
        let b = UniCase::<&str>::from("foobar");
371
        let c = UniCase::<String>::from(String::from("foobar"));
372
        let d = UniCase::<Cow<str>>::from(Cow::from("foobar"));
373
374
        assert!(a.is_ascii());
375
        assert!(b.is_ascii());
376
        assert!(c.is_ascii());
377
        assert!(d.is_ascii());
378
    }
379
380
    #[test]
381
    fn test_eq_unicode() {
382
        let a = UniCase::new("στιγμας");
383
        let b = UniCase::new("στιγμασ");
384
        assert_eq!(a, b);
385
        assert_eq!(b, a);
386
        assert_eq!(hash(&a), hash(&b));
387
    }
388
389
    #[test]
390
    fn test_eq_unicode_left_is_substring() {
391
        // https://github.com/seanmonstar/unicase/issues/38
392
        let a = UniCase::unicode("foo");
393
        let b = UniCase::unicode("foobar");
394
395
        assert!(a != b);
396
        assert!(b != a);
397
    }
398
399
    #[cfg(feature = "nightly")]
400
    #[bench]
401
    fn bench_unicase_ascii(b: &mut ::test::Bencher) {
402
        b.bytes = b"foobar".len() as u64;
403
        let x = UniCase::new("foobar");
404
        let y = UniCase::new("FOOBAR");
405
        b.iter(|| assert_eq!(x, y));
406
    }
407
408
    #[cfg(feature = "nightly")]
409
    static SUBJECT: &'static [u8] = b"ffoo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz oo bar baz quux herp derp";
410
411
    #[cfg(feature = "nightly")]
412
    #[inline(never)]
413
    fn is_ascii(bytes: &[u8]) -> bool {
414
        #[allow(unused, deprecated)]
415
        use std::ascii::AsciiExt;
416
        bytes.is_ascii()
417
    }
418
419
    #[cfg(feature = "nightly")]
420
    #[bench]
421
    fn bench_is_ascii(b: &mut ::test::Bencher) {
422
        b.iter(|| assert!(is_ascii(SUBJECT)));
423
    }
424
425
    #[cfg(feature = "nightly")]
426
    #[bench]
427
    fn bench_is_utf8(b: &mut ::test::Bencher) {
428
        b.iter(|| assert!(::std::str::from_utf8(SUBJECT).is_ok()));
429
    }
430
431
    #[test]
432
    fn test_case_cmp() {
433
        assert!(UniCase::new("a") < UniCase::new("B"));
434
435
        assert!(UniCase::new("A") < UniCase::new("b"));
436
        assert!(UniCase::new("aa") > UniCase::new("a"));
437
438
        assert!(UniCase::new("a") < UniCase::new("aa"));
439
        assert!(UniCase::new("a") < UniCase::new("AA"));
440
    }
441
442
    #[test]
443
    fn test_from_impls() {
444
        let view: &'static str = "foobar";
445
        let _: UniCase<&'static str> = view.into();
446
        let _: UniCase<&str> = view.into();
447
        let _: UniCase<String> = view.into();
448
449
        let owned: String = view.to_owned();
450
        let _: UniCase<&str> = (&owned).into();
451
        let _: UniCase<String> = owned.into();
452
    }
453
454
    #[test]
455
    fn test_into_impls() {
456
        let view: UniCase<&'static str> = UniCase::new("foobar");
457
        let _: &'static str = view.into();
458
        let _: &str = view.into();
459
460
        let owned: UniCase<String> = "foobar".into();
461
        let _: String = owned.clone().into();
462
        let _: &str = owned.as_ref();
463
    }
464
465
    #[test]
466
    fn test_unicase_unicode_const() {
467
        const _UNICASE: UniCase<&'static str> = UniCase::unicode("");
468
    }
469
}