btdt 0.4.4

"been there, done that" - a tool for flexible CI caching
Documentation
use std::io;
use std::io::ErrorKind;

pub trait PathIterExt {
    fn path_components(&self) -> io::Result<PathIter<'_, impl Iterator<Item = &str>>>;
}

impl PathIterExt for &str {
    fn path_components(&self) -> io::Result<PathIter<'_, impl Iterator<Item = &str>>> {
        if !self.starts_with('/') {
            return Err(io::Error::new(
                ErrorKind::InvalidInput,
                "Path must be absolute, i.e. start with a slash '/'",
            ));
        }
        let mut inner = self.split('/');
        Ok(PathIter {
            next: inner.find(|c| !c.is_empty()),
            inner,
        })
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PathComponent<'a> {
    pub name: &'a str,
    pub is_last: bool,
}

#[derive(Debug, Clone)]
pub struct PathIter<'a, I: Iterator<Item = &'a str>> {
    inner: I,
    next: Option<&'a str>,
}

impl<'a, I: Iterator<Item = &'a str>> Iterator for PathIter<'a, I> {
    type Item = PathComponent<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        let current = self.next;
        self.next = self.inner.find(|c| !c.is_empty());
        current.map(|name| PathComponent {
            name,
            is_last: self.next.is_none(),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_path_components_requires_leading_slash() {
        assert!("".path_components().is_err());
        assert!("foo".path_components().is_err());
        assert!("foo/bar".path_components().is_err());
    }

    #[test]
    fn test_path_components_marks_last_component() {
        let components: Vec<_> = "/foo/bar/baz".path_components().unwrap().collect();
        assert_eq!(
            components,
            vec![
                PathComponent {
                    name: "foo",
                    is_last: false
                },
                PathComponent {
                    name: "bar",
                    is_last: false
                },
                PathComponent {
                    name: "baz",
                    is_last: true
                },
            ]
        );
    }

    #[test]
    fn test_ignores_empty_components() {
        let components: Vec<_> = "/foo///bar/".path_components().unwrap().collect();
        assert_eq!(
            components,
            vec![
                PathComponent {
                    name: "foo",
                    is_last: false
                },
                PathComponent {
                    name: "bar",
                    is_last: true
                },
            ]
        );
    }
}