Add `--type empty`

Add a new `empty`/`e` type to search for empty files and/or directories.

To search for both empty files and directories, use one of the
following:

    fd --type empty
    fd -te

    fd --type empty --type file --type directory

To search for empty files, use

    fd --type empty --type file
    fd -te -tf

To search for empty directories, use

    fd --type empty --type directory
    fd -te -td

closes #273
This commit is contained in:
sharkdp 2018-08-19 17:05:04 +02:00
parent 50a2bab5cd
commit aa70c5a446
7 changed files with 68 additions and 3 deletions

View File

@ -86,6 +86,8 @@ directories
symbolic links
.IP "x, executable"
executable (files)
.IP "e, empty"
empty files or directories
.RE
.RS

View File

@ -87,6 +87,8 @@ pub fn build_app() -> App<'static, 'static> {
"symlink",
"x",
"executable",
"e",
"empty",
]).hide_possible_values(true),
).arg(
arg("extension")
@ -196,12 +198,13 @@ fn usage() -> HashMap<&'static str, Help> {
, "Limit the directory traversal to a given depth. By default, there is no limit \
on the search depth.");
doc!(h, "file-type"
, "Filter by type: file (f), directory (d), symlink (l),\nexecutable (x)"
, "Filter by type: file (f), directory (d), symlink (l),\nexecutable (x), empty (e)"
, "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\n \
'x' or 'executable': executables");
'x' or 'executable': executables\n \
'e' or 'empty': empty files or directories");
doc!(h, "extension"
, "Filter by file extension"
, "(Additionally) filter search results by their file extension. Multiple allowable file \

View File

@ -13,6 +13,8 @@ use std::io;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use ignore::DirEntry;
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
if path.is_absolute() {
Ok(path.to_path_buf())
@ -36,7 +38,7 @@ pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
Ok(path_buf)
}
// Path::is_dir() is not guarandteed to be intuitively correct for "." and ".."
// Path::is_dir() is not guaranteed to be intuitively correct for "." and ".."
// See: https://github.com/rust-lang/rust/issues/45302
pub fn is_dir(path: &Path) -> bool {
if path.file_name().is_some() {
@ -55,3 +57,21 @@ pub fn is_executable(md: &fs::Metadata) -> bool {
pub fn is_executable(_: &fs::Metadata) -> bool {
false
}
pub fn is_empty(entry: &DirEntry) -> bool {
if let Some(file_type) = entry.file_type() {
if file_type.is_dir() {
if let Ok(mut entries) = fs::read_dir(entry.path()) {
entries.next().is_none()
} else {
false
}
} else if file_type.is_file() {
entry.metadata().map(|m| m.len() == 0).unwrap_or(false)
} else {
false
}
} else {
false
}
}

View File

@ -28,6 +28,7 @@ pub struct FileTypes {
pub directories: bool,
pub symlinks: bool,
pub executables_only: bool,
pub empty_only: bool,
}
impl Default for FileTypes {
@ -37,6 +38,7 @@ impl Default for FileTypes {
directories: false,
symlinks: false,
executables_only: false,
empty_only: false,
}
}
}

View File

@ -182,9 +182,19 @@ fn main() {
file_types.executables_only = true;
file_types.files = true;
}
"e" | "empty" => {
file_types.empty_only = true;
}
_ => unreachable!(),
}
}
// If only 'empty' was specified, search for both files and directories:
if file_types.empty_only && !(file_types.files || file_types.directories) {
file_types.files = true;
file_types.directories = true;
}
file_types
}),
extensions: matches.values_of("extension").map(|exts| {

View File

@ -223,6 +223,7 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
.metadata()
.map(|m| fshelper::is_executable(&m))
.unwrap_or(false))
|| (file_types.empty_only && !fshelper::is_empty(&entry))
{
return ignore::WalkState::Continue;
} else if !(entry_type.is_file()

View File

@ -644,6 +644,33 @@ fn test_type_executable() {
);
}
/// Test `--type empty`
#[test]
fn test_type_empty() {
let te = TestEnv::new(&["dir_empty", "dir_nonempty"], &[]);
create_file_with_size(te.test_root().join("0_bytes.foo"), 0);
create_file_with_size(te.test_root().join("5_bytes.foo"), 5);
create_file_with_size(te.test_root().join("dir_nonempty").join("2_bytes.foo"), 2);
te.assert_output(
&["--type", "empty"],
"0_bytes.foo
dir_empty",
);
te.assert_output(
&["--type", "empty", "--type", "file", "--type", "directory"],
"0_bytes.foo
dir_empty",
);
te.assert_output(&["--type", "empty", "--type", "file"], "0_bytes.foo");
te.assert_output(&["--type", "empty", "--type", "directory"], "dir_empty");
}
/// File extension (--extension)
#[test]
fn test_extension() {