const_str/__ctfe/
find.rs

1use crate::utf8::CharEncodeUtf8;
2
3pub struct Contains<'a, P>(pub &'a str, pub P);
4
5impl Contains<'_, &str> {
6    pub const fn const_eval(&self) -> bool {
7        crate::str::contains(self.0, self.1)
8    }
9}
10
11impl Contains<'_, char> {
12    pub const fn const_eval(&self) -> bool {
13        let haystack = self.0;
14        let ch = CharEncodeUtf8::new(self.1);
15        let needle = ch.as_str();
16        crate::str::contains(haystack, needle)
17    }
18}
19
20/// Returns [`true`] if the given pattern matches a sub-slice of this string slice.
21///
22/// Returns [`false`] if it does not.
23///
24/// The pattern type must be one of
25///
26/// + [`&str`]
27/// + [`char`]
28///
29/// This macro is [const-fn compatible](./index.html#const-fn-compatible).
30///
31/// # Examples
32///
33/// ```
34/// const BANANAS: &str = "bananas";
35/// const A: bool = const_str::contains!(BANANAS, "nana");
36/// const B: bool = const_str::contains!(BANANAS, "apples");
37/// const C: bool = const_str::contains!(BANANAS, 'c');
38/// assert_eq!([A, B, C], [true, false, false]);
39/// ```
40///
41#[macro_export]
42macro_rules! contains {
43    ($haystack: expr, $pattern: expr) => {{
44        $crate::__ctfe::Contains($haystack, $pattern).const_eval()
45    }};
46}
47
48pub struct StartsWith<'a, P>(pub &'a str, pub P);
49
50impl StartsWith<'_, &str> {
51    pub const fn const_eval(&self) -> bool {
52        crate::str::starts_with(self.0, self.1)
53    }
54}
55
56impl StartsWith<'_, char> {
57    pub const fn const_eval(&self) -> bool {
58        let haystack = self.0.as_bytes();
59        let ch = CharEncodeUtf8::new(self.1);
60        let needle = ch.as_bytes();
61        crate::bytes::starts_with(haystack, needle)
62    }
63}
64
65/// Returns [`true`] if the given pattern matches a prefix of this string slice.
66///
67/// Returns [`false`] if it does not.
68///
69/// The pattern type must be one of
70///
71/// + [`&str`]
72/// + [`char`]
73///
74/// This macro is [const-fn compatible](./index.html#const-fn-compatible).
75///
76/// # Examples
77///
78/// ```
79/// const BANANAS: &str = "bananas";
80/// const A: bool = const_str::starts_with!(BANANAS, "bana");
81/// const B: bool = const_str::starts_with!(BANANAS, "nana");
82/// const C: bool = const_str::starts_with!(BANANAS, 'b');
83/// assert_eq!([A, B, C], [true, false, true]);
84/// ```
85///
86#[macro_export]
87macro_rules! starts_with {
88    ($haystack: expr, $pattern: expr) => {{
89        $crate::__ctfe::StartsWith($haystack, $pattern).const_eval()
90    }};
91}
92
93pub struct EndsWith<'a, P>(pub &'a str, pub P);
94
95impl EndsWith<'_, &str> {
96    pub const fn const_eval(&self) -> bool {
97        crate::str::ends_with(self.0, self.1)
98    }
99}
100
101impl EndsWith<'_, char> {
102    pub const fn const_eval(&self) -> bool {
103        let haystack = self.0.as_bytes();
104        let ch = CharEncodeUtf8::new(self.1);
105        let needle = ch.as_bytes();
106        crate::bytes::ends_with(haystack, needle)
107    }
108}
109
110/// Returns [`true`] if the given pattern matches a suffix of this string slice.
111///
112/// Returns [`false`] if it does not.
113///
114/// The pattern type must be one of
115///
116/// + [`&str`]
117/// + [`char`]
118///
119/// This macro is [const-fn compatible](./index.html#const-fn-compatible).
120///
121/// # Examples
122///
123/// ```
124/// const BANANAS: &str = "bananas";
125/// const A: bool = const_str::ends_with!(BANANAS, "anas");
126/// const B: bool = const_str::ends_with!(BANANAS, "nana");
127/// const C: bool = const_str::ends_with!(BANANAS, 's');
128/// assert_eq!([A, B, C], [true, false, true]);
129/// ```
130///
131#[macro_export]
132macro_rules! ends_with {
133    ($haystack: expr, $pattern: expr) => {{
134        $crate::__ctfe::EndsWith($haystack, $pattern).const_eval()
135    }};
136}
137
138pub struct StripPrefix<'a, P>(pub &'a str, pub P);
139
140impl<'a> StripPrefix<'a, &str> {
141    pub const fn const_eval(&self) -> Option<&'a str> {
142        crate::str::strip_prefix(self.0, self.1)
143    }
144}
145
146pub struct StripSuffix<'a, P>(pub &'a str, pub P);
147
148impl<'a> StripSuffix<'a, &str> {
149    pub const fn const_eval(&self) -> Option<&'a str> {
150        crate::str::strip_suffix(self.0, self.1)
151    }
152}
153
154/// Returns a string slice with the prefix removed.
155///
156/// This macro is [const-fn compatible](./index.html#const-fn-compatible).
157///
158/// # Examples
159///
160/// ```
161/// assert_eq!(const_str::strip_prefix!("foo:bar", "foo:"), Some("bar"));
162/// assert_eq!(const_str::strip_prefix!("foo:bar", "bar"), None);
163/// assert_eq!(const_str::strip_prefix!("foofoo", "foo"), Some("foo"));
164///
165/// const FOO_BAR: &str = "foo:bar";
166/// const BAR: &str = const_str::unwrap!(const_str::strip_prefix!(FOO_BAR, "foo:"));
167/// assert_eq!(BAR, "bar");
168/// ```
169///
170#[macro_export]
171macro_rules! strip_prefix {
172    ($s: expr, $prefix: expr) => {{
173        $crate::__ctfe::StripPrefix($s, $prefix).const_eval()
174    }};
175}
176
177/// Returns a string slice with the suffix removed.
178///
179/// This macro is [const-fn compatible](./index.html#const-fn-compatible).
180///
181/// # Examples
182///
183/// ```
184/// assert_eq!(const_str::strip_suffix!("bar:foo", ":foo"), Some("bar"));
185/// assert_eq!(const_str::strip_suffix!("bar:foo", "bar"), None);
186/// assert_eq!(const_str::strip_suffix!("foofoo", "foo"), Some("foo"));
187///
188/// const FOO_BAR: &str = "foo:bar";
189/// const FOO: &str = const_str::unwrap!(const_str::strip_suffix!(FOO_BAR, ":bar"));
190/// assert_eq!(FOO, "foo");
191/// ```
192///
193#[macro_export]
194macro_rules! strip_suffix {
195    ($s: expr, $suffix: expr) => {{
196        $crate::__ctfe::StripSuffix($s, $suffix).const_eval()
197    }};
198}
199
200#[cfg(test)]
201mod tests {
202    use crate::unwrap;
203
204    #[test]
205    fn test_contains() {
206        const BANANAS: &str = "bananas";
207        const A: bool = contains!(BANANAS, "nana");
208        const B: bool = contains!(BANANAS, "apples");
209        const C: bool = contains!(BANANAS, 'c');
210        const D: bool = contains!(BANANAS, 'a');
211
212        assert_eq!([A, B, C, D], [true, false, false, true]);
213
214        let f = contains!("hello", "");
215        assert!(f);
216    }
217
218    #[test]
219    fn test_starts_with() {
220        const BANANAS: &str = "bananas";
221        const A: bool = starts_with!(BANANAS, "bana");
222        const B: bool = starts_with!(BANANAS, "nana");
223        const C: bool = starts_with!(BANANAS, 'b');
224        const D: bool = starts_with!(BANANAS, 'n');
225
226        assert_eq!([A, B, C, D], [true, false, true, false]);
227
228        let f = starts_with!("hello", "");
229        assert!(f);
230    }
231
232    #[test]
233    fn test_ends_with() {
234        const BANANAS: &str = "bananas";
235        const A: bool = ends_with!(BANANAS, "anas");
236        const B: bool = ends_with!(BANANAS, "nana");
237        const C: bool = ends_with!(BANANAS, 's');
238        const D: bool = ends_with!(BANANAS, 'b');
239
240        assert_eq!([A, B, C, D], [true, false, true, false]);
241
242        let f = ends_with!("hello", "");
243        assert!(f);
244    }
245
246    #[test]
247    fn test_strip_prefix() {
248        const R1: Option<&str> = strip_prefix!("foo:bar", "foo:");
249        const R2: Option<&str> = strip_prefix!("foo:bar", "bar");
250        const R3: Option<&str> = strip_prefix!("foofoo", "foo");
251        const R4: Option<&str> = strip_prefix!("", "");
252
253        assert_eq!(R1, Some("bar"));
254        assert_eq!(R2, None);
255        assert_eq!(R3, Some("foo"));
256        assert_eq!(R4, Some(""));
257
258        const FOO_BAR: &str = "foo:bar";
259        const BAR: &str = unwrap!(strip_prefix!(FOO_BAR, "foo:"));
260        assert_eq!(BAR, "bar");
261    }
262
263    #[test]
264    fn test_strip_suffix() {
265        const R1: Option<&str> = strip_suffix!("bar:foo", ":foo");
266        const R2: Option<&str> = strip_suffix!("bar:foo", "bar");
267        const R3: Option<&str> = strip_suffix!("foofoo", "foo");
268        const R4: Option<&str> = strip_suffix!("", "");
269
270        assert_eq!(R1, Some("bar"));
271        assert_eq!(R2, None);
272        assert_eq!(R3, Some("foo"));
273        assert_eq!(R4, Some(""));
274
275        const FOO_BAR: &str = "foo:bar";
276        const FOO: &str = unwrap!(strip_suffix!(FOO_BAR, ":bar"));
277        assert_eq!(FOO, "foo");
278    }
279
280    #[test]
281    fn test_find_runtime() {
282        use super::*;
283
284        // Runtime tests for Contains
285        let contains1 = Contains("hello world", "world");
286        assert!(contains1.const_eval());
287
288        let contains2 = Contains("hello", "x");
289        assert!(!contains2.const_eval());
290
291        // Runtime tests for StartsWith
292        let starts1 = StartsWith("hello", "he");
293        assert!(starts1.const_eval());
294
295        let starts2 = StartsWith("hello", "lo");
296        assert!(!starts2.const_eval());
297
298        // Runtime tests for EndsWith
299        let ends1 = EndsWith("hello", "lo");
300        assert!(ends1.const_eval());
301
302        let ends2 = EndsWith("hello", "he");
303        assert!(!ends2.const_eval());
304
305        // Runtime tests for StripPrefix
306        let strip_pre = StripPrefix("hello world", "hello ");
307        let result = strip_pre.const_eval();
308        assert_eq!(result, Some("world"));
309
310        let strip_pre_none = StripPrefix("hello", "world");
311        let result_none = strip_pre_none.const_eval();
312        assert_eq!(result_none, None);
313
314        // Runtime tests for StripSuffix
315        let strip_suf = StripSuffix("hello world", " world");
316        let result_suf = strip_suf.const_eval();
317        assert_eq!(result_suf, Some("hello"));
318
319        let strip_suf_none = StripSuffix("hello", "world");
320        let result_suf_none = strip_suf_none.const_eval();
321        assert_eq!(result_suf_none, None);
322
323        // Test char patterns
324        let contains_char = Contains("hello", 'e');
325        assert!(contains_char.const_eval());
326
327        let contains_char_none = Contains("hello", 'x');
328        assert!(!contains_char_none.const_eval());
329
330        let starts_char = StartsWith("hello", 'h');
331        assert!(starts_char.const_eval());
332
333        let starts_char_false = StartsWith("hello", 'e');
334        assert!(!starts_char_false.const_eval());
335
336        let ends_char = EndsWith("hello", 'o');
337        assert!(ends_char.const_eval());
338
339        let ends_char_false = EndsWith("hello", 'h');
340        assert!(!ends_char_false.const_eval());
341    }
342}