use alloc::{collections::VecDeque, vec::Vec}; const SEPERATOR: u8 = b'/'; pub trait Path: AsRef<[u8]> { fn is_absolute(&self) -> bool { self.as_ref().get(0) == Some(&SEPERATOR) } fn is_relative(&self) -> bool { !self.is_absolute() } fn path_segment_iter<'a>(&'a self) -> PathIterator<'a> { let bytes = if self.is_absolute() { &self.as_ref()[1..] } else { self.as_ref() }; PathIterator { bytes } } fn segment_iter<'a>(&'a self) -> PathSegmentIter<'a> { PathSegmentIter { bytes: self.as_ref(), offset: 0, } } fn normalize(&self) -> NormalizedPath<'_> { let iter = self.segment_iter(); let mut segments = VecDeque::with_capacity(self.as_ref().iter().filter(|&b| b == &SEPERATOR).count()); for segment in iter { match segment { Segment::NoOp | Segment::CurrentDir => {} Segment::Root | Segment::File(_) => segments.push_back(segment), Segment::ParentDir => { if segments.back() != Some(&Segment::Root) { segments.pop_back(); } } } } NormalizedPath { segments } } } #[derive(Debug, PartialEq, Eq)] pub enum Segment<'a> { Root, NoOp, CurrentDir, ParentDir, File(&'a [u8]), } impl Path for T where T: AsRef<[u8]> {} pub struct NormalizedPath<'a> { segments: VecDeque>, } impl<'a> NormalizedPath<'a> { pub fn into_iter(self) -> alloc::collections::vec_deque::IntoIter> { self.segments.into_iter() } pub fn iter(&self) -> alloc::collections::vec_deque::Iter { self.segments.iter() } pub fn pop_segment(&mut self) -> Option> { self.segments.pop_front() } } pub struct PathSegmentIter<'a> { bytes: &'a [u8], offset: usize, } impl<'a> Iterator for PathSegmentIter<'a> { type Item = Segment<'a>; fn next(&mut self) -> Option { let bytes = &self.bytes[self.offset..]; if !bytes.is_empty() { let next = bytes .iter() .position(|c| c == &SEPERATOR) .unwrap_or(bytes.len()); // bytes of segment let segment = &bytes[..next]; log::info!("{}", alloc::string::String::from_utf8_lossy(segment)); let segment = if segment.is_empty() { if self.offset == 0 { // handle root segment Some(Segment::Root) } else if bytes.is_empty() { // trailign seperator None } else { // noop Some(Segment::NoOp) } } else if segment == &b"."[..] { Some(Segment::CurrentDir) } else if segment == &b".."[..] { Some(Segment::ParentDir) } else { Some(Segment::File(segment)) }; self.offset += bytes.len().min(next + 1); segment } else { None } } } /// iterator that returns path segments pub struct PathIterator<'a> { bytes: &'a [u8], } impl<'a> PathIterator<'a> {} impl<'a> Iterator for PathIterator<'a> { type Item = &'a [u8]; fn next(&mut self) -> Option { if !self.bytes.is_empty() { let next = self .bytes .iter() .position(|c| c == &SEPERATOR) .unwrap_or(self.bytes.len()); let segment = &self.bytes[..next]; self.bytes = &self.bytes[self.bytes.len().min(next + 1)..]; Some(segment) } else { None } } } #[cfg(test)] mod tests { use super::*; use test_log::test; #[test] fn test_iter() { let mut iter = b"/this/is/a//path/to/some.file".path_segment_iter(); assert_eq!(iter.next(), Some(&b"this"[..])); assert_eq!(iter.next(), Some(&b"is"[..])); assert_eq!(iter.next(), Some(&b"a"[..])); assert_eq!(iter.next(), Some(&b""[..])); assert_eq!(iter.next(), Some(&b"path"[..])); assert_eq!(iter.next(), Some(&b"to"[..])); assert_eq!(iter.next(), Some(&b"some.file"[..])); assert_eq!(iter.next(), None); } } #[cfg(test)] mod segment_iter_tests { use super::*; use test_log::test; #[test] fn test_noop() { let mut iter = b"this//a//path//some.file".segment_iter(); assert_eq!(iter.next(), Some(Segment::File(&b"this"[..]))); assert_eq!(iter.next(), Some(Segment::NoOp)); assert_eq!(iter.next(), Some(Segment::File(&b"a"[..]))); assert_eq!(iter.next(), Some(Segment::NoOp)); assert_eq!(iter.next(), Some(Segment::File(&b"path"[..]))); assert_eq!(iter.next(), Some(Segment::NoOp)); assert_eq!(iter.next(), Some(Segment::File(&b"some.file"[..]))); assert_eq!(iter.next(), None); } #[test] fn test_root() { let path = b"/absolute/path"; let path2 = b"relative/path"; let path3 = b"../relative/path"; let path4 = b"./relative/path"; let path5 = b".//relative/path"; let relative_paths = [&path2[..], &path3[..], &path4[..], &path5[..]]; assert!(path.is_absolute()); let mut iter = path.segment_iter(); assert_eq!(iter.next(), Some(Segment::Root)); for path in relative_paths { assert!(path.is_relative()); let mut iter = path.segment_iter(); assert_ne!(iter.next(), Some(Segment::Root)); } } #[test] fn test_trailing_seperator() { let mut iter = b"/this//".segment_iter(); assert_eq!(iter.next(), Some(Segment::Root)); assert_eq!(iter.next(), Some(Segment::File(&b"this"[..]))); assert_eq!(iter.next(), Some(Segment::NoOp)); assert_eq!(iter.next(), None); let mut iter = b"this//".segment_iter(); assert_eq!(iter.next(), Some(Segment::File(&b"this"[..]))); assert_eq!(iter.next(), Some(Segment::NoOp)); assert_eq!(iter.next(), None); let mut iter = b"/this////".segment_iter(); assert_eq!(iter.next(), Some(Segment::Root)); assert_eq!(iter.next(), Some(Segment::File(&b"this"[..]))); assert_eq!(iter.next(), Some(Segment::NoOp)); assert_eq!(iter.next(), Some(Segment::NoOp)); assert_eq!(iter.next(), Some(Segment::NoOp)); assert_eq!(iter.next(), None); let mut iter = b"/this////".normalize().into_iter(); assert_eq!(iter.next(), Some(Segment::Root)); assert_eq!(iter.next(), Some(Segment::File(&b"this"[..]))); assert_eq!(iter.next(), None); let mut iter = b"/this/".normalize().into_iter(); assert_eq!(iter.next(), Some(Segment::Root)); assert_eq!(iter.next(), Some(Segment::File(&b"this"[..]))); assert_eq!(iter.next(), None); let mut iter = b"/this".normalize().into_iter(); assert_eq!(iter.next(), Some(Segment::Root)); assert_eq!(iter.next(), Some(Segment::File(&b"this"[..]))); assert_eq!(iter.next(), None); } #[test] fn test_iter() { let mut iter = b"/this/is/a//path/to/some.file".segment_iter(); assert_eq!(iter.next(), Some(Segment::Root)); assert_eq!(iter.next(), Some(Segment::File(&b"this"[..]))); assert_eq!(iter.next(), Some(Segment::File(&b"is"[..]))); assert_eq!(iter.next(), Some(Segment::File(&b"a"[..]))); assert_eq!(iter.next(), Some(Segment::NoOp)); assert_eq!(iter.next(), Some(Segment::File(&b"path"[..]))); assert_eq!(iter.next(), Some(Segment::File(&b"to"[..]))); assert_eq!(iter.next(), Some(Segment::File(&b"some.file"[..]))); assert_eq!(iter.next(), None); } }