#[cfg(test)]
mod tests;
use core::fmt;
use core::mem;
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::vec::Vec;
use std::io;
use relative_path::{RelativePath, RelativePathBuf};
use crate::Root;
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
}
impl Error {
fn new(kind: ErrorKind) -> Self {
Self { kind }
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
ErrorKind::ReadDir(..) => write!(f, "Error reading directory"),
ErrorKind::DirEntry(..) => write!(f, "Error getting directory entry"),
}
}
}
impl From<ErrorKind> for Error {
#[inline]
fn from(kind: ErrorKind) -> Self {
Self::new(kind)
}
}
#[derive(Debug)]
enum ErrorKind {
ReadDir(io::Error),
DirEntry(io::Error),
}
impl std::error::Error for Error {
#[allow(clippy::match_same_arms)]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.kind {
ErrorKind::ReadDir(error) => Some(error),
ErrorKind::DirEntry(error) => Some(error),
}
}
}
pub struct Glob<'a> {
root: &'a Root,
components: Vec<Component<'a>>,
}
impl<'a> Glob<'a> {
pub(super) fn new(root: &'a Root, pattern: &'a RelativePath) -> Self {
let components = compile_pattern(pattern);
Self { root, components }
}
#[must_use]
pub fn matcher(&self) -> Matcher<'_> {
Matcher {
root: self.root,
queue: [(RelativePathBuf::new(), self.components.as_ref())]
.into_iter()
.collect(),
}
}
}
impl<'a> Matcher<'a> {
fn expand_filesystem<M>(
&mut self,
current: &RelativePath,
rest: &'a [Component<'a>],
mut m: M,
) -> Result<()>
where
M: FnMut(&str) -> bool,
{
if let Ok(m) = self.root.metadata(current) {
if !m.is_dir() {
return Ok(());
}
} else {
return Ok(());
}
for e in self.root.read_dir(current).map_err(ErrorKind::ReadDir)? {
let e = e.map_err(ErrorKind::DirEntry)?;
let c = e.file_name();
let c = c.to_string_lossy();
if !m(c.as_ref()) {
continue;
}
let mut new = current.to_owned();
new.push(c.as_ref());
self.queue.push_back((new, rest));
}
Ok(())
}
fn walk(&mut self, current: &RelativePathBuf, rest: &'a [Component<'a>]) -> Result<()> {
self.queue.push_back((current.clone(), rest));
let mut queue = VecDeque::new();
queue.push_back(current.clone());
while let Some(current) = queue.pop_front() {
let Ok(m) = self.root.metadata(¤t) else {
continue;
};
if !m.is_dir() {
continue;
}
for e in self.root.read_dir(¤t).map_err(ErrorKind::ReadDir)? {
let e = e.map_err(ErrorKind::DirEntry)?;
let c = e.file_name();
let c = c.to_string_lossy();
let next = current.join(c.as_ref());
self.queue.push_back((next.clone(), rest));
queue.push_back(next);
}
}
Ok(())
}
}
pub struct Matcher<'a> {
root: &'a Root,
queue: VecDeque<(RelativePathBuf, &'a [Component<'a>])>,
}
impl Iterator for Matcher<'_> {
type Item = Result<RelativePathBuf>;
fn next(&mut self) -> Option<Self::Item> {
'outer: loop {
let (mut path, mut components) = self.queue.pop_front()?;
while let [first, rest @ ..] = components {
match first {
Component::ParentDir => {
path = path.join(relative_path::Component::ParentDir);
}
Component::Normal(normal) => {
path = path.join(normal);
}
Component::Fragment(fragment) => {
if let Err(e) =
self.expand_filesystem(&path, rest, |name| fragment.is_match(name))
{
return Some(Err(e));
}
continue 'outer;
}
Component::StarStar => {
if let Err(e) = self.walk(&path, rest) {
return Some(Err(e));
}
continue 'outer;
}
}
components = rest;
}
return Some(Ok(path));
}
}
}
#[derive(Clone)]
enum Component<'a> {
ParentDir,
Normal(&'a str),
Fragment(Fragment<'a>),
StarStar,
}
fn compile_pattern(pattern: &RelativePath) -> Vec<Component<'_>> {
let mut output = Vec::new();
for c in pattern.components() {
output.push(match c {
relative_path::Component::CurDir => continue,
relative_path::Component::ParentDir => Component::ParentDir,
relative_path::Component::Normal("**") => Component::StarStar,
relative_path::Component::Normal(normal) => {
let fragment = Fragment::parse(normal);
if let Some(normal) = fragment.as_literal() {
Component::Normal(normal)
} else {
Component::Fragment(fragment)
}
}
});
}
output
}
#[derive(Debug, Clone, Copy)]
enum Part<'a> {
Star,
Literal(&'a str),
}
#[derive(Debug, Clone)]
pub(crate) struct Fragment<'a> {
parts: Box<[Part<'a>]>,
}
impl<'a> Fragment<'a> {
pub(crate) fn parse(string: &'a str) -> Fragment<'a> {
let mut literal = true;
let mut parts = Vec::new();
let mut start = None;
for (n, c) in string.char_indices() {
if c == '*' {
if let Some(s) = start.take() {
parts.push(Part::Literal(&string[s..n]));
}
if mem::take(&mut literal) {
parts.push(Part::Star);
}
} else {
if start.is_none() {
start = Some(n);
}
literal = true;
}
}
if let Some(s) = start {
parts.push(Part::Literal(&string[s..]));
}
Fragment {
parts: parts.into(),
}
}
pub(crate) fn is_match(&self, string: &str) -> bool {
let mut backtrack = VecDeque::new();
backtrack.push_back((self.parts.as_ref(), string));
while let Some((mut parts, mut string)) = backtrack.pop_front() {
while let Some(part) = parts.first() {
match part {
Part::Star => {
let Some(Part::Literal(peek)) = parts.get(1) else {
return true;
};
let Some(peek) = peek.chars().next() else {
return true;
};
while let Some(c) = string.chars().next() {
if c == peek {
backtrack.push_front((
parts,
string.get(c.len_utf8()..).unwrap_or_default(),
));
break;
}
string = string.get(c.len_utf8()..).unwrap_or_default();
}
}
Part::Literal(literal) => {
let Some(remainder) = string.strip_prefix(literal) else {
return false;
};
string = remainder;
}
}
parts = parts.get(1..).unwrap_or_default();
}
if string.is_empty() {
return true;
}
}
false
}
fn as_literal(&self) -> Option<&'a str> {
if let [Part::Literal(one)] = self.parts.as_ref() {
Some(one)
} else {
None
}
}
}