string_utility/
lib.rs

1#![doc = include_str ! ("./../README.md")]
2#![forbid(unsafe_code)]
3
4pub mod prelude {
5    pub use crate::{
6        SubstringExt,
7        StringKeeperCommonExt,
8        KeeperCommonExt,
9    };
10}
11
12pub trait SubstringExt {
13    fn substring<R: std::ops::RangeBounds<usize>>(&self, range: R) -> String;
14    fn substring_len(&self, reverse_count: usize) -> String;
15    fn try_substring<R: std::ops::RangeBounds<usize>>(&self, range: R) -> Option<String>;
16    fn trim_trailing_zeros(&self) -> String;
17}
18
19pub trait StringKeeperCommonExt<T, P> {
20    fn keep(self, pattern: T) -> StringKeeper<T, P>;
21    fn cut(self, pattern: T) -> StringKeeper<T, P>;
22}
23
24pub trait KeeperCommonExt<T, P> {
25    fn beginning_of_string(self) -> StringKeeper<T, P>;
26    fn end_of_string(self) -> StringKeeper<T, P>;
27    fn including_pattern(self) -> StringKeeper<T, P>;
28    fn excluding_pattern(self) -> StringKeeper<T, P>;
29    fn before_pattern(self) -> StringKeeper<T, P>;
30    fn after_pattern(self) -> StringKeeper<T, P>;
31    fn until_first_matched_pattern(self, until_pattern: T) -> StringKeeper<T, P>;
32    fn until_no_matched_pattern(self, until_pattern: T) -> StringKeeper<T, P>;
33
34    #[cfg(feature = "regex")]
35    fn utf8_encoding(self) -> StringKeeper<T, P>;
36
37    #[cfg(feature = "regex")]
38    fn utf16_encoding(self) -> StringKeeper<T, P>;
39
40    #[cfg(feature = "regex")]
41    fn set_encoding(self, enc: KeeperEncoding) -> StringKeeper<T, P>;
42}
43
44pub trait StringKeeperExt<T, P>: StringKeeperCommonExt<T, P> {}
45
46#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)]
47pub enum KeeperPeriod {
48    Start,
49    End,
50}
51
52#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)]
53pub enum KeeperCutoff {
54    After,
55    Before,
56}
57
58#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)]
59pub enum KeeperClusivity {
60    Including,
61    Excluding,
62}
63
64#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)]
65pub enum KeeperEncoding {
66    Utf8,
67    Utf16,
68    // Other(fn(original_text: &str, matched_text: &str, last_char: char) -> usize),
69}
70
71#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)]
72pub enum KeeperUntilMatch {
73    FirstMatch,
74    NoMatch,
75}
76
77#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)]
78pub enum StringKeeperMode {
79    Cut,
80    Keep,
81}
82
83#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)]
84pub struct StringKeeperOpts {
85    until_match: Option<KeeperUntilMatch>,
86    mode: StringKeeperMode,
87    period: KeeperPeriod,
88    clusivity: KeeperClusivity,
89    cutoff: KeeperCutoff,
90    encoding: Option<KeeperEncoding>,
91}
92
93#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)]
94pub struct StringKeeper<T, P> {
95    to_parse: P,
96    pattern: T,
97    until_pattern: Option<T>,
98    opt: StringKeeperOpts,
99}
100
101impl<T, P> StringKeeperCommonExt<T, P> for P {
102    fn keep(self, pattern: T) -> StringKeeper<T, P> {
103        StringKeeper {
104            pattern,
105            to_parse: self,
106            until_pattern: None,
107            opt: StringKeeperOpts {
108                until_match: None,
109                mode: StringKeeperMode::Keep,
110                period: KeeperPeriod::Start,
111                cutoff: KeeperCutoff::After,
112                clusivity: KeeperClusivity::Including,
113                encoding: None,
114            },
115        }
116    }
117
118    fn cut(self, pattern: T) -> StringKeeper<T, P> {
119        StringKeeper {
120            pattern,
121            to_parse: self,
122            until_pattern: None,
123            opt: StringKeeperOpts {
124                until_match: None,
125                mode: StringKeeperMode::Cut,
126                period: KeeperPeriod::Start,
127                cutoff: KeeperCutoff::After,
128                clusivity: KeeperClusivity::Including,
129                encoding: None,
130            },
131        }
132    }
133}
134
135impl<T, P> KeeperCommonExt<T, P> for StringKeeper<T, P> {
136    fn beginning_of_string(mut self) -> StringKeeper<T, P> {
137        self.opt.period = KeeperPeriod::Start;
138        self
139    }
140
141    fn end_of_string(mut self) -> StringKeeper<T, P> {
142        self.opt.period = KeeperPeriod::End;
143        self
144    }
145
146    fn including_pattern(mut self) -> StringKeeper<T, P> {
147        self.opt.clusivity = KeeperClusivity::Including;
148        self
149    }
150
151    fn excluding_pattern(mut self) -> StringKeeper<T, P> {
152        self.opt.clusivity = KeeperClusivity::Excluding;
153        self
154    }
155
156    fn before_pattern(mut self) -> StringKeeper<T, P> {
157        self.opt.cutoff = KeeperCutoff::Before;
158        self
159    }
160
161    fn after_pattern(mut self) -> StringKeeper<T, P> {
162        self.opt.cutoff = KeeperCutoff::After;
163        self
164    }
165
166    fn until_first_matched_pattern(mut self, until_pattern: T) -> StringKeeper<T, P> {
167        self.until_pattern = Some(until_pattern);
168        self.opt.until_match = Some(KeeperUntilMatch::FirstMatch);
169        self
170    }
171
172    fn until_no_matched_pattern(mut self, until_pattern: T) -> StringKeeper<T, P> {
173        self.until_pattern = Some(until_pattern);
174        self.opt.until_match = Some(KeeperUntilMatch::NoMatch);
175        self
176    }
177
178    #[cfg(feature = "regex")]
179    fn utf8_encoding(mut self) -> StringKeeper<T, P> {
180        self.opt.encoding = Some(KeeperEncoding::Utf8);
181        self
182    }
183
184    #[cfg(feature = "regex")]
185    fn utf16_encoding(mut self) -> StringKeeper<T, P> {
186        self.opt.encoding = Some(KeeperEncoding::Utf16);
187        self
188    }
189
190    #[cfg(feature = "regex")]
191    fn set_encoding(mut self, enc: KeeperEncoding) -> StringKeeper<T, P> {
192        self.opt.encoding = Some(enc);
193        self
194    }
195}
196
197impl std::fmt::Display for StringKeeper<String, String> {
198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        let try_find = match self.opt.period {
200            KeeperPeriod::Start => self.to_parse.find(&self.pattern),
201            KeeperPeriod::End => self.to_parse.rfind(&self.pattern),
202        };
203
204        let range = match try_find {
205            None => usize::MIN..usize::MIN,
206            Some(pos) => match self.opt.clusivity {
207                KeeperClusivity::Including => match self.opt.cutoff {
208                    KeeperCutoff::After => pos..usize::MAX,
209                    KeeperCutoff::Before => {
210                        let offset = pos + self.pattern.chars().count();
211                        usize::MIN..offset
212                    }
213                },
214                KeeperClusivity::Excluding => match self.opt.cutoff {
215                    KeeperCutoff::After => {
216                        let offset = pos + self.pattern.chars().count();
217                        offset..usize::MAX
218                    }
219                    KeeperCutoff::Before => usize::MIN..pos,
220                },
221            },
222        };
223
224        write!(f, "{}", self.to_parse.substring(range))
225    }
226}
227
228impl std::fmt::Display for StringKeeper<char, String> {
229    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230        let try_find = match self.opt.period {
231            KeeperPeriod::Start => self.to_parse.find(self.pattern),
232            KeeperPeriod::End => self.to_parse.rfind(self.pattern),
233        };
234
235        let range = match try_find {
236            None => usize::MIN..usize::MIN,
237            Some(pos) => match self.opt.clusivity {
238                KeeperClusivity::Including => match self.opt.cutoff {
239                    KeeperCutoff::After => pos..usize::MAX,
240                    KeeperCutoff::Before => usize::MIN..(pos).saturating_add(1),
241                },
242                KeeperClusivity::Excluding => match self.opt.cutoff {
243                    KeeperCutoff::After => (pos).saturating_add(1)..usize::MAX,
244                    KeeperCutoff::Before => usize::MIN..pos,
245                },
246            },
247        };
248
249        let mut result = self.to_parse.substring(range);
250
251        let opt_range = if let Some(until_pattern) = self.until_pattern {
252            if let Some(until_match) = self.opt.until_match.clone() {
253                let try_find = match self.opt.period {
254                    KeeperPeriod::Start => result.find(until_pattern),
255                    KeeperPeriod::End => result.rfind(until_pattern),
256                };
257
258                if let Some(pos) = try_find {
259                    match until_match {
260                        KeeperUntilMatch::FirstMatch => {
261                            match self.opt.cutoff {
262                                KeeperCutoff::After => {
263                                    result
264                                        .substring(pos..)
265                                        .find(until_pattern)
266                                        .map(|start_idx| start_idx..usize::MAX)
267                                }
268                                KeeperCutoff::Before => {
269                                    result
270                                        .substring(..=pos)
271                                        .rfind(until_pattern)
272                                        .map(|end_idx| pos..end_idx)
273                                }
274                            }
275                        }
276                        KeeperUntilMatch::NoMatch => {
277                            match self.opt.cutoff {
278                                KeeperCutoff::After => {
279                                    let found = {
280                                        let mut flag = false;
281                                        result
282                                            .chars()
283                                            .enumerate()
284                                            .skip(pos)
285                                            .find(|(c_idx, c)| {
286                                                if *c_idx > pos {
287                                                    let same = *c == until_pattern;
288                                                    if flag && !same {
289                                                        return true;
290                                                    }
291
292                                                    if same {
293                                                        flag = true;
294                                                    }
295                                                }
296
297                                                false
298                                            })
299                                    };
300
301                                    if let Some((start_pos, _)) = found {
302                                        Some(start_pos.saturating_add(1)..pos.saturating_add(1))
303                                    } else {
304                                        None
305                                    }
306                                }
307                                KeeperCutoff::Before => {
308                                    let col = result
309                                        .chars()
310                                        .enumerate()
311                                        .collect::<Vec<(usize, char)>>();
312
313                                    let found = {
314                                        let mut flag = false;
315                                        col
316                                            .iter()
317                                            .rev()
318                                            .find(|(c_idx, c)| {
319                                                if *c_idx <= pos {
320                                                    let same = *c == until_pattern;
321                                                    if flag && !same {
322                                                        return true;
323                                                    }
324
325                                                    if same {
326                                                        flag = true;
327                                                    }
328                                                }
329
330                                                false
331                                            })
332                                    };
333
334                                    if let Some(&(start_pos, _)) = found {
335                                        Some(start_pos.saturating_add(1)..pos.saturating_add(1))
336                                    } else {
337                                        None
338                                    }
339                                }
340                            }
341                        }
342                    }
343                } else {
344                    None
345                }
346            } else {
347                None
348            }
349        } else {
350            None
351        };
352
353        let result = if let Some(range) = opt_range {
354            match self.opt.mode {
355                StringKeeperMode::Cut => {
356                    result.replace_range(range, "");
357                    result
358                }
359                StringKeeperMode::Keep => {
360                    result.substring(range)
361                }
362            }
363        } else {
364            result
365        };
366        write!(f, "{}", result)
367    }
368}
369
370#[cfg(feature = "regex")]
371impl std::fmt::Display for StringKeeper<regex::Regex, String> {
372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        let to_parse = self.to_parse.as_str();
374        let try_find = match self.opt.period {
375            KeeperPeriod::Start => {
376                self.pattern.find(to_parse)
377            }
378            KeeperPeriod::End => {
379                self.pattern.find_iter(to_parse).last()
380            }
381        };
382
383        let range = match try_find {
384            None => usize::MIN..usize::MIN,
385            Some(pos) => match self.opt.clusivity {
386                KeeperClusivity::Including => match self.opt.cutoff {
387                    KeeperCutoff::After => pos.start()..usize::MAX,
388                    KeeperCutoff::Before => {
389                        let end_offset = if let Some(enc) = self.opt.encoding.clone() {
390                            let matched_string = pos.as_str();
391                            let char_len = matched_string
392                                .chars()
393                                .last()
394                                .map(|last_char| {
395                                    match enc {
396                                        KeeperEncoding::Utf8 => char::len_utf8(last_char),
397                                        KeeperEncoding::Utf16 => char::len_utf16(last_char),
398                                    }
399                                })
400                                .unwrap_or(std::mem::size_of::<char>());
401                            pos.end().saturating_sub(char_len)
402                        } else {
403                            pos.end()
404                        };
405                        usize::MIN..end_offset
406                    }
407                },
408                KeeperClusivity::Excluding => match self.opt.cutoff {
409                    KeeperCutoff::After => {
410                        let offset = pos.as_str().chars().count();
411                        let start = pos.start() + offset;
412                        start..usize::MAX
413                    }
414                    KeeperCutoff::Before => usize::MIN..pos.start(),
415                },
416            },
417        };
418
419        write!(f, "{}", self.to_parse.substring(range))
420    }
421}
422
423impl SubstringExt for str {
424    fn substring<R: std::ops::RangeBounds<usize>>(&self, range: R) -> String {
425        self.try_substring(range).unwrap_or_else(|| "".to_string())
426    }
427
428    fn substring_len(&self, reverse_count: usize) -> String {
429        self.substring(self.len().saturating_sub(reverse_count)..)
430    }
431
432    fn try_substring<R: std::ops::RangeBounds<usize>>(&self, range: R) -> Option<String> {
433        let start_idx = match range.start_bound() {
434            std::collections::Bound::Included(v) => *v,
435            std::collections::Bound::Excluded(v) => v.saturating_add(1),
436            std::collections::Bound::Unbounded => usize::MIN,
437        };
438
439        let end_idx = match range.end_bound() {
440            std::collections::Bound::Included(v) => v.saturating_add(1),
441            std::collections::Bound::Excluded(v) => *v,
442            std::collections::Bound::Unbounded => usize::MAX,
443        };
444
445        if end_idx > start_idx {
446            end_idx
447                .checked_sub(start_idx)
448                .map(|take_count| {
449                    self
450                        .chars()
451                        .skip(start_idx)
452                        .take(take_count)
453                        .collect()
454                })
455        } else {
456            None
457        }
458    }
459
460    fn trim_trailing_zeros(&self) -> String {
461        self
462            .to_string()
463            .cut('0')
464            .end_of_string()
465            .until_no_matched_pattern('0')
466            .before_pattern()
467            .excluding_pattern()
468            .to_string()
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::prelude::*;
475
476    #[test]
477    fn try_substring() {
478        let some_text = "hello, world!";
479        let result = some_text.substring(7..12);
480        assert_eq!(result, "world");
481
482        let some_text = "42Hello, world!".to_string();
483        let result = some_text.try_substring(2..7).unwrap();
484        let expected = "Hello";
485        assert_eq!(result, expected);
486    }
487
488    #[test]
489    fn substring() {
490        let some_text = "42Hello, world!".to_string();
491
492        let result = some_text.substring(2..7);
493        let expected = "Hello";
494        assert_eq!(result, expected);
495
496        let result = some_text.substring(2..424242);
497        let expected = "Hello, world!";
498        assert_eq!(result, expected);
499    }
500
501    #[test]
502    fn test_substring() {
503        assert_eq!("foobar".substring(..3), "foo");
504    }
505
506    #[test]
507    fn test_out_of_bounds() {
508        assert_eq!("foobar".substring(..10), "foobar");
509        assert_eq!("foobar".substring(6..10), "");
510    }
511
512    #[test]
513    fn test_start_and_end_equal() {
514        assert_eq!("foobar".substring(3..3), "");
515    }
516
517    #[test]
518    fn test_multiple_byte_characters() {
519        assert_eq!("ã".substring(..1), "a"); // As opposed to "ã".
520        assert_eq!("ã".substring(1..2), "\u{0303}");
521        assert_eq!("fõøbα®".substring(2..5), "øbα");
522    }
523
524    #[test]
525    fn mozilla_substring_cases() {
526        let any_string = "Mozilla";
527        assert_eq!(any_string.substring(..1), "M");
528        assert_eq!(any_string.substring(1..), "ozilla");
529        assert_eq!(any_string.substring(..6), "Mozill");
530        assert_eq!(any_string.substring(4..), "lla");
531        assert_eq!(any_string.substring(4..7), "lla");
532        assert_eq!(any_string.substring(..7), "Mozilla");
533        assert_eq!(any_string.substring(..10), "Mozilla");
534        assert_eq!(any_string.substring(any_string.len() - 4..), "illa");
535        assert_eq!(any_string.substring(any_string.len() - 5..), "zilla");
536        assert_eq!(any_string.substring_len(4), "illa");
537        assert_eq!(any_string.substring_len(5), "zilla");
538        assert_eq!(any_string.substring(2..5), "zil");
539        assert_eq!(any_string.substring(..2), "Mo");
540        assert_eq!(any_string.substring(..), "Mozilla");
541    }
542
543    #[test]
544    fn test_keep_after_include_string() {
545        assert_eq!(
546            "this is karøbα it was"
547                .to_string()
548                .keep("karøbα".to_string())
549                .beginning_of_string() // default
550                .after_pattern() // default
551                .including_pattern() // default
552                .to_string(),
553            "karøbα it was"
554        );
555        assert_eq!(
556            "this is karøbα it was"
557                .to_string()
558                .keep("karøbα".to_string())
559                .to_string(),
560            "karøbα it was"
561        );
562        assert_eq!(
563            "karøbα"
564                .to_string()
565                .keep("kar".to_string())
566                .after_pattern()
567                .including_pattern()
568                .to_string(),
569            "karøbα"
570        );
571    }
572
573    #[test]
574    fn test_keep_after_exclude_string() {
575        assert_eq!(
576            "this is karøbα it was"
577                .to_string()
578                .keep("karøbα".to_string())
579                .beginning_of_string()
580                .after_pattern()
581                .excluding_pattern()
582                .to_string(),
583            " it was"
584        );
585        assert_eq!(
586            "karøbα"
587                .to_string()
588                .keep("kar".to_string())
589                .after_pattern()
590                .excluding_pattern()
591                .to_string(),
592            "øbα"
593        );
594    }
595
596    #[test]
597    fn test_keep_after_include_char() {
598        assert_eq!(
599            "this is karøbα it was"
600                .to_string()
601                .keep('k')
602                .after_pattern()
603                .including_pattern()
604                .to_string(),
605            "karøbα it was"
606        );
607        assert_eq!(
608            "karøbα"
609                .to_string()
610                .keep('k')
611                .after_pattern()
612                .including_pattern()
613                .to_string(),
614            "karøbα"
615        );
616    }
617
618    #[test]
619    fn test_keep_after_exclude_char() {
620        assert_eq!(
621            "this is karøbα it was"
622                .to_string()
623                .keep('k')
624                .after_pattern()
625                .excluding_pattern()
626                .to_string(),
627            "arøbα it was"
628        );
629        assert_eq!(
630            "karøbα"
631                .to_string()
632                .keep('k')
633                .after_pattern()
634                .excluding_pattern()
635                .to_string(),
636            "arøbα"
637        );
638    }
639
640    #[test]
641    fn test_keep_before_include_string() {
642        assert_eq!(
643            "this is karøbα it was"
644                .to_string()
645                .keep("øbα".to_string())
646                .before_pattern()
647                .including_pattern()
648                .to_string(),
649            "this is karøbα"
650        );
651        assert_eq!(
652            "karøbα"
653                .to_string()
654                .keep("øbα".to_string())
655                .before_pattern()
656                .including_pattern()
657                .to_string(),
658            "karøbα"
659        );
660    }
661
662    #[test]
663    fn test_keep_before_include_char() {
664        assert_eq!(
665            "this is karøbα it was"
666                .to_string()
667                .keep('ø')
668                .before_pattern()
669                .including_pattern()
670                .to_string(),
671            "this is karø"
672        );
673        assert_eq!(
674            "karøbα"
675                .to_string()
676                .keep('ø')
677                .before_pattern()
678                .including_pattern()
679                .to_string(),
680            "karø"
681        );
682    }
683
684    #[test]
685    fn test_keep_before_exclude_string() {
686        assert_eq!(
687            "this is karøbα it was"
688                .to_string()
689                .keep("øbα".to_string())
690                .before_pattern()
691                .excluding_pattern()
692                .to_string(),
693            "this is kar"
694        );
695        assert_eq!(
696            "karøbα"
697                .to_string()
698                .keep("øbα".to_string())
699                .before_pattern()
700                .excluding_pattern()
701                .to_string(),
702            "kar"
703        );
704    }
705
706    #[test]
707    fn test_keep_before_exclude_char() {
708        assert_eq!(
709            "this is karøbα it was"
710                .to_string()
711                .keep('ø')
712                .before_pattern()
713                .excluding_pattern()
714                .to_string(),
715            "this is kar"
716        );
717        assert_eq!(
718            "karøbα"
719                .to_string()
720                .keep('ø')
721                .before_pattern()
722                .excluding_pattern()
723                .to_string(),
724            "kar"
725        );
726
727        assert_eq!(
728            "3.141592650991234200000000000000000000"
729                .to_string()
730                .keep('0')
731                .until_no_matched_pattern('0')
732                .beginning_of_string()
733                .before_pattern()
734                .excluding_pattern()
735                .to_string(),
736            "3.14159265"
737        );
738    }
739}
740
741#[cfg(test)]
742#[cfg(feature = "regex")]
743mod regex_feature_tests {
744    use super::prelude::*;
745    use regex::Regex;
746
747    #[test]
748    fn test_keep_after_include_string() {
749        assert_eq!(
750            "this is karøbα it was"
751                .to_string()
752                .keep(Regex::new("karøbα").unwrap())
753                .beginning_of_string() // default
754                .after_pattern() // default
755                .including_pattern() // default
756                .to_string(),
757            "karøbα it was"
758        );
759        assert_eq!(
760            "this is karøbα it was"
761                .to_string()
762                .keep(Regex::new("karøbα").unwrap())
763                .to_string(),
764            "karøbα it was"
765        );
766        assert_eq!(
767            "karøbα"
768                .to_string()
769                .keep(Regex::new("kar").unwrap())
770                .after_pattern()
771                .including_pattern()
772                .to_string(),
773            "karøbα"
774        );
775    }
776
777    #[test]
778    fn test_keep_after_exclude_string() {
779        assert_eq!(
780            "this is karøbα it was"
781                .to_string()
782                .keep(Regex::new("karøbα").unwrap())
783                .beginning_of_string()
784                .after_pattern()
785                .excluding_pattern()
786                .to_string(),
787            " it was"
788        );
789        assert_eq!(
790            "karøbα"
791                .to_string()
792                .keep(Regex::new("kar").unwrap())
793                .after_pattern()
794                .excluding_pattern()
795                .to_string(),
796            "øbα"
797        );
798    }
799
800    #[test]
801    fn test_keep_before_include_string() {
802        assert_eq!(
803            "this is karøbα it was"
804                .to_string()
805                .keep(Regex::new("øbα").unwrap())
806                .utf8_encoding()
807                .before_pattern()
808                .including_pattern()
809                .to_string(),
810            "this is karøbα"
811        );
812        assert_eq!(
813            "karøbα"
814                .to_string()
815                .keep(Regex::new("øbα").unwrap())
816                .before_pattern()
817                .including_pattern()
818                .to_string(),
819            "karøbα"
820        );
821
822        assert_eq!(
823            "My numbør is 555-0100 and this is some other useless information"
824                .to_string()
825                .keep(Regex::new(r"\d{3}-\d{4}").unwrap())
826                .utf8_encoding()
827                .before_pattern()
828                .including_pattern()
829                .to_string(),
830            "My numbør is 555-0100"
831        );
832
833        assert_eq!(
834            "My number is 555-0100 and this is some other useless information"
835                .to_string()
836                .keep(Regex::new(r"\d{3}-\d{4}").unwrap())
837                .before_pattern()
838                .including_pattern()
839                .to_string(),
840            "My number is 555-0100"
841        );
842    }
843
844    #[test]
845    fn test_keep_before_exclude_string() {
846        assert_eq!(
847            "this is karøbα it was"
848                .to_string()
849                .keep(Regex::new("øbα").unwrap())
850                .before_pattern()
851                .excluding_pattern()
852                .to_string(),
853            "this is kar"
854        );
855        assert_eq!(
856            "karøbα"
857                .to_string()
858                .keep(Regex::new("øbα").unwrap())
859                .before_pattern()
860                .excluding_pattern()
861                .to_string(),
862            "kar"
863        );
864    }
865}
866
867#[cfg(test)]
868mod pattern_keep_until {
869    use crate::prelude::*;
870
871    #[test]
872    fn to_keep_until_pattern() {
873        assert_eq!(
874            "42.141592650991234200000000000000000000"
875                .to_string()
876                .cut('0')
877                .end_of_string()
878                .until_no_matched_pattern('0')
879                .before_pattern()
880                .excluding_pattern()
881                .to_string(),
882            "42.1415926509912342"
883        );
884
885        assert_eq!(
886            "42.141592650991234200000000000000000000".trim_trailing_zeros(),
887            "42.1415926509912342"
888        );
889
890    }
891}