Skip to main content

cheetah_string/cheetah_string/
query.rs

1use core::str;
2
3use super::pattern::{SplitPattern, SplitStr, StrPattern, StrPatternImpl};
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 pat.as_str_pattern() {
27            StrPatternImpl::Char(c) => self.as_str().starts_with(c),
28            StrPatternImpl::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 pat.as_str_pattern() {
75            StrPatternImpl::Char(c) => self.as_str().ends_with(c),
76            StrPatternImpl::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 pat.as_str_pattern() {
122            StrPatternImpl::Char(c) => self.as_str().contains(c),
123            StrPatternImpl::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    /// # Examples
288    ///
289    /// ```
290    /// use cheetah_string::CheetahString;
291    ///
292    /// let s = CheetahString::from("line1\nline2\nline3");
293    /// let lines: Vec<&str> = s.lines().collect();
294    /// assert_eq!(lines, vec!["line1", "line2", "line3"]);
295    /// ```
296    #[inline]
297    pub fn lines(&self) -> impl Iterator<Item = &str> {
298        self.as_str().lines()
299    }
300
301    /// Returns an iterator over the characters of the string.
302    ///
303    /// # Examples
304    ///
305    /// ```
306    /// use cheetah_string::CheetahString;
307    ///
308    /// let s = CheetahString::from("hello");
309    /// let chars: Vec<char> = s.chars().collect();
310    /// assert_eq!(chars, vec!['h', 'e', 'l', 'l', 'o']);
311    /// let reversed: Vec<char> = s.chars().rev().collect();
312    /// assert_eq!(reversed, vec!['o', 'l', 'l', 'e', 'h']);
313    /// ```
314    #[inline]
315    pub fn chars(&self) -> str::Chars<'_> {
316        self.as_str().chars()
317    }
318
319    // Transformation methods - create new CheetahString
320
321    /// Returns a new `CheetahString` with all characters converted to uppercase.
322    ///
323    /// # Examples
324    ///
325    /// ```
326    /// use cheetah_string::CheetahString;
327    ///
328    /// let s = CheetahString::from("hello");
329    /// assert_eq!(s.to_uppercase(), "HELLO");
330    /// ```
331    #[inline]
332    pub fn to_uppercase(&self) -> CheetahString {
333        CheetahString::from_string(self.as_str().to_uppercase())
334    }
335
336    /// Returns a new `CheetahString` with all characters converted to lowercase.
337    ///
338    /// # Examples
339    ///
340    /// ```
341    /// use cheetah_string::CheetahString;
342    ///
343    /// let s = CheetahString::from("HELLO");
344    /// assert_eq!(s.to_lowercase(), "hello");
345    /// ```
346    #[inline]
347    pub fn to_lowercase(&self) -> CheetahString {
348        CheetahString::from_string(self.as_str().to_lowercase())
349    }
350
351    /// Replaces all occurrences of a pattern with another string.
352    ///
353    /// # Examples
354    ///
355    /// ```
356    /// use cheetah_string::CheetahString;
357    ///
358    /// let s = CheetahString::from("hello world");
359    /// assert_eq!(s.replace("world", "rust"), "hello rust");
360    /// ```
361    #[inline]
362    pub fn replace<P: AsRef<str>>(&self, from: P, to: &str) -> CheetahString {
363        CheetahString::from_string(self.as_str().replace(from.as_ref(), to))
364    }
365
366    /// Returns a new `CheetahString` with the specified range replaced.
367    ///
368    /// # Examples
369    ///
370    /// ```
371    /// use cheetah_string::CheetahString;
372    ///
373    /// let s = CheetahString::from("hello world");
374    /// assert_eq!(s.replacen("l", "L", 1), "heLlo world");
375    /// ```
376    #[inline]
377    pub fn replacen<P: AsRef<str>>(&self, from: P, to: &str, count: usize) -> CheetahString {
378        CheetahString::from_string(self.as_str().replacen(from.as_ref(), to, count))
379    }
380
381    /// Returns a substring as a new `CheetahString`.
382    ///
383    /// # Panics
384    ///
385    /// Panics if the range is out of bounds, inverted, or not on valid UTF-8
386    /// character boundaries. Use [`CheetahString::try_substring`] for a
387    /// recoverable error.
388    ///
389    /// # Examples
390    ///
391    /// ```
392    /// use cheetah_string::CheetahString;
393    ///
394    /// let s = CheetahString::from("hello world");
395    /// assert_eq!(s.substring(0, 5), "hello");
396    /// assert_eq!(s.substring(6, 11), "world");
397    /// ```
398    #[inline]
399    pub fn substring(&self, start: usize, end: usize) -> CheetahString {
400        self.try_substring(start, end)
401            .expect("substring range must be in bounds and on UTF-8 character boundaries")
402    }
403
404    /// Returns a substring as a new `CheetahString`, or a public error when
405    /// the requested range is invalid.
406    ///
407    /// # Examples
408    ///
409    /// ```
410    /// use cheetah_string::CheetahString;
411    ///
412    /// let s = CheetahString::from("hello world");
413    /// assert_eq!(s.try_substring(0, 5).unwrap(), "hello");
414    /// assert!(s.try_substring(0, 20).is_err());
415    /// ```
416    #[inline]
417    pub fn try_substring(&self, start: usize, end: usize) -> crate::Result<CheetahString> {
418        let value = self.as_str();
419        let len = value.len();
420
421        if start > end {
422            return Err(crate::Error::InvalidRange { start, end });
423        }
424
425        if start > len {
426            return Err(crate::Error::IndexOutOfBounds { index: start, len });
427        }
428
429        if end > len {
430            return Err(crate::Error::IndexOutOfBounds { index: end, len });
431        }
432
433        if !value.is_char_boundary(start) {
434            return Err(crate::Error::InvalidCharBoundary { index: start });
435        }
436
437        if !value.is_char_boundary(end) {
438            return Err(crate::Error::InvalidCharBoundary { index: end });
439        }
440
441        Ok(CheetahString::from_slice(&value[start..end]))
442    }
443
444    /// Repeats the string `n` times.
445    ///
446    /// # Examples
447    ///
448    /// ```
449    /// use cheetah_string::CheetahString;
450    ///
451    /// let s = CheetahString::from("abc");
452    /// assert_eq!(s.repeat(3), "abcabcabc");
453    /// ```
454    #[inline]
455    pub fn repeat(&self, n: usize) -> CheetahString {
456        CheetahString::from_string(self.as_str().repeat(n))
457    }
458}