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}