#![deny(missing_docs)]
#![allow(unknown_lints)]
#![allow(bare_trait_objects)]
#[cfg(test)]
#[macro_use]
extern crate doc_comment;
extern crate same_file;
#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
extern crate winapi_util;
#[cfg(test)]
doctest!("../README.md");
use std::cmp::{Ordering, min};
use std::fmt;
use std::fs::{self, ReadDir};
use std::io;
use std::path::{Path, PathBuf};
use std::result;
use std::vec;
use same_file::Handle;
pub use dent::DirEntry;
#[cfg(unix)]
pub use dent::DirEntryExt;
pub use error::Error;
mod dent;
mod error;
#[cfg(test)]
mod tests;
mod util;
macro_rules! itry {
($e:expr) => {
match $e {
Ok(v) => v,
Err(err) => return Some(Err(From::from(err))),
}
}
}
pub type Result<T> = ::std::result::Result<T, Error>;
#[derive(Debug)]
pub struct WalkDir {
opts: WalkDirOptions,
root: PathBuf,
}
struct WalkDirOptions {
follow_links: bool,
max_open: usize,
min_depth: usize,
max_depth: usize,
sorter: Option<Box<
FnMut(&DirEntry,&DirEntry) -> Ordering + Send + Sync + 'static
>>,
contents_first: bool,
same_file_system: bool,
}
impl fmt::Debug for WalkDirOptions {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
let sorter_str = if self.sorter.is_some() {
"Some(...)"
} else {
"None"
};
f.debug_struct("WalkDirOptions")
.field("follow_links", &self.follow_links)
.field("max_open", &self.max_open)
.field("min_depth", &self.min_depth)
.field("max_depth", &self.max_depth)
.field("sorter", &sorter_str)
.field("contents_first", &self.contents_first)
.field("same_file_system", &self.same_file_system)
.finish()
}
}
impl WalkDir {
pub fn new<P: AsRef<Path>>(root: P) -> Self {
WalkDir {
opts: WalkDirOptions {
follow_links: false,
max_open: 10,
min_depth: 0,
max_depth: ::std::usize::MAX,
sorter: None,
contents_first: false,
same_file_system: false,
},
root: root.as_ref().to_path_buf(),
}
}
pub fn min_depth(mut self, depth: usize) -> Self {
self.opts.min_depth = depth;
if self.opts.min_depth > self.opts.max_depth {
self.opts.min_depth = self.opts.max_depth;
}
self
}
pub fn max_depth(mut self, depth: usize) -> Self {
self.opts.max_depth = depth;
if self.opts.max_depth < self.opts.min_depth {
self.opts.max_depth = self.opts.min_depth;
}
self
}
pub fn follow_links(mut self, yes: bool) -> Self {
self.opts.follow_links = yes;
self
}
pub fn max_open(mut self, mut n: usize) -> Self {
if n == 0 {
n = 1;
}
self.opts.max_open = n;
self
}
pub fn sort_by<F>(mut self, cmp: F) -> Self
where F: FnMut(&DirEntry, &DirEntry) -> Ordering + Send + Sync + 'static
{
self.opts.sorter = Some(Box::new(cmp));
self
}
pub fn contents_first(mut self, yes: bool) -> Self {
self.opts.contents_first = yes;
self
}
pub fn same_file_system(mut self, yes: bool) -> Self {
self.opts.same_file_system = yes;
self
}
}
impl IntoIterator for WalkDir {
type Item = Result<DirEntry>;
type IntoIter = IntoIter;
fn into_iter(self) -> IntoIter {
IntoIter {
opts: self.opts,
start: Some(self.root),
stack_list: vec![],
stack_path: vec![],
oldest_opened: 0,
depth: 0,
deferred_dirs: vec![],
root_device: None,
}
}
}
#[derive(Debug)]
pub struct IntoIter {
opts: WalkDirOptions,
start: Option<PathBuf>,
stack_list: Vec<DirList>,
stack_path: Vec<Ancestor>,
oldest_opened: usize,
depth: usize,
deferred_dirs: Vec<DirEntry>,
root_device: Option<u64>,
}
#[derive(Debug)]
struct Ancestor {
path: PathBuf,
#[cfg(windows)]
handle: Handle,
}
impl Ancestor {
#[cfg(windows)]
fn new(dent: &DirEntry) -> io::Result<Ancestor> {
let handle = Handle::from_path(dent.path())?;
Ok(Ancestor {
path: dent.path().to_path_buf(),
handle: handle,
})
}
#[cfg(not(windows))]
fn new(dent: &DirEntry) -> io::Result<Ancestor> {
Ok(Ancestor { path: dent.path().to_path_buf() })
}
#[cfg(windows)]
fn is_same(&self, child: &Handle) -> io::Result<bool> {
Ok(child == &self.handle)
}
#[cfg(not(windows))]
fn is_same(&self, child: &Handle) -> io::Result<bool> {
Ok(child == &Handle::from_path(&self.path)?)
}
}
#[derive(Debug)]
enum DirList {
Opened { depth: usize, it: result::Result<ReadDir, Option<Error>> },
Closed(vec::IntoIter<Result<DirEntry>>),
}
impl Iterator for IntoIter {
type Item = Result<DirEntry>;
fn next(&mut self) -> Option<Result<DirEntry>> {
if let Some(start) = self.start.take() {
if self.opts.same_file_system {
let result = util::device_num(&start)
.map_err(|e| Error::from_path(0, start.clone(), e));
self.root_device = Some(itry!(result));
}
let dent = itry!(DirEntry::from_path(0, start, false));
if let Some(result) = self.handle_entry(dent) {
return Some(result);
}
}
while !self.stack_list.is_empty() {
self.depth = self.stack_list.len();
if let Some(dentry) = self.get_deferred_dir() {
return Some(Ok(dentry));
}
if self.depth > self.opts.max_depth {
self.pop();
continue;
}
let next = self.stack_list
.last_mut()
.expect("BUG: stack should be non-empty")
.next();
match next {
None => self.pop(),
Some(Err(err)) => return Some(Err(err)),
Some(Ok(dent)) => {
if let Some(result) = self.handle_entry(dent) {
return Some(result);
}
}
}
}
if self.opts.contents_first {
self.depth = self.stack_list.len();
if let Some(dentry) = self.get_deferred_dir() {
return Some(Ok(dentry));
}
}
None
}
}
impl IntoIter {
pub fn skip_current_dir(&mut self) {
if !self.stack_list.is_empty() {
self.pop();
}
}
pub fn filter_entry<P>(self, predicate: P) -> FilterEntry<Self, P>
where P: FnMut(&DirEntry) -> bool
{
FilterEntry { it: self, predicate: predicate }
}
fn handle_entry(
&mut self,
mut dent: DirEntry,
) -> Option<Result<DirEntry>> {
if self.opts.follow_links && dent.file_type().is_symlink() {
dent = itry!(self.follow(dent));
}
let is_normal_dir = !dent.file_type().is_symlink() && dent.is_dir();
if is_normal_dir {
if self.opts.same_file_system && dent.depth() > 0 {
if itry!(self.is_same_file_system(&dent)) {
itry!(self.push(&dent));
}
} else {
itry!(self.push(&dent));
}
} else if dent.depth() == 0 && dent.file_type().is_symlink() {
let md = itry!(fs::metadata(dent.path()).map_err(|err| {
Error::from_path(dent.depth(), dent.path().to_path_buf(), err)
}));
if md.file_type().is_dir() {
itry!(self.push(&dent));
}
}
if is_normal_dir && self.opts.contents_first {
self.deferred_dirs.push(dent);
None
} else if self.skippable() {
None
} else {
Some(Ok(dent))
}
}
fn get_deferred_dir(&mut self) -> Option<DirEntry> {
if self.opts.contents_first {
if self.depth < self.deferred_dirs.len() {
let deferred: DirEntry = self.deferred_dirs.pop()
.expect("BUG: deferred_dirs should be non-empty");
if !self.skippable() {
return Some(deferred);
}
}
}
None
}
fn push(&mut self, dent: &DirEntry) -> Result<()> {
let free = self.stack_list
.len()
.checked_sub(self.oldest_opened).unwrap();
if free == self.opts.max_open {
self.stack_list[self.oldest_opened].close();
}
let rd = fs::read_dir(dent.path()).map_err(|err| {
Some(Error::from_path(self.depth, dent.path().to_path_buf(), err))
});
let mut list = DirList::Opened { depth: self.depth, it: rd };
if let Some(ref mut cmp) = self.opts.sorter {
let mut entries: Vec<_> = list.collect();
entries.sort_by(|a, b| {
match (a, b) {
(&Ok(ref a), &Ok(ref b)) => cmp(a, b),
(&Err(_), &Err(_)) => Ordering::Equal,
(&Ok(_), &Err(_)) => Ordering::Greater,
(&Err(_), &Ok(_)) => Ordering::Less,
}
});
list = DirList::Closed(entries.into_iter());
}
if self.opts.follow_links {
let ancestor = Ancestor::new(&dent).map_err(|err| {
Error::from_io(self.depth, err)
})?;
self.stack_path.push(ancestor);
}
self.stack_list.push(list);
if free == self.opts.max_open {
self.oldest_opened = self.oldest_opened.checked_add(1).unwrap();
}
Ok(())
}
fn pop(&mut self) {
self.stack_list.pop().expect("BUG: cannot pop from empty stack");
if self.opts.follow_links {
self.stack_path.pop().expect("BUG: list/path stacks out of sync");
}
self.oldest_opened = min(self.oldest_opened, self.stack_list.len());
}
fn follow(&self, mut dent: DirEntry) -> Result<DirEntry> {
dent = DirEntry::from_path(
self.depth,
dent.path().to_path_buf(),
true,
)?;
if dent.is_dir() {
self.check_loop(dent.path())?;
}
Ok(dent)
}
fn check_loop<P: AsRef<Path>>(&self, child: P) -> Result<()> {
let hchild = Handle::from_path(&child).map_err(|err| {
Error::from_io(self.depth, err)
})?;
for ancestor in self.stack_path.iter().rev() {
let is_same = ancestor.is_same(&hchild).map_err(|err| {
Error::from_io(self.depth, err)
})?;
if is_same {
return Err(Error::from_loop(
self.depth, &ancestor.path, child.as_ref(),
));
}
}
Ok(())
}
fn is_same_file_system(&mut self, dent: &DirEntry) -> Result<bool> {
let dent_device = util::device_num(dent.path())
.map_err(|err| Error::from_entry(dent, err))?;
Ok(self.root_device
.map(|d| d == dent_device)
.expect("BUG: called is_same_file_system without root device"))
}
fn skippable(&self) -> bool {
self.depth < self.opts.min_depth || self.depth > self.opts.max_depth
}
}
impl DirList {
fn close(&mut self) {
if let DirList::Opened { .. } = *self {
*self = DirList::Closed(self.collect::<Vec<_>>().into_iter());
}
}
}
impl Iterator for DirList {
type Item = Result<DirEntry>;
#[inline(always)]
fn next(&mut self) -> Option<Result<DirEntry>> {
match *self {
DirList::Closed(ref mut it) => it.next(),
DirList::Opened { depth, ref mut it } => match *it {
Err(ref mut err) => err.take().map(Err),
Ok(ref mut rd) => rd.next().map(|r| match r {
Ok(r) => DirEntry::from_entry(depth + 1, &r),
Err(err) => Err(Error::from_io(depth + 1, err))
}),
}
}
}
}
#[derive(Debug)]
pub struct FilterEntry<I, P> {
it: I,
predicate: P,
}
impl<P> Iterator for FilterEntry<IntoIter, P>
where P: FnMut(&DirEntry) -> bool
{
type Item = Result<DirEntry>;
fn next(&mut self) -> Option<Result<DirEntry>> {
loop {
let dent = match self.it.next() {
None => return None,
Some(result) => itry!(result),
};
if !(self.predicate)(&dent) {
if dent.is_dir() {
self.it.skip_current_dir();
}
continue;
}
return Some(Ok(dent));
}
}
}
impl<P> FilterEntry<IntoIter, P> where P: FnMut(&DirEntry) -> bool {
pub fn filter_entry(self, predicate: P) -> FilterEntry<Self, P> {
FilterEntry { it: self, predicate: predicate }
}
pub fn skip_current_dir(&mut self) {
self.it.skip_current_dir();
}
}