Added support for filtering by multiple filetypes and extensions (#205)

Closes #177 
Closes #199
This commit is contained in:
Thejaswi Kadur 2018-01-01 06:09:33 -05:00 committed by David Peter
parent 4d66c84109
commit faf934da4b
5 changed files with 62 additions and 34 deletions

View file

@ -70,6 +70,8 @@ pub fn build_app() -> App<'static, 'static> {
arg("file-type")
.long("type")
.short("t")
.multiple(true)
.number_of_values(1)
.takes_value(true)
.value_name("filetype")
.possible_values(&["f", "file", "d", "directory", "l", "symlink"])
@ -79,6 +81,8 @@ pub fn build_app() -> App<'static, 'static> {
arg("extension")
.long("extension")
.short("e")
.multiple(true)
.number_of_values(1)
.takes_value(true)
.value_name("ext"),
)
@ -171,13 +175,14 @@ fn usage() -> HashMap<&'static str, Help> {
on the search depth.");
doc!(h, "file-type"
, "Filter by type: f(ile), d(irectory), (sym)l(ink)"
, "Filter the search by type:\n \
, "Filter the search by type (multiple allowable filetypes can be specified):\n \
'f' or 'file': regular files\n \
'd' or 'directory': directories\n \
'l' or 'symlink': symbolic links");
doc!(h, "extension"
, "Filter by file extension"
, "(Additionally) filter search results by their file extension.");
, "(Additionally) filter search results by their file extension. Multiple allowable file \
extensions can be specified.");
doc!(h, "exec"
, "Execute a command for each search result"
, "Execute a command for each search result.\n\

View file

@ -6,6 +6,7 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::collections::HashSet;
use std::process;
use std::time;
use std::io::Write;
@ -58,12 +59,12 @@ pub struct FdOptions {
pub ls_colors: Option<LsColors>,
/// The type of file to search for. All files other than the specified type will be ignored.
pub file_type: FileType,
pub file_types: HashSet<FileType>,
/// The extension to search for. Only entries matching the extension will be included.
///
/// The value (if present) will be a lowercase string without leading dots.
pub extension: Option<String>,
pub extensions: Option<HashSet<String>>,
/// If a value is supplied, each item found will be used to generate and execute commands.
pub command: Option<CommandTemplate>,

View file

@ -136,15 +136,20 @@ fn main() {
.and_then(|n| u64::from_str_radix(n, 10).ok())
.map(time::Duration::from_millis),
ls_colors,
file_type: match matches.value_of("file-type") {
Some("f") | Some("file") => FileType::RegularFile,
Some("d") |
Some("directory") => FileType::Directory,
Some("l") | Some("symlink") => FileType::SymLink,
_ => FileType::Any,
file_types: match matches.values_of("file-type") {
None => vec![FileType::RegularFile,
FileType::Directory,
FileType::SymLink]
.into_iter().collect(),
Some(values) => values.map(|value| match value {
"f" | "file" => FileType::RegularFile,
"d" | "directory" => FileType::Directory,
"l" | "symlink" => FileType::SymLink,
_ => FileType::RegularFile,
}).collect()
},
extension: matches.value_of("extension").map(|e| {
e.trim_left_matches('.').to_lowercase()
extensions: matches.values_of("extension").map(|exts| {
exts.map(|e| e.trim_left_matches('.').to_lowercase()).collect()
}),
command,
exclude_patterns: matches

View file

@ -34,10 +34,9 @@ enum ReceiverMode {
Streaming,
}
/// The type of file to search for.
#[derive(Copy, Clone)]
/// The types of file to search for.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum FileType {
Any,
RegularFile,
Directory,
SymLink,
@ -191,31 +190,21 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
}
// Filter out unwanted file types.
match config.file_type {
FileType::Any => (),
FileType::RegularFile => {
if entry.file_type().map_or(true, |ft| !ft.is_file()) {
return ignore::WalkState::Continue;
}
}
FileType::Directory => {
if entry.file_type().map_or(true, |ft| !ft.is_dir()) {
return ignore::WalkState::Continue;
}
}
FileType::SymLink => {
if entry.file_type().map_or(true, |ft| !ft.is_symlink()) {
return ignore::WalkState::Continue;
}
}
if (entry.file_type().map_or(false, |ft| ft.is_file()) &&
!config.file_types.contains(&FileType::RegularFile)) ||
(entry.file_type().map_or(false, |ft| ft.is_dir()) &&
!config.file_types.contains(&FileType::Directory)) ||
(entry.file_type().map_or(false, |ft| ft.is_symlink()) &&
!config.file_types.contains(&FileType::SymLink)) {
return ignore::WalkState::Continue;
}
// Filter out unwanted extensions.
if let Some(ref filter_ext) = config.extension {
if let Some(ref filter_exts) = config.extensions {
let entry_ext = entry_path.extension().map(
|e| e.to_string_lossy().to_lowercase(),
);
if entry_ext.map_or(true, |ext| ext != *filter_ext) {
if entry_ext.map_or(true, |ext| !filter_exts.contains(&ext)) {
return ignore::WalkState::Continue;
}
}

View file

@ -470,6 +470,11 @@ fn test_type() {
one/two/three/d.foo",
);
te.assert_output(
&["--type", "f", "e1"],
"e1 e2",
);
te.assert_output(
&["--type", "d"],
"one
@ -478,6 +483,15 @@ fn test_type() {
one/two/three/directory_foo",
);
te.assert_output(
&["--type", "d", "--type", "l"],
"one
one/two
one/two/three
one/two/three/directory_foo
symlink",
);
te.assert_output(&["--type", "l"], "symlink");
}
@ -502,6 +516,20 @@ fn test_extension() {
one/two/three/d.foo",
);
te.assert_output(
&["--extension", ".foo", "--extension", "foo2"],
"a.foo
one/b.foo
one/two/c.foo
one/two/three/d.foo
one/two/C.Foo2",
);
te.assert_output(
&["--extension", ".foo", "a"],
"a.foo",
);
te.assert_output(&["--extension", "foo2"], "one/two/C.Foo2");
}