- remove directory segment and instead just have "file" for directories and files and other kinds of files since we cant know just based on the path what it is - segmetns are stored as vecdeque in normalized path to allow for hopefully more efficient poping in FIFO order (but definitely more ergonomic) - test for trailing seperators, the last trailing seperator is ignored, multiple trailing seperators are turned into noops because i parse paths left to right (regex?) in the simplest way possible. in normalized paths noops are completely ignored/removed
269 lines
7.9 KiB
Rust
269 lines
7.9 KiB
Rust
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<T> Path for T where T: AsRef<[u8]> {}
|
|
|
|
pub struct NormalizedPath<'a> {
|
|
segments: VecDeque<Segment<'a>>,
|
|
}
|
|
|
|
impl<'a> NormalizedPath<'a> {
|
|
pub fn into_iter(self) -> alloc::collections::vec_deque::IntoIter<Segment<'a>> {
|
|
self.segments.into_iter()
|
|
}
|
|
pub fn iter(&self) -> alloc::collections::vec_deque::Iter<Segment> {
|
|
self.segments.iter()
|
|
}
|
|
|
|
pub fn pop_segment(&mut self) -> Option<Segment<'a>> {
|
|
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<Self::Item> {
|
|
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<Self::Item> {
|
|
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);
|
|
}
|
|
}
|