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 }
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"); 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() .after_pattern() .including_pattern() .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() .after_pattern() .including_pattern() .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}