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}