Components now iterates over Component.

This commit is contained in:
Filipe Rodrigues 2021-06-04 20:37:04 +01:00
parent 1fa7dffddf
commit 3828aeec9b
3 changed files with 138 additions and 45 deletions

View File

@ -1,23 +1,35 @@
//! Entry finding
// Imports
use crate::{DirEntry, DirEntryKind, DirEntryPtr, DirPtr, Path, PathBuf};
use crate::{path::Component, DirEntry, DirEntryKind, DirEntryPtr, DirPtr, Path, PathBuf};
use std::io;
/// Finds an entry given it's path
pub fn find_entry<R: io::Seek + io::Read>(
reader: &mut R, path: &Path,
) -> Result<(DirEntryPtr, DirEntry), FindEntryError> {
// Current directory pointer
let mut cur_dir_ptr = DirPtr::root();
// Current entry
let mut cur_entry = None;
let mut components = path.components();
while let Some(entry_name) = components.next() {
while let Some(component) = components.next() {
// Get the next entry's name
let entry_name = match component {
// Note: We start at the root, so `Root` doesn't do anything to us
Component::Root | Component::CurDir => continue,
Component::ParentDir => return Err(FindEntryError::ParentDir),
Component::Normal(entry) => entry,
};
// Find the entry
let (entry_ptr, entry) = cur_dir_ptr
.find_entry(reader, entry_name.as_ascii())
.find_entry(reader, entry_name)
.map_err(FindEntryError::FindEntry)?;
// If there's no component after this, return the current entry
// If there's no component after this, break
if components.clone().next().is_none() {
return Ok((entry_ptr, entry));
}
@ -32,12 +44,15 @@ pub fn find_entry<R: io::Seek + io::Read>(
},
// If we got a directory, continue
DirEntryKind::Dir { ptr } => cur_dir_ptr = ptr,
DirEntryKind::Dir { ptr } => {
cur_entry = Some((entry_ptr, entry));
cur_dir_ptr = ptr;
},
};
}
// If we get here, the path was empty
Err(FindEntryError::EmptyPath)
// If we got here, try to return our current entry
cur_entry.ok_or(FindEntryError::EmptyPath)
}
/// Error type for [`find_entry`]
@ -47,6 +62,10 @@ pub enum FindEntryError {
#[error("Unable to find entry")]
FindEntry(#[source] crate::ptr::FindEntryError),
/// Cannot go back to parent directory
#[error("Cannot go back to parent directory")]
ParentDir,
/// Expected directory
#[error("Expected directory at {path}")]
ExpectedDir {

View File

@ -2,10 +2,14 @@
//!
//! See the [`Path`] type for more details.
// Modules
#[cfg(test)]
mod test;
// Imports
use ascii::{AsciiChar, AsciiStr, AsciiString};
use ref_cast::RefCast;
use std::{fmt, ops};
use std::{fmt, iter::FusedIterator, ops};
/// A path
///
@ -74,34 +78,10 @@ impl Path {
/// Returns an iterator over all components of this path
#[must_use]
pub fn components(&self) -> Components {
pub const fn components(&self) -> Components {
Components::new(self)
}
/// Splits this path at it's first component
#[must_use]
pub fn split_first(&self) -> Option<(&AsciiStr, &Self)> {
let mut components = self.components();
let first = components.next()?;
Some((first.as_ascii(), components.path))
}
/// Splits this path at it's last component
#[must_use]
pub fn split_last(&self) -> Option<(&Self, &AsciiStr)> {
// Get the last component
let (idx, last) = self.components().enumerate().last()?;
// If it was the start component, return
if idx == 0 {
return None;
}
// Else separate them
let start = &self.0[..(self.len() - last.len())];
Some((Self::new(start), last.as_ascii()))
}
/// Converts this path into a [`PathBuf`]
#[must_use]
pub fn to_path_buf(&self) -> PathBuf {
@ -181,11 +161,9 @@ pub struct Components<'a> {
impl<'a> Components<'a> {
/// Creates new components
pub(self) fn new(path: &'a Path) -> Self {
pub(self) const fn new(path: &'a Path) -> Self {
// Trim all trailing `\`
Self {
path: path.trim_trailing(),
}
Self { path }
}
/// Returns the remaining path
@ -195,21 +173,55 @@ impl<'a> Components<'a> {
}
}
impl<'a> FusedIterator for Components<'a> {}
impl<'a> Iterator for Components<'a> {
type Item = &'a Path;
type Item = Component<'a>;
fn next(&mut self) -> Option<Self::Item> {
// Trim all leading `\`
self.path = self.path.trim_leading();
// Read until the next `\\` or eof
let (cmpt, rest) = match self.path.0.chars().position(|ch| ch == AsciiChar::BackSlash) {
// If we found it first, emit a root component
Some(0) => (Component::Root, self.path),
// Then split at the first `\` we find
let (path, rest) = match self.path.0.chars().position(|ch| ch == AsciiChar::BackSlash) {
Some(idx) => (Path::new(&self.path.0[..idx]), Path::new(&self.path.0[idx..])),
None if !self.path.is_empty() => (self.path, Path::empty()),
// Else it's a normal component
// Note: We handle `.` and `..` below
Some(idx) => (Component::Normal(self.path[..idx].as_ascii()), &self.path[idx..]),
// If we didn't find `\\`, but we're not empty, return the remaining path
None if !self.path.is_empty() => (Component::Normal(self.path.as_ascii()), Path::empty()),
// Else we're done
None => return None,
};
// Trim all remaining leading `\\`s
let rest = rest.trim_leading();
// If the component is a normal `.` or `..`, change it
let cmpt = match cmpt {
Component::Normal(path) if path == "." => Component::CurDir,
Component::Normal(path) if path == ".." => Component::ParentDir,
_ => cmpt,
};
self.path = rest;
Some(path)
Some(cmpt)
}
}
/// Component
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Component<'a> {
/// Root, `\\` at the start of the path
Root,
/// Cur dir, `.`
CurDir,
/// Parent dir, `..`
ParentDir,
/// Normal
Normal(&'a AsciiStr),
}

62
dcb-drv/src/path/test.rs Normal file
View File

@ -0,0 +1,62 @@
//! Tests
// Imports
use super::*;
#[allow(clippy::enum_glob_use)] // It's a limited scope
use Component::*;
/// Creates an ascii string from `path`
fn ascii(path: &str) -> &AsciiStr {
AsciiStr::from_ascii(path).expect("Unable to create path")
}
/// Asserts components of `path` are `cmpts`
fn assert_components_eq(path: &Path, cmpts: &[Component]) {
assert_eq!(path.components().collect::<Vec<_>>(), cmpts);
}
#[test]
fn simple() {
self::assert_components_eq(Path::new(ascii("A\\B\\C")), &[
Normal(ascii("A")),
Normal(ascii("B")),
Normal(ascii("C")),
]);
}
#[test]
fn root() {
self::assert_components_eq(Path::new(ascii("\\A")), &[Root, Normal(ascii("A"))]);
}
#[test]
fn cur() {
self::assert_components_eq(Path::new(ascii(".\\A\\.")), &[CurDir, Normal(ascii("A")), CurDir]);
}
#[test]
fn parent() {
self::assert_components_eq(Path::new(ascii("..\\A\\..")), &[
ParentDir,
Normal(ascii("A")),
ParentDir,
]);
}
#[test]
fn leading() {
self::assert_components_eq(Path::new(ascii("\\\\\\\\A")), &[Root, Normal(ascii("A"))]);
}
#[test]
fn trailing() {
self::assert_components_eq(Path::new(ascii("A\\\\\\\\")), &[Normal(ascii("A"))]);
}
#[test]
fn extra_separators() {
self::assert_components_eq(Path::new(ascii("A\\\\\\\\B")), &[
Normal(ascii("A")),
Normal(ascii("B")),
]);
}