sub_strs/
cain_str.rs

1/*
2==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--
3
4sub-strs
5
6Copyright (C) 2019-2024  Anonymous
7
8There are several releases over multiple years,
9they are listed as ranges, such as: "2019-2024".
10
11This program is free software: you can redistribute it and/or modify
12it under the terms of the GNU Lesser General Public License as published by
13the Free Software Foundation, either version 3 of the License, or
14(at your option) any later version.
15
16This program is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU Lesser General Public License for more details.
20
21You should have received a copy of the GNU Lesser General Public License
22along with this program.  If not, see <https://www.gnu.org/licenses/>.
23
24::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
25*/
26
27//! # Case-insensitive string
28
29use {
30    alloc::{
31        borrow::Cow,
32        string::{String, ToString},
33    },
34    core::{
35        cmp::Ordering,
36        fmt,
37        hash::{Hash, Hasher},
38        str::FromStr,
39    },
40    crate::Error,
41};
42
43/// # Case-insensitive string
44///
45/// ## Notes
46///
47/// - This struct is intended for sorting in lists, or to be used in sets, so it _generates_ a lower-case form of source string and stores it
48///   inside. That might look like a waste of memory, but it helps with performance. For example when you call [`Vec::sort()`][r://Vec/sort()]
49///   or when you insert it into a [`HashSet`][r://HashSet], it doesn't have to generate any string again and again...
50/// - Implementations of `From<&'a str>`, `From<&'a String>` and `From<String>` either borrow or take the source string and store it inside.
51/// - Implementation of `FromStr` will _clone_ the source strings.
52///
53/// ## Examples
54///
55/// ```
56/// use std::collections::HashSet;
57/// use sub_strs::CainStr;
58///
59/// let wild_data: HashSet<_> = vec!["swift", "C++", "rust", "SWIFT"].into_iter().collect();
60/// assert_eq!(wild_data.len(), 4);
61///
62/// let data: HashSet<_> = wild_data.into_iter().map(|s| CainStr::from(s)).collect();
63/// assert_eq!(data.len(), 3);
64///
65/// let mut data: Vec<_> = data.into_iter().collect();
66/// data.sort();
67/// let data: Vec<_> = data.iter().map(|cs| cs.as_ref()).collect();
68/// assert_eq!(&data[..2], &["C++", "rust"]);
69/// assert!(data[2].eq_ignore_ascii_case("swift"));
70/// ```
71///
72/// [r://HashSet]: https://doc.rust-lang.org/std/collections/struct.HashSet.html
73/// [r://Vec/sort()]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort
74#[derive(Debug, Eq)]
75pub struct CainStr<'a> {
76
77    /// # Source string
78    src: Cow<'a, str>,
79
80    /// # Lower-case form of source string
81    lowercase: String,
82
83}
84
85impl CainStr<'_> {
86
87    /// # Gets the lowercase form of source string
88    pub fn lowercase(&self) -> &str {
89        &self.lowercase
90    }
91
92    /// # Converts self into the lowercase form of source string
93    ///
94    /// This function simply takes the inner field out. There is no allocation.
95    pub fn into_lowercase(self) -> String {
96        self.lowercase
97    }
98
99}
100
101impl Clone for CainStr<'_> {
102
103    fn clone(&self) -> Self {
104        Self {
105            src: self.src.clone().into_owned().into(),
106            lowercase: self.lowercase.to_string(),
107        }
108    }
109
110}
111
112impl PartialEq for CainStr<'_> {
113
114    fn eq(&self, other: &Self) -> bool {
115        self.lowercase.eq(&other.lowercase)
116    }
117
118}
119
120impl Ord for CainStr<'_> {
121
122    fn cmp(&self, other: &Self) -> Ordering {
123        self.lowercase.cmp(&other.lowercase)
124    }
125
126}
127
128impl PartialOrd for CainStr<'_> {
129
130    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
131        Some(self.cmp(other))
132    }
133
134}
135
136impl Hash for CainStr<'_> {
137
138    fn hash<H>(&self, h: &mut H) where H: Hasher {
139        self.lowercase.hash(h);
140    }
141
142}
143
144impl fmt::Display for CainStr<'_> {
145
146    fn fmt(&self, f: &mut fmt::Formatter) -> core::result::Result<(), fmt::Error> {
147        f.write_str(&self.src)
148    }
149
150}
151
152impl<'a> From<Cow<'a, str>> for CainStr<'a> {
153
154    fn from(src: Cow<'a, str>) -> Self {
155        let lowercase = src.to_lowercase();
156        Self {
157            src,
158            lowercase,
159        }
160    }
161
162}
163
164impl<'a> From<CainStr<'a>> for Cow<'a, str> {
165
166    fn from(src: CainStr<'a>) -> Self {
167        src.src
168    }
169
170}
171
172impl<'a> From<&'a str> for CainStr<'a> {
173
174    fn from(src: &'a str) -> Self {
175        Self {
176            src: src.into(),
177            lowercase: src.to_lowercase(),
178        }
179    }
180
181}
182
183impl From<String> for CainStr<'_> {
184
185    fn from(src: String) -> Self {
186        let lowercase = src.to_lowercase();
187        Self {
188            src: src.into(),
189            lowercase,
190        }
191    }
192
193}
194
195impl FromStr for CainStr<'_> {
196
197    type Err = Error;
198
199    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
200        Ok(Self::from(String::from(s)))
201    }
202
203}
204
205impl AsRef<str> for CainStr<'_> {
206
207    fn as_ref(&self) -> &str {
208        &self.src
209    }
210
211}
212
213#[test]
214fn test_cain_str() {
215    let s = "UPPER-CASE";
216    assert_eq!(s.to_lowercase(), CainStr::from(s).lowercase);
217}