2017-10-04 14:31:08 +02:00
|
|
|
mod app;
|
2020-04-03 21:24:11 +02:00
|
|
|
mod error;
|
2017-10-14 18:04:11 +02:00
|
|
|
mod exec;
|
2018-10-03 11:25:16 +02:00
|
|
|
mod exit_codes;
|
2020-04-03 12:04:47 +02:00
|
|
|
mod filesystem;
|
2020-04-03 11:34:34 +02:00
|
|
|
mod filetypes;
|
2020-04-03 11:36:54 +02:00
|
|
|
mod filter;
|
2020-04-03 11:39:32 +02:00
|
|
|
mod options;
|
2017-10-10 08:01:17 +02:00
|
|
|
mod output;
|
2020-04-03 11:44:45 +02:00
|
|
|
mod regex_helper;
|
2017-10-10 08:01:17 +02:00
|
|
|
mod walk;
|
2017-05-12 11:50:03 +02:00
|
|
|
|
|
|
|
use std::env;
|
2017-10-14 19:57:15 +02:00
|
|
|
use std::path::{Path, PathBuf};
|
2019-09-13 22:26:27 +02:00
|
|
|
use std::process;
|
2017-06-11 22:43:30 +02:00
|
|
|
use std::sync::Arc;
|
2017-06-16 10:37:40 +02:00
|
|
|
use std::time;
|
2017-05-12 11:50:03 +02:00
|
|
|
|
2020-04-03 20:55:14 +02:00
|
|
|
use anyhow::{anyhow, Context, Result};
|
2017-06-10 17:30:48 +02:00
|
|
|
use atty::Stream;
|
2020-04-15 15:47:17 +02:00
|
|
|
use globset::GlobBuilder;
|
2018-12-09 14:56:05 +01:00
|
|
|
use lscolors::LsColors;
|
2021-08-08 14:13:32 +02:00
|
|
|
use normpath::PathExt;
|
2021-08-10 09:46:12 +02:00
|
|
|
use regex::bytes::{RegexBuilder, RegexSetBuilder};
|
2017-05-12 13:02:20 +02:00
|
|
|
|
2020-05-13 13:26:47 +02:00
|
|
|
use crate::error::print_error;
|
2019-01-05 18:17:22 +01:00
|
|
|
use crate::exec::CommandTemplate;
|
2020-04-03 20:55:14 +02:00
|
|
|
use crate::exit_codes::ExitCode;
|
2020-04-03 11:34:34 +02:00
|
|
|
use crate::filetypes::FileTypes;
|
2020-05-01 14:50:12 +02:00
|
|
|
#[cfg(unix)]
|
|
|
|
use crate::filter::OwnerFilter;
|
2020-04-03 11:36:54 +02:00
|
|
|
use crate::filter::{SizeFilter, TimeFilter};
|
2020-04-03 11:39:32 +02:00
|
|
|
use crate::options::Options;
|
2020-12-06 15:57:33 +01:00
|
|
|
use crate::regex_helper::{pattern_has_uppercase_char, pattern_matches_strings_with_leading_dot};
|
2017-05-12 11:50:03 +02:00
|
|
|
|
2019-09-15 18:13:29 +02:00
|
|
|
// We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481
|
2020-05-19 16:07:12 +02:00
|
|
|
// FIXME: re-enable jemalloc on macOS, see comment in Cargo.toml file for more infos
|
2020-10-25 21:18:53 +01:00
|
|
|
#[cfg(all(
|
|
|
|
not(windows),
|
|
|
|
not(target_os = "android"),
|
|
|
|
not(target_os = "macos"),
|
|
|
|
not(target_env = "musl")
|
|
|
|
))]
|
2019-09-15 17:47:36 +02:00
|
|
|
#[global_allocator]
|
|
|
|
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
|
|
|
|
2020-10-30 15:41:29 +01:00
|
|
|
// vivid --color-mode 8-bit generate molokai
|
|
|
|
const DEFAULT_LS_COLORS: &str = "
|
|
|
|
ow=0:or=0;38;5;16;48;5;203:no=0:ex=1;38;5;203:cd=0;38;5;203;48;5;236:mi=0;38;5;16;48;5;203:*~=0;38;5;243:st=0:pi=0;38;5;16;48;5;81:fi=0:di=0;38;5;81:so=0;38;5;16;48;5;203:bd=0;38;5;81;48;5;236:tw=0:ln=0;38;5;203:*.m=0;38;5;48:*.o=0;38;5;243:*.z=4;38;5;203:*.a=1;38;5;203:*.r=0;38;5;48:*.c=0;38;5;48:*.d=0;38;5;48:*.t=0;38;5;48:*.h=0;38;5;48:*.p=0;38;5;48:*.cc=0;38;5;48:*.ll=0;38;5;48:*.jl=0;38;5;48:*css=0;38;5;48:*.md=0;38;5;185:*.gz=4;38;5;203:*.nb=0;38;5;48:*.mn=0;38;5;48:*.go=0;38;5;48:*.xz=4;38;5;203:*.so=1;38;5;203:*.rb=0;38;5;48:*.pm=0;38;5;48:*.bc=0;38;5;243:*.py=0;38;5;48:*.as=0;38;5;48:*.pl=0;38;5;48:*.rs=0;38;5;48:*.sh=0;38;5;48:*.7z=4;38;5;203:*.ps=0;38;5;186:*.cs=0;38;5;48:*.el=0;38;5;48:*.rm=0;38;5;208:*.hs=0;38;5;48:*.td=0;38;5;48:*.ui=0;38;5;149:*.ex=0;38;5;48:*.js=0;38;5;48:*.cp=0;38;5;48:*.cr=0;38;5;48:*.la=0;38;5;243:*.kt=0;38;5;48:*.ml=0;38;5;48:*.vb=0;38;5;48:*.gv=0;38;5;48:*.lo=0;38;5;243:*.hi=0;38;5;243:*.ts=0;38;5;48:*.ko=1;38;5;203:*.hh=0;38;5;48:*.pp=0;38;5;48:*.di=0;38;5;48:*.bz=4;38;5;203:*.fs=0;38;5;48:*.png=0;38;5;208:*.zsh=0;38;5;48:*.mpg=0;38;5;208:*.pid=0;38;5;243:*.xmp=0;38;5;149:*.iso=4;38;5;203:*.m4v=0;38;5;208:*.dot=0;38;5;48:*.ods=0;38;5;186:*.inc=0;38;5;48:*.sxw=0;38;5;186:*.aif=0;38;5;208:*.git=0;38;5;243:*.gvy=0;38;5;48:*.tbz=4;38;5;203:*.log=0;38;5;243:*.txt=0;38;5;185:*.ico=0;38;5;208:*.csx=0;38;5;48:*.vob=0;38;5;208:*.pgm=0;38;5;208:*.pps=0;38;5;186:*.ics=0;38;5;186:*.img=4;38;5;203:*.fon=0;38;5;208:*.hpp=0;38;5;48:*.bsh=0;38;5;48:*.sql=0;38;5;48:*TODO=1:*.php=0;38;5;48:*.pkg=4;38;5;203:*.ps1=0;38;5;48:*.csv=0;38;5;185:*.ilg=0;38;5;243:*.ini=0;38;5;149:*.pyc=0;38;5;243:*.psd=0;38;5;208:*.htc=0;38;5;48:*.swp=0;38;5;243:*.mli=0;38;5;48:*hgrc=0;38;5;149:*.bst=0;38;5;149:*.ipp=0;38;5;48:*.fsi=0;38;5;48:*.tcl=0;38;5;48:*.exs=0;38;5;48:*.out=0;38;5;243:*.jar=4;38;5;203:*.xls=0;38;5;186:*.ppm=0;38;5;208:*.apk=4;38;5;203:*.aux=0;38;5;243:*.rpm=4;38;5;203:*.dll=1;38;5;203:*.eps=0;38;5;208:*.exe=1;38;5;203:*.doc=0;38;5;186:*.wma=0;38;5;208:*.deb=4;38;5;203:*.pod=0;38;5;48:*.ind=0;38;5;243:*.nix=0;38;5;149:*.lua=0;38;5;48:*.epp=0;38;5;48:*.dpr=0;38;5;48:*.htm=0;38;5;185:*.ogg=0;38;5;208:*.bin=4;38;5;203:*.otf=0;38;5;208:*.yml=0;38;5;149:*.pro=0;38;5;149:*.cxx=0;38;5;48:*.tex=0;38;5;48:*.fnt=0;38;5;208:*.erl=0;38;5;48:*.sty=0;38;5;243:*.bag=4;38;5;203:*.rst=0;38;5;185:*.pdf=0;38;5;186:*.pbm=0;38;5;208:*.xcf=0;38;5;208:*.clj=0;38;5;48:*.gif=0;38;5;208:*.rar=4;38;5;203:*.elm=0;38;5;48:*.bib=0;38;5;149:*.tsx=0;38;5;48:*.dmg=4;38;5;203:*.tmp=0;38;5;243:*.bcf=0;38;5;243:*.mkv=0;38;5;208:*.svg=0;38;5;208:*.cpp=0;38;5;48:*.vim=0;38;5;48:*.bmp=0;38;5;208:*.ltx=0;38;5;48:*.fls=0;38;5;243:*.flv=0;38;5;208:*.wav=0;38;5;208:*.m4a=0;38;5;208:*.mid=0;38;5;208:*.hxx=0;38;5;48:*.pas=0;38;5;48:*.wmv=0;38;5;208:*.tif=0;38;5;208:*.kex=0;38;5;186:*.mp4=0;38;5;208:*.bak=0;38;5;243:*.xlr=0;38;5;186:*.dox=0;38;5;149:*.swf=0;38;5;208:*.tar=4;38;5;203:*.tgz=4;38;5;203:*.cfg=0;38;5;149:*.xml=0;
|
|
|
|
38;5;185:*.jpg=0;38;5;208:*.mir=0;38;5;48:*.sxi=0;38;5;186:*.bz2=4;38;5;203:*.odt=0;38;5;186:*.mov=0;38;5;208:*.toc=0;38;5;243:*.bat=1;38;5;203:*.asa=0;38;5;48:*.awk=0;38;5;48:*.sbt=0;38;5;48:*.vcd=4;38;5;203:*.kts=0;38;5;48:*.arj=4;38;5;203:*.blg=0;38;5;243:*.c++=0;38;5;48:*.odp=0;38;5;186:*.bbl=0;38;5;243:*.idx=0;38;5;243:*.com=1;38;5;203:*.mp3=0;38;5;208:*.avi=0;38;5;208:*.def=0;38;5;48:*.cgi=0;38;5;48:*.zip=4;38;5;203:*.ttf=0;38;5;208:*.ppt=0;38;5;186:*.tml=0;38;5;149:*.fsx=0;38;5;48:*.h++=0;38;5;48:*.rtf=0;38;5;186:*.inl=0;38;5;48:*.yaml=0;38;5;149:*.html=0;38;5;185:*.mpeg=0;38;5;208:*.java=0;38;5;48:*.hgrc=0;38;5;149:*.orig=0;38;5;243:*.conf=0;38;5;149:*.dart=0;38;5;48:*.psm1=0;38;5;48:*.rlib=0;38;5;243:*.fish=0;38;5;48:*.bash=0;38;5;48:*.make=0;38;5;149:*.docx=0;38;5;186:*.json=0;38;5;149:*.psd1=0;38;5;48:*.lisp=0;38;5;48:*.tbz2=4;38;5;203:*.diff=0;38;5;48:*.epub=0;38;5;186:*.xlsx=0;38;5;186:*.pptx=0;38;5;186:*.toml=0;38;5;149:*.h264=0;38;5;208:*.purs=0;38;5;48:*.flac=0;38;5;208:*.tiff=0;38;5;208:*.jpeg=0;38;5;208:*.lock=0;38;5;243:*.less=0;38;5;48:*.dyn_o=0;38;5;243:*.scala=0;38;5;48:*.mdown=0;38;5;185:*.shtml=0;38;5;185:*.class=0;38;5;243:*.cache=0;38;5;243:*.cmake=0;38;5;149:*passwd=0;38;5;149:*.swift=0;38;5;48:*shadow=0;38;5;149:*.xhtml=0;38;5;185:*.patch=0;38;5;48:*.cabal=0;38;5;48:*README=0;38;5;16;48;5;186:*.toast=4;38;5;203:*.ipynb=0;38;5;48:*COPYING=0;38;5;249:*.gradle=0;38;5;48:*.matlab=0;38;5;48:*.config=0;38;5;149:*LICENSE=0;38;5;249:*.dyn_hi=0;38;5;243:*.flake8=0;38;5;149:*.groovy=0;38;5;48:*INSTALL=0;38;5;16;48;5;186:*TODO.md=1:*.ignore=0;38;5;149:*Doxyfile=0;38;5;149:*TODO.txt=1:*setup.py=0;38;5;149:*Makefile=0;38;5;149:*.gemspec=0;38;5;149:*.desktop=0;38;5;149:*.rgignore=0;38;5;149:*.markdown=0;38;5;185:*COPYRIGHT=0;38;5;249:*configure=0;38;5;149:*.DS_Store=0;38;5;243:*.kdevelop=0;38;5;149:*.fdignore=0;38;5;149:*README.md=0;38;5;16;48;5;186:*.cmake.in=0;38;5;149:*SConscript=0;38;5;149:*CODEOWNERS=0;38;5;149:*.localized=0;38;5;243:*.gitignore=0;38;5;149:*Dockerfile=0;38;5;149:*.gitconfig=0;38;5;149:*INSTALL.md=0;38;5;16;48;5;186:*README.txt=0;38;5;16;48;5;186:*SConstruct=0;38;5;149:*.scons_opt=0;38;5;243:*.travis.yml=0;38;5;186:*.gitmodules=0;38;5;149:*.synctex.gz=0;38;5;243:*LICENSE-MIT=0;38;5;249:*MANIFEST.in=0;38;5;149:*Makefile.in=0;38;5;243:*Makefile.am=0;38;5;149:*INSTALL.txt=0;38;5;16;48;5;186:*configure.ac=0;38;5;149:*.applescript=0;38;5;48:*appveyor.yml=0;38;5;186:*.fdb_latexmk=0;38;5;243:*CONTRIBUTORS=0;38;5;16;48;5;186:*.clang-format=0;38;5;149:*LICENSE-APACHE=0;38;5;249:*CMakeLists.txt=0;38;5;149:*CMakeCache.txt=0;38;5;243:*.gitattributes=0;38;5;149:*CONTRIBUTORS.md=0;38;5;16;48;5;186:*.sconsign.dblite=0;38;5;243:*requirements.txt=0;38;5;149:*CONTRIBUTORS.txt=0;38;5;16;48;5;186:*package-lock.json=0;38;5;243:*.CFUserTextEncoding=0;38;5;243
|
|
|
|
";
|
|
|
|
|
2020-04-03 20:55:14 +02:00
|
|
|
fn run() -> Result<ExitCode> {
|
2020-04-03 12:00:44 +02:00
|
|
|
let matches = app::build_app().get_matches_from(env::args_os());
|
2017-05-12 11:50:03 +02:00
|
|
|
|
2019-11-20 10:53:51 +01:00
|
|
|
// Set the current working directory of the process
|
2020-04-04 11:40:27 +02:00
|
|
|
if let Some(base_directory) = matches.value_of_os("base-directory") {
|
2020-04-03 19:41:43 +02:00
|
|
|
let base_directory = Path::new(base_directory);
|
2021-08-08 13:24:56 +02:00
|
|
|
if !filesystem::is_existing_directory(base_directory) {
|
2020-04-03 20:55:14 +02:00
|
|
|
return Err(anyhow!(
|
|
|
|
"The '--base-directory' path '{}' is not a directory.",
|
2020-04-03 19:41:43 +02:00
|
|
|
base_directory.to_string_lossy()
|
2020-04-03 20:55:14 +02:00
|
|
|
));
|
2019-11-20 10:53:51 +01:00
|
|
|
}
|
2020-04-03 20:55:14 +02:00
|
|
|
env::set_current_dir(base_directory).with_context(|| {
|
|
|
|
format!(
|
2020-10-25 21:18:53 +01:00
|
|
|
"Could not set '{}' as the current working directory",
|
2020-04-03 20:55:14 +02:00
|
|
|
base_directory.to_string_lossy()
|
|
|
|
)
|
|
|
|
})?;
|
2019-11-20 10:53:51 +01:00
|
|
|
}
|
|
|
|
|
2020-04-03 19:41:43 +02:00
|
|
|
let current_directory = Path::new(".");
|
2021-08-08 13:24:56 +02:00
|
|
|
if !filesystem::is_existing_directory(current_directory) {
|
2020-04-03 20:55:14 +02:00
|
|
|
return Err(anyhow!(
|
|
|
|
"Could not retrieve current directory (has it been deleted?)."
|
|
|
|
));
|
2017-10-14 19:57:15 +02:00
|
|
|
}
|
2017-05-12 11:50:03 +02:00
|
|
|
|
2020-04-03 19:41:43 +02:00
|
|
|
// Get the search pattern
|
2020-04-04 11:40:27 +02:00
|
|
|
let pattern = matches
|
|
|
|
.value_of_os("pattern")
|
|
|
|
.map(|p| {
|
2020-05-01 11:22:30 +02:00
|
|
|
p.to_str()
|
|
|
|
.ok_or_else(|| anyhow!("The search pattern includes invalid UTF-8 sequences."))
|
2020-04-04 11:40:27 +02:00
|
|
|
})
|
|
|
|
.transpose()?
|
|
|
|
.unwrap_or("");
|
2020-04-03 19:41:43 +02:00
|
|
|
|
2018-03-14 22:49:53 +01:00
|
|
|
// Get one or more root directories to search.
|
2020-05-19 13:40:19 +02:00
|
|
|
let passed_arguments = matches
|
2020-04-04 11:40:27 +02:00
|
|
|
.values_of_os("path")
|
2020-05-19 13:40:19 +02:00
|
|
|
.or_else(|| matches.values_of_os("search-path"));
|
|
|
|
|
2020-05-19 15:37:17 +02:00
|
|
|
let mut search_paths = if let Some(paths) = passed_arguments {
|
|
|
|
let mut directories = vec![];
|
2020-05-19 13:40:19 +02:00
|
|
|
for path in paths {
|
|
|
|
let path_buffer = PathBuf::from(path);
|
2021-08-08 13:24:56 +02:00
|
|
|
if filesystem::is_existing_directory(&path_buffer) {
|
2020-05-19 15:37:17 +02:00
|
|
|
directories.push(path_buffer);
|
|
|
|
} else {
|
2020-05-19 14:31:56 +02:00
|
|
|
print_error(format!(
|
|
|
|
"Search path '{}' is not a directory.",
|
|
|
|
path_buffer.to_string_lossy()
|
|
|
|
));
|
2020-05-19 13:40:19 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-19 15:37:17 +02:00
|
|
|
|
|
|
|
directories
|
|
|
|
} else {
|
|
|
|
vec![current_directory.to_path_buf()]
|
|
|
|
};
|
2017-06-11 20:41:32 +02:00
|
|
|
|
2020-05-19 13:40:32 +02:00
|
|
|
// Check if we have no valid search paths.
|
2020-05-19 15:37:17 +02:00
|
|
|
if search_paths.is_empty() {
|
2020-05-19 14:31:56 +02:00
|
|
|
return Err(anyhow!("No valid search paths given."));
|
2020-05-19 13:40:32 +02:00
|
|
|
}
|
|
|
|
|
2017-12-06 23:52:23 +01:00
|
|
|
if matches.is_present("absolute-path") {
|
2020-05-19 15:37:17 +02:00
|
|
|
search_paths = search_paths
|
2017-12-06 23:52:23 +01:00
|
|
|
.iter()
|
2018-03-25 23:47:58 +02:00
|
|
|
.map(|path_buffer| {
|
|
|
|
path_buffer
|
2021-08-08 14:13:32 +02:00
|
|
|
.normalize()
|
2020-04-03 12:04:47 +02:00
|
|
|
.and_then(|pb| filesystem::absolute_path(pb.as_path()))
|
2018-03-25 23:47:58 +02:00
|
|
|
.unwrap()
|
2018-09-27 23:01:38 +02:00
|
|
|
})
|
|
|
|
.collect();
|
2017-10-14 19:57:15 +02:00
|
|
|
}
|
2017-06-05 16:25:13 +02:00
|
|
|
|
2018-03-14 23:13:17 +01:00
|
|
|
// Detect if the user accidentally supplied a path instead of a search pattern
|
2018-05-14 18:39:47 +02:00
|
|
|
if !matches.is_present("full-path")
|
|
|
|
&& pattern.contains(std::path::MAIN_SEPARATOR)
|
2021-08-08 13:24:56 +02:00
|
|
|
&& Path::new(pattern).is_dir()
|
2018-03-14 23:13:17 +01:00
|
|
|
{
|
2020-04-03 20:55:14 +02:00
|
|
|
return Err(anyhow!(
|
2018-10-27 16:30:29 +02:00
|
|
|
"The search pattern '{pattern}' contains a path-separation character ('{sep}') \
|
2018-03-14 23:13:17 +01:00
|
|
|
and will not lead to any search results.\n\n\
|
|
|
|
If you want to search for all files inside the '{pattern}' directory, use a match-all pattern:\n\n \
|
|
|
|
fd . '{pattern}'\n\n\
|
2020-04-03 20:55:14 +02:00
|
|
|
Instead, if you want your pattern to match the full file path, use:\n\n \
|
2018-03-14 23:13:17 +01:00
|
|
|
fd --full-path '{pattern}'",
|
|
|
|
pattern = pattern,
|
|
|
|
sep = std::path::MAIN_SEPARATOR,
|
2020-04-03 20:55:14 +02:00
|
|
|
));
|
2018-03-14 23:13:17 +01:00
|
|
|
}
|
|
|
|
|
2020-05-01 11:22:30 +02:00
|
|
|
let pattern_regex = if matches.is_present("glob") && !pattern.is_empty() {
|
2020-04-15 15:47:17 +02:00
|
|
|
let glob = GlobBuilder::new(pattern).literal_separator(true).build()?;
|
2019-09-15 15:37:08 +02:00
|
|
|
glob.regex().to_owned()
|
|
|
|
} else if matches.is_present("fixed-strings") {
|
|
|
|
// Treat pattern as literal string if '--fixed-strings' is used
|
2018-02-10 15:19:53 +01:00
|
|
|
regex::escape(pattern)
|
|
|
|
} else {
|
|
|
|
String::from(pattern)
|
|
|
|
};
|
|
|
|
|
2017-06-05 14:14:01 +02:00
|
|
|
// The search will be case-sensitive if the command line flag is set or
|
|
|
|
// if the pattern has an uppercase character (smart case).
|
2018-01-01 12:16:43 +01:00
|
|
|
let case_sensitive = !matches.is_present("ignore-case")
|
2018-02-10 15:19:53 +01:00
|
|
|
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
|
2017-06-05 14:14:01 +02:00
|
|
|
|
2020-05-24 14:34:33 +02:00
|
|
|
#[cfg(windows)]
|
2020-05-24 14:37:52 +02:00
|
|
|
let ansi_colors_support =
|
|
|
|
ansi_term::enable_ansi_support().is_ok() || std::env::var_os("TERM").is_some();
|
2020-05-24 14:34:33 +02:00
|
|
|
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
let ansi_colors_support = true;
|
|
|
|
|
2020-04-04 12:51:15 +02:00
|
|
|
let interactive_terminal = atty::is(Stream::Stdout);
|
2017-09-30 23:02:57 +02:00
|
|
|
let colored_output = match matches.value_of("color") {
|
|
|
|
Some("always") => true,
|
|
|
|
Some("never") => false,
|
2020-05-24 14:34:33 +02:00
|
|
|
_ => ansi_colors_support && env::var_os("NO_COLOR").is_none() && interactive_terminal,
|
2017-09-30 23:02:57 +02:00
|
|
|
};
|
2017-10-26 21:44:40 +02:00
|
|
|
|
2021-02-15 00:13:56 +01:00
|
|
|
let path_separator = matches
|
|
|
|
.value_of("path-separator")
|
|
|
|
.map_or_else(filesystem::default_path_separator, |s| Some(s.to_owned()));
|
2021-08-08 12:02:34 +02:00
|
|
|
|
2021-08-08 12:02:57 +02:00
|
|
|
#[cfg(windows)]
|
|
|
|
{
|
|
|
|
if let Some(ref sep) = path_separator {
|
|
|
|
if sep.len() > 1 {
|
|
|
|
return Err(anyhow!(
|
|
|
|
"A path separator must be exactly one byte, but \
|
2021-08-08 12:02:28 +02:00
|
|
|
the given separator is {} bytes: '{}'.\n\
|
|
|
|
In some shells on Windows, '/' is automatically \
|
|
|
|
expanded. Try to use '//' instead.",
|
2021-08-08 12:02:57 +02:00
|
|
|
sep.len(),
|
|
|
|
sep
|
|
|
|
));
|
|
|
|
};
|
2021-07-01 00:31:56 +02:00
|
|
|
};
|
2021-08-08 12:02:57 +02:00
|
|
|
}
|
2019-04-11 00:30:56 +02:00
|
|
|
|
2017-10-12 08:01:51 +02:00
|
|
|
let ls_colors = if colored_output {
|
2020-12-06 12:04:47 +01:00
|
|
|
Some(LsColors::from_env().unwrap_or_else(|| LsColors::from_string(DEFAULT_LS_COLORS)))
|
2017-10-12 08:01:51 +02:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2017-05-12 15:44:09 +02:00
|
|
|
|
2020-04-03 20:55:14 +02:00
|
|
|
let command = if let Some(args) = matches.values_of("exec") {
|
2021-01-03 20:17:52 +01:00
|
|
|
Some(CommandTemplate::new(args, path_separator.clone()))
|
2020-04-03 20:55:14 +02:00
|
|
|
} else if let Some(args) = matches.values_of("exec-batch") {
|
2021-01-03 20:17:52 +01:00
|
|
|
Some(CommandTemplate::new_batch(args, path_separator.clone())?)
|
2020-04-03 20:55:14 +02:00
|
|
|
} else if matches.is_present("list-details") {
|
|
|
|
let color = matches.value_of("color").unwrap_or("auto");
|
|
|
|
let color_arg = ["--color=", color].concat();
|
2020-04-02 21:40:57 +02:00
|
|
|
|
2020-04-15 23:08:15 +02:00
|
|
|
#[allow(unused)]
|
2020-04-15 23:02:08 +02:00
|
|
|
let gnu_ls = |command_name| {
|
2021-08-10 20:39:10 +02:00
|
|
|
// Note: we use short options here (instead of --long-options) to support more
|
|
|
|
// platforms (like BusyBox).
|
2020-04-15 23:02:08 +02:00
|
|
|
vec![
|
|
|
|
command_name,
|
2021-08-10 20:39:10 +02:00
|
|
|
"-l", // long listing format
|
|
|
|
"-h", // human readable file sizes
|
|
|
|
"-d", // list directories themselves, not their contents
|
2020-04-15 23:02:08 +02:00
|
|
|
&color_arg,
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
2020-04-15 20:29:59 +02:00
|
|
|
let cmd: Vec<&str> = if cfg!(unix) {
|
2020-05-01 11:22:30 +02:00
|
|
|
if !cfg!(any(
|
|
|
|
target_os = "macos",
|
|
|
|
target_os = "dragonfly",
|
|
|
|
target_os = "freebsd",
|
|
|
|
target_os = "netbsd",
|
|
|
|
target_os = "openbsd"
|
|
|
|
)) {
|
2020-04-16 19:44:10 +02:00
|
|
|
// Assume ls is GNU ls
|
2020-04-15 23:02:08 +02:00
|
|
|
gnu_ls("ls")
|
2020-04-15 20:29:59 +02:00
|
|
|
} else {
|
2020-04-16 19:44:10 +02:00
|
|
|
// MacOS, DragonFlyBSD, FreeBSD
|
2020-04-15 20:31:59 +02:00
|
|
|
use std::process::{Command, Stdio};
|
2020-04-15 20:29:59 +02:00
|
|
|
|
2020-04-15 20:34:20 +02:00
|
|
|
// Use GNU ls, if available (support for --color=auto, better LS_COLORS support)
|
2020-04-15 20:31:59 +02:00
|
|
|
let gnu_ls_exists = Command::new("gls")
|
|
|
|
.arg("--version")
|
|
|
|
.stdout(Stdio::null())
|
|
|
|
.stderr(Stdio::null())
|
|
|
|
.status()
|
|
|
|
.is_ok();
|
2020-04-15 20:29:59 +02:00
|
|
|
|
|
|
|
if gnu_ls_exists {
|
2020-04-15 23:02:08 +02:00
|
|
|
gnu_ls("gls")
|
2020-04-15 20:29:59 +02:00
|
|
|
} else {
|
|
|
|
let mut cmd = vec![
|
2020-04-16 19:44:10 +02:00
|
|
|
"ls", // BSD version of ls
|
2020-04-15 20:29:59 +02:00
|
|
|
"-l", // long listing format
|
2020-04-16 19:44:10 +02:00
|
|
|
"-h", // '--human-readable' is not available, '-h' is
|
2020-04-15 20:29:59 +02:00
|
|
|
"-d", // '--directory' is not available, but '-d' is
|
|
|
|
];
|
|
|
|
|
2020-04-16 20:10:00 +02:00
|
|
|
if !cfg!(any(target_os = "netbsd", target_os = "openbsd")) && colored_output {
|
|
|
|
// -G is not available in NetBSD's and OpenBSD's ls
|
2020-04-15 20:29:59 +02:00
|
|
|
cmd.push("-G");
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd
|
|
|
|
}
|
|
|
|
}
|
2020-04-15 23:02:08 +02:00
|
|
|
} else if cfg!(windows) {
|
|
|
|
use std::process::{Command, Stdio};
|
|
|
|
|
|
|
|
// Use GNU ls, if available
|
|
|
|
let gnu_ls_exists = Command::new("ls")
|
|
|
|
.arg("--version")
|
|
|
|
.stdout(Stdio::null())
|
|
|
|
.stderr(Stdio::null())
|
|
|
|
.status()
|
|
|
|
.is_ok();
|
|
|
|
|
|
|
|
if gnu_ls_exists {
|
|
|
|
gnu_ls("ls")
|
|
|
|
} else {
|
|
|
|
return Err(anyhow!(
|
2020-04-15 23:08:15 +02:00
|
|
|
"'fd --list-details' is not supported on Windows unless GNU 'ls' is installed."
|
2020-04-15 23:02:08 +02:00
|
|
|
));
|
|
|
|
}
|
2020-04-15 20:29:59 +02:00
|
|
|
} else {
|
|
|
|
return Err(anyhow!(
|
2020-04-15 23:08:15 +02:00
|
|
|
"'fd --list-details' is not supported on this platform."
|
2020-04-15 20:29:59 +02:00
|
|
|
));
|
|
|
|
};
|
|
|
|
|
2021-01-03 20:17:52 +01:00
|
|
|
Some(CommandTemplate::new_batch(&cmd, path_separator.clone()).unwrap())
|
2020-04-03 20:55:14 +02:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2017-10-14 18:04:11 +02:00
|
|
|
|
2020-04-03 20:55:14 +02:00
|
|
|
let size_limits = if let Some(vs) = matches.values_of("size") {
|
|
|
|
vs.map(|sf| {
|
2020-05-01 11:22:30 +02:00
|
|
|
SizeFilter::from_string(sf)
|
|
|
|
.ok_or_else(|| anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", sf))
|
2018-09-27 23:01:38 +02:00
|
|
|
})
|
2020-04-03 20:55:14 +02:00
|
|
|
.collect::<Result<Vec<_>>>()?
|
|
|
|
} else {
|
|
|
|
vec![]
|
|
|
|
};
|
2018-04-23 01:38:10 +02:00
|
|
|
|
2018-10-10 12:13:19 +02:00
|
|
|
let now = time::SystemTime::now();
|
|
|
|
let mut time_constraints: Vec<TimeFilter> = Vec::new();
|
2018-10-09 13:47:42 +02:00
|
|
|
if let Some(t) = matches.value_of("changed-within") {
|
2018-10-10 12:13:19 +02:00
|
|
|
if let Some(f) = TimeFilter::after(&now, t) {
|
|
|
|
time_constraints.push(f);
|
2018-10-09 13:47:42 +02:00
|
|
|
} else {
|
2020-04-03 20:55:14 +02:00
|
|
|
return Err(anyhow!(
|
|
|
|
"'{}' is not a valid date or duration. See 'fd --help'.",
|
|
|
|
t
|
|
|
|
));
|
2018-10-09 13:47:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(t) = matches.value_of("changed-before") {
|
2018-10-10 12:13:19 +02:00
|
|
|
if let Some(f) = TimeFilter::before(&now, t) {
|
|
|
|
time_constraints.push(f);
|
2018-10-09 13:47:42 +02:00
|
|
|
} else {
|
2020-04-03 20:55:14 +02:00
|
|
|
return Err(anyhow!(
|
|
|
|
"'{}' is not a valid date or duration. See 'fd --help'.",
|
|
|
|
t
|
|
|
|
));
|
2018-10-09 13:47:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-01 14:50:12 +02:00
|
|
|
#[cfg(unix)]
|
|
|
|
let owner_constraint = if let Some(s) = matches.value_of("owner") {
|
2020-05-13 09:10:43 +02:00
|
|
|
OwnerFilter::from_string(s)?
|
2020-05-01 14:50:12 +02:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2020-04-03 11:39:32 +02:00
|
|
|
let config = Options {
|
2017-10-14 18:04:11 +02:00
|
|
|
case_sensitive,
|
2017-10-12 08:01:51 +02:00
|
|
|
search_full_path: matches.is_present("full-path"),
|
2018-01-01 12:16:43 +01:00
|
|
|
ignore_hidden: !(matches.is_present("hidden")
|
|
|
|
|| matches.occurrences_of("rg-alias-hidden-ignore") >= 2),
|
2018-02-21 21:41:52 +01:00
|
|
|
read_fdignore: !(matches.is_present("no-ignore")
|
2018-01-01 12:16:43 +01:00
|
|
|
|| matches.is_present("rg-alias-hidden-ignore")),
|
2018-02-21 21:41:52 +01:00
|
|
|
read_vcsignore: !(matches.is_present("no-ignore")
|
2018-01-01 12:16:43 +01:00
|
|
|
|| matches.is_present("rg-alias-hidden-ignore")
|
|
|
|
|| matches.is_present("no-ignore-vcs")),
|
2021-08-08 23:38:24 +02:00
|
|
|
read_parent_ignore: !(matches.is_present("no-ignore")
|
|
|
|
|| matches.is_present("rg-alias-hidden-ignore")
|
|
|
|
|| matches.is_present("no-ignore-vcs")
|
|
|
|
|| matches.is_present("no-ignore-parent")),
|
2020-04-25 21:32:17 +02:00
|
|
|
read_global_ignore: !(matches.is_present("no-ignore")
|
|
|
|
|| matches.is_present("rg-alias-hidden-ignore")
|
|
|
|
|| matches.is_present("no-global-ignore-file")),
|
2017-10-12 08:01:51 +02:00
|
|
|
follow_links: matches.is_present("follow"),
|
2019-12-30 21:49:22 +01:00
|
|
|
one_file_system: matches.is_present("one-file-system"),
|
2017-10-12 08:01:51 +02:00
|
|
|
null_separator: matches.is_present("null_separator"),
|
2021-08-14 17:57:01 +02:00
|
|
|
quiet: matches.is_present("quiet"),
|
2018-01-01 12:16:43 +01:00
|
|
|
max_depth: matches
|
2020-04-15 16:17:01 +02:00
|
|
|
.value_of("max-depth")
|
2018-09-13 18:19:17 +02:00
|
|
|
.or_else(|| matches.value_of("rg-depth"))
|
2020-04-15 16:17:01 +02:00
|
|
|
.or_else(|| matches.value_of("exact-depth"))
|
2021-07-27 08:38:09 +02:00
|
|
|
.map(|n| n.parse::<usize>())
|
2020-10-25 21:18:53 +01:00
|
|
|
.transpose()
|
|
|
|
.context("Failed to parse argument to --max-depth/--exact-depth")?,
|
2020-04-15 16:17:01 +02:00
|
|
|
min_depth: matches
|
|
|
|
.value_of("min-depth")
|
|
|
|
.or_else(|| matches.value_of("exact-depth"))
|
2021-07-27 08:38:09 +02:00
|
|
|
.map(|n| n.parse::<usize>())
|
2020-10-25 21:18:53 +01:00
|
|
|
.transpose()
|
|
|
|
.context("Failed to parse argument to --min-depth/--exact-depth")?,
|
2020-10-25 08:16:01 +01:00
|
|
|
prune: matches.is_present("prune"),
|
2017-10-12 08:01:51 +02:00
|
|
|
threads: std::cmp::max(
|
|
|
|
matches
|
|
|
|
.value_of("threads")
|
2021-07-27 08:38:09 +02:00
|
|
|
.map(|n| n.parse::<usize>())
|
2020-10-25 21:18:53 +01:00
|
|
|
.transpose()
|
2020-12-06 12:04:47 +01:00
|
|
|
.context("Failed to parse number of threads")?
|
2020-10-25 21:18:53 +01:00
|
|
|
.map(|n| {
|
|
|
|
if n > 0 {
|
|
|
|
Ok(n)
|
|
|
|
} else {
|
|
|
|
Err(anyhow!("Number of threads must be positive."))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.transpose()?
|
2017-10-12 08:01:51 +02:00
|
|
|
.unwrap_or_else(num_cpus::get),
|
|
|
|
1,
|
|
|
|
),
|
|
|
|
max_buffer_time: matches
|
|
|
|
.value_of("max-buffer-time")
|
2021-07-27 08:38:09 +02:00
|
|
|
.map(|n| n.parse::<u64>())
|
2020-10-25 21:18:53 +01:00
|
|
|
.transpose()
|
|
|
|
.context("Failed to parse max. buffer time argument")?
|
2017-10-12 08:01:51 +02:00
|
|
|
.map(time::Duration::from_millis),
|
2017-10-14 18:04:11 +02:00
|
|
|
ls_colors,
|
2020-04-04 12:51:15 +02:00
|
|
|
interactive_terminal,
|
2018-02-25 11:11:14 +01:00
|
|
|
file_types: matches.values_of("file-type").map(|values| {
|
|
|
|
let mut file_types = FileTypes::default();
|
|
|
|
for value in values {
|
|
|
|
match value {
|
|
|
|
"f" | "file" => file_types.files = true,
|
|
|
|
"d" | "directory" => file_types.directories = true,
|
|
|
|
"l" | "symlink" => file_types.symlinks = true,
|
2018-03-25 12:19:51 +02:00
|
|
|
"x" | "executable" => {
|
|
|
|
file_types.executables_only = true;
|
|
|
|
file_types.files = true;
|
2018-03-25 16:36:37 +02:00
|
|
|
}
|
2020-04-16 09:41:24 +02:00
|
|
|
"e" | "empty" => file_types.empty_only = true,
|
|
|
|
"s" | "socket" => file_types.sockets = true,
|
|
|
|
"p" | "pipe" => file_types.pipes = true,
|
2018-02-25 11:11:14 +01:00
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
2018-08-19 17:05:04 +02:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2018-02-25 11:11:14 +01:00
|
|
|
file_types
|
|
|
|
}),
|
2020-04-03 20:55:14 +02:00
|
|
|
extensions: matches
|
|
|
|
.values_of("extension")
|
|
|
|
.map(|exts| {
|
|
|
|
let patterns = exts
|
|
|
|
.map(|e| e.trim_start_matches('.'))
|
|
|
|
.map(|e| format!(r".\.{}$", regex::escape(e)));
|
|
|
|
RegexSetBuilder::new(patterns)
|
|
|
|
.case_insensitive(true)
|
|
|
|
.build()
|
|
|
|
})
|
|
|
|
.transpose()?,
|
2019-01-29 23:09:45 +01:00
|
|
|
command: command.map(Arc::new),
|
2017-10-22 23:00:19 +02:00
|
|
|
exclude_patterns: matches
|
|
|
|
.values_of("exclude")
|
|
|
|
.map(|v| v.map(|p| String::from("!") + p).collect())
|
2020-10-16 16:17:05 +02:00
|
|
|
.unwrap_or_else(Vec::new),
|
2018-03-26 00:15:01 +02:00
|
|
|
ignore_files: matches
|
|
|
|
.values_of("ignore-file")
|
|
|
|
.map(|vs| vs.map(PathBuf::from).collect())
|
2020-10-16 16:17:05 +02:00
|
|
|
.unwrap_or_else(Vec::new),
|
2018-04-23 01:38:10 +02:00
|
|
|
size_constraints: size_limits,
|
2018-10-10 12:13:19 +02:00
|
|
|
time_constraints,
|
2020-05-01 14:50:12 +02:00
|
|
|
#[cfg(unix)]
|
|
|
|
owner_constraint,
|
2018-10-22 14:20:08 +02:00
|
|
|
show_filesystem_errors: matches.is_present("show-errors"),
|
2019-04-11 00:30:56 +02:00
|
|
|
path_separator,
|
2020-04-02 17:52:44 +02:00
|
|
|
max_results: matches
|
|
|
|
.value_of("max-results")
|
2021-07-27 08:38:09 +02:00
|
|
|
.map(|n| n.parse::<usize>())
|
2020-10-25 21:18:53 +01:00
|
|
|
.transpose()
|
|
|
|
.context("Failed to parse --max-results argument")?
|
|
|
|
.filter(|&n| n > 0)
|
2020-04-15 15:47:17 +02:00
|
|
|
.or_else(|| {
|
|
|
|
if matches.is_present("max-one-result") {
|
|
|
|
Some(1)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}),
|
2017-05-12 15:44:09 +02:00
|
|
|
};
|
2017-05-12 13:32:30 +02:00
|
|
|
|
2020-12-06 15:57:33 +01:00
|
|
|
if cfg!(unix)
|
|
|
|
&& config.ignore_hidden
|
|
|
|
&& pattern_matches_strings_with_leading_dot(&pattern_regex)
|
|
|
|
{
|
|
|
|
return Err(anyhow!(
|
|
|
|
"The pattern seems to only match files with a leading dot, but hidden files are \
|
|
|
|
filtered by default. Consider adding -H/--hidden to search hidden files as well \
|
|
|
|
or adjust your search pattern."
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2020-04-03 20:55:14 +02:00
|
|
|
let re = RegexBuilder::new(&pattern_regex)
|
2017-10-12 08:01:51 +02:00
|
|
|
.case_insensitive(!config.case_sensitive)
|
2017-10-14 15:41:38 +02:00
|
|
|
.dot_matches_new_line(true)
|
2018-01-01 12:16:43 +01:00
|
|
|
.build()
|
2020-04-03 20:55:14 +02:00
|
|
|
.map_err(|e| {
|
|
|
|
anyhow!(
|
|
|
|
"{}\n\nNote: You can use the '--fixed-strings' option to search for a \
|
|
|
|
literal string instead of a regular expression. Alternatively, you can \
|
|
|
|
also use the '--glob' option to match on a glob pattern.",
|
|
|
|
e.to_string()
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
|
2020-05-19 15:37:17 +02:00
|
|
|
walk::scan(&search_paths, Arc::new(re), Arc::new(config))
|
2020-04-03 20:55:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let result = run();
|
|
|
|
match result {
|
|
|
|
Ok(exit_code) => {
|
2019-09-13 22:26:27 +02:00
|
|
|
process::exit(exit_code.into());
|
|
|
|
}
|
2018-10-03 15:53:59 +02:00
|
|
|
Err(err) => {
|
2020-10-25 21:18:53 +01:00
|
|
|
eprintln!("[fd error]: {:#}", err);
|
2020-04-03 21:34:59 +02:00
|
|
|
process::exit(ExitCode::GeneralError.into());
|
2018-10-03 15:53:59 +02:00
|
|
|
}
|
2017-05-12 11:50:03 +02:00
|
|
|
}
|
|
|
|
}
|