summaryrefslogtreecommitdiffstats
path: root/src/walk.rs
blob: f661c4cfa3314828d602c3eb8c7f8dfdf2ab6619 (plain)
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
/*!
The walk module implements a recursive directory iterator (using the `walkdir`)
crate that can efficiently skip and ignore files and directories specified in
a user's ignore patterns.
*/

use walkdir::{self, DirEntry, WalkDir, WalkDirIterator};

use ignore::Ignore;

/// Iter is a recursive directory iterator over file paths in a directory.
/// Only file paths should be searched are yielded.
pub struct Iter {
    ig: Ignore,
    it: WalkEventIter,
}

impl Iter {
    /// Create a new recursive directory iterator using the ignore patterns
    /// and walkdir iterator given.
    pub fn new(ig: Ignore, wd: WalkDir) -> Iter {
        Iter {
            ig: ig,
            it: WalkEventIter::from(wd),
        }
    }

    /// Returns true if this entry should be skipped.
    #[inline(always)]
    fn skip_entry(&self, ent: &DirEntry) -> bool {
        if ent.depth() == 0 {
            // Never skip the root directory.
            return false;
        }
        if self.ig.ignored(ent.path(), ent.file_type().is_dir()) {
            return true;
        }
        false
    }
}

impl Iterator for Iter {
    type Item = DirEntry;

    #[inline(always)]
    fn next(&mut self) -> Option<DirEntry> {
        while let Some(ev) = self.it.next() {
            match ev {
                Err(err) => {
                    eprintln!("{}", err);
                }
                Ok(WalkEvent::Exit) => {
                    self.ig.pop();
                }
                Ok(WalkEvent::Dir(ent)) => {
                    if self.skip_entry(&ent) {
                        self.it.it.skip_current_dir();
                        // Still need to push this on the stack because we'll
                        // get a WalkEvent::Exit event for this dir. We don't
                        // care if it errors though.
                        let _ = self.ig.push(ent.path());
                        continue;
                    }
                    if let Err(err) = self.ig.push(ent.path()) {
                        eprintln!("{}", err);
                        self.it.it.skip_current_dir();
                        continue;
                    }
                }
                Ok(WalkEvent::File(ent)) => {
                    if self.skip_entry(&ent) {
                        continue;
                    }
                    // If this isn't actually a file (e.g., a symlink), then
                    // skip it.
                    if !ent.file_type().is_file() {
                        continue;
                    }
                    return Some(ent);
                }
            }
        }
        None
    }
}

/// WalkEventIter transforms a WalkDir iterator into an iterator that more
/// accurately describes the directory tree. Namely, it emits events that are
/// one of three types: directory, file or "exit." An "exit" event means that
/// the entire contents of a directory have been enumerated.
struct WalkEventIter {
    depth: usize,
    it: walkdir::Iter,
    next: Option<Result<DirEntry, walkdir::Error>>,
}

#[derive(Debug)]
enum WalkEvent {
    Dir(DirEntry),
    File(DirEntry),
    Exit,
}

impl From<WalkDir> for WalkEventIter {
    fn from(it: WalkDir) -> WalkEventIter {
        WalkEventIter { depth: 0, it: it.into_iter(), next: None }
    }
}

impl Iterator for WalkEventIter {
    type Item = walkdir::Result<WalkEvent>;

    #[inline(always)]
    fn next(&mut self) -> Option<walkdir::Result<WalkEvent>> {
        let dent = self.next.take().or_else(|| self.it.next());
        let depth = match dent {
            None => 0,
            Some(Ok(ref dent)) => dent.depth(),
            Some(Err(ref err)) => err.depth(),
        };
        if depth < self.depth {
            self.depth -= 1;
            self.next = dent;
            return Some(Ok(WalkEvent::Exit));
        }
        self.depth = depth;
        match dent {
            None => None,
            Some(Err(err)) => Some(Err(err)),
            Some(Ok(dent)) => {
                if dent.file_type().is_dir() {
                    self.depth += 1;
                    Some(Ok(WalkEvent::Dir(dent)))
                } else {
                    Some(Ok(WalkEvent::File(dent)))
                }
            }
        }
    }
}