#[cfg(windows)] extern crate kernel32;
#[cfg(windows)] extern crate winapi;
#[cfg(test)] extern crate quickcheck;
#[cfg(test)] extern crate rand;
use std::cmp::{Ordering, min};
use std::error;
use std::fmt;
use std::fs::{self, FileType, ReadDir};
use std::io;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::result;
use std::vec;
pub use same_file::is_same_file;
mod same_file;
#[cfg(test)] mod tests;
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>;
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(&OsString,&OsString) -> Ordering + 'static>>,
}
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,
},
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(&OsString, &OsString) -> Ordering + 'static {
self.opts.sorter = Some(Box::new(cmp));
self
}
}
impl IntoIterator for WalkDir {
type Item = Result<DirEntry>;
type IntoIter = Iter;
fn into_iter(self) -> Iter {
Iter {
opts: self.opts,
start: Some(self.root),
stack_list: vec![],
stack_path: vec![],
oldest_opened: 0,
depth: 0,
}
}
}
pub trait WalkDirIterator: Iterator {
fn skip_current_dir(&mut self);
fn filter_entry<P>(self, predicate: P) -> IterFilterEntry<Self, P>
where Self: Sized, P: FnMut(&DirEntry) -> bool {
IterFilterEntry { it: self, predicate: predicate }
}
}
pub struct Iter {
opts: WalkDirOptions,
start: Option<PathBuf>,
stack_list: Vec<DirList>,
stack_path: Vec<PathBuf>,
oldest_opened: usize,
depth: usize,
}
enum DirList {
Opened { depth: usize, it: result::Result<ReadDir, Option<Error>> },
Closed(vec::IntoIter<Result<fs::DirEntry>>),
}
pub struct DirEntry {
path: PathBuf,
ty: FileType,
follow_link: bool,
depth: usize,
}
impl Iterator for Iter {
type Item = Result<DirEntry>;
fn next(&mut self) -> Option<Result<DirEntry>> {
if let Some(start) = self.start.take() {
let dent = itry!(DirEntry::from_path(0, start));
if let Some(result) = self.handle_entry(dent) {
return Some(result);
}
}
while !self.stack_list.is_empty() {
self.depth = self.stack_list.len();
if self.depth > self.opts.max_depth {
self.pop();
continue;
}
match self.stack_list.last_mut().unwrap().next() {
None => self.pop(),
Some(Err(err)) => return Some(Err(err)),
Some(Ok(dent)) => {
let dent = itry!(DirEntry::from_entry(self.depth, &dent));
if let Some(result) = self.handle_entry(dent) {
return Some(result);
}
}
}
}
None
}
}
impl WalkDirIterator for Iter {
fn skip_current_dir(&mut self) {
if !self.stack_list.is_empty() {
self.stack_list.pop();
}
if !self.stack_path.is_empty() {
self.stack_path.pop();
}
}
}
impl Iter {
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));
}
if dent.file_type().is_dir() {
self.push(&dent);
}
if self.skippable() { None } else { Some(Ok(dent)) }
}
fn push(&mut self, dent: &DirEntry) {
if self.stack_list.len() - self.oldest_opened == self.opts.max_open {
self.stack_list[self.oldest_opened].close();
self.oldest_opened = self.oldest_opened.checked_add(1).unwrap();
}
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.file_name(), &b.file_name())
}
(&Err(_), &Err(_)) => Ordering::Equal,
(&Ok(_), &Err(_)) => Ordering::Greater,
(&Err(_), &Ok(_)) => Ordering::Less,
}
});
list = DirList::Closed(entries.into_iter());
}
self.stack_list.push(list);
if self.opts.follow_links {
self.stack_path.push(dent.path().to_path_buf());
}
}
fn pop(&mut self) {
self.stack_list.pop().expect("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 = try!(DirEntry::from_link(self.depth,
dent.path().to_path_buf()));
if dent.file_type().is_dir() {
try!(self.check_loop(dent.path()));
}
Ok(dent)
}
fn check_loop<P: AsRef<Path>>(&self, child: P) -> Result<()> {
for ancestor in self.stack_path.iter().rev() {
let same = try!(is_same_file(ancestor, &child).map_err(|err| {
Error::from_io(self.depth, err)
}));
if same {
return Err(Error {
depth: self.depth,
inner: ErrorInner::Loop {
ancestor: ancestor.to_path_buf(),
child: child.as_ref().to_path_buf(),
},
});
}
}
Ok(())
}
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<fs::DirEntry>;
#[inline(always)]
fn next(&mut self) -> Option<Result<fs::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| r.map_err(|err| {
Error::from_io(depth + 1, err)
})),
}
}
}
}
impl DirEntry {
pub fn path(&self) -> &Path {
&self.path
}
pub fn path_is_symbolic_link(&self) -> bool {
self.ty.is_symlink() || self.follow_link
}
pub fn metadata(&self) -> Result<fs::Metadata> {
if self.follow_link {
fs::metadata(&self.path)
} else {
fs::symlink_metadata(&self.path)
}.map_err(|err| Error::from_entry(self, err))
}
pub fn file_type(&self) -> fs::FileType {
self.ty
}
pub fn file_name(&self) -> &OsStr {
self.path.file_name().unwrap_or_else(|| self.path.as_os_str())
}
pub fn depth(&self) -> usize {
self.depth
}
fn from_entry(depth: usize, ent: &fs::DirEntry) -> Result<DirEntry> {
let ty = try!(ent.file_type().map_err(|err| {
Error::from_path(depth, ent.path(), err)
}));
Ok(DirEntry {
path: ent.path(),
ty: ty,
follow_link: false,
depth: depth,
})
}
fn from_link(depth: usize, pb: PathBuf) -> Result<DirEntry> {
let md = try!(fs::metadata(&pb).map_err(|err| {
Error::from_path(depth, pb.clone(), err)
}));
Ok(DirEntry {
path: pb,
ty: md.file_type(),
follow_link: true,
depth: depth,
})
}
fn from_path(depth: usize, pb: PathBuf) -> Result<DirEntry> {
let md = try!(fs::symlink_metadata(&pb).map_err(|err| {
Error::from_path(depth, pb.clone(), err)
}));
Ok(DirEntry {
path: pb,
ty: md.file_type(),
follow_link: false,
depth: depth,
})
}
}
impl Clone for DirEntry {
fn clone(&self) -> DirEntry {
DirEntry {
path: self.path.clone(),
ty: self.ty,
follow_link: self.follow_link,
depth: self.depth,
}
}
}
impl fmt::Debug for DirEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DirEntry({:?})", self.path)
}
}
pub struct IterFilterEntry<I, P> {
it: I,
predicate: P,
}
impl<I, P> Iterator for IterFilterEntry<I, P>
where I: WalkDirIterator<Item=Result<DirEntry>>,
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.file_type().is_dir() {
self.it.skip_current_dir();
}
continue;
}
return Some(Ok(dent));
}
}
}
impl<I, P> WalkDirIterator for IterFilterEntry<I, P>
where I: WalkDirIterator<Item=Result<DirEntry>>,
P: FnMut(&DirEntry) -> bool {
fn skip_current_dir(&mut self) {
self.it.skip_current_dir();
}
}
#[derive(Debug)]
pub struct Error {
depth: usize,
inner: ErrorInner,
}
#[derive(Debug)]
enum ErrorInner {
Io { path: Option<PathBuf>, err: io::Error },
Loop { ancestor: PathBuf, child: PathBuf },
}
impl Error {
pub fn path(&self) -> Option<&Path> {
match self.inner {
ErrorInner::Io { path: None, .. } => None,
ErrorInner::Io { path: Some(ref path), .. } => Some(path),
ErrorInner::Loop { ref child, .. } => Some(child),
}
}
pub fn loop_ancestor(&self) -> Option<&Path> {
match self.inner {
ErrorInner::Loop { ref ancestor, .. } => Some(ancestor),
_ => None,
}
}
pub fn depth(&self) -> usize {
self.depth
}
fn from_path(depth: usize, pb: PathBuf, err: io::Error) -> Self {
Error {
depth: depth,
inner: ErrorInner::Io { path: Some(pb), err: err },
}
}
fn from_entry(dent: &DirEntry, err: io::Error) -> Self {
Error {
depth: dent.depth,
inner: ErrorInner::Io {
path: Some(dent.path().to_path_buf()),
err: err,
},
}
}
fn from_io(depth: usize, err: io::Error) -> Self {
Error {
depth: depth,
inner: ErrorInner::Io { path: None, err: err },
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
match self.inner {
ErrorInner::Io { ref err, .. } => err.description(),
ErrorInner::Loop { .. } => "file system loop found",
}
}
fn cause(&self) -> Option<&error::Error> {
match self.inner {
ErrorInner::Io { ref err, .. } => Some(err),
ErrorInner::Loop { .. } => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.inner {
ErrorInner::Io { path: None, ref err } => {
err.fmt(f)
}
ErrorInner::Io { path: Some(ref path), ref err } => {
write!(f, "IO error for operation on {}: {}",
path.display(), err)
}
ErrorInner::Loop { ref ancestor, ref child } => {
write!(f, "File system loop found: \
{} points to an ancestor {}",
child.display(), ancestor.display())
}
}
}
}
impl From<Error> for io::Error {
fn from(err: Error) -> io::Error {
match err {
Error { inner: ErrorInner::Io { err, .. }, .. } => err,
err @ Error { inner: ErrorInner::Loop { .. }, .. } => {
io::Error::new(io::ErrorKind::Other, err)
}
}
}
}