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}