From 8ce787a837965061eaff0c2f3563286262e484c0 Mon Sep 17 00:00:00 2001 From: Janis Date: Sat, 1 Apr 2023 16:35:16 +0200 Subject: [PATCH] path helpers --- btrfs/src/lib.rs | 1 + btrfs/src/path.rs | 220 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 btrfs/src/path.rs diff --git a/btrfs/src/lib.rs b/btrfs/src/lib.rs index 799c0d4..11a4abf 100644 --- a/btrfs/src/lib.rs +++ b/btrfs/src/lib.rs @@ -23,6 +23,7 @@ extern crate alloc; pub mod crc32c; pub mod files; +pub mod path; pub mod structs; pub mod tree; pub mod v2; diff --git a/btrfs/src/path.rs b/btrfs/src/path.rs new file mode 100644 index 0000000..1702291 --- /dev/null +++ b/btrfs/src/path.rs @@ -0,0 +1,220 @@ +use alloc::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 = + Vec::with_capacity(self.as_ref().iter().filter(|&b| b == &SEPERATOR).count()); + + for segment in iter { + match segment { + Segment::NoOp | Segment::CurrentDir => {} + Segment::Root | Segment::Directory(_) | Segment::File(_) => segments.push(segment), + Segment::ParentDir => { + if segments.last() != Some(&Segment::Root) { + segments.pop(); + } + } + } + } + + NormalizedPath { segments } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Segment<'a> { + Root, + NoOp, + CurrentDir, + ParentDir, + Directory(&'a [u8]), + File(&'a [u8]), +} + +impl Path for T where T: AsRef<[u8]> {} + +pub struct NormalizedPath<'a> { + segments: Vec>, +} + +impl<'a> NormalizedPath<'a> { + pub fn iter(&self) -> core::slice::Iter { + self.segments.iter() + } +} + +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 { + // noop + Some(Segment::NoOp) + } + } else if segment == &b"."[..] { + Some(Segment::CurrentDir) + } else if segment == &b".."[..] { + Some(Segment::ParentDir) + } else { + if next == bytes.len() { + Some(Segment::File(segment)) + } else { + Some(Segment::Directory(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::Directory(&b"this"[..]))); + assert_eq!(iter.next(), Some(Segment::NoOp)); + assert_eq!(iter.next(), Some(Segment::Directory(&b"a"[..]))); + assert_eq!(iter.next(), Some(Segment::NoOp)); + assert_eq!(iter.next(), Some(Segment::Directory(&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_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::Directory(&b"this"[..]))); + assert_eq!(iter.next(), Some(Segment::Directory(&b"is"[..]))); + assert_eq!(iter.next(), Some(Segment::Directory(&b"a"[..]))); + assert_eq!(iter.next(), Some(Segment::NoOp)); + assert_eq!(iter.next(), Some(Segment::Directory(&b"path"[..]))); + assert_eq!(iter.next(), Some(Segment::Directory(&b"to"[..]))); + assert_eq!(iter.next(), Some(Segment::File(&b"some.file"[..]))); + assert_eq!(iter.next(), None); + } +}