1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use std::mem::replace;
use std::fs::{read_dir, ReadDir, DirEntry};
use std::path::{Path, PathBuf};

use filter::name_matches;
use {Error, ScanDir};

/// Iterator over pairs of (DirEntry, String) where latter is the file name
///
/// Iterator walks over files/directories in the depth-first order and doesn't
/// sort items any way. Only utf-8 decodable directory names are visited.
/// Same rules applied to both files and directories. If you want more
/// control, you may either filter files in the iterator itself or walk over
/// directory tree and use `ScanDir::read()` over files in each directory.
///
/// The iterator ensures that whole file name is utf-8 decodable,
/// so you may use `.to_str().unwrap()` on `file_name()`, `extension()`
/// and other methods of `entry.path()` that touch file name. But parent
/// path components may contain undecodable characters.
pub struct Walker<'a> {
    settings: &'a ScanDir,
    errors: &'a mut Vec<Error>,
    cur: Option<(ReadDir, PathBuf)>,
    stack: Vec<(ReadDir, PathBuf)>,
}

pub fn new<'x>(settings: &'x ScanDir, errors: &'x mut Vec<Error>,
    path: &'x Path)
    -> Walker<'x>
{
    let iter = read_dir(path).map_err(|e| {
        errors.push(Error::Io(e, path.to_path_buf()));
    }).ok().map(|i| (i, path.to_path_buf()));
    Walker {
        settings: settings,
        errors: errors,
        cur: iter,
        stack: Vec::new(),
    }
}

impl<'a> Walker<'a> {
    /// Premature exit from directory that we are currently scanning
    ///
    /// This is useful for skipping certain directories. For directories
    /// which are emitted from the iterator the walker is considered to be
    /// inside the directory.
    ///
    /// You need to use `while let Some(..) = iter.next()` form (instead of
    /// `for .. in iter`) to iterate over the entries to satisfy borrow
    /// checker.
    ///
    /// Note: the method works even if you are scanning over files, but you
    /// should be careful when chosing when to exit (i.e. check the
    /// `entry.path().parent()` instead of the `name`)
    ///
    /// So to walk over all subdirs in current directory except "target"
    /// dir (anywhere), you may use this example:
    ///
    /// ```rust
    /// use scan_dir::ScanDir;
    ///
    /// ScanDir::dirs().walk(".", |mut iter| {
    ///     while let Some((entry, name)) = iter.next() {
    ///         if name == "target" {
    ///             iter.exit_current_dir();
    ///         } else {
    ///             println!("Dir {:?}", entry.path());
    ///         }
    ///     }
    /// }).unwrap();
    /// ```
    pub fn exit_current_dir(&mut self) {
        self.cur = self.stack.pop();
    }
}

impl<'a> Iterator for Walker<'a> {
    type Item = (DirEntry, String);
    fn next(&mut self) -> Option<(DirEntry, String)> {
        loop {
            if let Some((ref mut iter, ref mut path)) = self.cur {
                match iter.next() {
                    Some(Ok(entry)) => {
                        let osname = entry.file_name();
                        if let Ok(name) = osname.into_string() {
                            if !name_matches(self.settings, &name) {
                                continue;
                            }
                            let typ = match entry.file_type() {
                                Ok(typ) => typ,
                                Err(e) => {
                                    self.errors.push(
                                        Error::Io(e, entry.path()));
                                    continue;
                                }
                            };
                            if typ.is_dir() {
                                let new_path = entry.path();
                                match read_dir(&new_path) {
                                    Ok(new_iter) => {
                                        let old_iter = replace(iter, new_iter);
                                        let old_path = replace(path, new_path);
                                        self.stack.push((old_iter, old_path));

                                    }
                                    Err(e) => {
                                        self.errors.push(
                                            Error::Io(e, entry.path()));
                                    }
                                }
                                if !self.settings.skip_dirs {
                                    return Some((entry, name));
                                }
                            } else {
                                if !self.settings.skip_files {
                                    return Some((entry, name));
                                }
                            }
                        } else {
                            self.errors.push(
                                Error::Decode(entry.path()));
                        }
                    }
                    Some(Err(e)) => {
                        self.errors.push(
                            Error::Io(e, path.to_path_buf()));
                    }
                    None => {
                        if let Some((new_iter, new_path)) = self.stack.pop() {
                            *iter = new_iter;
                            *path = new_path;
                        } else {
                            break;
                        }
                    }
                }
            } else {
                return None
            }
        }
        // We can only clean self.cur here because of borrowing rules
        self.cur = None;
        return None;
    }
}