use std::ffi::OsStr;
use std::fs::{FileType, Metadata};
use std::path::{Component, Path, PathBuf};
use std::sync::Arc;
use ignore::overrides::OverrideBuilder;
use ignore::{DirEntry as IgnoreDirEntry, Walk as IgnoreWalk, WalkBuilder as IgnoreWalkBuilder};
use crate::pre::*;
#[inline(always)]
pub fn walker(root: impl AsRef<Path>) -> WalkBuilder {
WalkBuilder::new(root.as_ref().to_path_buf())
}
#[inline(always)]
pub fn walk(root: impl AsRef<Path>) -> cu::Result<Walk> {
walker(root).walk()
}
pub struct WalkBuilder {
inner: IgnoreWalkBuilder,
overrides: OverrideBuilder,
has_overrides: bool,
include_dir_entries: bool,
root: PathBuf,
}
impl WalkBuilder {
fn new(root: PathBuf) -> Self {
let mut inner = IgnoreWalkBuilder::new(root.clone());
inner.require_git(true);
inner.ignore(false);
inner.hidden(false);
let mut s = Self {
inner,
overrides: OverrideBuilder::new(root.clone()),
has_overrides: false,
include_dir_entries: false,
root,
};
s.git(false);
s
}
pub fn as_inner_mut(&mut self) -> &mut IgnoreWalkBuilder {
&mut self.inner
}
pub fn glob_includes(
&mut self,
globs: impl IntoIterator<Item = impl AsRef<str>>,
) -> crate::Result<&mut Self> {
for g in globs {
let g = g.as_ref();
if g.starts_with("./") || g.starts_with(".\\") {
let g2 = &g[1..];
crate::check!(
self.overrides.add(g2),
"failed to add glob include pattern: '{g2}' (resolved from '{g}')"
)?;
} else {
crate::check!(
self.overrides.add(g),
"failed to add glob include pattern: '{g}'"
)?;
}
self.has_overrides = true;
}
Ok(self)
}
pub fn glob_excludes(
&mut self,
globs: impl IntoIterator<Item = impl AsRef<str>>,
) -> crate::Result<&mut Self> {
let mut s = String::new();
s.push('!');
for g in globs {
let g = g.as_ref();
if g.starts_with("./") || g.starts_with(".\\") {
let g2 = &g[1..];
s.push_str(g2);
crate::check!(
self.overrides.add(&s),
"failed to add glob exclude pattern: '{g2}' (resolved from '{g}')"
)?;
} else {
s.push_str(g);
crate::check!(
self.overrides.add(&s),
"failed to add glob exclude pattern: '{g}'"
)?;
}
s.truncate(1);
self.has_overrides = true;
}
Ok(self)
}
#[inline(always)]
pub fn include_dir_entries(&mut self, include: bool) -> &mut Self {
self.include_dir_entries = include;
self
}
#[inline(always)]
pub fn git(&mut self, yes: bool) -> &mut Self {
self.inner.git_global(yes);
self.inner.git_ignore(yes);
self.inner.git_exclude(yes);
self
}
#[inline(always)]
pub fn ignore_hidden(&mut self, yes: bool) -> &mut Self {
self.inner.hidden(yes);
self
}
#[inline(always)]
pub fn follow_links(&mut self, yes: bool) -> &mut Self {
self.inner.follow_links(yes);
self
}
#[inline(always)]
pub fn add_ignore_filename(&mut self, ignore_file: &str) -> &mut Self {
self.inner.add_custom_ignore_filename(ignore_file);
self
}
pub fn walk(mut self) -> cu::Result<Walk> {
if self.has_overrides {
let overrides = cu::check!(
self.overrides.build(),
"walk: failed to build glob pattern overrides"
)?;
self.inner.overrides(overrides);
}
let walk = self.inner.build();
Ok(Walk {
inner: walk,
include_dir_entries: self.include_dir_entries,
root: Arc::new(self.root),
})
}
}
pub struct Walk {
inner: IgnoreWalk,
include_dir_entries: bool,
root: Arc<PathBuf>,
}
impl Iterator for Walk {
type Item = crate::Result<WalkEntry>;
fn next(&mut self) -> Option<Self::Item> {
match self.next_internal() {
Err(e) => Some(Err(e)),
Ok(None) => None,
Ok(Some(e)) => Some(Ok(e)),
}
}
}
impl Walk {
fn next_internal(&mut self) -> crate::Result<Option<WalkEntry>> {
let (entry, file_type) = loop {
let entry = crate::some!(self.inner.next());
let entry = crate::check!(entry, "walk: failed to read the next entry")?;
if entry.depth() == 0 {
continue;
}
match entry.file_type() {
None => {
continue;
}
Some(t) => {
if self.include_dir_entries {
break (entry, t);
}
if t.is_file() {
break (entry, t);
}
if t.is_dir() {
crate::trace!(
"walk: not emitting entry for directory: '{}'",
entry.path().display()
);
continue;
}
if t.is_symlink() && entry.path().is_dir() {
crate::trace!(
"walk: not emitting entry for symlinked directory: '{}'",
entry.path().display()
);
continue;
}
crate::trace!(
"walk: skipping entry with unknown file type: '{}'",
entry.path().display()
);
continue;
}
}
};
Ok(Some(WalkEntry {
root: Arc::clone(&self.root),
inner: entry,
file_type,
}))
}
}
#[derive(Debug)]
pub struct WalkEntry {
root: Arc<PathBuf>,
inner: IgnoreDirEntry,
file_type: FileType,
}
impl WalkEntry {
#[inline(always)]
pub fn root(&self) -> &Path {
&self.root
}
pub fn depth(&self) -> usize {
self.inner.depth()
}
#[inline(always)]
pub fn path(&self) -> &Path {
self.inner.path()
}
pub fn rel_path(&self) -> cu::Result<PathBuf> {
let root_norm = self.root.normalize()?;
let path_norm = root_norm.join(self.inner.path());
let root_iter = root_norm
.components()
.filter(|x| !matches!(x, Component::CurDir));
let mut path_iter = path_norm
.components()
.filter(|x| !matches!(x, Component::CurDir));
for root_comp in root_iter {
let path_comp = cu::check!(
path_iter.next(),
"unexpected: walk entry path is shorter than root"
)?;
cu::ensure!(
root_comp == path_comp,
"unexpected: walk entry path is not in root"
)?;
}
let Some(next) = path_iter.next() else {
cu::bail!("unexpected: walk entry path is same as root");
};
let mut p = PathBuf::new();
p.push(next);
p.extend(path_iter);
Ok(p)
}
#[inline(always)]
pub fn file_type(&self) -> FileType {
self.file_type
}
#[inline(always)]
pub fn is_file(&self) -> bool {
self.file_type.is_file()
}
#[inline(always)]
pub fn is_dir(&self) -> bool {
self.file_type.is_dir()
}
#[inline(always)]
pub fn is_symlink(&self) -> bool {
self.file_type.is_symlink()
}
#[inline(always)]
pub fn file_name(&self) -> Option<&OsStr> {
self.inner.path().file_name()
}
pub fn metadata(&self) -> crate::Result<Metadata> {
crate::check!(
self.inner.metadata(),
"failed to get metadata for file '{}' while walking directory '{}'",
self.inner.path().try_to_rel_from(&*self.root).display(),
self.root.display()
)
}
}