cheetah_string/cheetah_string/query.rs
1use core::str;
2
3use super::pattern::{classify, SplitPattern, SplitStr, StrPattern, StrPatternKind};
4use super::CheetahString;
5
6impl CheetahString {
7 // Query methods - delegate to &str
8
9 /// Returns `true` if the string starts with the given pattern.
10 ///
11 /// The stable path delegates to `str::starts_with`. The optional
12 /// `experimental-simd` feature enables a benchmark-gated x86_64 experiment.
13 ///
14 /// # Examples
15 ///
16 /// ```
17 /// use cheetah_string::CheetahString;
18 ///
19 /// let s = CheetahString::from("hello world");
20 /// assert!(s.starts_with("hello"));
21 /// assert!(!s.starts_with("world"));
22 /// assert!(s.starts_with('h'));
23 /// ```
24 #[inline]
25 pub fn starts_with<P: StrPattern>(&self, pat: P) -> bool {
26 match classify(&pat) {
27 StrPatternKind::Char(c) => self.as_str().starts_with(c),
28 StrPatternKind::Str(s) => {
29 #[cfg(all(feature = "experimental-simd", target_arch = "x86_64"))]
30 {
31 if s.len() >= crate::simd::SIMD_THRESHOLD {
32 return crate::simd::starts_with_bytes(self.as_bytes(), s.as_bytes());
33 }
34 }
35
36 self.as_str().starts_with(s)
37 }
38 }
39 }
40
41 /// Returns `true` if the string starts with the given character.
42 ///
43 /// # Examples
44 ///
45 /// ```
46 /// use cheetah_string::CheetahString;
47 ///
48 /// let s = CheetahString::from("hello world");
49 /// assert!(s.starts_with_char('h'));
50 /// assert!(!s.starts_with_char('w'));
51 /// ```
52 #[inline]
53 pub fn starts_with_char(&self, pat: char) -> bool {
54 self.as_str().starts_with(pat)
55 }
56
57 /// Returns `true` if the string ends with the given pattern.
58 ///
59 /// The stable path delegates to `str::ends_with`. The optional
60 /// `experimental-simd` feature enables a benchmark-gated x86_64 experiment.
61 ///
62 /// # Examples
63 ///
64 /// ```
65 /// use cheetah_string::CheetahString;
66 ///
67 /// let s = CheetahString::from("hello world");
68 /// assert!(s.ends_with("world"));
69 /// assert!(!s.ends_with("hello"));
70 /// assert!(s.ends_with('d'));
71 /// ```
72 #[inline]
73 pub fn ends_with<P: StrPattern>(&self, pat: P) -> bool {
74 match classify(&pat) {
75 StrPatternKind::Char(c) => self.as_str().ends_with(c),
76 StrPatternKind::Str(s) => {
77 #[cfg(all(feature = "experimental-simd", target_arch = "x86_64"))]
78 {
79 if s.len() >= crate::simd::SIMD_THRESHOLD {
80 return crate::simd::ends_with_bytes(self.as_bytes(), s.as_bytes());
81 }
82 }
83
84 self.as_str().ends_with(s)
85 }
86 }
87 }
88
89 /// Returns `true` if the string ends with the given character.
90 ///
91 /// # Examples
92 ///
93 /// ```
94 /// use cheetah_string::CheetahString;
95 ///
96 /// let s = CheetahString::from("hello world");
97 /// assert!(s.ends_with_char('d'));
98 /// assert!(!s.ends_with_char('h'));
99 /// ```
100 #[inline]
101 pub fn ends_with_char(&self, pat: char) -> bool {
102 self.as_str().ends_with(pat)
103 }
104
105 /// Returns `true` if the string contains the given pattern.
106 ///
107 /// This method uses the `memchr`/`memmem` search backend.
108 ///
109 /// # Examples
110 ///
111 /// ```
112 /// use cheetah_string::CheetahString;
113 ///
114 /// let s = CheetahString::from("hello world");
115 /// assert!(s.contains("llo"));
116 /// assert!(!s.contains("xyz"));
117 /// assert!(s.contains('o'));
118 /// ```
119 #[inline]
120 pub fn contains<P: StrPattern>(&self, pat: P) -> bool {
121 match classify(&pat) {
122 StrPatternKind::Char(c) => self.as_str().contains(c),
123 StrPatternKind::Str(s) => {
124 crate::search::find_bytes(self.as_bytes(), s.as_bytes()).is_some()
125 }
126 }
127 }
128
129 /// Returns `true` if the string contains the given character.
130 ///
131 /// # Examples
132 ///
133 /// ```
134 /// use cheetah_string::CheetahString;
135 ///
136 /// let s = CheetahString::from("hello world");
137 /// assert!(s.contains_char('o'));
138 /// assert!(!s.contains_char('x'));
139 /// ```
140 #[inline]
141 pub fn contains_char(&self, pat: char) -> bool {
142 self.as_str().contains(pat)
143 }
144
145 /// Returns the byte index of the first occurrence of the pattern, or `None` if not found.
146 ///
147 /// This method uses the `memchr`/`memmem` search backend.
148 ///
149 /// # Examples
150 ///
151 /// ```
152 /// use cheetah_string::CheetahString;
153 ///
154 /// let s = CheetahString::from("hello world");
155 /// assert_eq!(s.find("world"), Some(6));
156 /// assert_eq!(s.find("xyz"), None);
157 /// ```
158 #[inline]
159 pub fn find<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
160 let pat = pat.as_ref();
161 crate::search::find_bytes(self.as_bytes(), pat.as_bytes())
162 }
163
164 /// Returns the byte index of the last occurrence of the pattern, or `None` if not found.
165 ///
166 /// # Examples
167 ///
168 /// ```
169 /// use cheetah_string::CheetahString;
170 ///
171 /// let s = CheetahString::from("hello hello");
172 /// assert_eq!(s.rfind("hello"), Some(6));
173 /// ```
174 #[inline]
175 pub fn rfind<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
176 crate::search::rfind_bytes(self.as_bytes(), pat.as_ref().as_bytes())
177 }
178
179 /// Returns a string slice with leading and trailing whitespace removed.
180 ///
181 /// # Examples
182 ///
183 /// ```
184 /// use cheetah_string::CheetahString;
185 ///
186 /// let s = CheetahString::from(" hello ");
187 /// assert_eq!(s.trim(), "hello");
188 /// ```
189 #[inline]
190 pub fn trim(&self) -> &str {
191 self.as_str().trim()
192 }
193
194 /// Returns a string slice with leading whitespace removed.
195 ///
196 /// # Examples
197 ///
198 /// ```
199 /// use cheetah_string::CheetahString;
200 ///
201 /// let s = CheetahString::from(" hello");
202 /// assert_eq!(s.trim_start(), "hello");
203 /// ```
204 #[inline]
205 pub fn trim_start(&self) -> &str {
206 self.as_str().trim_start()
207 }
208
209 /// Returns a string slice with trailing whitespace removed.
210 ///
211 /// # Examples
212 ///
213 /// ```
214 /// use cheetah_string::CheetahString;
215 ///
216 /// let s = CheetahString::from("hello ");
217 /// assert_eq!(s.trim_end(), "hello");
218 /// ```
219 #[inline]
220 pub fn trim_end(&self) -> &str {
221 self.as_str().trim_end()
222 }
223
224 /// Splits the string by a character pattern.
225 ///
226 /// The returned iterator supports reverse iteration.
227 ///
228 /// # Examples
229 ///
230 /// ```
231 /// use cheetah_string::CheetahString;
232 ///
233 /// let s = CheetahString::from("a,b,c");
234 /// let parts: Vec<&str> = s.split_char(',').rev().collect();
235 /// assert_eq!(parts, vec!["c", "b", "a"]);
236 /// ```
237 #[inline]
238 pub fn split_char(&self, pat: char) -> str::Split<'_, char> {
239 self.as_str().split(pat)
240 }
241
242 /// Splits the string by a string pattern.
243 ///
244 /// The returned iterator is forward-only. This makes unsupported reverse
245 /// iteration a compile-time error instead of a runtime panic.
246 ///
247 /// # Examples
248 ///
249 /// ```
250 /// use cheetah_string::CheetahString;
251 ///
252 /// let s = CheetahString::from("a::b::c");
253 /// let parts: Vec<&str> = s.split_str("::").collect();
254 /// assert_eq!(parts, vec!["a", "b", "c"]);
255 /// ```
256 ///
257 /// ```compile_fail
258 /// use cheetah_string::CheetahString;
259 ///
260 /// let s = CheetahString::from("a::b::c");
261 /// let _ = s.split_str("::").rev();
262 /// ```
263 #[inline]
264 pub fn split_str<'a, 'p>(&'a self, pat: &'p str) -> SplitStr<'a, 'p> {
265 SplitStr::new(self.as_str(), pat)
266 }
267
268 /// Splits with a v2-compatible pattern while retaining its concrete
269 /// iterator capability in the return type.
270 ///
271 /// New code should use [`CheetahString::split_char`] or
272 /// [`CheetahString::split_str`] for a self-documenting capability.
273 #[deprecated(
274 since = "3.0.0",
275 note = "use split_char() for char patterns or split_str() for string patterns"
276 )]
277 #[inline]
278 pub fn split<'a, P>(&'a self, pat: P) -> P::Iter
279 where
280 P: SplitPattern<'a>,
281 {
282 pat.split_pattern(self.as_str())
283 }
284
285 /// Returns an iterator over the lines of the string.
286 ///
287 /// The opaque iterator exposes reverse iteration, cloning, and fused
288 /// iteration because those capabilities are guaranteed by `str::Lines`.
289 ///
290 /// # Examples
291 ///
292 /// ```
293 /// use cheetah_string::CheetahString;
294 ///
295 /// let s = CheetahString::from("line1\nline2\nline3");
296 /// let lines: Vec<&str> = s.lines().collect();
297 /// assert_eq!(lines, vec!["line1", "line2", "line3"]);
298 /// ```
299 #[inline]
300 pub fn lines(
301 &self,
302 ) -> impl DoubleEndedIterator<Item = &str> + Clone + core::iter::FusedIterator {
303 self.as_str().lines()
304 }
305
306 /// Returns an iterator over the characters of the string.
307 ///
308 /// # Examples
309 ///
310 /// ```
311 /// use cheetah_string::CheetahString;
312 ///
313 /// let s = CheetahString::from("hello");
314 /// let chars: Vec<char> = s.chars().collect();
315 /// assert_eq!(chars, vec!['h', 'e', 'l', 'l', 'o']);
316 /// let reversed: Vec<char> = s.chars().rev().collect();
317 /// assert_eq!(reversed, vec!['o', 'l', 'l', 'e', 'h']);
318 /// ```
319 #[inline]
320 pub fn chars(&self) -> str::Chars<'_> {
321 self.as_str().chars()
322 }
323
324 // Transformation methods - create new CheetahString
325
326 /// Returns a new `CheetahString` with all characters converted to uppercase.
327 ///
328 /// # Examples
329 ///
330 /// ```
331 /// use cheetah_string::CheetahString;
332 ///
333 /// let s = CheetahString::from("hello");
334 /// assert_eq!(s.to_uppercase(), "HELLO");
335 /// ```
336 #[inline]
337 pub fn to_uppercase(&self) -> CheetahString {
338 CheetahString::from_string(self.as_str().to_uppercase())
339 }
340
341 /// Returns a new `CheetahString` with all characters converted to lowercase.
342 ///
343 /// # Examples
344 ///
345 /// ```
346 /// use cheetah_string::CheetahString;
347 ///
348 /// let s = CheetahString::from("HELLO");
349 /// assert_eq!(s.to_lowercase(), "hello");
350 /// ```
351 #[inline]
352 pub fn to_lowercase(&self) -> CheetahString {
353 CheetahString::from_string(self.as_str().to_lowercase())
354 }
355
356 /// Replaces all occurrences of a pattern with another string.
357 ///
358 /// # Examples
359 ///
360 /// ```
361 /// use cheetah_string::CheetahString;
362 ///
363 /// let s = CheetahString::from("hello world");
364 /// assert_eq!(s.replace("world", "rust"), "hello rust");
365 /// ```
366 #[inline]
367 pub fn replace<P: AsRef<str>>(&self, from: P, to: &str) -> CheetahString {
368 CheetahString::from_string(self.as_str().replace(from.as_ref(), to))
369 }
370
371 /// Returns a new `CheetahString` with the specified range replaced.
372 ///
373 /// # Examples
374 ///
375 /// ```
376 /// use cheetah_string::CheetahString;
377 ///
378 /// let s = CheetahString::from("hello world");
379 /// assert_eq!(s.replacen("l", "L", 1), "heLlo world");
380 /// ```
381 #[inline]
382 pub fn replacen<P: AsRef<str>>(&self, from: P, to: &str, count: usize) -> CheetahString {
383 CheetahString::from_string(self.as_str().replacen(from.as_ref(), to, count))
384 }
385
386 /// Returns a substring as a new `CheetahString`.
387 ///
388 /// # Panics
389 ///
390 /// Panics if the range is out of bounds, inverted, or not on valid UTF-8
391 /// character boundaries. Use [`CheetahString::try_substring`] for a
392 /// recoverable error.
393 ///
394 /// # Examples
395 ///
396 /// ```
397 /// use cheetah_string::CheetahString;
398 ///
399 /// let s = CheetahString::from("hello world");
400 /// assert_eq!(s.substring(0, 5), "hello");
401 /// assert_eq!(s.substring(6, 11), "world");
402 /// ```
403 #[inline]
404 pub fn substring(&self, start: usize, end: usize) -> CheetahString {
405 self.try_substring(start, end)
406 .expect("substring range must be in bounds and on UTF-8 character boundaries")
407 }
408
409 /// Returns a substring as a new `CheetahString`, or a public error when
410 /// the requested range is invalid.
411 ///
412 /// # Examples
413 ///
414 /// ```
415 /// use cheetah_string::CheetahString;
416 ///
417 /// let s = CheetahString::from("hello world");
418 /// assert_eq!(s.try_substring(0, 5).unwrap(), "hello");
419 /// assert!(s.try_substring(0, 20).is_err());
420 /// ```
421 #[inline]
422 pub fn try_substring(&self, start: usize, end: usize) -> crate::Result<CheetahString> {
423 let value = self.as_str();
424 let len = value.len();
425
426 if start > end {
427 return Err(crate::Error::InvalidRange { start, end });
428 }
429
430 if start > len {
431 return Err(crate::Error::IndexOutOfBounds { index: start, len });
432 }
433
434 if end > len {
435 return Err(crate::Error::IndexOutOfBounds { index: end, len });
436 }
437
438 if !value.is_char_boundary(start) {
439 return Err(crate::Error::InvalidCharBoundary { index: start });
440 }
441
442 if !value.is_char_boundary(end) {
443 return Err(crate::Error::InvalidCharBoundary { index: end });
444 }
445
446 Ok(CheetahString::from_slice(&value[start..end]))
447 }
448
449 /// Repeats the string `n` times.
450 ///
451 /// # Examples
452 ///
453 /// ```
454 /// use cheetah_string::CheetahString;
455 ///
456 /// let s = CheetahString::from("abc");
457 /// assert_eq!(s.repeat(3), "abcabcabc");
458 /// ```
459 #[inline]
460 pub fn repeat(&self, n: usize) -> CheetahString {
461 CheetahString::from_string(self.as_str().repeat(n))
462 }
463}