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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
use crate::fs::feature::git::GitCache;
use crate::fs::fields::GitStatus;
use std::io::{self, Result as IOResult};
use std::fs;
use std::path::{Path, PathBuf};
use std::slice::Iter as SliceIter;
use log::*;
use crate::fs::File;
/// A **Dir** provides a cached list of the file paths in a directory that's
/// being listed.
///
/// This object gets passed to the Files themselves, in order for them to
/// check the existence of surrounding files, then highlight themselves
/// accordingly. (See `File#get_source_files`)
pub struct Dir {
/// A vector of the files that have been read from this directory.
contents: Vec<PathBuf>,
/// The path that was read.
pub path: PathBuf,
}
impl Dir {
/// Create a new Dir object filled with all the files in the directory
/// pointed to by the given path. Fails if the directory can’t be read, or
/// isn’t actually a directory, or if there’s an IO error that occurs at
/// any point.
///
/// The `read_dir` iterator doesn’t actually yield the `.` and `..`
/// entries, so if the user wants to see them, we’ll have to add them
/// ourselves after the files have been read.
pub fn read_dir(path: PathBuf) -> IOResult<Self> {
info!("Reading directory {:?}", &path);
let contents = fs::read_dir(&path)?
.map(|result| result.map(|entry| entry.path()))
.collect::<Result<_,_>>()?;
Ok(Self { contents, path })
}
/// Produce an iterator of IO results of trying to read all the files in
/// this directory.
pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig GitCache>, git_ignoring: bool) -> Files<'dir, 'ig> {
Files {
inner: self.contents.iter(),
dir: self,
dotfiles: dots.shows_dotfiles(),
dots: dots.dots(),
git, git_ignoring,
}
}
/// Whether this directory contains a file with the given path.
pub fn contains(&self, path: &Path) -> bool {
self.contents.iter().any(|p| p.as_path() == path)
}
/// Append a path onto the path specified by this directory.
pub fn join(&self, child: &Path) -> PathBuf {
self.path.join(child)
}
}
/// Iterator over reading the contents of a directory as `File` objects.
pub struct Files<'dir, 'ig> {
/// The internal iterator over the paths that have been read already.
inner: SliceIter<'dir, PathBuf>,
/// The directory that begat those paths.
dir: &'dir Dir,
/// Whether to include dotfiles in the list.
dotfiles: bool,
/// Whether the `.` or `..` directories should be produced first, before
/// any files have been listed.
dots: DotsNext,
git: Option<&'ig GitCache>,
git_ignoring: bool,
}
impl<'dir, 'ig> Files<'dir, 'ig> {
fn parent(&self) -> PathBuf {
// We can’t use `Path#parent` here because all it does is remove the
// last path component, which is no good for us if the path is
// relative. For example, while the parent of `/testcases/files` is
// `/testcases`, the parent of `.` is an empty path. Adding `..` on
// the end is the only way to get to the *actual* parent directory.
self.dir.path.join("..")
}
/// Go through the directory until we encounter a file we can list (which
/// varies depending on the dotfile visibility flag)
fn next_visible_file(&mut self) -> Option<Result<File<'dir>, (PathBuf, io::Error)>> {
loop {
if let Some(path) = self.inner.next() {
let filename = File::filename(path);
if !self.dotfiles && filename.starts_with('.') { continue }
if self.git_ignoring {
let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
if git_status.unstaged == GitStatus::Ignored {
continue;
}
}
return Some(File::from_args(path.clone(), self.dir, filename)
.map_err(|e| (path.clone(), e)))
}
else {
return None
}
}
}
}
/// The dot directories that need to be listed before actual files, if any.
/// If these aren’t being printed, then `FilesNext` is used to skip them.
enum DotsNext {
/// List the `.` directory next.
Dot,
/// List the `..` directory next.
DotDot,
/// Forget about the dot directories and just list files.
Files,
}
impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
type Item = Result<File<'dir>, (PathBuf, io::Error)>;
fn next(&mut self) -> Option<Self::Item> {
match self.dots {
DotsNext::Dot => {
self.dots = DotsNext::DotDot;
Some(File::new_aa_current(self.dir)
.map_err(|e| (Path::new(".").to_path_buf(), e)))
},
DotsNext::DotDot => {
self.dots = DotsNext::Files;
Some(File::new_aa_parent(self.parent(), self.dir)
.map_err(|e| (self.parent(), e)))
},
DotsNext::Files => {
self.next_visible_file()
},
}
}
}
/// Usually files in Unix use a leading dot to be hidden or visible, but two
/// entries in particular are "extra-hidden": `.` and `..`, which only become
/// visible after an extra `-a` option.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum DotFilter {
/// Shows files, dotfiles, and `.` and `..`.
DotfilesAndDots,
/// Show files and dotfiles, but hide `.` and `..`.
Dotfiles,
/// Just show files, hiding anything beginning with a dot.
JustFiles,
}
impl Default for DotFilter {
fn default() -> Self {
Self::JustFiles
}
}
impl DotFilter {
/// Whether this filter should show dotfiles in a listing.
fn shows_dotfiles(self) -> bool {
match self {
Self::JustFiles => false,
Self::Dotfiles => true,
Self::DotfilesAndDots => true,
}
}
/// Whether this filter should add dot directories to a listing.
fn dots(self) -> DotsNext {
match self {
Self::JustFiles => DotsNext::Files,
Self::Dotfiles => DotsNext::Files,
Self::DotfilesAndDots => DotsNext::Dot,
}
}
}
|