konst/string.rs
1//! `const fn` equivalents of `str` methods.
2//!
3
4#[cfg(feature = "iter")]
5mod chars_methods;
6
7#[cfg(feature = "iter")]
8pub use chars_methods::*;
9
10mod concatenation;
11
12pub use concatenation::*;
13
14#[cfg(test)]
15mod priv_string_tests;
16
17mod pattern;
18
19use core::fmt::{self, Debug, Display};
20
21pub use self::pattern::Pattern;
22
23pub(crate) use self::pattern::PatternNorm;
24
25mod split_once;
26
27pub use split_once::*;
28
29#[cfg(feature = "iter")]
30mod splitting;
31
32#[cfg(feature = "iter")]
33pub use splitting::*;
34
35#[cfg(feature = "iter")]
36mod split_terminator_items;
37
38#[cfg(feature = "iter")]
39pub use split_terminator_items::*;
40
41use konst_kernel::string::__is_char_boundary_bytes;
42
43__declare_string_cmp_fns! {
44 import_path = "konst",
45 equality_fn = eq_str,
46 ordering_fn = cmp_str,
47 ordering_fn_inner = cmp_str_inner,
48}
49
50#[cfg(feature = "cmp")]
51__declare_fns_with_docs! {
52 (Option<&'a str>, (eq_option_str, cmp_option_str))
53
54 docs(default)
55
56 macro = __impl_option_cmp_fns!(
57 #[cfg_attr(feature = "docsrs", doc(cfg(feature = "cmp")))]
58 for['a,]
59 params(l, r)
60 eq_comparison = crate::cmp::CmpWrapper(l).const_eq(r),
61 cmp_comparison = crate::cmp::CmpWrapper(l).const_cmp(r),
62 parameter_copyability = copy,
63 ),
64}
65
66/// Delegates to [`core::str::from_utf8`],
67/// wrapping the error to provide a `panic` method for use in [`unwrap_ctx`]
68///
69/// # Example
70///
71/// ### Basic
72///
73/// ```rust
74/// use konst::{
75/// result::unwrap_ctx,
76/// string,
77/// };
78///
79/// const STR: &str = unwrap_ctx!(string::from_utf8(b"foo bar"));
80///
81/// assert_eq!(STR, "foo bar")
82/// ```
83///
84/// ### Compile-time error
85///
86/// ```compile_fail
87/// use konst::{
88/// result::unwrap_ctx,
89/// string,
90/// };
91///
92/// const _: &str = unwrap_ctx!(string::from_utf8(&[255, 255, 255]));
93/// ```
94///
95/// ```text
96/// error[E0080]: evaluation of constant value failed
97/// --> src/string.rs:88:17
98/// |
99/// 9 | const _: &str = unwrap_ctx!(string::from_utf8(&[255, 255, 255]));
100/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid utf-8 sequence of 1 bytes from index 0', src/string.rs:9:17
101/// |
102/// = note: this error originates in the macro `unwrap_ctx` (in Nightly builds, run with -Z macro-backtrace for more info)
103///
104/// ```
105///
106/// [`unwrap_ctx`]: crate::result::unwrap_ctx
107pub const fn from_utf8(slice: &[u8]) -> Result<&str, Utf8Error> {
108 match core::str::from_utf8(slice) {
109 Ok(x) => Ok(x),
110 Err(e) => Err(Utf8Error(e)),
111 }
112}
113
114/// Wrapper around [`core::str::Utf8Error`]
115/// to provide a `panic` method for use in [`unwrap_ctx`],
116/// returned by [`from_utf8`](crate::string::from_utf8).
117///
118/// [`unwrap_ctx`]: crate::result::unwrap_ctx
119#[derive(Copy, Clone)]
120pub struct Utf8Error(pub core::str::Utf8Error);
121
122impl Utf8Error {
123 /// Panics with a `Display` formatted error message
124 #[track_caller]
125 pub const fn panic(self) -> ! {
126 let pvs = const_panic::StdWrapper(&self.0).to_panicvals(const_panic::FmtArg::DISPLAY);
127 const_panic::concat_panic(&[&pvs])
128 }
129}
130
131impl Debug for Utf8Error {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 Debug::fmt(&self.0, f)
134 }
135}
136
137impl Display for Utf8Error {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 Display::fmt(&self.0, f)
140 }
141}
142
143/// A const equivalent of
144/// [`str::starts_with`](https://doc.rust-lang.org/std/primitive.str.html#method.starts_with)
145/// , taking a [`Pattern`] parameter.
146///
147/// # Example
148///
149/// ```rust
150/// use konst::string;
151///
152/// assert!( string::starts_with("foo,bar,baz", "foo,"));
153///
154/// assert!(!string::starts_with("foo,bar,baz", "bar"));
155/// assert!(!string::starts_with("foo,bar,baz", "baz"));
156///
157/// ```
158///
159#[inline(always)]
160pub const fn starts_with<'a, P>(left: &str, pat: P) -> bool
161where
162 P: Pattern<'a>,
163{
164 let pat = PatternNorm::new(pat);
165 crate::slice::__bytes_start_with(left.as_bytes(), pat.as_bytes())
166}
167
168/// A const equivalent of
169/// [`str::ends_with`](https://doc.rust-lang.org/std/primitive.str.html#method.ends_with)
170/// , taking a [`Pattern`] parameter.
171///
172/// # Example
173///
174/// ```rust
175/// use konst::string;
176///
177/// assert!( string::ends_with("foo,bar,baz", ",baz"));
178/// assert!( string::ends_with("abc...z", 'z'));
179///
180/// assert!(!string::ends_with("foo,bar,baz", "bar"));
181/// assert!(!string::ends_with("foo,bar,baz", "foo"));
182/// assert!(!string::ends_with("abc", 'z'));
183///
184/// ```
185///
186#[inline(always)]
187pub const fn ends_with<'a, P>(left: &str, pat: P) -> bool
188where
189 P: Pattern<'a>,
190{
191 let pat = PatternNorm::new(pat);
192 crate::slice::__bytes_end_with(left.as_bytes(), pat.as_bytes())
193}
194
195/// A const equivalent of
196/// [`str::find`](https://doc.rust-lang.org/std/primitive.str.html#method.find)
197/// , taking a [`Pattern`] parameter.
198///
199/// # Example
200///
201/// ```rust
202/// use konst::string;
203///
204/// assert_eq!(string::find("foo-bar-baz", 'q'), None);
205/// assert_eq!(string::find("foo-bar-baz", '-'), Some(3));
206///
207/// assert_eq!(string::find("foo-bar-baz-foo", "qux"), None);
208/// assert_eq!(string::find("foo-bar-baz-foo", "foo"), Some(0));
209/// assert_eq!(string::find("foo-bar-baz-foo-bar", "bar"), Some(4));
210/// assert_eq!(string::find("foo-the-baz-foo-bar", "bar"), Some(16));
211///
212/// ```
213///
214#[inline]
215pub const fn find<'a, P>(left: &str, pat: P) -> Option<usize>
216where
217 P: Pattern<'a>,
218{
219 let pat = PatternNorm::new(pat);
220 crate::slice::__bytes_find(left.as_bytes(), pat.as_bytes())
221}
222
223/// A const equivalent of
224/// [`str::contains`](https://doc.rust-lang.org/std/primitive.str.html#method.contains)
225/// , taking a [`Pattern`] parameter.
226///
227/// # Example
228///
229/// ```rust
230/// use konst::string;
231///
232/// assert!(string::contains("foo-bar-baz", '-'));
233/// assert!(!string::contains("foo-bar-baz", 'q'));
234///
235/// assert!(string::contains("foo-bar-baz-foo", "foo"));
236///
237/// assert!( string::contains("foo-bar-baz-foo-bar", "bar"));
238/// assert!(!string::contains("foo-he-baz-foo-he", "bar"));
239///
240/// ```
241///
242#[inline]
243pub const fn contains<'a, P>(left: &str, pat: P) -> bool
244where
245 P: Pattern<'a>,
246{
247 let pat = PatternNorm::new(pat);
248 matches!(
249 crate::slice::__bytes_find(left.as_bytes(), pat.as_bytes()),
250 Some(_)
251 )
252}
253
254/// A const equivalent of
255/// [`str::rfind`](https://doc.rust-lang.org/std/primitive.str.html#method.rfind)
256/// , taking a [`Pattern`] parameter.
257///
258/// # Example
259///
260/// ```rust
261/// use konst::string;
262///
263/// assert_eq!(string::rfind("bar-baz-baz", 'q'), None);
264/// assert_eq!(string::rfind("bar-baz-baz", '-'), Some(7));
265///
266/// assert_eq!(string::rfind("bar-baz", "foo"), None);
267/// assert_eq!(string::rfind("bar-baz-foo", "foo"), Some(8));
268/// assert_eq!(string::rfind("foo-bar-baz", "foo"), Some(0));
269///
270/// ```
271///
272#[inline]
273pub const fn rfind<'a, P>(left: &str, pat: P) -> Option<usize>
274where
275 P: Pattern<'a>,
276{
277 let pat = PatternNorm::new(pat);
278 crate::slice::__bytes_rfind(left.as_bytes(), pat.as_bytes())
279}
280
281/// A const equivalent of
282/// [`str::contains`](https://doc.rust-lang.org/std/primitive.str.html#method.contains)
283/// , taking a [`Pattern`] parameter.
284///
285/// # Example
286///
287/// ```rust
288/// use konst::string;
289///
290/// assert!(string::rcontains("foo-bar-baz", '-'));
291/// assert!(!string::rcontains("foo-bar-baz", 'q'));
292///
293/// assert!(!string::rcontains("bar-baz", "foo"));
294/// assert!(string::rcontains("foo-bar", "foo"));
295///
296/// ```
297///
298#[inline(always)]
299pub const fn rcontains<'a, P>(left: &str, pat: P) -> bool
300where
301 P: Pattern<'a>,
302{
303 let pat = PatternNorm::new(pat);
304 matches!(
305 crate::slice::__bytes_rfind(left.as_bytes(), pat.as_bytes()),
306 Some(_)
307 )
308}
309
310/// A const equivalent of `&string[..len]`.
311///
312/// If `string.len() < len`, this simply returns `string` back.
313///
314/// # Panics
315///
316/// This function panics if `len` is inside the string but doesn't fall on a char boundary.
317///
318/// # Example
319///
320/// ```
321/// use konst::string::str_up_to;
322///
323/// const STR: &str = "foo bar baz";
324///
325/// const SUB0: &str = str_up_to(STR, 3);
326/// assert_eq!(SUB0, "foo");
327///
328/// const SUB1: &str = str_up_to(STR, 7);
329/// assert_eq!(SUB1, "foo bar");
330///
331/// const SUB2: &str = str_up_to(STR, 11);
332/// assert_eq!(SUB2, STR);
333///
334/// const SUB3: &str = str_up_to(STR, 100);
335/// assert_eq!(SUB3, STR);
336///
337///
338/// ```
339#[doc(inline)]
340pub use konst_kernel::string::str_up_to;
341
342/// A const equivalent of `&string[start..]`.
343///
344/// If `string.len() < start`, this simply returns an empty string` back.
345///
346/// # Panics
347///
348/// This function panics if `start` is inside the string but doesn't fall on a char boundary.
349///
350/// # Example
351///
352/// ```
353/// use konst::string::str_from;
354///
355/// const STR: &str = "foo bar baz";
356///
357/// const SUB0: &str = str_from(STR, 0);
358/// assert_eq!(SUB0, STR);
359///
360/// const SUB1: &str = str_from(STR, 4);
361/// assert_eq!(SUB1, "bar baz");
362///
363/// const SUB2: &str = str_from(STR, 8);
364/// assert_eq!(SUB2, "baz");
365///
366/// const SUB3: &str = str_from(STR, 11);
367/// assert_eq!(SUB3, "");
368///
369/// const SUB4: &str = str_from(STR, 1000);
370/// assert_eq!(SUB3, "");
371///
372///
373/// ```
374#[doc(inline)]
375pub use konst_kernel::string::str_from;
376
377/// A const equivalent of `&string[start..end]`.
378///
379/// If `start >= end ` or `string.len() < start `, this returns an empty string.
380///
381/// If `string.len() < end`, this returns the string from `start`.
382///
383/// # Alternatives
384///
385/// For a const equivalent of `&string[start..]` there's [`str_from`].
386///
387/// For a const equivalent of `&string[..end]` there's [`str_up_to`].
388///
389/// [`str_from`]: ./fn.str_from.html
390/// [`str_up_to`]: ./fn.str_up_to.html
391///
392/// # Panics
393///
394/// This function panics if either `start` or `end` are inside the string and
395/// don't fall on a char boundary.
396///
397/// # Example
398///
399/// ```
400/// use konst::string::str_range;
401///
402/// const STR: &str = "foo bar baz";
403///
404/// const SUB0: &str = str_range(STR, 0, 3);
405/// assert_eq!(SUB0, "foo");
406///
407/// const SUB1: &str = str_range(STR, 0, 7);
408/// assert_eq!(SUB1, "foo bar");
409///
410/// const SUB2: &str = str_range(STR, 4, 11);
411/// assert_eq!(SUB2, "bar baz");
412///
413/// const SUB3: &str = str_range(STR, 0, 1000);
414/// assert_eq!(SUB3, STR);
415///
416///
417/// ```
418#[doc(inline)]
419pub use konst_kernel::string::str_range;
420
421/// Const equivalent of [`str::is_char_boundary`].
422///
423/// # Example
424///
425/// ```
426/// use konst::string::is_char_boundary;
427///
428/// let string = "锈 is 🧠";
429///
430/// // Start of "锈"
431/// assert!(is_char_boundary(string, 0));
432/// assert!(!is_char_boundary(string, 1));
433/// assert!(!is_char_boundary(string, 2));
434///
435/// // start of " "
436/// assert!(is_char_boundary(string, 3));
437///
438/// // start of "🧠"
439/// assert!(is_char_boundary(string, 7));
440/// assert!(!is_char_boundary(string, 8));
441///
442/// // end of string
443/// assert!(is_char_boundary(string, string.len()));
444///
445/// // after end of string
446/// assert!(!is_char_boundary(string, string.len() + 1));
447///
448///
449/// ```
450#[doc(inline)]
451pub use konst_kernel::string::is_char_boundary;
452
453/// Checks that the start and end are valid utf8 char boundaries
454/// when the `"debug"` feature is enabled.
455///
456/// When the `"debug"` feature is disabled,
457/// this is equivalent to calling `core::str::from_utf8_unchecled`
458///
459/// # Safety
460///
461/// The input byte slice must be a subslice of a `&str`,
462/// so that only the start and end need to be checked.
463#[doc(inline)]
464pub use konst_kernel::string::__from_u8_subslice_of_str;
465
466/// A const equivalent of `string.get(..len)`.
467///
468/// # Example
469///
470/// ```
471/// use konst::string;
472///
473/// const STR: &str = "foo bar baz";
474///
475/// const SUB0: Option<&str> = string::get_up_to(STR, 3);
476/// assert_eq!(SUB0, Some("foo"));
477///
478/// const SUB1: Option<&str> = string::get_up_to(STR, 7);
479/// assert_eq!(SUB1, Some("foo bar"));
480///
481/// const SUB2: Option<&str> = string::get_up_to(STR, 11);
482/// assert_eq!(SUB2, Some(STR));
483///
484/// const SUB3: Option<&str> = string::get_up_to(STR, 100);
485/// assert_eq!(SUB3, None);
486///
487///
488/// ```
489pub const fn get_up_to(string: &str, len: usize) -> Option<&str> {
490 let bytes = string.as_bytes();
491
492 crate::option::and_then!(
493 crate::slice::get_up_to(bytes, len),
494 |x| if __is_char_boundary_bytes(bytes, len) {
495 // Safety: __is_char_boundary_bytes checks that `len` falls on a char boundary.
496 unsafe { Some(__from_u8_subslice_of_str(x)) }
497 } else {
498 None
499 }
500 )
501}
502
503/// A const equivalent of `string.get(from..)`.
504///
505/// # Example
506///
507/// ```
508/// use konst::string;
509///
510/// const STR: &str = "foo bar baz";
511///
512/// const SUB0: Option<&str> = string::get_from(STR, 0);
513/// assert_eq!(SUB0, Some(STR));
514///
515/// const SUB1: Option<&str> = string::get_from(STR, 4);
516/// assert_eq!(SUB1, Some("bar baz"));
517///
518/// const SUB2: Option<&str> = string::get_from(STR, 8);
519/// assert_eq!(SUB2, Some("baz"));
520///
521/// const SUB3: Option<&str> = string::get_from(STR, 100);
522/// assert_eq!(SUB3, None);
523///
524///
525/// ```
526pub const fn get_from(string: &str, from: usize) -> Option<&str> {
527 let bytes = string.as_bytes();
528
529 crate::option::and_then!(
530 crate::slice::get_from(bytes, from),
531 |x| if __is_char_boundary_bytes(bytes, from) {
532 // Safety: __is_char_boundary_bytes checks that `from` falls on a char boundary.
533 unsafe { Some(__from_u8_subslice_of_str(x)) }
534 } else {
535 None
536 }
537 )
538}
539
540/// A const equivalent of [`str::split_at`]
541///
542/// If `at > string.len()` this returns `(string, "")`.
543///
544/// # Panics
545///
546/// This function panics if `at` is inside the string but doesn't fall on a char boundary.
547///
548/// # Example
549///
550/// ```rust
551/// use konst::string;
552///
553/// const IN: &str = "foo bar baz";
554///
555/// {
556/// const SPLIT0: (&str, &str) = string::split_at(IN, 0);
557/// assert_eq!(SPLIT0, ("", "foo bar baz"));
558/// }
559/// {
560/// const SPLIT1: (&str, &str) = string::split_at(IN, 4);
561/// assert_eq!(SPLIT1, ("foo ", "bar baz"));
562/// }
563/// {
564/// const SPLIT2: (&str, &str) = string::split_at(IN, 8);
565/// assert_eq!(SPLIT2, ("foo bar ", "baz"));
566/// }
567/// {
568/// const SPLIT3: (&str, &str) = string::split_at(IN, 11);
569/// assert_eq!(SPLIT3, ("foo bar baz", ""));
570/// }
571/// {
572/// const SPLIT4: (&str, &str) = string::split_at(IN, 13);
573/// assert_eq!(SPLIT4, ("foo bar baz", ""));
574/// }
575///
576/// ```
577///
578/// [`str::split_at`]: https://doc.rust-lang.org/std/primitive.str.html#method.split_at
579pub const fn split_at(string: &str, at: usize) -> (&str, &str) {
580 (str_up_to(string, at), str_from(string, at))
581}
582
583/// A const equivalent of `string.get(start..end)`.
584///
585/// # Alternatives
586///
587/// For a const equivalent of `string.get(start..)` there's [`get_from`].
588///
589/// For a const equivalent of `string.get(..end)` there's [`get_up_to`].
590///
591/// [`get_from`]: ./fn.get_from.html
592/// [`get_up_to`]: ./fn.get_up_to.html
593///
594/// # Example
595///
596/// ```
597/// use konst::string;
598///
599/// const STR: &str = "foo bar baz";
600///
601/// const SUB0: Option<&str> = string::get_range(STR, 0, 3);
602/// assert_eq!(SUB0, Some("foo"));
603///
604/// const SUB1: Option<&str> = string::get_range(STR, 0, 7);
605/// assert_eq!(SUB1, Some("foo bar"));
606///
607/// const SUB2: Option<&str> = string::get_range(STR, 4, 11);
608/// assert_eq!(SUB2, Some("bar baz"));
609///
610/// const SUB3: Option<&str> = string::get_range(STR, 0, 1000);
611/// assert_eq!(SUB3, None);
612///
613///
614/// ```
615pub const fn get_range(string: &str, start: usize, end: usize) -> Option<&str> {
616 let bytes = string.as_bytes();
617
618 crate::option::and_then!(crate::slice::get_range(bytes, start, end), |x| {
619 if __is_char_boundary_bytes(bytes, start) && __is_char_boundary_bytes(bytes, end) {
620 // Safety: __is_char_boundary_bytes checks that `start` and `end` fall on a char boundary.
621 unsafe { Some(__from_u8_subslice_of_str(x)) }
622 } else {
623 None
624 }
625 })
626}
627
628/// A const subset of [`str::strip_prefix`].
629///
630/// This takes [`Pattern`] implementors as the pattern.
631///
632/// # Example
633///
634/// ```rust
635/// use konst::string;
636///
637/// {
638/// const STRIP: Option<&str> = string::strip_prefix("--5 8", '-');
639/// assert_eq!(STRIP, Some("-5 8"));
640/// }
641/// {
642/// const STRIP: Option<&str> = string::strip_prefix("--5 8", '_');
643/// assert_eq!(STRIP, None);
644/// }
645///
646/// {
647/// const STRIP: Option<&str> = string::strip_prefix("33 5 8", "3");
648/// assert_eq!(STRIP, Some("3 5 8"));
649/// }
650/// {
651/// const STRIP: Option<&str> = string::strip_prefix("3 5 8", "hello");
652/// assert_eq!(STRIP, None);
653/// }
654///
655///
656/// ```
657///
658/// [`str::strip_prefix`]: https://doc.rust-lang.org/std/primitive.str.html#method.strip_prefix
659pub const fn strip_prefix<'a, 'p, P>(string: &'a str, pattern: P) -> Option<&'a str>
660where
661 P: Pattern<'p>,
662{
663 let pat = PatternNorm::new(pattern);
664
665 // Safety: because `pat` is a `Pattern`, removing it should result in a valid `&str`
666 unsafe {
667 crate::option::map!(
668 crate::slice::__bytes_strip_prefix(string.as_bytes(), pat.as_bytes()),
669 __from_u8_subslice_of_str,
670 )
671 }
672}
673
674/// A const subset of [`str::strip_suffix`].
675///
676/// This takes [`Pattern`] implementors as the pattern.
677///
678/// # Example
679///
680/// ```rust
681/// use konst::string;
682///
683/// {
684/// const STRIP: Option<&str> = string::strip_suffix("3 5 8--", '-');
685/// assert_eq!(STRIP, Some("3 5 8-"));
686/// }
687/// {
688/// const STRIP: Option<&str> = string::strip_suffix("3 5 8", '_');
689/// assert_eq!(STRIP, None);
690/// }
691///
692/// {
693/// const STRIP: Option<&str> = string::strip_suffix("3 5 6868", "68");
694/// assert_eq!(STRIP, Some("3 5 68"));
695/// }
696/// {
697/// const STRIP: Option<&str> = string::strip_suffix("3 5 8", "hello");
698/// assert_eq!(STRIP, None);
699/// }
700///
701///
702/// ```
703///
704pub const fn strip_suffix<'a, 'p, P>(string: &'a str, pattern: P) -> Option<&'a str>
705where
706 P: Pattern<'p>,
707{
708 let pat = PatternNorm::new(pattern);
709
710 // Safety: because `suffix` is a `&str`, removing it should result in a valid `&str`
711 unsafe {
712 crate::option::map!(
713 crate::slice::__bytes_strip_suffix(string.as_bytes(), pat.as_bytes()),
714 __from_u8_subslice_of_str,
715 )
716 }
717}
718
719/// A const subset of [`str::trim`] which only removes ascii whitespace.
720///
721/// # Const stabilization
722///
723/// The [equivalent std function](str::trim_ascii) was const-stabilized in Rust 1.80.0.
724///
725/// # Example
726///
727/// ```rust
728/// use konst::string;
729///
730/// const TRIMMED: &str = string::trim("\nhello world ");
731///
732/// assert_eq!(TRIMMED, "hello world");
733///
734/// ```
735pub const fn trim(this: &str) -> &str {
736 let trimmed = crate::slice::bytes_trim(this.as_bytes());
737 // safety: bytes_trim only removes ascii bytes
738 unsafe { __from_u8_subslice_of_str(trimmed) }
739}
740
741/// A const subset of [`str::trim_start`] which only removes ascii whitespace.
742///
743/// # Const stabilization
744///
745/// The [equivalent std function](str::trim_ascii_start) was const-stabilized in Rust 1.80.0.
746///
747/// # Example
748///
749/// ```rust
750/// use konst::string;
751///
752/// const TRIMMED: &str = string::trim_start("\rfoo bar ");
753///
754/// assert_eq!(TRIMMED, "foo bar ");
755///
756/// ```
757pub const fn trim_start(this: &str) -> &str {
758 let trimmed = crate::slice::bytes_trim_start(this.as_bytes());
759 // safety: bytes_trim_start only removes ascii bytes
760 unsafe { __from_u8_subslice_of_str(trimmed) }
761}
762
763/// A const subset of [`str::trim_end`] which only removes ascii whitespace.
764///
765/// # Const stabilization
766///
767/// The [equivalent std function](str::trim_ascii_end) was const-stabilized in Rust 1.80.0.
768///
769/// # Example
770///
771/// ```rust
772/// use konst::string;
773///
774/// const TRIMMED: &str = string::trim_end("\rfoo bar ");
775///
776/// assert_eq!(TRIMMED, "\rfoo bar");
777///
778/// ```
779///
780pub const fn trim_end(this: &str) -> &str {
781 let trimmed = crate::slice::bytes_trim_end(this.as_bytes());
782 // safety: bytes_trim_end only removes ascii bytes
783 unsafe { __from_u8_subslice_of_str(trimmed) }
784}
785
786/// A const subset of [`str::trim_matches`].
787///
788/// This takes [`Pattern`] implementors as the needle.
789///
790/// # Example
791///
792/// ```rust
793/// use konst::string;
794///
795/// const CHAR_TRIMMED: &str = string::trim_matches("---baz qux---", '-');
796/// const STR_TRIMMED: &str = string::trim_matches("<>baz qux<><><>", "<>");
797///
798/// assert_eq!(CHAR_TRIMMED, "baz qux");
799/// assert_eq!(STR_TRIMMED, "baz qux");
800///
801/// ```
802pub const fn trim_matches<'a, 'p, P>(this: &'a str, needle: P) -> &'a str
803where
804 P: Pattern<'p>,
805{
806 let needle = PatternNorm::new(needle);
807 let trimmed = crate::slice::__bytes_trim_matches(this.as_bytes(), needle.as_bytes());
808 // safety:
809 // because bytes_trim_matches was passed `&str`s casted to `&[u8]`s,
810 // it returns a valid utf8 sequence.
811 unsafe { __from_u8_subslice_of_str(trimmed) }
812}
813
814/// A const subset of [`str::trim_start_matches`].
815///
816/// This takes [`Pattern`] implementors as the needle.
817///
818/// # Example
819///
820/// ```rust
821/// use konst::string;
822///
823/// const CHAR_TRIMMED: &str = string::trim_start_matches("#####huh###", '#');
824/// const STR_TRIMMED: &str = string::trim_start_matches("#####huh###", "##");
825///
826/// assert_eq!(CHAR_TRIMMED, "huh###");
827/// assert_eq!(STR_TRIMMED, "#huh###");
828///
829/// ```
830pub const fn trim_start_matches<'a, 'p, P>(this: &'a str, needle: P) -> &'a str
831where
832 P: Pattern<'p>,
833{
834 let needle = PatternNorm::new(needle);
835 let trimmed = crate::slice::__bytes_trim_start_matches(this.as_bytes(), needle.as_bytes());
836 // safety:
837 // because bytes_trim_start_matches was passed `&str`s casted to `&[u8]`s,
838 // it returns a valid utf8 sequence.
839 unsafe { __from_u8_subslice_of_str(trimmed) }
840}
841
842/// A const subset of [`str::trim_end_matches`].
843///
844/// This takes [`Pattern`] implementors as the needle.
845///
846/// # Example
847///
848/// ```rust
849/// use konst::string;
850///
851/// const CHAR_TRIMMED: &str = string::trim_end_matches("oowowooooo", 'o');
852/// const STR_TRIMMED: &str = string::trim_end_matches("oowowooooo", "oo");
853///
854/// assert_eq!(CHAR_TRIMMED, "oowow");
855/// assert_eq!(STR_TRIMMED, "oowowo");
856///
857/// ```
858pub const fn trim_end_matches<'a, 'p, P>(this: &'a str, needle: P) -> &'a str
859where
860 P: Pattern<'p>,
861{
862 let needle = PatternNorm::new(needle);
863 let trimmed = crate::slice::__bytes_trim_end_matches(this.as_bytes(), needle.as_bytes());
864 // safety:
865 // because bytes_trim_end_matches was passed `&str`s casted to `&[u8]`s,
866 // it returns a valid utf8 sequence.
867 unsafe { __from_u8_subslice_of_str(trimmed) }
868}
869
870/// Advances `this` past the first instance of `needle`.
871///
872/// Returns `None` if no instance of `needle` is found.
873///
874/// Returns `Some(this)` if `needle` is empty.
875///
876/// This takes [`Pattern`] implementors as the needle.
877///
878/// # Example
879///
880/// ```rust
881/// use konst::string;
882///
883/// {
884/// const FOUND: Option<&str> = string::find_skip("foo bar baz", ' ');
885/// assert_eq!(FOUND, Some("bar baz"));
886/// }
887///
888/// {
889/// const FOUND: Option<&str> = string::find_skip("foo bar baz", "bar");
890/// assert_eq!(FOUND, Some(" baz"));
891/// }
892/// {
893/// const NOT_FOUND: Option<&str> = string::find_skip("foo bar baz", "qux");
894/// assert_eq!(NOT_FOUND, None);
895/// }
896/// ```
897pub const fn find_skip<'a, 'p, P>(this: &'a str, needle: P) -> Option<&'a str>
898where
899 P: Pattern<'p>,
900{
901 let needle = PatternNorm::new(needle);
902 unsafe {
903 crate::option::map!(
904 crate::slice::__bytes_find_skip(this.as_bytes(), needle.as_bytes()),
905 // safety:
906 // because bytes_find_skip was passed `&str`s casted to `&[u8]`s,
907 // it returns a valid utf8 sequence.
908 __from_u8_subslice_of_str,
909 )
910 }
911}
912
913/// Advances `this` up to the first instance of `needle`.
914///
915/// Returns `None` if no instance of `needle` is found.
916///
917/// Returns `Some(this)` if `needle` is empty.
918///
919/// This takes [`Pattern`] implementors as the needle.
920///
921/// # Example
922///
923/// ```rust
924/// use konst::string;
925///
926/// {
927/// const FOUND: Option<&str> = string::find_keep("foo-bar-baz", '-');
928/// assert_eq!(FOUND, Some("-bar-baz"));
929/// }
930///
931/// {
932/// const FOUND: Option<&str> = string::find_keep("foo bar baz", "bar");
933/// assert_eq!(FOUND, Some("bar baz"));
934/// }
935/// {
936/// const NOT_FOUND: Option<&str> = string::find_keep("foo bar baz", "qux");
937/// assert_eq!(NOT_FOUND, None);
938/// }
939/// ```
940pub const fn find_keep<'a, 'p, P>(this: &'a str, needle: P) -> Option<&'a str>
941where
942 P: Pattern<'p>,
943{
944 let needle = PatternNorm::new(needle);
945 unsafe {
946 crate::option::map!(
947 crate::slice::__bytes_find_keep(this.as_bytes(), needle.as_bytes()),
948 // safety:
949 // because bytes_find_keep was passed `&str`s casted to `&[u8]`s,
950 // it returns a valid utf8 sequence.
951 __from_u8_subslice_of_str,
952 )
953 }
954}
955
956/// Truncates `this` to before the last instance of `needle`.
957///
958/// Returns `None` if no instance of `needle` is found.
959///
960/// Returns `Some(this)` if `needle` is empty.
961///
962/// This takes [`Pattern`] implementors as the needle.
963///
964/// # Example
965///
966/// ```rust
967/// use konst::string;
968///
969/// {
970/// const FOUND: Option<&str> = string::rfind_skip("foo bar _ bar baz", '_');
971/// assert_eq!(FOUND, Some("foo bar "));
972/// }
973///
974/// {
975/// const FOUND: Option<&str> = string::rfind_skip("foo bar _ bar baz", "bar");
976/// assert_eq!(FOUND, Some("foo bar _ "));
977/// }
978/// {
979/// const NOT_FOUND: Option<&str> = string::rfind_skip("foo bar baz", "qux");
980/// assert_eq!(NOT_FOUND, None);
981/// }
982/// ```
983pub const fn rfind_skip<'a, 'p, P>(this: &'a str, needle: P) -> Option<&'a str>
984where
985 P: Pattern<'p>,
986{
987 let needle = PatternNorm::new(needle);
988 unsafe {
989 crate::option::map!(
990 crate::slice::__bytes_rfind_skip(this.as_bytes(), needle.as_bytes()),
991 // safety:
992 // because bytes_rfind_skip was passed `&str`s casted to `&[u8]`s,
993 // it returns a valid utf8 sequence.
994 __from_u8_subslice_of_str,
995 )
996 }
997}
998
999/// Truncates `this` to the last instance of `needle`.
1000///
1001/// Returns `None` if no instance of `needle` is found.
1002///
1003/// Returns `Some(this)` if `needle` is empty.
1004///
1005/// This takes [`Pattern`] implementors as the needle.
1006///
1007/// # Example
1008///
1009/// ```rust
1010/// use konst::string;
1011///
1012/// {
1013/// const FOUND: Option<&str> = string::rfind_keep("foo bar _ bar baz", '_');
1014/// assert_eq!(FOUND, Some("foo bar _"));
1015/// }
1016///
1017/// {
1018/// const FOUND: Option<&str> = string::rfind_keep("foo bar _ bar baz", "bar");
1019/// assert_eq!(FOUND, Some("foo bar _ bar"));
1020/// }
1021/// {
1022/// const NOT_FOUND: Option<&str> = string::rfind_keep("foo bar baz", "qux");
1023/// assert_eq!(NOT_FOUND, None);
1024/// }
1025/// ```
1026pub const fn rfind_keep<'a, 'p, P>(this: &'a str, needle: P) -> Option<&'a str>
1027where
1028 P: Pattern<'p>,
1029{
1030 let needle = PatternNorm::new(needle);
1031 unsafe {
1032 crate::option::map!(
1033 crate::slice::__bytes_rfind_keep(this.as_bytes(), needle.as_bytes()),
1034 // safety:
1035 // because bytes_rfind_keep was passed `&str`s casted to `&[u8]`s,
1036 // it returns a valid utf8 sequence.
1037 __from_u8_subslice_of_str,
1038 )
1039 }
1040}