stylish_stringlike/text/splitable.rs
1use super::{RawText, Sliceable};
2use std::iter::once;
3
4#[derive(Clone, Debug, Eq, PartialEq)]
5/// A segment of text split on a delimiter.
6/// The delimiter and the segment are both included because
7/// the delimiter may have a style applied to it.
8pub struct Split<T, U> {
9 pub delim: Option<T>,
10 pub segment: Option<U>,
11}
12
13/// Text objects that can be split on a delimiter or pattern
14pub trait Splitable<'a, T> {
15 /// Split a text object on the given pattern.
16 ///
17 /// # Example
18 ///
19 /// ```rust
20 /// use stylish_stringlike::text::{Split, Splitable};
21 /// let path = String::from("/Some/complicated/path");
22 /// let mut split = Splitable::<&str>::split(&path, "/");
23 /// assert_eq!(
24 /// Some(Split {
25 /// delim: Some(String::from("/")),
26 /// segment: None,
27 /// }),
28 /// split.next()
29 /// );
30 /// assert_eq!(
31 /// Some(Split {
32 /// delim: Some(String::from("/")),
33 /// segment: Some(String::from("Some"))
34 /// }),
35 /// split.next()
36 /// );
37 /// assert_eq!(
38 /// Some(Split {
39 /// delim: Some(String::from("/")),
40 /// segment: Some(String::from("complicated"))
41 /// }),
42 /// split.next()
43 /// );
44 /// assert_eq!(
45 /// Some(Split {
46 /// delim: None,
47 /// segment: Some(String::from("path"))
48 /// }),
49 /// split.next()
50 /// );
51 /// ```
52 fn split(&'a self, pattern: T) -> Box<dyn Iterator<Item = Split<Self, Self>> + 'a>
53 where
54 Self: Sized;
55}
56
57impl<'a, T> Splitable<'a, &'a str> for T
58where
59 T: Sliceable + RawText,
60{
61 #[allow(clippy::type_complexity)]
62 fn split(&'a self, pattern: &'a str) -> Box<dyn Iterator<Item = Split<Self, Self>> + 'a> {
63 Box::new(
64 self.raw_ref()
65 .match_indices(pattern)
66 // This is a silly hack to flag when we get to the last element in the list.
67 // Items in the list will be Some.
68 .map(Some)
69 // The last item will be None.
70 .chain(once(None))
71 .scan(0, move |last_end, item| {
72 if let Some((start, pat)) = item {
73 let end = start + pat.len();
74 let delim = self.slice(start..end);
75 let res = if start == 0 {
76 // String starts with delimiter
77 Some(Split {
78 segment: None,
79 delim,
80 })
81 } else {
82 Some(Split {
83 segment: self.slice(*last_end..start),
84 delim,
85 })
86 };
87 *last_end = end;
88 res
89 } else {
90 // This is the last item.
91 if *last_end == self.raw().len() {
92 // After consuming the last match, we are at the end of the string
93 None
94 } else {
95 // After consuming the last match, we still have some string yet
96 Some(Split {
97 segment: self.slice(*last_end..),
98 delim: None,
99 })
100 }
101 }
102 }),
103 )
104 }
105}
106
107#[cfg(test)]
108mod test {
109 use super::*;
110 #[test]
111 fn test_split_str() {
112 let path = String::from("Some/really/long/and/overly/complicated/path");
113 let mut split = Splitable::<&str>::split(&path, "/");
114 assert_eq!(
115 Some(Split {
116 delim: Some(String::from("/")),
117 segment: Some(String::from("Some"))
118 }),
119 split.next()
120 );
121 }
122}