mirror of https://github.com/sharkdp/fd.git
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:
parent
50a2bab5cd
commit
aa70c5a446
2
doc/fd.1
2
doc/fd.1
|
@ -86,6 +86,8 @@ directories
|
|||
symbolic links
|
||||
.IP "x, executable"
|
||||
executable (files)
|
||||
.IP "e, empty"
|
||||
empty files or directories
|
||||
.RE
|
||||
|
||||
.RS
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -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| {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue