kstd/path/
components.rs

1use crate::path::{Path, SEPARATOR};
2use core::iter::FusedIterator;
3
4#[derive(Clone)]
5pub struct Components<'a> {
6    path: &'a str,
7    state_front: State,
8    state_back: State,
9}
10
11#[derive(Clone, Debug, Eq, PartialEq)]
12enum State {
13    StartDir,
14    Body,
15    Done,
16}
17
18impl<'a> Components<'a> {
19    pub fn new(path: &'a Path) -> Self {
20        Components {
21            path: &path.inner,
22            state_front: if path.starts_with(SEPARATOR) {
23                State::StartDir
24            } else {
25                State::Body
26            },
27            state_back: State::Body,
28        }
29    }
30
31    pub fn as_path(&self) -> &'a Path {
32        self.path.into()
33    }
34
35    fn next_component_front(&mut self) -> (usize, Option<Component<'a>>) {
36        debug_assert_eq!(State::Body, self.state_front);
37        if self.path.is_empty() {
38            return (0, None); // nothing more to parse
39        }
40
41        let (extra, component) = match self
42            .path
43            .as_bytes()
44            .iter()
45            .position(|&b| b as char == SEPARATOR)
46        {
47            None => (0, self.path),
48            Some(i) => (1, &self.path[..i]),
49        };
50        (component.len() + extra, Some(component.into()))
51    }
52
53    fn next_component_back(&mut self) -> (usize, Option<Component<'a>>) {
54        debug_assert_eq!(State::Body, self.state_back);
55        let (extra, component) = match self
56            .path
57            .as_bytes()
58            .iter()
59            .rposition(|&b| b as char == SEPARATOR)
60        {
61            None => (0, self.path),
62            Some(i) => (1, &self.path[i + 1..]),
63        };
64        (component.len() + extra, Some(component.into()))
65    }
66}
67
68/// Paths are made of single components. If a path is absolute,
69/// the first component is the [`Component::RootDir`]. Every subsequent
70/// component that is not a `.` or a `..` is a [`Component::Normal`].
71/// Empty segments in a path will be ignored.
72///
73/// ## Examples
74/// `/foo/./bar` produces the following iterator:
75/// ```rust
76/// use kstd::path::components::Component;
77/// use kstd::path::Path;
78/// let path = Path::new("/foo/./bar");
79/// let mut components = path.components();
80/// assert_eq!(components.next(), Some(Component::RootDir));
81/// assert_eq!(components.next(), Some(Component::Normal("foo")));
82/// assert_eq!(components.next(), Some(Component::CurrentDir));
83/// assert_eq!(components.next(), Some(Component::Normal("bar")));
84/// assert_eq!(components.next(), None);
85/// ```
86///
87/// `foo///bar` produces the following iterator:
88/// ```rust
89/// use kstd::path::components::Component;
90/// use kstd::path::Path;
91/// let path = Path::new("foo///bar");
92/// let mut components = path.components();
93/// assert_eq!(components.next(), Some(Component::Normal("foo")));  
94/// assert_eq!(components.next(), Some(Component::Normal("bar")));
95/// assert_eq!(components.next(), None);
96/// ```
97///
98/// `//foo/bar` produces the following iterator:
99/// ```rust
100/// use kstd::path::components::Component;
101/// use kstd::path::Path;
102/// let path = Path::new("//foo/bar");
103/// let mut components = path.components();
104/// assert_eq!(components.next(), Some(Component::RootDir));
105/// assert_eq!(components.next(), Some(Component::Normal("foo")));
106/// assert_eq!(components.next(), Some(Component::Normal("bar")));
107/// assert_eq!(components.next(), None);
108/// ```
109#[derive(Debug, Eq, PartialEq)]
110pub enum Component<'a> {
111    /// The root dir component. If it is encountered, it is always
112    /// the first component of a path, and the path is absolute.
113    /// If this is not encountered, then the path is not absolute.
114    RootDir,
115    /// The current directory, produced if the path contains a `.`
116    /// as a part, such as in `/foo/./bar`.
117    CurrentDir,
118    /// The parent directory, produced if the path contains a `..`
119    /// as a part, such as in `/foo/../bar`.
120    ParentDir,
121    /// A normal component, which is a non-empty string. Empty
122    /// parts in the path are ignored, meaning that a path like
123    /// `foo//bar` and `foo///bar` will have two components,
124    /// `foo` and `bar`.
125    Normal(&'a str),
126}
127
128impl<'a> From<&'a str> for Component<'a> {
129    fn from(s: &'a str) -> Self {
130        match s {
131            "." => Component::CurrentDir,
132            ".." => Component::ParentDir,
133            "/" => Component::RootDir, // '/' as a component means root dir, everything else is considered a separator and must not reach this
134            _ => Component::Normal(s),
135        }
136    }
137}
138
139impl<'a> FusedIterator for Components<'a> {}
140
141impl<'a> Iterator for Components<'a> {
142    type Item = Component<'a>;
143
144    fn next(&mut self) -> Option<Self::Item> {
145        'outer: while !self.path.is_empty() {
146            return match self.state_front {
147                State::StartDir => {
148                    self.state_front = State::Body;
149                    self.path = &self.path[SEPARATOR.len_utf8()..];
150                    Some(Component::RootDir)
151                }
152                State::Body => {
153                    if let (count, Some(comp)) = self.next_component_front() {
154                        self.path = &self.path[count..];
155                        if let Component::Normal("") = comp {
156                            continue 'outer; // don't return empty fragments
157                        }
158                        Some(comp)
159                    } else {
160                        self.state_front = State::Done;
161                        None
162                    }
163                }
164                State::Done => None,
165            };
166        }
167        None
168    }
169}
170
171impl<'a> DoubleEndedIterator for Components<'a> {
172    fn next_back(&mut self) -> Option<Self::Item> {
173        'outer: while !self.path.is_empty() {
174            return match self.state_back {
175                State::StartDir => {
176                    self.state_back = State::Done;
177                    Some(Component::RootDir)
178                }
179                State::Body => {
180                    if let (count, Some(comp)) = self.next_component_back() {
181                        self.path = &self.path[..self.path.len() - count];
182                        if let Component::Normal("") = comp {
183                            continue 'outer; // don't return empty fragments
184                        }
185                        Some(comp)
186                    } else {
187                        self.state_back = State::Done;
188                        None
189                    }
190                }
191                State::Done => None,
192            };
193        }
194        None
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_components() {
204        let p = Path::new("hello/world");
205        let mut c = p.components();
206        assert_eq!(Some(Component::Normal("hello")), c.next());
207        assert_eq!(Some(Component::Normal("world")), c.next());
208        assert_eq!(None, c.next());
209    }
210
211    #[test]
212    fn test_components_absolute() {
213        let p = Path::new("/hello/world");
214        let mut c = p.components();
215        assert_eq!(Some(Component::RootDir), c.next());
216        assert_eq!(Some(Component::Normal("hello")), c.next());
217        assert_eq!(Some(Component::Normal("world")), c.next());
218        assert_eq!(None, c.next());
219    }
220
221    #[test]
222    fn test_empty_fragments() {
223        let p = Path::new("hello//////world");
224        let mut c = p.components();
225        assert_eq!(Some(Component::Normal("hello")), c.next());
226        assert_eq!(Some(Component::Normal("world")), c.next());
227        assert_eq!(None, c.next());
228    }
229
230    #[test]
231    fn test_somewhat_fused() {
232        let p = Path::new("/hello");
233        let mut c = p.components();
234        assert_eq!(Some(Component::RootDir), c.next());
235        assert_eq!(Some(Component::Normal("hello")), c.next());
236        for _ in 0..100 {
237            assert_eq!(None, c.next());
238        }
239    }
240
241    #[test]
242    fn test_trailing_slash() {
243        let p = Path::new("/hello/");
244        let mut c = p.components();
245        assert_eq!(Some(Component::RootDir), c.next());
246        assert_eq!(Some(Component::Normal("hello")), c.next());
247        for _ in 0..100 {
248            assert_eq!(None, c.next());
249        }
250    }
251
252    #[test]
253    fn test_trailing_slashes() {
254        let p = Path::new("/hello/////");
255        let mut c = p.components();
256        assert_eq!(Some(Component::RootDir), c.next());
257        assert_eq!(Some(Component::Normal("hello")), c.next());
258        for _ in 0..100 {
259            assert_eq!(None, c.next());
260        }
261    }
262}