diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index c2e76be..89f652b 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1,7 +1,7 @@ name: CICD env: - MIN_SUPPORTED_RUST_VERSION: "1.56.1" + MIN_SUPPORTED_RUST_VERSION: "1.57.0" CICD_INTERMEDIATES_DIR: "_cicd-intermediates" on: diff --git a/CHANGELOG.md b/CHANGELOG.md index 69d1dc0..53df238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,17 +2,25 @@ ## Features +- `--type executable`/`-t` now works on Windows, see #1051 and #1061 (@tavianator) ## Bugfixes -- fd returns an error when current working directory does not exist while a search path is specified, see #1072 (@vijfhoek) - +- Fixed differences between piped / non-piped output. This changes `fd`s behavior back to what we + had before 8.3.0, i.e. there will be no leading `./` prefixes, unless `--exec`/`-x`, + `--exec-batch`/`-X`, or `--print0`/`-0` are used. `--strip-cwd-prefix` can be used to strip that + prefix in those cases. See #1046, #1115, and #1121 (@tavianator) +- fd returns an error when current working directory does not exist while a search path is + specified, see #1072 (@vijfhoek) +- Improved "command not found" error message, see #1083 and #1109 (@themkat) ## Changes +- No leading `./` prefix for non-interactive results, see above. ## Other +- Added link back to GitHub in man page and `--help` text, see #1086 (@scottchiefbaker) # v8.4.0 diff --git a/Cargo.lock b/Cargo.lock index 57ce4d6..68211a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,13 +4,22 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -22,19 +31,19 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.59" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "argmax" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "932fb17af6e53a41ce7312f1ae1ba2a6f3f613fe36f38ad655b212906eb9657f" +checksum = "5b7e3ef5e3a7f2c5e5a49d90ad087c03d38258e75155daac64deb62c50972c66" dependencies = [ "lazy_static", "libc", - "nix 0.23.1", + "nix 0.24.2", ] [[package]] @@ -69,6 +78,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "bumpalo" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" + [[package]] name = "cc" version = "1.0.73" @@ -83,22 +98,24 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time", + "wasm-bindgen", "winapi", ] [[package]] name = "clap" -version = "4.0.10" +version = "4.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b1a0a4208c6c483b952ad35c6eed505fc13b46f08f631b81e828084a9318d74" +checksum = "4ed45cc2c62a3eff523e718d8576ba762c83a3146151093283ac62ae11933a73" dependencies = [ "atty", "bitflags", @@ -142,10 +159,16 @@ dependencies = [ ] [[package]] -name = "crossbeam-utils" -version = "0.8.10" +name = "core-foundation-sys" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if", "once_cell", @@ -209,6 +232,17 @@ dependencies = [ "libc", ] +[[package]] +name = "faccess" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ae66425802d6a903e268ae1a08b8c38ba143520f227a205edf4e9c7e3e26d5" +dependencies = [ + "bitflags", + "libc", + "winapi", +] + [[package]] name = "fd-find" version = "8.4.0" @@ -223,6 +257,7 @@ dependencies = [ "ctrlc", "diff", "dirs-next", + "faccess", "filetime", "globset", "humantime", @@ -317,6 +352,20 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "iana-time-zone" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "once_cell", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ignore" version = "0.4.18" @@ -343,9 +392,9 @@ checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" [[package]] name = "jemalloc-sys" -version = "0.5.0+5.3.0" +version = "0.5.1+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f655c3ecfa6b0d03634595b4b54551d4bd5ac208b9e0124873949a7ab168f70b" +checksum = "b7c2b313609b95939cb0c5a5c6917fb9b7c9394562aa3ef44eb66ffa51736432" dependencies = [ "cc", "fs_extra", @@ -362,6 +411,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -370,9 +428,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "linux-raw-sys" @@ -404,28 +462,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "nix" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "memoffset", -] - [[package]] name = "nix" version = "0.24.2" @@ -489,15 +525,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "os_str_bytes" -version = "6.1.0" +version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" [[package]] name = "proc-macro-error" @@ -525,18 +561,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.46" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -580,9 +616,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -626,9 +662,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.35.9" +version = "0.35.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" +checksum = "fbb2fda4666def1433b1b05431ab402e42a1084285477222b72d6c564c417cef" dependencies = [ "bitflags", "errno", @@ -655,9 +691,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -717,18 +753,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" dependencies = [ "proc-macro2", "quote", @@ -757,9 +793,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "users" @@ -800,6 +836,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 09b953d..18f2baa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ version_check = "0.9" [dependencies] ansi_term = "0.12" -argmax = "0.3.0" +argmax = "0.3.1" atty = "0.2" ignore = "0.4.3" num_cpus = "1.13" @@ -48,7 +48,7 @@ anyhow = "1.0" dirs-next = "2.0" normpath = "0.3.2" chrono = "0.4" -once_cell = "1.13.1" +once_cell = "1.15.0" clap_complete = {version = "4.0", optional = true} [dependencies.clap] @@ -62,10 +62,13 @@ nix = { version = "0.24.2", default-features = false, features = ["signal"] } [target.'cfg(all(unix, not(target_os = "redox")))'.dependencies] libc = "0.2" +[target.'cfg(windows)'.dependencies] +faccess = "0.2.4" + # FIXME: Re-enable jemalloc on macOS # jemalloc is currently disabled on macOS due to a bug in jemalloc in combination with macOS # Catalina. See https://github.com/sharkdp/fd/issues/498 for details. -[target.'cfg(all(not(windows), not(target_os = "android"), not(target_os = "macos"), not(target_os = "freebsd"), not(target_env = "musl"), not(target_arch = "riscv64")))'.dependencies] +[target.'cfg(all(not(windows), not(target_os = "android"), not(target_os = "macos"), not(target_os = "freebsd"), not(all(target_env = "musl", target_pointer_width = "32")), not(target_arch = "riscv64")))'.dependencies] jemallocator = {version = "0.5.0", optional = true} [dev-dependencies] diff --git a/README.md b/README.md index f630dfa..c2c4b3d 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ Here, `{}` is a placeholder for the search result. `{.}` is the same, without th See below for more details on the placeholder syntax. The terminal output of commands run from parallel threads using `-x` will not be interlaced or garbled, -so `fd -x` can be used to rudimentarily parallelize a task run over many files. +so `fd -x` can be used to rudimentarily parallelize a task run over many files. An example of this is calculating the checksum of each individual file within a directory. ``` fd -tf -x md5sum > file_checksums.txt @@ -599,7 +599,7 @@ You can install `fd` via xbps-install: xbps-install -S fd ``` -### On RedHat Enterprise Linux 8 (RHEL8) or Almalinux 8 or Rocky Linux 8 +### On RedHat Enterprise Linux 8 (RHEL8), Almalinux 8, EuroLinux 8 or Rocky Linux 8 Get the latest fd-v*-x86_64-unknown-linux-gnu.tar.gz file from [sharkdp on github](https://github.com/sharkdp/fd/releases) ``` @@ -676,7 +676,7 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can ``` cargo install fd-find ``` -Note that rust version *1.56.1* or later is required. +Note that rust version *1.57.0* or later is required. `make` is also needed for the build. diff --git a/build.rs b/build.rs index fc54326..ab1219b 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,5 @@ fn main() { - let min_version = "1.56"; + let min_version = "1.57"; match version_check::is_min_version(min_version) { Some(true) => {} diff --git a/clippy.toml b/clippy.toml index 56ce04e..23b32c1 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.56.1" +msrv = "1.57.0" diff --git a/doc/fd.1 b/doc/fd.1 index 6144f73..6616ee9 100644 --- a/doc/fd.1 +++ b/doc/fd.1 @@ -455,5 +455,7 @@ $ fd -e py .TP .RI "Open all search results with vim:" $ fd pattern -X vim +.SH BUGS +Bugs can be reported on GitHub: https://github.com/sharkdp/fd/issues .SH SEE ALSO .BR find (1) diff --git a/src/cli.rs b/src/cli.rs index 2af1fa3..0194518 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -87,6 +87,7 @@ impl clap::Args for Negations { dont_collapse_args_in_usage = true, after_help = "Note: `fd -h` prints a short and concise overview while `fd --help` gives all \ details.", + after_long_help = "Bugs can be reported on GitHub: https://github.com/sharkdp/fd/issues", args_override_self = true, group(ArgGroup::new("execs").args(&["exec", "exec_batch", "list_details"]).conflicts_with_all(&[ "max_results", "has_results", "count"])), @@ -477,10 +478,12 @@ pub struct Opts { /// Changes the usage to `fd [OPTIONS] --search-path --search-path []` #[clap(long, conflicts_with("path"), action, hide_short_help = true)] search_path: Vec, - /// strip './' prefix from non-tty outputs + /// strip './' prefix from -0/--print-0 output /// - /// By default, relative paths are prefixed with './' when the output goes to a non - /// interactive terminal (TTY). Use this flag to disable this behaviour. + /// By default, relative paths are prefixed with './' when -x/--exec, + /// -X/--exec-batch, or -0/--print0 are given, to reduce the risk of a + /// path starting with '-' being treated as a command line option. Use + /// this flag to disable this behaviour. #[clap(long, conflicts_with_all(&["path", "search_path"]), hide_short_help = true, action)] pub strip_cwd_prefix: bool, /// Filter by owning user and/or group diff --git a/src/dir_entry.rs b/src/dir_entry.rs index 5def5de..7c34be5 100644 --- a/src/dir_entry.rs +++ b/src/dir_entry.rs @@ -5,6 +5,9 @@ use std::{ use once_cell::unsync::OnceCell; +use crate::config::Config; +use crate::filesystem::strip_current_dir; + enum DirEntryInner { Normal(ignore::DirEntry), BrokenSymlink(PathBuf), @@ -45,6 +48,24 @@ impl DirEntry { } } + /// Returns the path as it should be presented to the user. + pub fn stripped_path(&self, config: &Config) -> &Path { + if config.strip_cwd_prefix { + strip_current_dir(self.path()) + } else { + self.path() + } + } + + /// Returns the path as it should be presented to the user. + pub fn into_stripped_path(self, config: &Config) -> PathBuf { + if config.strip_cwd_prefix { + self.stripped_path(config).to_path_buf() + } else { + self.into_path() + } + } + pub fn file_type(&self) -> Option { match &self.inner { DirEntryInner::Normal(e) => e.file_type(), diff --git a/src/exec/command.rs b/src/exec/command.rs index a642a40..ff13f91 100644 --- a/src/exec/command.rs +++ b/src/exec/command.rs @@ -96,7 +96,10 @@ pub fn execute_commands>>( pub fn handle_cmd_error(cmd: Option<&Command>, err: io::Error) -> ExitCode { match (cmd, err) { (Some(cmd), err) if err.kind() == io::ErrorKind::NotFound => { - print_error(format!("Command not found: {:?}", cmd)); + print_error(format!( + "Command not found: {}", + cmd.get_program().to_string_lossy() + )); ExitCode::GeneralError } (_, err) => { diff --git a/src/exec/job.rs b/src/exec/job.rs index c4d93ee..36d52e4 100644 --- a/src/exec/job.rs +++ b/src/exec/job.rs @@ -1,6 +1,7 @@ use std::sync::mpsc::Receiver; use std::sync::{Arc, Mutex}; +use crate::config::Config; use crate::dir_entry::DirEntry; use crate::error::print_error; use crate::exit_codes::{merge_exitcodes, ExitCode}; @@ -15,10 +16,11 @@ pub fn job( rx: Arc>>, cmd: Arc, out_perm: Arc>, - show_filesystem_errors: bool, - buffer_output: bool, - path_separator: Option<&str>, + config: &Config, ) -> ExitCode { + // Output should be buffered when only running a single thread + let buffer_output: bool = config.threads > 1; + let mut results: Vec = Vec::new(); loop { // Create a lock on the shared receiver for this thread. @@ -29,7 +31,7 @@ pub fn job( let dir_entry: DirEntry = match lock.recv() { Ok(WorkerResult::Entry(dir_entry)) => dir_entry, Ok(WorkerResult::Error(err)) => { - if show_filesystem_errors { + if config.show_filesystem_errors { print_error(err.to_string()); } continue; @@ -41,8 +43,8 @@ pub fn job( drop(lock); // Generate a command, execute it and store its exit code. results.push(cmd.execute( - dir_entry.path(), - path_separator, + dir_entry.stripped_path(config), + config.path_separator.as_deref(), Arc::clone(&out_perm), buffer_output, )) @@ -51,24 +53,18 @@ pub fn job( merge_exitcodes(results) } -pub fn batch( - rx: Receiver, - cmd: &CommandSet, - show_filesystem_errors: bool, - limit: usize, - path_separator: Option<&str>, -) -> ExitCode { +pub fn batch(rx: Receiver, cmd: &CommandSet, config: &Config) -> ExitCode { let paths = rx .into_iter() .filter_map(|worker_result| match worker_result { - WorkerResult::Entry(dir_entry) => Some(dir_entry.into_path()), + WorkerResult::Entry(dir_entry) => Some(dir_entry.into_stripped_path(config)), WorkerResult::Error(err) => { - if show_filesystem_errors { + if config.show_filesystem_errors { print_error(err.to_string()); } None } }); - cmd.execute_batch(paths, limit, path_separator) + cmd.execute_batch(paths, config.batch_size, config.path_separator.as_deref()) } diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 21ebfea..bcc1690 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -24,7 +24,7 @@ pub use self::job::{batch, job}; use self::token::Token; /// Execution mode of the command -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ExecutionMode { /// Command is executed for each search result OneByOne, diff --git a/src/exec/token.rs b/src/exec/token.rs index 0cf4df3..a295745 100644 --- a/src/exec/token.rs +++ b/src/exec/token.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Display, Formatter}; /// /// Each `Token` contains either text, or a placeholder variant, which will be used to generate /// commands after all tokens for a given command template have been collected. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Token { Placeholder, Basename, diff --git a/src/exit_codes.rs b/src/exit_codes.rs index e44111b..c87d8c2 100644 --- a/src/exit_codes.rs +++ b/src/exit_codes.rs @@ -3,7 +3,7 @@ use std::process; #[cfg(unix)] use nix::sys::signal::{raise, signal, SigHandler, Signal}; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ExitCode { Success, HasResults(bool), diff --git a/src/filesystem.rs b/src/filesystem.rs index a025895..2e79591 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -7,6 +7,9 @@ use std::io; use std::os::unix::fs::{FileTypeExt, PermissionsExt}; use std::path::{Path, PathBuf}; +#[cfg(windows)] +use faccess::PathExt as _; + use normpath::PathExt; use crate::dir_entry; @@ -42,13 +45,13 @@ pub fn is_existing_directory(path: &Path) -> bool { } #[cfg(any(unix, target_os = "redox"))] -pub fn is_executable(md: &fs::Metadata) -> bool { +pub fn is_executable(_: &Path, md: &fs::Metadata) -> bool { md.permissions().mode() & 0o111 != 0 } #[cfg(windows)] -pub fn is_executable(_: &fs::Metadata) -> bool { - false +pub fn is_executable(path: &Path, _: &fs::Metadata) -> bool { + path.executable() } pub fn is_empty(entry: &dir_entry::DirEntry) -> bool { diff --git a/src/filetypes.rs b/src/filetypes.rs index 1209549..42e95d2 100644 --- a/src/filetypes.rs +++ b/src/filetypes.rs @@ -24,7 +24,7 @@ impl FileTypes { || (self.executables_only && !entry .metadata() - .map(filesystem::is_executable) + .map(|md| filesystem::is_executable(entry.path(), md)) .unwrap_or(false)) || (self.empty_only && !filesystem::is_empty(entry)) || !(entry_type.is_file() diff --git a/src/filter/size.rs b/src/filter/size.rs index a3d7c41..5df60ab 100644 --- a/src/filter/size.rs +++ b/src/filter/size.rs @@ -5,7 +5,7 @@ use regex::Regex; static SIZE_CAPTURES: Lazy = Lazy::new(|| Regex::new(r"(?i)^([+-]?)(\d+)(b|[kmgt]i?b?)$").unwrap()); -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum SizeFilter { Max(u64), Min(u64), diff --git a/src/filter/time.rs b/src/filter/time.rs index eb43c4b..e854c39 100644 --- a/src/filter/time.rs +++ b/src/filter/time.rs @@ -3,7 +3,7 @@ use chrono::{offset::TimeZone, DateTime, Local, NaiveDate}; use std::time::SystemTime; /// Filter based on time ranges. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum TimeFilter { Before(SystemTime), After(SystemTime), diff --git a/src/main.rs b/src/main.rs index 86c4ef5..1288423 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,7 +40,7 @@ use crate::regex_helper::{pattern_has_uppercase_char, pattern_matches_strings_wi not(target_os = "android"), not(target_os = "macos"), not(target_os = "freebsd"), - not(target_env = "musl"), + not(all(target_env = "musl", target_pointer_width = "32")), not(target_arch = "riscv64"), feature = "use-jemalloc" ))] @@ -280,7 +280,7 @@ fn construct_config(mut opts: Opts, pattern_regex: &str) -> Result { actual_path_separator, max_results: opts.max_results(), strip_cwd_prefix: (opts.no_search_paths() - && (interactive_terminal || opts.strip_cwd_prefix)), + && (opts.strip_cwd_prefix || !(opts.null_separator || opts.exec.command.is_some()))), }) } diff --git a/src/output.rs b/src/output.rs index 261dbf3..82a7e68 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; use std::io::{self, Write}; -use std::path::Path; use lscolors::{Indicator, LsColors, Style}; @@ -8,21 +7,11 @@ use crate::config::Config; use crate::dir_entry::DirEntry; use crate::error::print_error; use crate::exit_codes::ExitCode; -use crate::filesystem::strip_current_dir; fn replace_path_separator(path: &str, new_path_separator: &str) -> String { path.replace(std::path::MAIN_SEPARATOR, new_path_separator) } -fn stripped_path<'a>(entry: &'a DirEntry, config: &Config) -> &'a Path { - let path = entry.path(); - if config.strip_cwd_prefix { - strip_current_dir(path) - } else { - path - } -} - // TODO: this function is performance critical and can probably be optimized pub fn print_entry(stdout: &mut W, entry: &DirEntry, config: &Config) { let r = if let Some(ref ls_colors) = config.ls_colors { @@ -74,7 +63,7 @@ fn print_entry_colorized( ) -> io::Result<()> { // Split the path between the parent and the last component let mut offset = 0; - let path = stripped_path(entry, config); + let path = entry.stripped_path(config); let path_str = path.to_string_lossy(); if let Some(parent) = path.parent() { @@ -130,7 +119,7 @@ fn print_entry_uncolorized_base( config: &Config, ) -> io::Result<()> { let separator = if config.null_separator { "\0" } else { "\n" }; - let path = stripped_path(entry, config); + let path = entry.stripped_path(config); let mut path_string = path.to_string_lossy(); if let Some(ref separator) = config.path_separator { @@ -164,7 +153,7 @@ fn print_entry_uncolorized( } else { // Print path as raw bytes, allowing invalid UTF-8 filenames to be passed to other processes let separator = if config.null_separator { b"\0" } else { b"\n" }; - stdout.write_all(stripped_path(entry, config).as_os_str().as_bytes())?; + stdout.write_all(entry.stripped_path(config).as_os_str().as_bytes())?; print_trailing_slash(stdout, entry, config, None)?; stdout.write_all(separator) } diff --git a/src/walk.rs b/src/walk.rs index 6cffd52..1577fa0 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -341,48 +341,27 @@ fn spawn_receiver( let quit_flag = Arc::clone(quit_flag); let interrupt_flag = Arc::clone(interrupt_flag); - let show_filesystem_errors = config.show_filesystem_errors; let threads = config.threads; - // This will be used to check if output should be buffered when only running a single thread - let enable_output_buffering: bool = threads > 1; thread::spawn(move || { // This will be set to `Some` if the `--exec` argument was supplied. if let Some(ref cmd) = config.command { if cmd.in_batch_mode() { - exec::batch( - rx, - cmd, - show_filesystem_errors, - config.batch_size, - config.path_separator.as_deref(), - ) + exec::batch(rx, cmd, &config) } else { let shared_rx = Arc::new(Mutex::new(rx)); let out_perm = Arc::new(Mutex::new(())); - let path_separator = Arc::new(config.path_separator.clone()); - // Each spawned job will store it's thread handle in here. let mut handles = Vec::with_capacity(threads); for _ in 0..threads { + let config = Arc::clone(&config); let rx = Arc::clone(&shared_rx); let cmd = Arc::clone(cmd); let out_perm = Arc::clone(&out_perm); - let path_separator = path_separator.clone(); - // Spawn a job thread that will listen for and execute inputs. - let handle = thread::spawn(move || { - exec::job( - rx, - cmd, - out_perm, - show_filesystem_errors, - enable_output_buffering, - path_separator.as_deref(), - ) - }); + let handle = thread::spawn(move || exec::job(rx, cmd, out_perm, &config)); // Push the handle of the spawned thread into the vector for later joining. handles.push(handle); diff --git a/tests/tests.rs b/tests/tests.rs index ab49543..9d4ee5a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -61,18 +61,18 @@ fn create_file_with_size>(path: P, size_in_bytes: usize) { fn test_simple() { let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES); - te.assert_output(&["a.foo"], "./a.foo"); - te.assert_output(&["b.foo"], "./one/b.foo"); - te.assert_output(&["d.foo"], "./one/two/three/d.foo"); + te.assert_output(&["a.foo"], "a.foo"); + te.assert_output(&["b.foo"], "one/b.foo"); + te.assert_output(&["d.foo"], "one/two/three/d.foo"); te.assert_output( &["foo"], - "./a.foo - ./one/b.foo - ./one/two/c.foo - ./one/two/C.Foo2 - ./one/two/three/d.foo - ./one/two/three/directory_foo/", + "a.foo + one/b.foo + one/two/c.foo + one/two/C.Foo2 + one/two/three/d.foo + one/two/three/directory_foo/", ); } @@ -80,17 +80,17 @@ fn test_simple() { #[test] fn test_empty_pattern() { let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES); - let expected = "./a.foo - ./e1 e2 - ./one/ - ./one/b.foo - ./one/two/ - ./one/two/c.foo - ./one/two/C.Foo2 - ./one/two/three/ - ./one/two/three/d.foo - ./one/two/three/directory_foo/ - ./symlink"; + let expected = "a.foo + e1 e2 + one/ + one/b.foo + one/two/ + one/two/c.foo + one/two/C.Foo2 + one/two/three/ + one/two/three/d.foo + one/two/three/directory_foo/ + symlink"; te.assert_output(&["--regex"], expected); te.assert_output(&["--fixed-strings"], expected); @@ -209,17 +209,17 @@ fn test_regex_searches() { te.assert_output( &["[a-c].foo"], - "./a.foo - ./one/b.foo - ./one/two/c.foo - ./one/two/C.Foo2", + "a.foo + one/b.foo + one/two/c.foo + one/two/C.Foo2", ); te.assert_output( &["--case-sensitive", "[a-c].foo"], - "./a.foo - ./one/b.foo - ./one/two/c.foo", + "a.foo + one/b.foo + one/two/c.foo", ); } @@ -230,21 +230,21 @@ fn test_smart_case() { te.assert_output( &["c.foo"], - "./one/two/c.foo - ./one/two/C.Foo2", + "one/two/c.foo + one/two/C.Foo2", ); - te.assert_output(&["C.Foo"], "./one/two/C.Foo2"); + te.assert_output(&["C.Foo"], "one/two/C.Foo2"); - te.assert_output(&["Foo"], "./one/two/C.Foo2"); + te.assert_output(&["Foo"], "one/two/C.Foo2"); // Only literal uppercase chars should trigger case sensitivity. te.assert_output( &["\\Ac"], - "./one/two/c.foo - ./one/two/C.Foo2", + "one/two/c.foo + one/two/C.Foo2", ); - te.assert_output(&["\\AC"], "./one/two/C.Foo2"); + te.assert_output(&["\\AC"], "one/two/C.Foo2"); } /// Case sensitivity (--case-sensitive) @@ -252,13 +252,13 @@ fn test_smart_case() { fn test_case_sensitive() { let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES); - te.assert_output(&["--case-sensitive", "c.foo"], "./one/two/c.foo"); + te.assert_output(&["--case-sensitive", "c.foo"], "one/two/c.foo"); - te.assert_output(&["--case-sensitive", "C.Foo"], "./one/two/C.Foo2"); + te.assert_output(&["--case-sensitive", "C.Foo"], "one/two/C.Foo2"); te.assert_output( &["--ignore-case", "--case-sensitive", "C.Foo"], - "./one/two/C.Foo2", + "one/two/C.Foo2", ); } @@ -269,14 +269,14 @@ fn test_case_insensitive() { te.assert_output( &["--ignore-case", "C.Foo"], - "./one/two/c.foo - ./one/two/C.Foo2", + "one/two/c.foo + one/two/C.Foo2", ); te.assert_output( &["--case-sensitive", "--ignore-case", "C.Foo"], - "./one/two/c.foo - ./one/two/C.Foo2", + "one/two/c.foo + one/two/C.Foo2", ); } @@ -287,25 +287,25 @@ fn test_glob_searches() { te.assert_output( &["--glob", "*.foo"], - "./a.foo - ./one/b.foo - ./one/two/c.foo - ./one/two/three/d.foo", + "a.foo + one/b.foo + one/two/c.foo + one/two/three/d.foo", ); te.assert_output( &["--glob", "[a-c].foo"], - "./a.foo - ./one/b.foo - ./one/two/c.foo", + "a.foo + one/b.foo + one/two/c.foo", ); te.assert_output( &["--glob", "[a-c].foo*"], - "./a.foo - ./one/b.foo - ./one/two/C.Foo2 - ./one/two/c.foo", + "a.foo + one/b.foo + one/two/C.Foo2 + one/two/c.foo", ); } @@ -317,19 +317,19 @@ fn test_full_path_glob_searches() { te.assert_output( &["--glob", "--full-path", "**/one/**/*.foo"], - "./one/b.foo - ./one/two/c.foo - ./one/two/three/d.foo", + "one/b.foo + one/two/c.foo + one/two/three/d.foo", ); te.assert_output( &["--glob", "--full-path", "**/one/*/*.foo"], - " ./one/two/c.foo", + " one/two/c.foo", ); te.assert_output( &["--glob", "--full-path", "**/one/*/*/*.foo"], - " ./one/two/three/d.foo", + " one/two/three/d.foo", ); } @@ -339,11 +339,11 @@ fn test_smart_case_glob_searches() { te.assert_output( &["--glob", "c.foo*"], - "./one/two/C.Foo2 - ./one/two/c.foo", + "one/two/C.Foo2 + one/two/c.foo", ); - te.assert_output(&["--glob", "C.Foo*"], "./one/two/C.Foo2"); + te.assert_output(&["--glob", "C.Foo*"], "one/two/C.Foo2"); } /// Glob-based searches (--glob) in combination with --case-sensitive @@ -351,7 +351,7 @@ fn test_smart_case_glob_searches() { fn test_case_sensitive_glob_searches() { let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES); - te.assert_output(&["--glob", "--case-sensitive", "c.foo*"], "./one/two/c.foo"); + te.assert_output(&["--glob", "--case-sensitive", "c.foo*"], "one/two/c.foo"); } /// Glob-based searches (--glob) in combination with --extension @@ -361,7 +361,7 @@ fn test_glob_searches_with_extension() { te.assert_output( &["--glob", "--extension", "foo2", "[a-z].*"], - "./one/two/C.Foo2", + "one/two/C.Foo2", ); } @@ -370,7 +370,7 @@ fn test_glob_searches_with_extension() { fn test_regex_overrides_glob() { let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES); - te.assert_output(&["--glob", "--regex", "Foo2$"], "./one/two/C.Foo2"); + te.assert_output(&["--glob", "--regex", "Foo2$"], "one/two/C.Foo2"); } /// Full path search (--full-path) @@ -386,8 +386,8 @@ fn test_full_path() { "--full-path", &format!("^{prefix}.*three.*foo$", prefix = prefix), ], - "./one/two/three/d.foo - ./one/two/three/directory_foo/", + "one/two/three/d.foo + one/two/three/directory_foo/", ); } @@ -398,13 +398,13 @@ fn test_hidden() { te.assert_output( &["--hidden", "foo"], - "./.hidden.foo - ./a.foo - ./one/b.foo - ./one/two/c.foo - ./one/two/C.Foo2 - ./one/two/three/d.foo - ./one/two/three/directory_foo/", + ".hidden.foo + a.foo + one/b.foo + one/two/c.foo + one/two/C.Foo2 + one/two/three/d.foo + one/two/three/directory_foo/", ); } @@ -426,7 +426,7 @@ fn test_hidden_file_attribute() { .open(te.test_root().join("hidden-file.txt")) .unwrap(); - te.assert_output(&["--hidden", "hidden-file.txt"], "./hidden-file.txt"); + te.assert_output(&["--hidden", "hidden-file.txt"], "hidden-file.txt"); te.assert_output(&["hidden-file.txt"], ""); } @@ -437,27 +437,27 @@ fn test_no_ignore() { te.assert_output( &["--no-ignore", "foo"], - "./a.foo - ./fdignored.foo - ./gitignored.foo - ./one/b.foo - ./one/two/c.foo - ./one/two/C.Foo2 - ./one/two/three/d.foo - ./one/two/three/directory_foo/", + "a.foo + fdignored.foo + gitignored.foo + one/b.foo + one/two/c.foo + one/two/C.Foo2 + one/two/three/d.foo + one/two/three/directory_foo/", ); te.assert_output( &["--hidden", "--no-ignore", "foo"], - "./.hidden.foo - ./a.foo - ./fdignored.foo - ./gitignored.foo - ./one/b.foo - ./one/two/c.foo - ./one/two/C.Foo2 - ./one/two/three/d.foo - ./one/two/three/directory_foo/", + ".hidden.foo + a.foo + fdignored.foo + gitignored.foo + one/b.foo + one/two/c.foo + one/two/C.Foo2 + one/two/three/d.foo + one/two/three/directory_foo/", ); } @@ -482,20 +482,20 @@ fn test_gitignore_and_fdignore() { .write_all(b"ignored-by-gitignore\nignored-by-both") .unwrap(); - te.assert_output(&["ignored"], "./ignored-by-nothing"); + te.assert_output(&["ignored"], "ignored-by-nothing"); te.assert_output( &["--no-ignore-vcs", "ignored"], - "./ignored-by-nothing - ./ignored-by-gitignore", + "ignored-by-nothing + ignored-by-gitignore", ); te.assert_output( &["--no-ignore", "ignored"], - "./ignored-by-nothing - ./ignored-by-fdignore - ./ignored-by-gitignore - ./ignored-by-both", + "ignored-by-nothing + ignored-by-fdignore + ignored-by-gitignore + ignored-by-both", ); } @@ -521,13 +521,13 @@ fn test_no_ignore_parent() { .write_all(b"child-ignored") .unwrap(); - te.assert_output_subdirectory("inner", &[], "./not-ignored"); + te.assert_output_subdirectory("inner", &[], "not-ignored"); te.assert_output_subdirectory( "inner", &["--no-ignore-parent"], - "./parent-ignored - ./not-ignored", + "parent-ignored + not-ignored", ); } @@ -559,15 +559,15 @@ fn test_no_ignore_parent_inner_git() { te.assert_output_subdirectory( "inner", &[], - "./not-ignored - ./parent-ignored", + "not-ignored + parent-ignored", ); te.assert_output_subdirectory( "inner", &["--no-ignore-parent"], - "./not-ignored - ./parent-ignored", + "not-ignored + parent-ignored", ); } @@ -590,11 +590,11 @@ fn test_custom_ignore_precedence() { .write_all(b"!foo") .unwrap(); - te.assert_output(&["foo"], "./inner/foo"); + te.assert_output(&["foo"], "inner/foo"); - te.assert_output(&["--no-ignore-vcs", "foo"], "./inner/foo"); + te.assert_output(&["--no-ignore-vcs", "foo"], "inner/foo"); - te.assert_output(&["--no-ignore", "foo"], "./inner/foo"); + te.assert_output(&["--no-ignore", "foo"], "inner/foo"); } /// VCS ignored files (--no-ignore-vcs) @@ -604,13 +604,13 @@ fn test_no_ignore_vcs() { te.assert_output( &["--no-ignore-vcs", "foo"], - "./a.foo - ./gitignored.foo - ./one/b.foo - ./one/two/c.foo - ./one/two/C.Foo2 - ./one/two/three/d.foo - ./one/two/three/directory_foo/", + "a.foo + gitignored.foo + one/b.foo + one/two/c.foo + one/two/C.Foo2 + one/two/three/d.foo + one/two/three/directory_foo/", ); } @@ -625,8 +625,8 @@ fn test_no_ignore_vcs_child_dir() { te.assert_output_subdirectory( "inner", &["--no-ignore-vcs", "foo"], - "./foo - ./gitignored.foo", + "foo + gitignored.foo", ); } @@ -643,9 +643,9 @@ fn test_custom_ignore_files() { te.assert_output( &["--ignore-file", "custom.ignore", "foo"], - "./a.foo - ./one/b.foo - ./one/two/c.foo", + "a.foo + one/b.foo + one/two/c.foo", ); } @@ -656,15 +656,15 @@ fn test_no_ignore_aliases() { te.assert_output( &["-u", "foo"], - "./.hidden.foo - ./a.foo - ./fdignored.foo - ./gitignored.foo - ./one/b.foo - ./one/two/c.foo - ./one/two/C.Foo2 - ./one/two/three/d.foo - ./one/two/three/directory_foo/", + ".hidden.foo + a.foo + fdignored.foo + gitignored.foo + one/b.foo + one/two/c.foo + one/two/C.Foo2 + one/two/three/d.foo + one/two/three/directory_foo/", ); } @@ -675,10 +675,10 @@ fn test_follow() { te.assert_output( &["--follow", "c.foo"], - "./one/two/c.foo - ./one/two/C.Foo2 - ./symlink/c.foo - ./symlink/C.Foo2", + "one/two/c.foo + one/two/C.Foo2 + symlink/c.foo + symlink/C.Foo2", ); } @@ -733,20 +733,20 @@ fn test_follow_broken_symlink() { te.assert_output( &["symlink"], - "./broken_symlink - ./symlink", + "broken_symlink + symlink", ); te.assert_output( &["--type", "symlink", "symlink"], - "./broken_symlink - ./symlink", + "broken_symlink + symlink", ); te.assert_output(&["--type", "file", "symlink"], ""); te.assert_output( &["--follow", "--type", "symlink", "symlink"], - "./broken_symlink", + "broken_symlink", ); te.assert_output(&["--follow", "--type", "file", "symlink"], ""); } @@ -774,33 +774,33 @@ fn test_max_depth() { te.assert_output( &["--max-depth", "3"], - "./a.foo - ./e1 e2 - ./one/ - ./one/b.foo - ./one/two/ - ./one/two/c.foo - ./one/two/C.Foo2 - ./one/two/three/ - ./symlink", + "a.foo + e1 e2 + one/ + one/b.foo + one/two/ + one/two/c.foo + one/two/C.Foo2 + one/two/three/ + symlink", ); te.assert_output( &["--max-depth", "2"], - "./a.foo - ./e1 e2 - ./one/ - ./one/b.foo - ./one/two/ - ./symlink", + "a.foo + e1 e2 + one/ + one/b.foo + one/two/ + symlink", ); te.assert_output( &["--max-depth", "1"], - "./a.foo - ./e1 e2 - ./one/ - ./symlink", + "a.foo + e1 e2 + one/ + symlink", ); } @@ -811,17 +811,17 @@ fn test_min_depth() { te.assert_output( &["--min-depth", "3"], - "./one/two/c.foo - ./one/two/C.Foo2 - ./one/two/three/ - ./one/two/three/d.foo - ./one/two/three/directory_foo/", + "one/two/c.foo + one/two/C.Foo2 + one/two/three/ + one/two/three/d.foo + one/two/three/directory_foo/", ); te.assert_output( &["--min-depth", "4"], - "./one/two/three/d.foo - ./one/two/three/directory_foo/", + "one/two/three/d.foo + one/two/three/directory_foo/", ); } @@ -832,9 +832,9 @@ fn test_exact_depth() { te.assert_output( &["--exact-depth", "3"], - "./one/two/c.foo - ./one/two/C.Foo2 - ./one/two/three/", + "one/two/c.foo + one/two/C.Foo2 + one/two/three/", ); } @@ -843,32 +843,32 @@ fn test_exact_depth() { fn test_prune() { let dirs = &["foo/bar", "bar/foo", "baz"]; let files = &[ - "./foo/foo.file", - "./foo/bar/foo.file", - "./bar/foo.file", - "./bar/foo/foo.file", - "./baz/foo.file", + "foo/foo.file", + "foo/bar/foo.file", + "bar/foo.file", + "bar/foo/foo.file", + "baz/foo.file", ]; let te = TestEnv::new(dirs, files); te.assert_output( &["foo"], - "./foo/ - ./foo/foo.file - ./foo/bar/foo.file - ./bar/foo.file - ./bar/foo/ - ./bar/foo/foo.file - ./baz/foo.file", + "foo/ + foo/foo.file + foo/bar/foo.file + bar/foo.file + bar/foo/ + bar/foo/foo.file + baz/foo.file", ); te.assert_output( &["--prune", "foo"], - "./foo/ - ./bar/foo/ - ./bar/foo.file - ./baz/foo.file", + "foo/ + bar/foo/ + bar/foo.file + baz/foo.file", ); } @@ -955,34 +955,34 @@ fn test_type() { te.assert_output( &["--type", "f"], - "./a.foo - ./e1 e2 - ./one/b.foo - ./one/two/c.foo - ./one/two/C.Foo2 - ./one/two/three/d.foo", + "a.foo + e1 e2 + one/b.foo + one/two/c.foo + one/two/C.Foo2 + one/two/three/d.foo", ); - te.assert_output(&["--type", "f", "e1"], "./e1 e2"); + te.assert_output(&["--type", "f", "e1"], "e1 e2"); te.assert_output( &["--type", "d"], - "./one/ - ./one/two/ - ./one/two/three/ - ./one/two/three/directory_foo/", + "one/ + one/two/ + one/two/three/ + one/two/three/directory_foo/", ); te.assert_output( &["--type", "d", "--type", "l"], - "./one/ - ./one/two/ - ./one/two/three/ - ./one/two/three/directory_foo/ - ./symlink", + "one/ + one/two/ + one/two/three/ + one/two/three/directory_foo/ + symlink", ); - te.assert_output(&["--type", "l"], "./symlink"); + te.assert_output(&["--type", "l"], "symlink"); } /// Test `--type executable` @@ -1000,15 +1000,15 @@ fn test_type_executable() { .open(te.test_root().join("executable-file.sh")) .unwrap(); - te.assert_output(&["--type", "executable"], "./executable-file.sh"); + te.assert_output(&["--type", "executable"], "executable-file.sh"); te.assert_output( &["--type", "executable", "--type", "directory"], - "./executable-file.sh - ./one/ - ./one/two/ - ./one/two/three/ - ./one/two/three/directory_foo/", + "executable-file.sh + one/ + one/two/ + one/two/three/ + one/two/three/directory_foo/", ); } @@ -1024,19 +1024,19 @@ fn test_type_empty() { te.assert_output( &["--type", "empty"], - "./0_bytes.foo - ./dir_empty/", + "0_bytes.foo + dir_empty/", ); te.assert_output( &["--type", "empty", "--type", "file", "--type", "directory"], - "./0_bytes.foo - ./dir_empty/", + "0_bytes.foo + dir_empty/", ); - te.assert_output(&["--type", "empty", "--type", "file"], "./0_bytes.foo"); + te.assert_output(&["--type", "empty", "--type", "file"], "0_bytes.foo"); - te.assert_output(&["--type", "empty", "--type", "directory"], "./dir_empty/"); + te.assert_output(&["--type", "empty", "--type", "directory"], "dir_empty/"); } /// File extension (--extension) @@ -1046,54 +1046,54 @@ fn test_extension() { te.assert_output( &["--extension", "foo"], - "./a.foo - ./one/b.foo - ./one/two/c.foo - ./one/two/three/d.foo", + "a.foo + one/b.foo + one/two/c.foo + one/two/three/d.foo", ); te.assert_output( &["--extension", ".foo"], - "./a.foo - ./one/b.foo - ./one/two/c.foo - ./one/two/three/d.foo", + "a.foo + one/b.foo + one/two/c.foo + 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", + "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", ".foo", "a"], "a.foo"); - te.assert_output(&["--extension", "foo2"], "./one/two/C.Foo2"); + te.assert_output(&["--extension", "foo2"], "one/two/C.Foo2"); let te2 = TestEnv::new(&[], &["spam.bar.baz", "egg.bar.baz", "yolk.bar.baz.sig"]); te2.assert_output( &["--extension", ".bar.baz"], - "./spam.bar.baz - ./egg.bar.baz", + "spam.bar.baz + egg.bar.baz", ); - te2.assert_output(&["--extension", "sig"], "./yolk.bar.baz.sig"); + te2.assert_output(&["--extension", "sig"], "yolk.bar.baz.sig"); - te2.assert_output(&["--extension", "bar.baz.sig"], "./yolk.bar.baz.sig"); + te2.assert_output(&["--extension", "bar.baz.sig"], "yolk.bar.baz.sig"); let te3 = TestEnv::new(&[], &["latin1.e\u{301}xt", "smiley.☻"]); - te3.assert_output(&["--extension", "☻"], "./smiley.☻"); + te3.assert_output(&["--extension", "☻"], "smiley.☻"); - te3.assert_output(&["--extension", ".e\u{301}xt"], "./latin1.e\u{301}xt"); + te3.assert_output(&["--extension", ".e\u{301}xt"], "latin1.e\u{301}xt"); let te4 = TestEnv::new(&[], &[".hidden", "test.hidden"]); - te4.assert_output(&["--hidden", "--extension", ".hidden"], "./test.hidden"); + te4.assert_output(&["--hidden", "--extension", ".hidden"], "test.hidden"); } /// No file extension (test for the pattern provided in the --help text) @@ -1106,21 +1106,21 @@ fn test_no_extension() { te.assert_output( &["^[^.]+$"], - "./aa - ./one/ - ./one/bb - ./one/two/ - ./one/two/three/ - ./one/two/three/d - ./one/two/three/directory_foo/ - ./symlink", + "aa + one/ + one/bb + one/two/ + one/two/three/ + one/two/three/d + one/two/three/directory_foo/ + symlink", ); te.assert_output( &["^[^.]+$", "--type", "file"], - "./aa - ./one/bb - ./one/two/three/d", + "aa + one/bb + one/two/three/d", ); } @@ -1255,46 +1255,46 @@ fn test_excludes() { te.assert_output( &["--exclude", "*.foo"], - "./one/ - ./one/two/ - ./one/two/C.Foo2 - ./one/two/three/ - ./one/two/three/directory_foo/ - ./e1 e2 - ./symlink", + "one/ + one/two/ + one/two/C.Foo2 + one/two/three/ + one/two/three/directory_foo/ + e1 e2 + symlink", ); te.assert_output( &["--exclude", "*.foo", "--exclude", "*.Foo2"], - "./one/ - ./one/two/ - ./one/two/three/ - ./one/two/three/directory_foo/ - ./e1 e2 - ./symlink", + "one/ + one/two/ + one/two/three/ + one/two/three/directory_foo/ + e1 e2 + symlink", ); te.assert_output( &["--exclude", "*.foo", "--exclude", "*.Foo2", "foo"], - "./one/two/three/directory_foo/", + "one/two/three/directory_foo/", ); te.assert_output( &["--exclude", "one/two/", "foo"], - "./a.foo - ./one/b.foo", + "a.foo + one/b.foo", ); te.assert_output( &["--exclude", "one/**/*.foo"], - "./a.foo - ./e1 e2 - ./one/ - ./one/two/ - ./one/two/C.Foo2 - ./one/two/three/ - ./one/two/three/directory_foo/ - ./symlink", + "a.foo + e1 e2 + one/ + one/two/ + one/two/C.Foo2 + one/two/three/ + one/two/three/directory_foo/ + symlink", ); } @@ -1327,6 +1327,16 @@ fn test_exec() { ./one/two/three/directory_foo", ); + te.assert_output( + &["foo", "--strip-cwd-prefix", "--exec", "echo", "{}"], + "a.foo + one/b.foo + one/two/C.Foo2 + one/two/c.foo + one/two/three/d.foo + one/two/three/directory_foo", + ); + te.assert_output( &["foo", "--exec", "echo", "{.}"], "a @@ -1452,6 +1462,11 @@ fn test_exec_batch() { "./a.foo ./one/b.foo ./one/two/C.Foo2 ./one/two/c.foo ./one/two/three/d.foo ./one/two/three/directory_foo", ); + te.assert_output( + &["foo", "--strip-cwd-prefix", "--exec-batch", "echo", "{}"], + "a.foo one/b.foo one/two/C.Foo2 one/two/c.foo one/two/three/d.foo one/two/three/directory_foo", + ); + te.assert_output( &["foo", "--exec-batch", "echo", "{/}"], "a.foo b.foo C.Foo2 c.foo d.foo directory_foo", @@ -1687,12 +1702,12 @@ fn test_fixed_strings() { // Regex search, dot is treated as "any character" te.assert_output( &["a.foo"], - "./test1/a.foo - ./test1/a_foo", + "test1/a.foo + test1/a_foo", ); // Literal search, dot is treated as character - te.assert_output(&["--fixed-strings", "a.foo"], "./test1/a.foo"); + te.assert_output(&["--fixed-strings", "a.foo"], "test1/a.foo"); // Regex search, parens are treated as group te.assert_output(&["download (1)"], ""); @@ -1700,7 +1715,7 @@ fn test_fixed_strings() { // Literal search, parens are treated as characters te.assert_output( &["--fixed-strings", "download (1)"], - "./test2/Download (1).tar.gz", + "test2/Download (1).tar.gz", ); // Combine with --case-sensitive @@ -1746,81 +1761,69 @@ fn test_size() { // Zero and non-zero sized files. te.assert_output( &["", "--size", "+0B"], - "./0_bytes.foo - ./11_bytes.foo - ./30_bytes.foo - ./3_kilobytes.foo - ./4_kibibytes.foo", + "0_bytes.foo + 11_bytes.foo + 30_bytes.foo + 3_kilobytes.foo + 4_kibibytes.foo", ); // Zero sized files. - te.assert_output(&["", "--size", "-0B"], "./0_bytes.foo"); - te.assert_output(&["", "--size", "0B"], "./0_bytes.foo"); - te.assert_output(&["", "--size=0B"], "./0_bytes.foo"); - te.assert_output(&["", "-S", "0B"], "./0_bytes.foo"); + te.assert_output(&["", "--size", "-0B"], "0_bytes.foo"); + te.assert_output(&["", "--size", "0B"], "0_bytes.foo"); + te.assert_output(&["", "--size=0B"], "0_bytes.foo"); + te.assert_output(&["", "-S", "0B"], "0_bytes.foo"); // Files with 2 bytes or more. te.assert_output( &["", "--size", "+2B"], - "./11_bytes.foo - ./30_bytes.foo - ./3_kilobytes.foo - ./4_kibibytes.foo", + "11_bytes.foo + 30_bytes.foo + 3_kilobytes.foo + 4_kibibytes.foo", ); // Files with 2 bytes or less. - te.assert_output(&["", "--size", "-2B"], "./0_bytes.foo"); + te.assert_output(&["", "--size", "-2B"], "0_bytes.foo"); // Files with size between 1 byte and 11 bytes. - te.assert_output(&["", "--size", "+1B", "--size", "-11B"], "./11_bytes.foo"); + te.assert_output(&["", "--size", "+1B", "--size", "-11B"], "11_bytes.foo"); // Files with size equal 11 bytes. - te.assert_output(&["", "--size", "11B"], "./11_bytes.foo"); + te.assert_output(&["", "--size", "11B"], "11_bytes.foo"); // Files with size between 1 byte and 30 bytes. te.assert_output( &["", "--size", "+1B", "--size", "-30B"], - "./11_bytes.foo - ./30_bytes.foo", + "11_bytes.foo + 30_bytes.foo", ); // Combine with a search pattern - te.assert_output( - &["^11_", "--size", "+1B", "--size", "-30B"], - "./11_bytes.foo", - ); + te.assert_output(&["^11_", "--size", "+1B", "--size", "-30B"], "11_bytes.foo"); // Files with size between 12 and 30 bytes. - te.assert_output(&["", "--size", "+12B", "--size", "-30B"], "./30_bytes.foo"); + te.assert_output(&["", "--size", "+12B", "--size", "-30B"], "30_bytes.foo"); // Files with size between 31 and 100 bytes. te.assert_output(&["", "--size", "+31B", "--size", "-100B"], ""); // Files with size between 3 kibibytes and 5 kibibytes. - te.assert_output( - &["", "--size", "+3ki", "--size", "-5ki"], - "./4_kibibytes.foo", - ); + te.assert_output(&["", "--size", "+3ki", "--size", "-5ki"], "4_kibibytes.foo"); // Files with size between 3 kilobytes and 5 kilobytes. te.assert_output( &["", "--size", "+3k", "--size", "-5k"], - "./3_kilobytes.foo - ./4_kibibytes.foo", + "3_kilobytes.foo + 4_kibibytes.foo", ); // Files with size greater than 3 kilobytes and less than 3 kibibytes. - te.assert_output( - &["", "--size", "+3k", "--size", "-3ki"], - "./3_kilobytes.foo", - ); + te.assert_output(&["", "--size", "+3k", "--size", "-3ki"], "3_kilobytes.foo"); // Files with size equal 4 kibibytes. - te.assert_output( - &["", "--size", "+4ki", "--size", "-4ki"], - "./4_kibibytes.foo", - ); - te.assert_output(&["", "--size", "4ki"], "./4_kibibytes.foo"); + te.assert_output(&["", "--size", "+4ki", "--size", "-4ki"], "4_kibibytes.foo"); + te.assert_output(&["", "--size", "4ki"], "4_kibibytes.foo"); } #[cfg(test)] @@ -1856,23 +1859,23 @@ fn test_modified_relative() { te.assert_output( &["", "--changed-within", "15min"], - "./foo_0_now - ./bar_1_min - ./foo_10_min", + "foo_0_now + bar_1_min + foo_10_min", ); te.assert_output( &["", "--change-older-than", "15min"], - "./bar_1_h - ./foo_2_h - ./bar_1_day", + "bar_1_h + foo_2_h + bar_1_day", ); te.assert_output( &["foo", "--changed-within", "12h"], - "./foo_0_now - ./foo_10_min - ./foo_2_h", + "foo_0_now + foo_10_min + foo_2_h", ); } @@ -1892,11 +1895,11 @@ fn test_modified_absolute() { te.assert_output( &["", "--change-newer-than", "2018-01-01 00:00:00"], - "./15mar2018", + "15mar2018", ); te.assert_output( &["", "--changed-before", "2018-01-01 00:00:00"], - "./30dec2017", + "30dec2017", ); } @@ -1920,21 +1923,21 @@ fn test_base_directory() { te.assert_output( &["--base-directory", "one"], - "./b.foo - ./two/ - ./two/c.foo - ./two/C.Foo2 - ./two/three/ - ./two/three/d.foo - ./two/three/directory_foo/", + "b.foo + two/ + two/c.foo + two/C.Foo2 + two/three/ + two/three/d.foo + two/three/directory_foo/", ); te.assert_output( &["--base-directory", "one/two/", "foo"], - "./c.foo - ./C.Foo2 - ./three/d.foo - ./three/directory_foo/", + "c.foo + C.Foo2 + three/d.foo + three/directory_foo/", ); // Explicit root path @@ -1970,15 +1973,15 @@ fn test_max_results() { // Unrestricted te.assert_output( &["--max-results=0", "c.foo"], - "./one/two/C.Foo2 - ./one/two/c.foo", + "one/two/C.Foo2 + one/two/c.foo", ); // Limited to two results te.assert_output( &["--max-results=2", "c.foo"], - "./one/two/C.Foo2 - ./one/two/c.foo", + "one/two/C.Foo2 + one/two/c.foo", ); // Limited to one result. We could find either C.Foo2 or c.foo @@ -1987,7 +1990,7 @@ fn test_max_results() { let stdout = String::from_utf8_lossy(&output.stdout) .trim() .replace(&std::path::MAIN_SEPARATOR.to_string(), "/"); - assert!(stdout == "./one/two/C.Foo2" || stdout == "./one/two/c.foo"); + assert!(stdout == "one/two/C.Foo2" || stdout == "one/two/c.foo"); }; assert_just_one_result_with_option("--max-results=1"); assert_just_one_result_with_option("-1"); @@ -2100,8 +2103,8 @@ fn test_error_if_hidden_not_set_and_pattern_starts_with_dot() { te.assert_failure(&["^\\.gitignore"]); te.assert_failure(&["--glob", ".gitignore"]); - te.assert_output(&["--hidden", "^\\.gitignore"], "./.gitignore"); - te.assert_output(&["--hidden", "--glob", ".gitignore"], "./.gitignore"); + te.assert_output(&["--hidden", "^\\.gitignore"], ".gitignore"); + te.assert_output(&["--hidden", "--glob", ".gitignore"], ".gitignore"); te.assert_output(&[".gitignore"], ""); }