uncased/borrowed.rs
1use core::cmp::Ordering;
2use core::hash::{Hash, Hasher};
3use core::fmt;
4
5#[cfg(feature = "alloc")]
6use alloc::{string::String, sync::Arc};
7
8/// A cost-free reference to an uncased (case-insensitive, case-preserving)
9/// ASCII string.
10///
11/// This is typically created from an `&str` as follows:
12///
13/// ```rust
14/// use uncased::UncasedStr;
15///
16/// let ascii_ref: &UncasedStr = "Hello, world!".into();
17/// ```
18#[derive(Debug)]
19#[repr(transparent)]
20pub struct UncasedStr(str);
21
22impl UncasedStr {
23 /// Cost-free conversion from an `&str` reference to an `UncasedStr`.
24 ///
25 /// This is a `const fn` on Rust 1.56+.
26 ///
27 /// # Example
28 ///
29 /// ```rust
30 /// use uncased::UncasedStr;
31 ///
32 /// let uncased_str = UncasedStr::new("Hello!");
33 /// assert_eq!(uncased_str, "hello!");
34 /// assert_eq!(uncased_str, "Hello!");
35 /// assert_eq!(uncased_str, "HeLLo!");
36 /// ```
37 #[inline(always)]
38 #[cfg(not(const_fn_transmute))]
39 pub fn new(string: &str) -> &UncasedStr {
40 // This is a `newtype`-like transformation. `repr(transparent)` ensures
41 // that this is safe and correct.
42 unsafe { &*(string as *const str as *const UncasedStr) }
43 }
44
45 /// Cost-free conversion from an `&str` reference to an `UncasedStr`.
46 ///
47 /// This is a `const fn` on Rust 1.56+.
48 ///
49 /// # Example
50 ///
51 /// ```rust
52 /// use uncased::UncasedStr;
53 ///
54 /// let uncased_str = UncasedStr::new("Hello!");
55 /// assert_eq!(uncased_str, "hello!");
56 /// assert_eq!(uncased_str, "Hello!");
57 /// assert_eq!(uncased_str, "HeLLo!");
58 /// ```
59 #[inline(always)]
60 #[cfg(const_fn_transmute)]
61 pub const fn new(string: &str) -> &UncasedStr {
62 // This is a `newtype`-like transformation. `repr(transparent)` ensures
63 // that this is safe and correct.
64 unsafe { core::mem::transmute(string) }
65 }
66
67 /// Returns `self` as an `&str`.
68 ///
69 /// # Example
70 ///
71 /// ```rust
72 /// use uncased::UncasedStr;
73 ///
74 /// let uncased_str = UncasedStr::new("Hello!");
75 /// assert_eq!(uncased_str.as_str(), "Hello!");
76 /// assert_ne!(uncased_str.as_str(), "hELLo!");
77 /// ```
78 #[inline(always)]
79 pub fn as_str(&self) -> &str {
80 &self.0
81 }
82
83 /// Returns the length, in bytes, of `self`.
84 ///
85 /// # Example
86 ///
87 /// ```rust
88 /// use uncased::UncasedStr;
89 ///
90 /// let uncased_str = UncasedStr::new("Hello!");
91 /// assert_eq!(uncased_str.len(), 6);
92 /// ```
93 #[inline(always)]
94 pub fn len(&self) -> usize {
95 self.as_str().len()
96 }
97
98 /// Returns `true` if `self` has a length of zero bytes.
99 ///
100 /// # Examples
101 ///
102 /// ```
103 /// use uncased::UncasedStr;
104 ///
105 /// let s = UncasedStr::new("");
106 /// assert!(s.is_empty());
107 ///
108 /// let s = UncasedStr::new("not empty");
109 /// assert!(!s.is_empty());
110 /// ```
111 #[inline]
112 pub fn is_empty(&self) -> bool {
113 self.as_str().is_empty()
114 }
115
116 /// Returns `true` if `self` starts with any casing of the string `string`;
117 /// otherwise, returns `false`.
118 ///
119 /// # Example
120 ///
121 /// ```rust
122 /// use uncased::UncasedStr;
123 ///
124 /// let uncased_str = UncasedStr::new("MoOO");
125 /// assert!(uncased_str.starts_with("moo"));
126 /// assert!(uncased_str.starts_with("MOO"));
127 /// assert!(uncased_str.starts_with("MOOO"));
128 /// assert!(!uncased_str.starts_with("boo"));
129 ///
130 /// let uncased_str = UncasedStr::new("Bèe");
131 /// assert!(!uncased_str.starts_with("Be"));
132 /// assert!(uncased_str.starts_with("Bè"));
133 /// assert!(uncased_str.starts_with("Bè"));
134 /// assert!(uncased_str.starts_with("bèe"));
135 /// assert!(uncased_str.starts_with("BèE"));
136 /// ```
137 #[inline(always)]
138 pub fn starts_with(&self, string: &str) -> bool {
139 self.as_str()
140 .get(..string.len())
141 .map(|s| Self::new(s) == string)
142 .unwrap_or(false)
143 }
144
145 /// Converts a `Box<UncasedStr>` into an `Uncased` without copying or
146 /// allocating.
147 ///
148 /// # Example
149 ///
150 /// ```rust
151 /// use uncased::Uncased;
152 ///
153 /// let uncased = Uncased::new("Hello!");
154 /// let boxed = uncased.clone().into_boxed_uncased();
155 /// assert_eq!(boxed.into_uncased(), uncased);
156 /// ```
157 #[inline(always)]
158 #[cfg(feature = "alloc")]
159 #[cfg_attr(nightly, doc(cfg(feature = "alloc")))]
160 pub fn into_uncased(self: alloc::boxed::Box<UncasedStr>) -> crate::Uncased<'static> {
161 // This is the inverse of a `newtype`-like transformation. The
162 // `repr(transparent)` ensures that this is safe and correct.
163 unsafe {
164 let raw_str = alloc::boxed::Box::into_raw(self) as *mut str;
165 crate::Uncased::from(alloc::boxed::Box::from_raw(raw_str).into_string())
166 }
167 }
168}
169
170impl<'a> From<&'a str> for &'a UncasedStr {
171 #[inline(always)]
172 fn from(string: &'a str) -> &'a UncasedStr {
173 UncasedStr::new(string)
174 }
175}
176
177impl<I: core::slice::SliceIndex<str, Output = str>> core::ops::Index<I> for UncasedStr {
178 type Output = UncasedStr;
179
180 #[inline]
181 fn index(&self, index: I) -> &Self::Output {
182 self.as_str()[index].into()
183 }
184}
185
186impl AsRef<str> for UncasedStr {
187 fn as_ref(&self) -> &str {
188 self.as_str()
189 }
190}
191
192impl AsRef<[u8]> for UncasedStr {
193 fn as_ref(&self) -> &[u8] {
194 self.as_str().as_bytes()
195 }
196}
197
198impl fmt::Display for UncasedStr {
199 #[inline(always)]
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 self.0.fmt(f)
202 }
203}
204
205macro_rules! impl_partial_eq {
206 ($other:ty $([$o_i:ident])? = $this:ty $([$t_i:ident])?) => (
207 impl PartialEq<$other> for $this {
208 #[inline(always)]
209 fn eq(&self, other: &$other) -> bool {
210 self $(.$t_i())? .eq_ignore_ascii_case(other $(.$o_i())?)
211 }
212 }
213 )
214}
215
216impl_partial_eq!(UncasedStr [as_str] = UncasedStr [as_str]);
217impl_partial_eq!(str = UncasedStr [as_str]);
218impl_partial_eq!(UncasedStr [as_str] = str);
219impl_partial_eq!(str = &UncasedStr [as_str]);
220impl_partial_eq!(&UncasedStr [as_str] = str);
221impl_partial_eq!(&str = UncasedStr [as_str]);
222impl_partial_eq!(UncasedStr [as_str] = &str);
223
224#[cfg(feature = "alloc")] impl_partial_eq!(String [as_str] = UncasedStr [as_str]);
225
226#[cfg(feature = "alloc")] impl_partial_eq!(UncasedStr [as_str] = String [as_str] );
227
228impl Eq for UncasedStr { }
229
230macro_rules! impl_partial_ord {
231 ($other:ty $([$o_i:ident])? >< $this:ty $([$t_i:ident])?) => (
232 impl PartialOrd<$other> for $this {
233 #[inline(always)]
234 fn partial_cmp(&self, other: &$other) -> Option<Ordering> {
235 let this: &UncasedStr = self$(.$t_i())?.into();
236 let other: &UncasedStr = other$(.$o_i())?.into();
237 this.partial_cmp(other)
238 }
239 }
240 )
241}
242
243impl PartialOrd for UncasedStr {
244 #[inline(always)]
245 fn partial_cmp(&self, other: &UncasedStr) -> Option<Ordering> {
246 Some(self.cmp(other))
247 }
248}
249
250impl Ord for UncasedStr {
251 fn cmp(&self, other: &Self) -> Ordering {
252 let self_chars = self.0.chars().map(|c| c.to_ascii_lowercase());
253 let other_chars = other.0.chars().map(|c| c.to_ascii_lowercase());
254 self_chars.cmp(other_chars)
255 }
256}
257
258impl_partial_ord!(str >< UncasedStr);
259impl_partial_ord!(UncasedStr >< str);
260
261#[cfg(feature = "alloc")] impl_partial_ord!(String [as_str] >< UncasedStr);
262#[cfg(feature = "alloc")] impl_partial_ord!(UncasedStr >< String [as_str]);
263
264impl Hash for UncasedStr {
265 #[inline(always)]
266 fn hash<H: Hasher>(&self, hasher: &mut H) {
267 self.0.bytes().for_each(|b| hasher.write_u8(b.to_ascii_lowercase()));
268 }
269}
270
271#[cfg(feature = "alloc")]
272impl From<&UncasedStr> for Arc<UncasedStr> {
273 #[inline]
274 fn from(v: &UncasedStr) -> Arc<UncasedStr> {
275 // SAFETY: `UncasedStr` is repr(transparent)(str). As a result, `str`
276 // and `UncasedStr` have the same size and alignment. Furthermore, the
277 // pointer passed to `from_raw()` is clearly obtained by calling
278 // `into_raw()`. This fulfills the safety requirements of `from_raw()`.
279 let arc: Arc<str> = Arc::from(&v.0);
280 let raw = Arc::into_raw(arc) as *const str as *const UncasedStr;
281 unsafe { Arc::from_raw(raw) }
282 }
283}