Compare commits

...

148 Commits

Author SHA1 Message Date
sharkdp f064ee5509 Properly use .ignore files, see #156 2018-10-27 18:11:50 +02:00
sharkdp 26c2371835 Bump version 2018-10-27 17:09:00 +02:00
sharkdp 8832e471bc Update man page 2018-10-27 17:07:09 +02:00
sharkdp daf5191aac Add note about .ignore files 2018-10-27 16:44:34 +02:00
sharkdp c0c38872c4 Update usage 2018-10-27 16:37:41 +02:00
sharkdp 81a27fa9bd Unify error messages, closes #342 2018-10-27 16:30:29 +02:00
sharkdp 6ff58abb6c Re-enable .ignore files, closes #156 2018-10-27 15:42:02 +02:00
sharkdp 974802e817 Hide a few options from short '-h' text 2018-10-27 15:38:26 +02:00
sharkdp deea31cbbc Add aliases, change help text 2018-10-27 15:34:10 +02:00
Josh Leeb-du Toit caad43fe6c Use oss_vec fn in internal/mod.rs tests 2018-10-24 16:31:16 +02:00
Park Juhyung 095bad550f Print errors when --verbose is set 2018-10-23 21:50:25 +02:00
Park Juhyung f7a96bd980 Fix typo at max-buffer-time's help message
"tothe" -> "to the"
2018-10-23 07:03:21 +02:00
Casey Primozic cdf737bf5c Add search-path option
* Multiple `--search-path` arguments can be supplied which replace the positional `path` argument for providing directories which will be searched.
 * When a `--search-path` argument is provided, positional `path` arguments will not be allowed
2018-10-22 19:26:36 +02:00
yobrave e2bb932f87 Doc: add chinese readme link (#347) 2018-10-19 22:05:15 +02:00
David Peter 437bd1188e Remove shell prompt 2018-10-14 19:27:03 +02:00
David Peter dae09ecf6d Add recommendation for "alias fd=fdfind" 2018-10-14 19:26:22 +02:00
Sylvestre Ledru d81cf34771 fd is now officially in Debian (#345)
* fd is now officially in Debian
2018-10-14 19:21:16 +02:00
Benjamin Fox b98f0783ff Enable link-time optimization in release builds
This gives a significant improvement to runtime performance, at the cost of somewhat worse compile times.
2018-10-14 18:47:25 +02:00
Josh Leeb-du Toit 8543ca645d Split internals.rs into module and multiple files
This PR splits `internals.rs` into the `internal` module with multiple
files, and moves `FdOptions` into `opts.rs`.

The main motivation behind this is to move logic for constructing
`FdOptions` out of the main function and more readable and easier to
understand in the `opts` module. The goal will eventually to be able
to write `FdOptions::from(matches)` and have the options constructed.
2018-10-12 19:14:19 +02:00
Josh Leeb-du Toit 984f04f142 Add print_error macros to replace functions
This patch replaces the `print_error` and `print_error_and_exit`
functions with equivalent macros.
2018-10-12 07:31:21 +02:00
Karim SENHAJI abe8aa55c0 clean-up first implementation of modification date filter 2018-10-10 19:52:37 +02:00
Karim SENHAJI 54c117d72f Add support for --changed-before and --changed-with for modification time based search 2018-10-10 19:52:37 +02:00
Josh Leeb-du Toit 8691ab4bed Minor changes to improve readability
This patch makes minor changes to make the code a little easier to
understand. For example, using the `?` operator in some fns that return
an `Option` type.
2018-10-04 09:44:09 +02:00
Josh Leeb-du Toit cb1cfa108b ErrorCode enum variants to be more descriptive 2018-10-03 16:06:18 +02:00
Josh Leeb-du Toit 8bdd8f8e8f Move exit code consts into enum
Previously, the constants defined in `src/exit_codes` weren't being
used, and the constants for exit codes were being redefined in the
`internals` module.

This PR removes the exit code consts and instead uses an enum defined in
`src/exit_codes`. This centralizes the definitions of exit codes making
them easier to modify and keep track of.
2018-10-03 16:06:18 +02:00
psinghal20 f9c4e8d399 refactor: loosen strict handling of missing --ignore-file 2018-10-03 13:11:51 +02:00
Josh Leeb-du Toit adc467b2b2 Add exec tests and cfg(test) compiler guards
Some tests throughout the codebase were just functions marked with the
`#[test]` macro. It is convention to have these test functions in a
`test` module with a compiler guard `cfg(test)`. This PR updates the
tests that aren't already setup like this to be in the `test` module.

Additionally, this PR also adds tests to the `exec` module to check the
remaining `Token` enum variations.
2018-10-03 13:04:22 +02:00
sharkdp 56b6389dac --exec: Do not exit on errors 2018-10-01 22:00:23 +02:00
sharkdp 046b0574dc Rename error => print_error_and_exit and introduce print_error 2018-10-01 22:00:23 +02:00
psinghal20 6407dc4eb4 chore: format code via rustfmt 2018-10-01 22:00:23 +02:00
psinghal20 2ebef2d46f feat: enable display of error messages 2018-10-01 22:00:23 +02:00
sharkdp 27caa33729 cargo fmt 2018-09-27 23:01:38 +02:00
sharkdp 53226f82eb Upgrade minimum reqiured version of Rust to 1.29
This upgrades the minimum required version of Rust to 1.29 in order to
fix #288.

See also:
- Rust compiler bug ticket: rust-lang/rust#50619
- Rust compiler PR with the fix: rust-lang/rust#50630

closes #288
2018-09-18 21:04:09 +02:00
sharkdp 8fb25f15c9 Just reference --max-depth in --maxdepth help 2018-09-13 22:33:18 +02:00
Mahmoud Al-Qudsi d375b5e1f5 Add --maxdepth as hidden alias for --max-depth
For muscle memory compatibility with `rg`. This variant will not show in
the help or in the program options, and is only checked if `--maxdepth`
is not specified.
2018-09-13 22:33:18 +02:00
sharkdp 2465cd1399 Update man page 2018-08-19 18:50:47 +02:00
sharkdp 1c7e647f61 Bump version 2018-08-19 18:50:41 +02:00
sharkdp 67e76b8b5b Update USAGE 2018-08-19 18:50:35 +02:00
sharkdp aa70c5a446 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
2018-08-19 17:05:04 +02:00
sharkdp 50a2bab5cd Use short-circuiting for --type searches
Reverses the order of boolean checks for `--type` searches,
making them about 10% to 50% faster(!).
2018-08-19 16:27:23 +02:00
sharkdp 641976cf7a Remove duplicated lstat syscall
Removes a unnecessary `lstat` syscall by calling `.metadata()` only
once. This makes `--type executable` searches about 15% faster.
2018-08-19 16:23:06 +02:00
sharkdp 6fb5004b85 Enable ARM cross-compilation
closes #244
2018-08-19 15:30:59 +02:00
kpcyrd e4f61bc795 Remove unused windows dependencies 2018-08-18 16:40:08 +02:00
sharkdp c428f52209 Run cargo fmt 2018-08-03 21:20:48 +02:00
sharkdp 59c9901cde Update dependencies, fix #313 2018-08-03 21:20:48 +02:00
Alexandru Macovei 4dee8b7940 fix build for rust 1.20.0 2018-07-02 19:55:03 +02:00
Alexandru Macovei 9a45947234 simplify SizeFilter as enum with value instead of struct
Also move the touched cfg(test) code into cfg(test) module and trim test names
2018-07-02 19:55:03 +02:00
sharkdp c1ef68662c Update for new rustfmt 2018-05-14 21:00:00 +02:00
Josh Sleeper 9628d7fdcb chore(crate-updates): updated all crates to latest versions 2018-05-05 08:56:04 +02:00
David Peter cb47be6497 Fedora is now in the official repositories 2018-05-02 20:54:08 +02:00
sharkdp a3200204dc Simplifications to --size tests 2018-04-29 22:26:10 +02:00
Jonathan e2b2701965 Add helper method to create sized files, use meaningful names and add kibi and kilo tests. 2018-04-29 21:56:31 +02:00
Jonathan 409cf0c7a1 Remove use (which broke the build) 2018-04-29 21:56:31 +02:00
Jonathan f01685c63f Add tests for SizeFilter (--size) fixes sharkdp/fd#289 2018-04-29 21:56:31 +02:00
David Peter 060d14b5dc Update README.md 2018-04-26 19:53:04 +02:00
Rainer Müller fe922688b3 README: Add install instructions for MacPorts 2018-04-26 19:51:04 +02:00
sharkdp 4172ed03f0 Do not include non-files when using --size 2018-04-25 23:08:05 +02:00
Steve Pentland 0bbc7f5219 Update regex and match, update comments 2018-04-25 08:14:12 +02:00
Steve Pentland ace3f512d3 Make compatible with min rust version 2018-04-25 08:14:12 +02:00
Steve Pentland 2f3b472dfd Changes from review 2018-04-25 08:14:12 +02:00
Steve Pentland 0207c1371e Initial impl of size constraints.
This adds find-style size constraints with + or - indicating greater
or less than, a numerical size, and a unit
2018-04-25 08:14:12 +02:00
David Peter fea85dbfb3 Re-enable nightly 2018-04-21 12:54:00 +02:00
sharkdp b5e5b132c8 Temporarily disable nightly, rustc-ap-rustc_errors is broken 2018-04-20 08:28:18 +02:00
sharkdp 6a9f16e159 Run latest version of rustfmt 2018-04-13 23:13:22 +02:00
sharkdp f02b3cba6a Fix typos 2018-04-13 23:13:22 +02:00
Chen Bin 666f90db87 Re-org fzf part. Doc on using fd with Emacs 2018-04-13 23:13:22 +02:00
Avindra Goolcharan 2a1ee0b17d README: openSUSE package available
add software.opensuse.org link and `zypper in fd`
2018-04-01 17:25:23 +02:00
sharkdp 388b90e624 Update USAGE and man page 2018-03-26 10:25:33 +02:00
sharkdp 011e09840a Bump version number 2018-03-26 10:16:18 +02:00
sharkdp 37483036e0 Implement --ignore-file 2018-03-26 08:28:22 +02:00
David Peter 8c1b037279 Normalize absolute paths, fixes #268 (#270)
* Normalize absolute paths, fixes #268
* Split unit tests into several tests
2018-03-25 23:47:58 +02:00
sharkdp f9a32583a5 Move is_executable to fshelper module 2018-03-25 19:48:09 +02:00
sharkdp 9d840dd485 Separate unit tests 2018-03-25 19:00:46 +02:00
sharkdp 4f6c43a6dc Unit test for --type x 2018-03-25 18:37:50 +02:00
sharkdp 2cf8e7b8a5 Move is_executable to internal module 2018-03-25 18:37:50 +02:00
Pramod Bisht 67f6fdf6a7 Code formating 2018-03-25 16:53:12 +02:00
Pramod Bisht 8ce127e443 changed executable=> executable_only 2018-03-25 16:53:12 +02:00
Pramod Bisht b1e48efc4a Addresses #246
Some implementation to search by filetype `executables`
2018-03-25 16:53:12 +02:00
joachimschmidt557 ab3ded8d27 Update README.md 2018-03-16 06:11:32 +01:00
sharkdp 5397787824 Skip fifos, sockets, .. when using --type f/d/l 2018-03-15 21:38:52 +01:00
sharkdp ffd49c60bd Error if user supplied path instead of pattern 2018-03-15 07:53:00 +01:00
sharkdp 89a8f5573d Formatting change 2018-03-15 07:53:00 +01:00
sharkdp d49b0aa977 Fix small mistake in README 2018-03-14 22:40:49 +01:00
KmBKeef ddddc3f1db fd on Fedora copr (#255)
created fd rpm and added it to Fedora copr
2018-03-14 21:12:16 +01:00
David Peter 87d0ccd4c0 Documentation for -E and .fdignore, see #156 2018-03-13 22:54:08 +01:00
sharkdp c654159bf2 Update to ansi_term 0.11 2018-03-12 23:19:20 +01:00
Josh Stone 588b0dc7c9 Update to regex-syntax 0.5
The new regex 0.2.7 has updated to regex-syntax 0.5, so it seems prudent
to update fd's dependency too, if only to avoid duplication.
2018-03-12 22:11:00 +01:00
sharkdp 88fb9d73d1 Run updated version of rustfmt 2018-03-12 17:46:13 +01:00
sharkdp 3386b854b0 Run invalid-utf8 on Linux only 2018-02-26 08:20:31 +01:00
sharkdp 47d95284aa Skip invalid utf8 filenames, closes #250 2018-02-26 08:20:31 +01:00
sharkdp 631931b431 Only construct entry_path if needed 2018-02-25 18:17:11 +01:00
sharkdp 40d811c7be Rewrite of file-type filtering leading to 5% speed-up 2018-02-25 18:17:11 +01:00
sharkdp e5ee5eb7b3 Minor speedup for strip_prefix 2018-02-25 10:25:59 +01:00
David Peter 82eeeda504 Update README.md 2018-02-24 12:07:24 +01:00
Chawye Hsu f16552f29c Add a installation method for Windows 2018-02-24 12:06:46 +01:00
sharkdp e30bc16780 Fix symlink filetype shortcut in man page 2018-02-21 22:58:22 +01:00
sharkdp b4be1f161c Add support for .fdignore files 2018-02-21 22:55:26 +01:00
sharkdp 8805bd32e2 Update dependencies 2018-02-21 22:55:26 +01:00
Pramod Bisht 7a3994decb Changes as per the instruction of cargo fmt -- --write-mode=diff 2018-02-19 21:17:11 +01:00
Pramod Bisht 7e8c2248d0 Fixes: #234
Giving suggestion to use `--fixed-strings` in case user passes invalid metacharacters as a regex.
2018-02-19 21:17:11 +01:00
Georgy Yakovlev ad8256c217 README.md: gentoo now has fd ebuild, document it 2018-02-19 08:38:14 +01:00
David Peter 1bf193af60 Update links to license files 2018-02-13 20:41:04 +01:00
David Peter 1c1be76f35 Update README.md 2018-02-13 08:10:16 +01:00
David Peter 267598fba5 Arch Linux package was renamed to 'fd' 2018-02-11 11:41:54 +01:00
sharkdp 11b3e96d2d Install fakeroot when deploying 2018-02-10 17:23:26 +01:00
sharkdp a6086abe7f Updates for v6.3.0 2018-02-10 16:46:42 +01:00
David Peter 7f65e7a0d6 Update benchmark results 2018-02-10 16:43:36 +01:00
sharkdp 814d37030e Implement -F/--fixed-strings/--literal 2018-02-10 15:55:06 +01:00
Lewis Belcher 089c183d30 Update README.md
The suggested use of `--ignore` no longer exists (renamed to `--no-ignore`)
2018-02-10 14:19:54 +01:00
Martin Larralde 370d9f081f Use RegexSet instead of hand-written parser 2018-02-10 14:18:37 +01:00
Martin Larralde 86fe9977e8 Implement multiple suffixes extension support with tests (#214) 2018-02-10 14:18:37 +01:00
Martin Larralde b1bb60ebb9 Install rustfmt-nightly in debug mode in Travis (#231) 2018-02-07 19:50:10 +01:00
Steve Pentland b4d39270d6 Allow -exec to work as --exec (#226)
* Replace usage of `-exec` by `--exec`, except when it is used as a value that is given to `--exec/-exec/-x`.
* Limit usage of --exec option to single instance.
2018-01-29 20:32:46 +01:00
sharkdp 6d83eea32b Small screencast updates 2018-01-28 15:44:07 +01:00
Mario Nebl 26beaf97a6 Update screencast 2018-01-28 12:59:27 +01:00
Mario Nebl 33d37d7623 Avoid glitches on new prompt lines 2018-01-28 12:59:27 +01:00
Mario Nebl dce004afef Use updated screencast 2018-01-28 12:59:27 +01:00
Mario Nebl c1941cc7ed Change to working char to signify carriage returns 2018-01-28 12:59:27 +01:00
Mario Nebl 0a1c65b9ce Delay command with output before them 2018-01-28 12:59:27 +01:00
Mario Nebl 4fb9d84ffc Use new screencast
* Might not require rawgit as per https://github.com/marionebl/svg-term-cli/issues/5#issuecomment-360037548
2018-01-28 12:59:27 +01:00
Mario Nebl 2a84eb5cea Update screencast 2018-01-28 12:59:27 +01:00
Mario Nebl 6dd59b33b1 Document svg-terminal command 2018-01-28 12:59:27 +01:00
Mario Nebl 4fea5f3a3c Ensure -h output can be read at end of cast 2018-01-28 12:59:27 +01:00
Mario Nebl b269f4902e Demonstrate -e and --exec flags 2018-01-28 12:59:27 +01:00
Mario Nebl 33791d8400 Use ▶ prompt 2018-01-28 12:59:27 +01:00
Mario Nebl 4e737b4b44 Use upstream src 2018-01-28 12:59:27 +01:00
Mario Nebl c985ba2e6f Use local svg demo 2018-01-28 12:59:27 +01:00
Mario Nebl 658a97a94a Add svg screencast 2018-01-28 12:59:27 +01:00
Dock O'Neal bc2b7a33ae Fixing exec hang by disabling ctrl-c handling for exec 2018-01-25 07:47:04 +01:00
Vinaya 1045930dd9 Fix typos (#218)
This fixes few typos: "distributions", "want to" instead of "want so".
2018-01-10 13:02:25 +01:00
David Peter cc36543da2 Add GA
I'm curious :-)
2018-01-09 17:41:02 +01:00
sharkdp 050fa9c3b1 Fix file owner in deb package, closes #213
Also, fix a few lintian warnings / errors.
2018-01-04 19:48:59 +01:00
sharkdp 6be33fbf33 Update dependencies 2018-01-03 10:40:28 +01:00
sharkdp 61a065dd92 Update version information 2018-01-03 10:40:28 +01:00
sharkdp 941caeb053 Update documentation 2018-01-03 10:40:28 +01:00
David Peter 32a34cf8c9 Introduce maximum output buffer size (#212)
Closes #191
2018-01-03 10:40:08 +01:00
sharkdp 39fb41f05a Move exit codes to 'internal' module 2018-01-03 10:00:22 +01:00
Dock 26ad7da347 Using wants_to_quit to exit the sender as well as the reciever (#211)
Closes #210
2018-01-03 09:26:11 +01:00
sharkdp c5b920b913 Fix shell completion directories for Ubuntu 2018-01-03 09:10:33 +01:00
sharkdp 37613d6989 Fix rustftm check 2018-01-02 23:05:16 +01:00
David Peter b4ae0dca7e Add instructions on how to use fd with fzf 2018-01-02 22:26:38 +01:00
sharkdp 8dde7c3365 Re-enable help-text wrapping 2018-01-01 16:01:29 +01:00
sharkdp a5f5ad6254 Re-enable code style check 2018-01-01 16:00:32 +01:00
sharkdp aeb3722b69 Update installation instructions, see #174 2018-01-01 15:27:05 +01:00
Thejaswi Kadur faf934da4b Added support for filtering by multiple filetypes and extensions (#205)
Closes #177 
Closes #199
2018-01-01 12:09:33 +01:00
Dock 4d66c84109 Correct exit codes (#202)
* Changed exit code on ctrl-c to 130 to match specification
* Changed exit code when stdout errors. 1 for general error.
2017-12-18 06:53:34 -08:00
Thayne McCombs 2bc72a5eca Fix bash_completion path in deb. 2017-12-14 19:26:24 -08:00
sharkdp 8702133ee8 Temporarily disable the cargo fmt check 2017-12-13 19:23:38 -08:00
33 changed files with 2459 additions and 794 deletions
+19 -25
View File
@@ -1,8 +1,8 @@
language: rust
cache: cargo
matrix:
allow_failures:
- rust: nightly
# allow_failures:
# - rust: nightly
include:
# Stable channel.
- os: linux
@@ -20,6 +20,11 @@ matrix:
- os: osx
rust: stable
env: TARGET=x86_64-apple-darwin
- os: linux
rust: stable
env:
- TARGET=arm-unknown-linux-gnueabihf
- CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
# Beta channel.
- os: linux
@@ -59,42 +64,33 @@ matrix:
# Minimum Rust supported channel.
- os: linux
rust: 1.20.0
rust: 1.29.0
env: TARGET=x86_64-unknown-linux-gnu
- os: linux
rust: 1.20.0
rust: 1.29.0
env: TARGET=x86_64-unknown-linux-musl
- os: linux
rust: 1.20.0
rust: 1.29.0
env: TARGET=i686-unknown-linux-gnu
- os: linux
rust: 1.20.0
rust: 1.29.0
env: TARGET=i686-unknown-linux-musl
- os: osx
rust: 1.20.0
rust: 1.29.0
env: TARGET=x86_64-apple-darwin
# Code formatting check
- os: linux
rust: stable
# skip the global install step
install:
- which cargo-install-update || cargo install cargo-update
- cargo install-update -i rustfmt
script: cargo fmt -- --write-mode=diff
- os: linux
rust: nightly
# skip the global install step
install:
- which cargo-install-update || cargo install cargo-update
- cargo install-update -i rustfmt-nightly
script: cargo fmt -- --write-mode=diff
- cargo install --debug --force rustfmt-nightly
script: cargo fmt -- --check
addons:
apt:
packages:
# needed for i686-unknown-linux-gnu target
- gcc-multilib
sudo: required
before_install:
- ci/before_install.bash
env:
global:
@@ -109,9 +105,7 @@ install:
- if [[ $TRAVIS_OS_NAME = linux && $HOST != $TARGET ]]; then rustup target add $TARGET; fi
script:
# Incorporate TARGET env var to the build and test process
- cargo build --target $TARGET --verbose
- cargo test --target $TARGET --verbose
- ci/script.bash
before_deploy:
- bash ci/before_deploy.bash
+1 -1
View File
@@ -16,5 +16,5 @@ give us the chance to discuss any potential changes first.
* [Open pull requests](https://github.com/sharkdp/fd/pulls)
* [Development section in the README](https://github.com/sharkdp/fd#development)
* [fd on crates.io](https://crates.io/crates/fd-find)
* [LICENSE](https://github.com/sharkdp/fd/blob/master/LICENSE)
* [LICENSE-APACHE](https://github.com/sharkdp/fd/blob/master/LICENSE-APACHE) and [LICENSE-MIT](https://github.com/sharkdp/fd/blob/master/LICENSE-MIT)
Generated
+218 -162
View File
@@ -1,27 +1,6 @@
[root]
name = "fd-find"
version = "6.1.0"
dependencies = [
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "aho-corasick"
version = "0.6.4"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -29,67 +8,64 @@ dependencies = [
[[package]]
name = "ansi_term"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ansi_term"
version = "0.10.2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.3"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "0.7.0"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.0.1"
name = "cc"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.2"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.29.0"
version = "2.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam"
version = "0.2.10"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ctrlc"
version = "3.0.3"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -97,6 +73,37 @@ name = "diff"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fd-find"
version = "7.2.0"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"filetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "filetime"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fnv"
version = "1.0.6"
@@ -104,45 +111,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-zircon"
version = "0.2.1"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.2.0"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "globset"
version = "0.2.1"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "humantime"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ignore"
version = "0.2.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -156,30 +171,23 @@ dependencies = [
[[package]]
name = "lazy_static"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.0.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libc"
version = "0.2.34"
version = "0.2.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memchr"
version = "1.0.2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -187,40 +195,47 @@ name = "memchr"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nix"
version = "0.8.1"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num_cpus"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quick-error"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand"
version = "0.3.18"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.32"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -228,46 +243,67 @@ name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "0.2.3"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.4.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "remove_dir_all"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "same-file"
version = "0.1.3"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "tempdir"
version = "0.3.5"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "term_size"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -275,41 +311,38 @@ name = "termion"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.9.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ucd-util"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unreachable"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "utf8-ranges"
version = "1.0.0"
@@ -317,12 +350,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "version_check"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -332,12 +365,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "walkdir"
version = "1.0.7"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -345,54 +377,78 @@ name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
"checksum atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "21e50800ec991574876040fff8ee46b136a53e985286fbe6a3bdfe6421b78860"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f"
"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97"
"checksum ctrlc 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "df391ea008fca636e41e40863a0b39a850e2ab26b0cdeed0c3657fd05a66d44c"
"checksum aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c1c6d463cbe7ed28720b5b489e7c083eeb8f90d08be2a0d6bb9e1ffea9ce1afa"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
"checksum cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "2119ea4867bd2b8ed3aecab467709720b2d55b1bcfe09f772fd68066eaf15275"
"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3"
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
"checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19"
"checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e"
"checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
"checksum filetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "da4b9849e77b13195302c174324b5ba73eec9b236b24c221a61000daefb95c5f"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f6c0581a4e363262e52b87f59ee2afe3415361c6ec35e665924eb08afe8ff159"
"checksum fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43f3795b4bae048dc6123a6b972cadde2e676f9ded08aef6bb77f5f157684a82"
"checksum globset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "464627f948c3190ae3d04b1bc6d7dca2f785bda0ac01278e6db129ad383dbeb6"
"checksum ignore 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3fcaf2365eb14b28ec7603c98c06cc531f19de9eb283d89a3dff8417c8c99f5"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum globset 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8e49edbcc9c7fc5beb8c0a54e7319ff8bed353a2b55e85811c6281188c2a6c84"
"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e"
"checksum ignore 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3e9faa7c84064f07b40da27044af629f578bc7994b650d3e458d0c29183c1d91"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
"checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0"
"checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b"
"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7"
"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d"
"checksum log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cba860f648db8e6f269df990180c2217f333472b4a6e901e97446858487971e2"
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
"checksum nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47e49f6982987135c5e9620ab317623e723bd06738fd85377e8d55f57c8b6487"
"checksum num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "514f0d73e64be53ff320680ca671b64fe3fb91da01e1ae2ddc99eb51d453b20d"
"checksum rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "6475140dfd8655aeb72e1fd4b7a1cc1c202be65d71669476e392fe62532b9edd"
"checksum redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ab105df655884ede59d45b7070c8a65002d921461ee813a024558ca16030eea0"
"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd"
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ac6ab4e9218ade5b423358bbd2567d1617418403c7a512603630181813316322"
"checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
"checksum regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5bbbea44c5490a1e84357ff28b7d518b4619a159fed5d25f6c1de2d19cc42814"
"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d"
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
"checksum walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1b768ba943161a9226ccd59b26bcd901e5d60e6061f4fcad3034784e0c7372b"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+19 -14
View File
@@ -16,7 +16,7 @@ license = "MIT/Apache-2.0"
name = "fd-find"
readme = "README.md"
repository = "https://github.com/sharkdp/fd"
version = "6.1.0"
version = "7.2.0"
[[bin]]
name = "fd"
@@ -28,27 +28,32 @@ repository = "sharkdp/fd"
repository = "sharkdp/fd"
[build-dependencies]
clap = "2.26.0"
clap = "2.31.2"
version_check = "0.1.3"
[dependencies]
ansi_term = "0.9"
ansi_term = "0.11"
atty = "0.2"
clap = "2.26.0"
ignore = "0.2"
lazy_static = "1"
num_cpus = "1.6.2"
regex = "0.2"
regex-syntax = "0.4"
ctrlc = "3.0"
ignore = "0.4.3"
lazy_static = "1.1.0"
num_cpus = "1.8"
regex = "1.0.0"
regex-syntax = "0.6"
ctrlc = "3.1"
humantime = "1.1.1"
[dependencies.clap]
version = "2.31.2"
features = ["suggestions", "color", "wrap_help"]
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
libc = "0.2"
[target.'cfg(windows)'.dependencies]
kernel32-sys = "0.2"
winapi = "0.2"
[dev-dependencies]
diff = "0.1"
tempdir = "0.3"
filetime = "0.2.1"
[profile.release]
lto = true
codegen-units = 1
+194 -66
View File
@@ -2,6 +2,7 @@
[![Build Status](https://travis-ci.org/sharkdp/fd.svg?branch=master)](https://travis-ci.org/sharkdp/fd)
[![Build status](https://ci.appveyor.com/api/projects/status/21c4p5fwggc5gy3j?svg=true)](https://ci.appveyor.com/project/sharkdp/fd)
[![Version info](https://img.shields.io/crates/v/fd-find.svg)](https://crates.io/crates/fd-find)
[中文](https://github.com/chinanf-boy/fd-zh)
*fd* is a simple, fast and user-friendly alternative to
[*find*](https://www.gnu.org/software/findutils/).
@@ -26,63 +27,62 @@ While it does not seek to mirror all of *find*'s powerful functionality, it prov
## Demo
![Demo](http://i.imgur.com/kTMFSVU.gif)
![Demo](doc/screencast.svg)
## Benchmark
Let's search my home folder for files that end in `[0-9].jpg`. It contains ~150.000
Let's search my home folder for files that end in `[0-9].jpg`. It contains ~190.000
subdirectories and about a million files. For averaging and statistical analysis, I'm using
[bench](https://github.com/Gabriel439/bench). All benchmarks are performed for a "warm
cache". Results for a cold cache are similar.
[hyperfine](https://github.com/sharkdp/hyperfine). The following benchmarks are performed
with a "warm"/pre-filled disk-cache (results for a "cold" disk-cache show the same trends).
Let's start with `find`:
```
find ~ -iregex '.*[0-9]\.jpg$'
Benchmark #1: find ~ -iregex '.*[0-9]\.jpg$'
time 6.265 s (6.127 s .. NaN s)
1.000 R² (1.000 R² .. 1.000 R²)
mean 6.162 s (6.140 s .. 6.181 s)
std dev 31.73 ms (0.0 s .. 33.48 ms)
Time (mean ± σ): 7.236 s ± 0.090 s
Range (min … max): 7.133 s 7.385 s
```
`find` is much faster if it does not need to perform a regular-expression search:
```
find ~ -iname '*[0-9].jpg'
Benchmark #2: find ~ -iname '*[0-9].jpg'
time 2.866 s (2.754 s .. 2.964 s)
1.000 R² (0.999 R² .. 1.000 R²)
mean 2.860 s (2.834 s .. 2.875 s)
std dev 23.11 ms (0.0 s .. 25.09 ms)
Time (mean ± σ): 3.914 s ± 0.027 s
Range (min … max): 3.876 s 3.964 s
```
Now let's try the same for `fd`. Note that `fd` *always* performs a regular expression
search. The options `--hidden` and `--no-ignore` are needed for a fair comparison,
otherwise `fd` does not have to traverse hidden folders and ignored paths (see below):
```
fd --hidden --no-ignore '.*[0-9]\.jpg$' ~
Benchmark #3: fd -HI '.*[0-9]\.jpg$' ~
time 892.6 ms (839.0 ms .. 915.4 ms)
0.999 R² (0.997 R² .. 1.000 R²)
mean 871.2 ms (857.9 ms .. 881.3 ms)
std dev 15.50 ms (0.0 s .. 17.49 ms)
Time (mean ± σ): 811.6 ms ± 26.9 ms
Range (min … max): 786.0 ms … 870.7 ms
```
For this particular example, `fd` is approximately seven times faster than `find -iregex`
and about three times faster than `find -iname`. By the way, both tools found the exact
same 14030 files :smile:.
For this particular example, `fd` is approximately nine times faster than `find -iregex`
and about five times faster than `find -iname`. By the way, both tools found the exact
same 20880 files :smile:.
Finally, let's run `fd` without `--hidden` and `--no-ignore` (this can lead to different
search results, of course):
search results, of course). If *fd* does not have to traverse the hidden and git-ignored
folders, it is almost an order of magnitude faster:
```
fd '[0-9]\.jpg$' ~
Benchmark #4: fd '[0-9]\.jpg$' ~
time 159.5 ms (155.8 ms .. 165.3 ms)
0.999 R² (0.996 R² .. 1.000 R²)
mean 158.7 ms (156.5 ms .. 161.6 ms)
std dev 3.263 ms (2.401 ms .. 4.298 ms)
Time (mean ± σ): 123.7 ms ± 6.0 ms
Range (min … max): 118.8 ms … 140.0 ms
```
**Note**: This is *one particular* benchmark on *one particular* machine. While I have
performed quite a lot of different tests (and found consistent results), things might
be different for you! I encourage everyone to try it out on their own.
be different for you! I encourage everyone to try it out on their own. See
[this repository](https://github.com/sharkdp/fd-benchmarks) for all necessary scripts.
Concerning *fd*'s speed, the main credit goes to the `regex` and `ignore` crates that are also used
in [ripgrep](https://github.com/BurntSushi/ripgrep) (check it out!).
@@ -123,32 +123,91 @@ fd -e flac -x ffmpeg -i {} -c:a libopus {.}.opus
fd -x wc -l \; -e rs
```
## Install
## Installation
### From source
### On Ubuntu
*... and other Debian-based Linux distributions.*
With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can install *fd* via:
Download the latest `.deb` package from the [release page](https://github.com/sharkdp/fd/releases) and install it via:
``` bash
sudo dpkg -i fd_7.2.0_amd64.deb # adapt version number and architecture
```
cargo install fd-find
### On Debian
If you run Debian sid (and soon Debian Buster), fd is
[officially maintained by Debian](https://tracker.debian.org/pkg/rust-fd-find).
```
Note that rust version *1.20.0* or later is required.
### From binaries
The [release page](https://github.com/sharkdp/fd/releases) includes precompiled binaries for Linux, macOS and Windows.
### On macOS
You can install [this Homebrew package](http://braumeister.org/formula/fd):
sudo apt-get install fd-find
```
brew install fd
The binary is called `fdfind` as the binary name `fd` is already used by another package.
It is recommended that you add an `alias fd=fdfind` to your shells initialization file,
in order to use `fd` in the same way as in this documentation.
### On Fedora
Starting with Fedora 28, you can install `fd` from the official package sources:
``` bash
dnf install fd-find
```
For older versions, you can use this [Fedora copr](https://copr.fedorainfracloud.org/coprs/keefle/fd/) to install `fd`:
``` bash
dnf copr enable keefle/fd
dnf install fd
```
### On Arch Linux
You can install [the fd-rs package](https://www.archlinux.org/packages/community/x86_64/fd-rs/) from the official repos:
You can install [the fd package](https://www.archlinux.org/packages/community/x86_64/fd/) from the official repos:
```
pacman -S fd-rs
pacman -S fd
```
### On Gentoo Linux
You can use [the fd ebuild](https://packages.gentoo.org/packages/sys-apps/fd) from the official repo:
```
emerge -av fd
```
### On openSUSE Linux
You can install [the fd package](https://software.opensuse.org/package/fd) from the official repo:
```
zypper in fd
```
### On Void Linux
You can install `fd` via xbps-install:
```
xbps-install -S fd
```
### On macOS
You can install `fd` with [Homebrew](http://braumeister.org/formula/fd):
```
brew install fd
```
… or with MacPorts:
```
sudo port install fd
```
### On Windows
You can download pre-built binaries from the [release page](https://github.com/sharkdp/fd/releases).
Alternatively, you can install `fd` via [Scoop](http://scoop.sh):
```
scoop install fd
```
Or via [Chocolatey](https://chocolatey.org):
```
choco install fd
```
### On NixOS / via Nix
@@ -158,23 +217,24 @@ You can use the [Nix package manager](https://nixos.org/nix/) to install `fd`:
nix-env -i fd
```
### Windows
You can download pre-built binaries from the [release page](https://github.com/sharkdp/fd/releases).
### FreeBSD
### On FreeBSD
You can install `sysutils/fd` via portmaster:
```
portmaster sysutils/fd
```
### On Void Linux
### From source
You can install `fd` via xbps-install:
With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can install *fd* via:
```
xbps-install -S fd
cargo install fd-find
```
Note that rust version *1.29.0* or later is required.
### From binaries
The [release page](https://github.com/sharkdp/fd/releases) includes precompiled binaries for Linux, macOS and Windows.
## Development
```bash
@@ -198,10 +258,11 @@ USAGE:
FLAGS:
-H, --hidden Search hidden files and directories
-I, --no-ignore Do not respect .(git)ignore files
-I, --no-ignore Do not respect .(git|fd)ignore files
--no-ignore-vcs Do not respect .gitignore files
-s, --case-sensitive Case-sensitive search (default: smart case)
-i, --ignore-case Case-insensitive search (default: smart case)
-F, --fixed-strings Treat the pattern as a literal string
-a, --absolute-path Show absolute instead of relative paths
-L, --follow Follow symbolic links
-p, --full-path Search full path (default: file-/dirname only)
@@ -210,14 +271,16 @@ FLAGS:
-V, --version Prints version information
OPTIONS:
-d, --max-depth <depth> Set maximum search depth (default: none)
-t, --type <filetype> Filter by type: f(ile), d(irectory), (sym)l(ink)
-e, --extension <ext> Filter by file extension
-x, --exec <cmd>... Execute a command for each search result
-E, --exclude <pattern>... Exclude entries that match the given glob pattern.
-c, --color <when> When to use colors: never, *auto*, always
-j, --threads <num> Set number of threads to use for searching &
executing
-d, --max-depth <depth> Set maximum search depth (default: none)
-t, --type <filetype>... Filter by type: file (f), directory (d), symlink (l),
executable (x), empty (e)
-e, --extension <ext>... Filter by file extension
-x, --exec <cmd> Execute a command for each search result
-E, --exclude <pattern>... Exclude entries that match the given glob pattern
-c, --color <when> When to use colors: never, *auto*, always
-S, --size <size>... Limit results based on the size of files.
--changed-within <date|dur> Filter by file modification time (newer than)
--changed-before <date|dur> Filter by file modification time (older than)
ARGS:
<pattern> the search pattern, a regular expression (optional)
@@ -255,7 +318,7 @@ X11/xinit/xserverrc
### Specifying the root directory
If we want so search a specific directory, it can be given as a second argument to *fd*:
If we want to search a specific directory, it can be given as a second argument to *fd*:
``` bash
> fd passwd /etc
/etc/default/passwd
@@ -305,7 +368,7 @@ search results. To disable this behavior, we can use the `-H` (or `--hidden`) op
If we work in a directory that is a Git repository (or includes Git repositories), *fd* does not
search folders (and does not show files) that match one of the `.gitignore` patterns. To disable
this behavior, we can use the `-I` (or `--ignore`) option:
this behavior, we can use the `-I` (or `--no-ignore`) option:
``` bash
> fd num_cpu
> fd -I num_cpu
@@ -315,11 +378,76 @@ target/debug/deps/libnum_cpus-f5ce7ef99006aa05.rlib
To really search *all* files and directories, simply combine the hidden and ignore features to show
everything (`-HI`).
### Excluding specific files or directories
Sometimes we want to ignore search results from a specific subdirectory. For example, we might
want to search all hidden files and directories (`-H`) but exclude all matches from `.git`
directories. We can use the `-E` (or `--exclude`) option for this. It takes an arbitrary glob
pattern as an argument:
``` bash
> fd -H -E .git …
```
We can also use this to skip mounted directories:
``` bash
> fd -E /mnt/external-drive …
```
.. or to skip certain file types:
``` bash
> fd -E '*.bak' …
```
To make exclude-patterns like these permanent, you can create a `.fdignore` file. They work like
`.gitignore` files, but are specific to `fd`. For example:
``` bash
> cat ~/.fdignore
/mnt/external-drive
*.bak
```
Note: `fd` also supports `.ignore` files that are used by other programs such as `rg` or `ag`.
### Using fd with `xargs` or `parallel`
If we want to run a command on all search results, we can pipe the output to `xargs`:
``` bash
> fd -0 -e rs | xargs -0 wc -l
```
Here, the `-0` option tells *fd* to separate search results by the NULL character (instead of .
newlines) In the same way, the `-0` option of `xargs` tells it to read the input in this way .
Here, the `-0` option tells *fd* to separate search results by the NULL character (instead of
newlines). In the same way, the `-0` option of `xargs` tells it to read the input in this way.
### Integration with other programs
#### Using fd with `fzf`
You can use *fd* to generate input for the command-line fuzzy finder [fzf](https://github.com/junegunn/fzf):
``` bash
export FZF_DEFAULT_COMMAND='fd --type file'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
```
Then, you can type `vim <Ctrl-T>` on your terminal to open fzf and search through the fd-results.
Alternatively, you might like to follow symbolic links and include hidden files (but exclude `.git` folders):
``` bash
export FZF_DEFAULT_COMMAND='fd --type file --follow --hidden --exclude .git'
```
You can even use fd's colored output inside fzf by setting:
``` bash
export FZF_DEFAULT_COMMAND="fd --type file --color=always"
export FZF_DEFAULT_OPTS="--ansi"
```
For more details, see the [Tips section](https://github.com/junegunn/fzf#tips) of the fzf README.
#### Using fd with `emacs`
The emacs package [find-file-in-project](https://github.com/technomancy/find-file-in-project) can
use *fd* to find files.
After installing `find-file-in-project`, add the line `(setq ffip-use-rust-fd t)` to your
`~/.emacs` or `~/.emacs.d/init.el` file.
In emacs, run `M-x find-file-in-project-by-selected` to find matching files. Alternatively, run
`M-x find-file-in-project` to list all available files in the project.
+4 -5
View File
@@ -11,19 +11,18 @@ extern crate clap;
extern crate version_check;
use clap::Shell;
use std::fs;
use std::io::{self, Write};
use std::process::exit;
use std::fs;
include!("src/app.rs");
fn main() {
match version_check::is_min_version("1.20") {
// rustc >= 1.20
match version_check::is_min_version("1.29") {
Some((true, _)) => {}
// rustc < 1.20 or can't figure it out
// rustc version too small or can't figure it out
_ => {
writeln!(&mut io::stderr(), "This crate requires rustc >= 1.20").unwrap();
writeln!(&mut io::stderr(), "'fd' requires rustc >= 1.29").unwrap();
exit(1);
}
}
+18 -8
View File
@@ -11,18 +11,25 @@ pack() {
local tempdir
local out_dir
local package_name
local gcc_prefix
tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
out_dir=$(pwd)
package_name="$PROJECT_NAME-$TRAVIS_TAG-$TARGET"
if [[ $TARGET == "arm-unknown-linux-gnueabihf" ]]; then
gcc_prefix="arm-linux-gnueabihf-"
else
gcc_prefix=""
fi
# create a "staging" directory
mkdir "$tempdir/$package_name"
mkdir "$tempdir/$package_name/autocomplete"
# copying the main binary
cp "target/$TARGET/release/$PROJECT_NAME" "$tempdir/$package_name/"
strip "$tempdir/$package_name/$PROJECT_NAME"
"${gcc_prefix}"strip "$tempdir/$package_name/$PROJECT_NAME"
# manpage, readme and license
cp "doc/$PROJECT_NAME.1" "$tempdir/$package_name"
@@ -57,8 +64,8 @@ make_deb() {
architecture=i386
;;
*)
echo "ERROR: unknown target" >&2
return 1
echo "make_deb: skipping target '${TARGET}'" >&2
return 0
;;
esac
version=${TRAVIS_TAG#v}
@@ -78,6 +85,7 @@ make_deb() {
# manpage
install -Dm644 "doc/$PROJECT_NAME.1" "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
gzip --best "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
# readme and license
install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md"
@@ -85,9 +93,9 @@ make_deb() {
install -Dm644 LICENSE-APACHE "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-APACHE"
# completions
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.bash "$tempdir/usr/share/bash_completion/completions/$PROJECT_NAME/${PROJECT_NAME}.bash-completion"
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.fish "$tempdir/usr/share/fish/vendor_completions.d/$PROJECT_NAME.fish"
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/_$PROJECT_NAME "$tempdir/usr/share/zsh/site-functions/_$PROJECT_NAME"
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.bash "$tempdir/usr/share/bash-completion/completions/${PROJECT_NAME}"
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.fish "$tempdir/usr/share/fish/completions/$PROJECT_NAME.fish"
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/_$PROJECT_NAME "$tempdir/usr/share/zsh/vendor-completions/_$PROJECT_NAME"
# Control file
mkdir "$tempdir/DEBIAN"
@@ -96,14 +104,16 @@ Package: $dpkgname
Version: $version
Section: utils
Priority: optional
Maintainer: David Peter
Maintainer: David Peter <mail@david-peter.de>
Architecture: $architecture
Provides: $PROJECT_NAME
Conflicts: $conflictname
Description: Simple, fast and user-friendly alternative to find
While fd does not seek to mirror all of find's powerful functionality, it
provides sensible (opinionated) defaults for 80% of the use cases.
EOF
dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
fakeroot dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
}
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -ex
if [ "$TRAVIS_OS_NAME" != linux ]; then
exit 0
fi
sudo apt-get update
# needed to build deb packages
sudo apt-get install -y fakeroot
# needed for i686 linux gnu target
if [[ $TARGET == i686-unknown-linux-gnu ]]; then
sudo apt-get install -y gcc-multilib
fi
# needed for cross-compiling for arm
if [[ $TARGET == arm-unknown-linux-gnueabihf ]]; then
sudo apt-get install -y \
gcc-4.8-arm-linux-gnueabihf \
binutils-arm-linux-gnueabihf \
libc6-armhf-cross \
libc6-dev-armhf-cross
fi
Executable
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -ex
# Incorporate TARGET env var to the build and test process
cargo build --target "$TARGET" --verbose
# We cannot run arm executables on linux
if [[ $TARGET != arm-unknown-linux-gnueabihf ]]; then
cargo test --target "$TARGET" --verbose
fi
+104 -16
View File
@@ -27,44 +27,64 @@ is a simple, fast and user-friendly alternative to
.SH OPTIONS
.TP
.B \-H, \-\-hidden
Search hidden files and directories.
Include hidden files and directories in the search results
(default: hidden files and directories are skipped).
.TP
.B \-I, \-\-no\-ignore
Don't respect ignore files (.gitignore, .ignore, etc.)
Show search results from files and directories that would otherwise be ignored by
.IR .gitignore ,
.I .ignore
or
.I .fdignore
files.
.TP
.B \-\-no\-ignore\-vcs
Don't respect version control ignore files (like .gitignore).
Show search results from files and directories that would otherwise be ignored by
.I .gitignore
files.
.TP
.B \-s, \-\-case\-sensitive
Perform a case-sensitive search (default: smart case).
Perform a case-sensitive search. By default, fd uses case-insensitive searches, unless the
pattern contains an uppercase character (smart case).
.TP
.B \-i, \-\-ignore\-case
Perform a case-insensitive search (default: smart case).
Perform a case-insensitive search. By default, fd uses case-insensitive searches, unless the
pattern contains an uppercase character (smart case).
.TP
.B \-F, \-\-fixed\-strings
Treat the pattern as a literal string instead of a regular expression.
.TP
.B \-a, \-\-absolute\-path
Show absolute instead of relative paths.
Shows the full path starting from the root as opposed to relative paths.
.TP
.B \-L, \-\-follow
Dereference all symbolic links encountered.
By default, fd does not descend into symlinked directories. Using this flag, symbolic links are
also traversed.
.TP
.B \-p, \-\-full\-path
Match
By default, the search pattern is only matched against the filename (or directory name). Using
this flag, the
.I pattern
against the full path
is matched against the full path.
.TP
.B \-0, \-\-print0
Print results followed by null character instead of newlines.
Separate search results by the null character (instead of newlines). Useful for piping results to
.IR xargs .
.TP
.B \-\-show-errors
Enable the display of filesystem errors for situations such as insufficient
permissions or dead symlinks.
.TP
.B \-h, \-\-help
Prints help information.
Print help information.
.TP
.B \-V, \-\-version
Prints version information.
Print version information.
.TP
.BI "\-d, \-\-max\-depth " levels
.BI "\-d, \-\-max\-depth " d
Limit directory traversal to at most
.I levels
of depth.
.I d
levels of depth. By default, there is no limit on the search depth.
.TP
.BI "\-t, \-\-type " filetype
Filter search by type:
@@ -73,19 +93,32 @@ Filter search by type:
regular files
.IP "d, directories"
directories
.IP "s, symlink"
.IP "l, symlink"
symbolic links
.IP "x, executable"
executable (files)
.IP "e, empty"
empty files or directories
.RE
.RS
This option can be used repeatedly to allow for multiple file types.
.RE
.TP
.BI "\-e, \-\-extension " ext
Filter search results by file extension
.IR ext .
This option can be used repeatedly to allow for multiple possible file extensions.
.TP
.BI "\-E, \-\-exclude " pattern
Exclude files/directories that match the given glob pattern.
This overrides any other ignore logic.
Multiple exclude patterns can be specified.
.TP
.BI "\-\-ignore-file " path
Add a custom ignore-file in '.gitignore' format.
These files have a low precedence.
.TP
.BI "\-c, \-\-color " when
Declare
.I when
@@ -102,6 +135,61 @@ Always colorize output.
.BI "\-j, \-\-threads " num
Number of threads to use for searching (default: number of available CPUs).
.TP
.BI "\-S, \-\-size " size
Limit results based on the size of files using the format
.I <+-><NUM><UNIT>
.RS
.IP '+'
file size must be greater than or equal to this
.IP '-'
file size must be less than or equal to this
.IP 'NUM'
The numeric size (e.g. 500)
.IP 'UNIT'
The units for NUM. They are not case-sensitive.
Allowed unit values:
.RS
.IP 'b'
bytes
.IP 'k'
kilobytes
.IP 'm'
megabytes
.IP 'g'
gigabytes
.IP 't'
terabytes
.IP 'ki'
kibibytes
.IP 'mi'
mebibytes
.IP 'gi'
gibibytes
.IP 'ti'
tebibytes
.RE
.RE
.TP
.BI "\-\-changed-within " date|duration
Filter results based on the file modification time. The argument can be provided as a specific
point in time (\fIYYYY-MM-DD HH:MM:SS\fR) or as a duration (\fI10h, 1d, 35min\fR).
.B --change-newer-than
can be used as an alias.
Examples:
\-\-changed-within 2weeks
\-\-change-newer-than "2018-10-27 10:00:00"
.TP
.BI "\-\-changed-before " date|duration
Filter results based on the file modification time. The argument can be provided as a specific
point in time (\fIYYYY-MM-DD HH:MM:SS\fR) or as a duration (\fI10h, 1d, 35min\fR).
.B --change-older-than
can be used as an alias.
Examples:
\-\-changed-before "2018-10-27 10:00:00"
\-\-change-older-than 2weeks
.TP
.BI "\-x, \-\-exec " command "\fR [args...] ;"
Execute
.I command
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# Designed to be executed via svg-term from the fd root directory:
# svg-term --command="bash doc/screencast.sh" --out doc/screencast.svg --padding=10
set -e
set -u
PROMPT="▶"
enter() {
INPUT=$1
DELAY=1
prompt
sleep "$DELAY"
type "$INPUT"
sleep 0.5
printf '%b' "\\n"
eval "$INPUT"
type "\\n"
}
prompt() {
printf '%b ' $PROMPT | pv -q
}
type() {
printf '%b' "$1" | pv -qL $((10+(-2 + RANDOM%5)))
}
main() {
IFS='%'
enter "fd"
enter "fd app"
enter "fd sh"
enter "fd sh --type f"
enter "fd -e md"
enter "fd -e md --exec wc -l"
enter "fd '^[A-Z]'"
enter "fd --exclude src"
enter "fd --hidden sample"
prompt
sleep 3
echo ""
unset IFS
}
main
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 115 KiB

+150 -20
View File
@@ -5,7 +5,6 @@
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::collections::HashMap;
use clap::{App, AppSettings, Arg};
@@ -20,19 +19,22 @@ macro_rules! doc {
doc!($map, $name, $short, $short)
};
($map:expr, $name:expr, $short:expr, $long:expr) => {
$map.insert($name, Help {
short: $short,
long: concat!($long, "\n ")
});
$map.insert(
$name,
Help {
short: $short,
long: concat!($long, "\n "),
},
);
};
}
pub fn build_app() -> App<'static, 'static> {
let helps = usage();
let arg = |name| {
Arg::with_name(name).help(helps[name].short).long_help(
helps[name].long,
)
Arg::with_name(name)
.help(helps[name].short)
.long_help(helps[name].long)
};
App::new("fd")
@@ -61,24 +63,52 @@ pub fn build_app() -> App<'static, 'static> {
.short("i")
.overrides_with("case-sensitive"),
)
.arg(
arg("fixed-strings")
.long("fixed-strings")
.short("F")
.alias("literal"),
)
.arg(arg("absolute-path").long("absolute-path").short("a"))
.arg(arg("follow").long("follow").short("L").alias("dereference"))
.arg(arg("full-path").long("full-path").short("p"))
.arg(arg("null_separator").long("print0").short("0"))
.arg(arg("depth").long("max-depth").short("d").takes_value(true))
// support --maxdepth as well, for compatibility with rg
.arg(
arg("rg-depth")
.long("maxdepth")
.hidden(true)
.takes_value(true),
)
.arg(
arg("file-type")
.long("type")
.short("t")
.multiple(true)
.number_of_values(1)
.takes_value(true)
.value_name("filetype")
.possible_values(&["f", "file", "d", "directory", "l", "symlink"])
.possible_values(&[
"f",
"file",
"d",
"directory",
"l",
"symlink",
"x",
"executable",
"e",
"empty",
])
.hide_possible_values(true),
)
.arg(
arg("extension")
.long("extension")
.short("e")
.multiple(true)
.number_of_values(1)
.takes_value(true)
.value_name("ext"),
)
@@ -86,7 +116,6 @@ pub fn build_app() -> App<'static, 'static> {
arg("exec")
.long("exec")
.short("x")
.multiple(true)
.min_values(1)
.allow_hyphen_values(true)
.value_terminator(";")
@@ -101,6 +130,15 @@ pub fn build_app() -> App<'static, 'static> {
.number_of_values(1)
.multiple(true),
)
.arg(
arg("ignore-file")
.long("ignore-file")
.takes_value(true)
.value_name("path")
.number_of_values(1)
.multiple(true)
.hidden_short_help(true),
)
.arg(
arg("color")
.long("color")
@@ -115,7 +153,17 @@ pub fn build_app() -> App<'static, 'static> {
.long("threads")
.short("j")
.takes_value(true)
.value_name("num"),
.value_name("num")
.hidden_short_help(true),
)
.arg(
arg("size")
.long("size")
.short("S")
.takes_value(true)
.number_of_values(1)
.allow_hyphen_values(true)
.multiple(true),
)
.arg(
arg("max-buffer-time")
@@ -123,8 +171,38 @@ pub fn build_app() -> App<'static, 'static> {
.takes_value(true)
.hidden(true),
)
.arg(
arg("changed-within")
.long("changed-within")
.alias("change-newer-than")
.takes_value(true)
.value_name("date|dur")
.number_of_values(1),
)
.arg(
arg("changed-before")
.long("changed-before")
.alias("change-older-than")
.takes_value(true)
.value_name("date|dur")
.number_of_values(1),
)
.arg(
arg("show-errors")
.long("show-errors")
.hidden_short_help(true),
)
.arg(arg("pattern"))
.arg(arg("path").multiple(true))
.arg(
arg("search-path")
.long("search-path")
.takes_value(true)
.conflicts_with("path")
.multiple(true)
.hidden_short_help(true)
.number_of_values(1),
)
}
#[cfg_attr(rustfmt, rustfmt_skip)]
@@ -135,9 +213,9 @@ fn usage() -> HashMap<&'static str, Help> {
, "Include hidden directories and files in the search results (default: hidden files \
and directories are skipped).");
doc!(h, "no-ignore"
, "Do not respect .(git)ignore files"
, "Do not respect .(git|fd)ignore files"
, "Show search results from files and directories that would otherwise be ignored by \
'.*ignore' files.");
'.gitignore', '.ignore' or '.fdignore' files.");
doc!(h, "no-ignore-vcs"
, "Do not respect .gitignore files"
, "Show search results from files and directories that would otherwise be ignored by \
@@ -150,6 +228,9 @@ fn usage() -> HashMap<&'static str, Help> {
, "Case-insensitive search (default: smart case)"
, "Perform a case-insensitive search. By default, fd uses case-insensitive searches, \
unless the pattern contains an uppercase character (smart case).");
doc!(h, "fixed-strings"
, "Treat the pattern as a literal string"
, "Treat the pattern as a literal string instead of a regular expression.");
doc!(h, "absolute-path"
, "Show absolute instead of relative paths"
, "Shows the full path starting from the root as opposed to relative paths.");
@@ -169,15 +250,21 @@ fn usage() -> HashMap<&'static str, Help> {
, "Set maximum search depth (default: none)"
, "Limit the directory traversal to a given depth. By default, there is no limit \
on the search depth.");
doc!(h, "rg-depth"
, "See --max-depth"
, "See --max-depth");
doc!(h, "file-type"
, "Filter by type: f(ile), d(irectory), (sym)l(ink)"
, "Filter the search by type:\n \
, "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");
'l' or 'symlink': symbolic links\n \
'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.");
, "(Additionally) filter search results by their file extension. Multiple allowable file \
extensions can be specified.");
doc!(h, "exec"
, "Execute a command for each search result"
, "Execute a command for each search result.\n\
@@ -191,9 +278,12 @@ fn usage() -> HashMap<&'static str, Help> {
'{.}': path without file extension\n \
'{/.}': basename without file extension");
doc!(h, "exclude"
, "Exclude entries that match the given glob pattern."
, "Exclude entries that match the given glob pattern"
, "Exclude files/directories that match the given glob pattern. This overrides any \
other ignore logic. Multiple exclude patterns can be specified.");
doc!(h, "ignore-file"
, "Add a custom ignore-file in .gitignore format"
, "Add a custom ignore-file in '.gitignore' format. These files have a low precedence.");
doc!(h, "color"
, "When to use colors: never, *auto*, always"
, "Declare when to use color for the pattern match output:\n \
@@ -206,7 +296,7 @@ fn usage() -> HashMap<&'static str, Help> {
CPU cores)");
doc!(h, "max-buffer-time"
, "the time (in ms) to buffer, before streaming to the console"
, "Amount of time in milliseconds to buffer, before streaming the search results to\
, "Amount of time in milliseconds to buffer, before streaming the search results to \
the console.");
doc!(h, "pattern"
, "the search pattern, a regular expression (optional)");
@@ -217,6 +307,46 @@ fn usage() -> HashMap<&'static str, Help> {
doc!(h, "rg-alias-hidden-ignore"
, "Alias for no-ignore and/or hidden"
, "Alias for no-ignore ('u') and no-ignore and hidden ('uu')");
doc!(h, "size"
, "Limit results based on the size of files."
, "Limit results based on the size of files using the format <+-><NUM><UNIT>.\n \
'+': file size must be greater than or equal to this\n \
'-': file size must be less than or equal to this\n \
'NUM': The numeric size (e.g. 500)\n \
'UNIT': The units for NUM. They are not case-sensitive.\n\
Allowed unit values:\n \
'b': bytes\n \
'k': kilobytes\n \
'm': megabytes\n \
'g': gigabytes\n \
't': terabytes\n \
'ki': kibibytes\n \
'mi': mebibytes\n \
'gi': gibibytes\n \
'ti': tebibytes");
doc!(h, "changed-within"
, "Filter by file modification time (newer than)"
, "Filter results based on the file modification time. The argument can be provided \
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
'--change-newer-than' can be used as an alias.\n\
Examples:\n \
--changed-within 2weeks\n \
--change-newer-than '2018-10-27 10:00:00'");
doc!(h, "changed-before"
, "Filter by file modification time (older than)"
, "Filter results based on the file modification time. The argument can be provided \
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
'--change-older-than' can be used as an alias.\n\
Examples:\n \
--changed-before '2018-10-27 10:00:00'\n \
--change-older-than 2weeks");
doc!(h, "show-errors"
, "Enable display of filesystem errors"
, "Enable the display of filesystem errors for situations such as insufficient permissions \
or dead symlinks.");
doc!(h, "search-path"
, "(hidden)"
, "Provide paths to search as an alternative to the positional <path> argument. \
Changes the usage to `fd [FLAGS/OPTIONS] --search-path <path> --search-path <path2> [<pattern>]`");
h
}
+6 -7
View File
@@ -6,10 +6,10 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::io;
use std::io::Write;
use std::process::Command;
use std::sync::{Arc, Mutex};
/// Executes a command.
pub fn execute_command(mut cmd: Command, out_perm: Arc<Mutex<()>>) {
@@ -29,12 +29,11 @@ pub fn execute_command(mut cmd: Command, out_perm: Arc<Mutex<()>>) {
let _ = stdout.lock().write_all(&output.stdout);
let _ = stderr.lock().write_all(&output.stderr);
}
Err(ref why) if why.kind() == io::ErrorKind::NotFound => {
print_error!("Command not found: {:?}", cmd);
}
Err(why) => {
if why.kind() == io::ErrorKind::NotFound {
eprintln!("fd: execution error: command not found");
} else {
eprintln!("fd: execution error: {}", why);
}
print_error!("Problem while executing command: {}", why);
}
}
}
+1 -1
View File
@@ -72,7 +72,7 @@ pub fn dirname(path: &str) -> &str {
#[cfg(test)]
mod tests {
use super::{MAIN_SEPARATOR, basename, dirname, remove_extension};
use super::*;
fn correct(input: &str) -> String {
input.replace('/', &MAIN_SEPARATOR.to_string())
+18 -7
View File
@@ -6,24 +6,35 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::Receiver;
use super::CommandTemplate;
use std::path::PathBuf;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex};
use walk::WorkerResult;
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
/// generate a command with the supplied command template. The generated command will then
/// be executed, and this process will continue until the receiver's sender has closed.
pub fn job(rx: Arc<Mutex<Receiver<PathBuf>>>, cmd: Arc<CommandTemplate>, out_perm: Arc<Mutex<()>>) {
pub fn job(
rx: Arc<Mutex<Receiver<WorkerResult>>>,
cmd: Arc<CommandTemplate>,
out_perm: Arc<Mutex<()>>,
show_filesystem_errors: bool,
) {
loop {
// Create a lock on the shared receiver for this thread.
let lock = rx.lock().unwrap();
// Obtain the next path from the receiver, else if the channel
// Obtain the next result from the receiver, else if the channel
// has closed, exit from the loop
let value: PathBuf = match lock.recv() {
Ok(value) => value,
Ok(WorkerResult::Entry(val)) => val,
Ok(WorkerResult::Error(err)) => {
if show_filesystem_errors {
print_error!("{}", err);
}
continue;
}
Err(_) => break,
};
+58 -15
View File
@@ -8,9 +8,9 @@
// TODO: Possible optimization could avoid pushing characters on a buffer.
mod command;
mod token;
mod job;
mod input;
mod job;
mod token;
use std::borrow::Cow;
use std::path::Path;
@@ -19,10 +19,10 @@ use std::sync::{Arc, Mutex};
use regex::Regex;
use self::input::{basename, dirname, remove_extension};
use self::command::execute_command;
use self::token::Token;
use self::input::{basename, dirname, remove_extension};
pub use self::job::job;
use self::token::Token;
/// Represents a template that is utilized to generate command strings.
///
@@ -150,20 +150,24 @@ impl ArgumentTemplate {
#[cfg(test)]
mod tests {
use super::{CommandTemplate, ArgumentTemplate, Token};
use super::*;
#[test]
fn tokens() {
let expected = CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Text("${SHELL}:".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
],
};
assert_eq!(CommandTemplate::new(&[&"echo", &"${SHELL}:"]), expected);
fn tokens_with_placeholder() {
assert_eq!(
CommandTemplate::new(&[&"echo", &"${SHELL}:"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Text("${SHELL}:".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
],
}
);
}
#[test]
fn tokens_with_no_extension() {
assert_eq!(
CommandTemplate::new(&["echo", "{.}"]),
CommandTemplate {
@@ -174,4 +178,43 @@ mod tests {
}
);
}
#[test]
fn tokens_with_basename() {
assert_eq!(
CommandTemplate::new(&["echo", "{/}"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Basename]),
],
}
);
}
#[test]
fn tokens_with_parent() {
assert_eq!(
CommandTemplate::new(&["echo", "{//}"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Parent]),
],
}
);
}
#[test]
fn tokens_with_basename_no_extension() {
assert_eq!(
CommandTemplate::new(&["echo", "{/.}"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
],
}
);
}
}
+13
View File
@@ -0,0 +1,13 @@
pub enum ExitCode {
GeneralError,
KilledBySigint,
}
impl Into<i32> for ExitCode {
fn into(self) -> i32 {
match self {
ExitCode::GeneralError => 1,
ExitCode::KilledBySigint => 130,
}
}
}
+45 -12
View File
@@ -7,35 +7,68 @@
// according to those terms.
use std::env::current_dir;
use std::path::{Path, PathBuf};
use std::fs;
use std::io;
#[cfg(any(unix, target_os = "redox"))]
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())
} else {
let path = path.strip_prefix(".").unwrap_or(path);
current_dir().map(|path_buf| path_buf.join(path))
return Ok(path.to_path_buf());
}
let path = path.strip_prefix(".").unwrap_or(path);
current_dir().map(|path_buf| path_buf.join(path))
}
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
let path_buf = path_absolute_form(path)?;
#[cfg(windows)]
let path_buf = Path::new(path_buf.as_path().to_string_lossy().trim_left_matches(
r"\\?\",
)).to_path_buf();
let path_buf = Path::new(
path_buf
.as_path()
.to_string_lossy()
.trim_left_matches(r"\\?\"),
)
.to_path_buf();
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() {
path.is_dir()
path.is_dir() && (path.file_name().is_some() || path.canonicalize().is_ok())
}
#[cfg(any(unix, target_os = "redox"))]
pub fn is_executable(md: &fs::Metadata) -> bool {
md.permissions().mode() & 0o111 != 0
}
#[cfg(windows)]
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 {
path.is_dir() && path.canonicalize().is_ok()
false
}
}
-104
View File
@@ -1,104 +0,0 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::process;
use std::time;
use std::io::Write;
use exec::CommandTemplate;
use lscolors::LsColors;
use walk::FileType;
use regex_syntax::{Expr, ExprBuilder};
/// Configuration options for *fd*.
pub struct FdOptions {
/// Whether the search is case-sensitive or case-insensitive.
pub case_sensitive: bool,
/// Whether to search within the full file path or just the base name (filename or directory
/// name).
pub search_full_path: bool,
/// Whether to ignore hidden files and directories (or not).
pub ignore_hidden: bool,
/// Whether to respect ignore files (`.gitignore`, `.ignore`, ..) or not.
pub read_ignore: bool,
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
pub read_gitignore: bool,
/// Whether to follow symlinks or not.
pub follow_links: bool,
/// Whether elements of output should be separated by a null character
pub null_separator: bool,
/// The maximum search depth, or `None` if no maximum search depth should be set.
///
/// A depth of `1` includes all files under the current directory, a depth of `2` also includes
/// all files under subdirectories of the current directory, etc.
pub max_depth: Option<usize>,
/// The number of threads to use.
pub threads: usize,
/// Time to buffer results internally before streaming to the console. This is useful to
/// provide a sorted output, in case the total execution time is shorter than
/// `max_buffer_time`.
pub max_buffer_time: Option<time::Duration>,
/// `None` if the output should not be colorized. Otherwise, a `LsColors` instance that defines
/// how to style different filetypes.
pub ls_colors: Option<LsColors>,
/// The type of file to search for. All files other than the specified type will be ignored.
pub file_type: FileType,
/// The extension to search for. Only entries matching the extension will be included.
///
/// The value (if present) will be a lowercase string without leading dots.
pub extension: Option<String>,
/// If a value is supplied, each item found will be used to generate and execute commands.
pub command: Option<CommandTemplate>,
/// A list of glob patterns that should be excluded from the search.
pub exclude_patterns: Vec<String>,
}
/// Print error message to stderr and exit with status `1`.
pub fn error(message: &str) -> ! {
writeln!(&mut ::std::io::stderr(), "{}", message).expect("Failed writing to stderr");
process::exit(1);
}
/// Determine if a regex pattern contains a literal uppercase character.
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
ExprBuilder::new()
.parse(pattern)
.map(|expr| expr_has_uppercase_char(&expr))
.unwrap_or(false)
}
/// Determine if a regex expression contains a literal uppercase character.
fn expr_has_uppercase_char(expr: &Expr) -> bool {
match *expr {
Expr::Literal { ref chars, .. } => chars.iter().any(|c| c.is_uppercase()),
Expr::Class(ref ranges) => {
ranges.iter().any(|r| {
r.start.is_uppercase() || r.end.is_uppercase()
})
}
Expr::Group { ref e, .. } |
Expr::Repeat { ref e, .. } => expr_has_uppercase_char(e),
Expr::Concat(ref es) => es.iter().any(expr_has_uppercase_char),
Expr::Alternate(ref es) => es.iter().any(expr_has_uppercase_char),
_ => false,
}
}
+20
View File
@@ -0,0 +1,20 @@
/// Whether or not to show
pub struct FileTypes {
pub files: bool,
pub directories: bool,
pub symlinks: bool,
pub executables_only: bool,
pub empty_only: bool,
}
impl Default for FileTypes {
fn default() -> FileTypes {
FileTypes {
files: false,
directories: false,
symlinks: false,
executables_only: false,
empty_only: false,
}
}
}
+5
View File
@@ -0,0 +1,5 @@
pub use self::size::SizeFilter;
pub use self::time::TimeFilter;
mod size;
mod time;
+208
View File
@@ -0,0 +1,208 @@
use regex::Regex;
lazy_static! {
static ref SIZE_CAPTURES: Regex = { Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap() };
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SizeFilter {
Max(u64),
Min(u64),
}
// SI prefixes (powers of 10)
const KILO: u64 = 1000;
const MEGA: u64 = KILO * 1000;
const GIGA: u64 = MEGA * 1000;
const TERA: u64 = GIGA * 1000;
// Binary prefixes (powers of 2)
const KIBI: u64 = 1024;
const MEBI: u64 = KIBI * 1024;
const GIBI: u64 = MEBI * 1024;
const TEBI: u64 = GIBI * 1024;
impl SizeFilter {
pub fn from_string<'a>(s: &str) -> Option<Self> {
if !SIZE_CAPTURES.is_match(s) {
return None;
}
let captures = SIZE_CAPTURES.captures(s)?;
let limit_kind = captures.get(1).map_or("+", |m| m.as_str());
let quantity = captures
.get(2)
.and_then(|v| v.as_str().parse::<u64>().ok())?;
let multiplier = match &captures.get(3).map_or("b", |m| m.as_str()).to_lowercase()[..] {
v if v.starts_with("ki") => KIBI,
v if v.starts_with("k") => KILO,
v if v.starts_with("mi") => MEBI,
v if v.starts_with("m") => MEGA,
v if v.starts_with("gi") => GIBI,
v if v.starts_with("g") => GIGA,
v if v.starts_with("ti") => TEBI,
v if v.starts_with("t") => TERA,
"b" => 1,
_ => return None,
};
let size = quantity * multiplier;
Some(match limit_kind {
"+" => SizeFilter::Min(size),
_ => SizeFilter::Max(size),
})
}
pub fn is_within(&self, size: u64) -> bool {
match self {
&SizeFilter::Max(limit) => size <= limit,
&SizeFilter::Min(limit) => size >= limit,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! gen_size_filter_parse_test {
($($name: ident: $val: expr,)*) => {
$(
#[test]
fn $name() {
let (txt, expected) = $val;
let actual = SizeFilter::from_string(txt).unwrap();
assert_eq!(actual, expected);
}
)*
};
}
/// Parsing and size conversion tests data. Ensure that each type gets properly interpreted.
/// Call with higher base values to ensure expected multiplication (only need a couple)
gen_size_filter_parse_test! {
byte_plus: ("+1b", SizeFilter::Min(1)),
byte_plus_multiplier: ("+10b", SizeFilter::Min(10)),
byte_minus: ("-1b", SizeFilter::Max(1)),
kilo_plus: ("+1k", SizeFilter::Min(1000)),
kilo_plus_suffix: ("+1kb", SizeFilter::Min(1000)),
kilo_minus: ("-1k", SizeFilter::Max(1000)),
kilo_minus_multiplier: ("-100k", SizeFilter::Max(100000)),
kilo_minus_suffix: ("-1kb", SizeFilter::Max(1000)),
kilo_plus_upper: ("+1K", SizeFilter::Min(1000)),
kilo_plus_suffix_upper: ("+1KB", SizeFilter::Min(1000)),
kilo_minus_upper: ("-1K", SizeFilter::Max(1000)),
kilo_minus_suffix_upper: ("-1Kb", SizeFilter::Max(1000)),
kibi_plus: ("+1ki", SizeFilter::Min(1024)),
kibi_plus_multiplier: ("+10ki", SizeFilter::Min(10240)),
kibi_plus_suffix: ("+1kib", SizeFilter::Min(1024)),
kibi_minus: ("-1ki", SizeFilter::Max(1024)),
kibi_minus_multiplier: ("-100ki", SizeFilter::Max(102400)),
kibi_minus_suffix: ("-1kib", SizeFilter::Max(1024)),
kibi_plus_upper: ("+1KI", SizeFilter::Min(1024)),
kibi_plus_suffix_upper: ("+1KiB", SizeFilter::Min(1024)),
kibi_minus_upper: ("-1Ki", SizeFilter::Max(1024)),
kibi_minus_suffix_upper: ("-1KIB", SizeFilter::Max(1024)),
mega_plus: ("+1m", SizeFilter::Min(1000000)),
mega_plus_suffix: ("+1mb", SizeFilter::Min(1000000)),
mega_minus: ("-1m", SizeFilter::Max(1000000)),
mega_minus_suffix: ("-1mb", SizeFilter::Max(1000000)),
mega_plus_upper: ("+1M", SizeFilter::Min(1000000)),
mega_plus_suffix_upper: ("+1MB", SizeFilter::Min(1000000)),
mega_minus_upper: ("-1M", SizeFilter::Max(1000000)),
mega_minus_suffix_upper: ("-1Mb", SizeFilter::Max(1000000)),
mebi_plus: ("+1mi", SizeFilter::Min(1048576)),
mebi_plus_suffix: ("+1mib", SizeFilter::Min(1048576)),
mebi_minus: ("-1mi", SizeFilter::Max(1048576)),
mebi_minus_suffix: ("-1mib", SizeFilter::Max(1048576)),
mebi_plus_upper: ("+1MI", SizeFilter::Min(1048576)),
mebi_plus_suffix_upper: ("+1MiB", SizeFilter::Min(1048576)),
mebi_minus_upper: ("-1Mi", SizeFilter::Max(1048576)),
mebi_minus_suffix_upper: ("-1MIB", SizeFilter::Max(1048576)),
giga_plus: ("+1g", SizeFilter::Min(1000000000)),
giga_plus_suffix: ("+1gb", SizeFilter::Min(1000000000)),
giga_minus: ("-1g", SizeFilter::Max(1000000000)),
giga_minus_suffix: ("-1gb", SizeFilter::Max(1000000000)),
giga_plus_upper: ("+1G", SizeFilter::Min(1000000000)),
giga_plus_suffix_upper: ("+1GB", SizeFilter::Min(1000000000)),
giga_minus_upper: ("-1G", SizeFilter::Max(1000000000)),
giga_minus_suffix_upper: ("-1Gb", SizeFilter::Max(1000000000)),
gibi_plus: ("+1gi", SizeFilter::Min(1073741824)),
gibi_plus_suffix: ("+1gib", SizeFilter::Min(1073741824)),
gibi_minus: ("-1gi", SizeFilter::Max(1073741824)),
gibi_minus_suffix: ("-1gib", SizeFilter::Max(1073741824)),
gibi_plus_upper: ("+1GI", SizeFilter::Min(1073741824)),
gibi_plus_suffix_upper: ("+1GiB", SizeFilter::Min(1073741824)),
gibi_minus_upper: ("-1Gi", SizeFilter::Max(1073741824)),
gibi_minus_suffix_upper: ("-1GIB", SizeFilter::Max(1073741824)),
tera_plus: ("+1t", SizeFilter::Min(1000000000000)),
tera_plus_suffix: ("+1tb", SizeFilter::Min(1000000000000)),
tera_minus: ("-1t", SizeFilter::Max(1000000000000)),
tera_minus_suffix: ("-1tb", SizeFilter::Max(1000000000000)),
tera_plus_upper: ("+1T", SizeFilter::Min(1000000000000)),
tera_plus_suffix_upper: ("+1TB", SizeFilter::Min(1000000000000)),
tera_minus_upper: ("-1T", SizeFilter::Max(1000000000000)),
tera_minus_suffix_upper: ("-1Tb", SizeFilter::Max(1000000000000)),
tebi_plus: ("+1ti", SizeFilter::Min(1099511627776)),
tebi_plus_suffix: ("+1tib", SizeFilter::Min(1099511627776)),
tebi_minus: ("-1ti", SizeFilter::Max(1099511627776)),
tebi_minus_suffix: ("-1tib", SizeFilter::Max(1099511627776)),
tebi_plus_upper: ("+1TI", SizeFilter::Min(1099511627776)),
tebi_plus_suffix_upper: ("+1TiB", SizeFilter::Min(1099511627776)),
tebi_minus_upper: ("-1Ti", SizeFilter::Max(1099511627776)),
tebi_minus_suffix_upper: ("-1TIB", SizeFilter::Max(1099511627776)),
}
/// Invalid parse testing
macro_rules! gen_size_filter_failure {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
let i = SizeFilter::from_string($value);
assert!(i.is_none());
}
)*
};
}
/// Invalid parse data
gen_size_filter_failure! {
ensure_missing_symbol_returns_none: "10M",
ensure_missing_number_returns_none: "+g",
ensure_missing_unit_returns_none: "+18",
ensure_bad_format_returns_none_1: "$10M",
ensure_bad_format_returns_none_2: "badval",
ensure_bad_format_returns_none_3: "9999",
ensure_invalid_unit_returns_none_1: "+50a",
ensure_invalid_unit_returns_none_2: "-10v",
ensure_invalid_unit_returns_none_3: "+1Mv",
ensure_bib_format_returns_none: "+1bib",
ensure_bb_format_returns_none: "+1bb",
}
#[test]
fn is_within_less_than() {
let f = SizeFilter::from_string("-1k").unwrap();
assert!(f.is_within(999));
}
#[test]
fn is_within_less_than_equal() {
let f = SizeFilter::from_string("-1k").unwrap();
assert!(f.is_within(1000));
}
#[test]
fn is_within_greater_than() {
let f = SizeFilter::from_string("+1k").unwrap();
assert!(f.is_within(1001));
}
#[test]
fn is_within_greater_than_equal() {
let f = SizeFilter::from_string("+1K").unwrap();
assert!(f.is_within(1000));
}
}
+82
View File
@@ -0,0 +1,82 @@
use std::time::SystemTime;
/// Filter based on time ranges.
#[derive(Debug, PartialEq)]
pub enum TimeFilter {
Before(SystemTime),
After(SystemTime),
}
impl TimeFilter {
fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
use humantime;
humantime::parse_duration(s)
.map(|duration| *ref_time - duration)
.or_else(|_| humantime::parse_rfc3339_weak(s))
.ok()
}
pub fn before(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
TimeFilter::from_str(ref_time, s).map(TimeFilter::Before)
}
pub fn after(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
TimeFilter::from_str(ref_time, s).map(TimeFilter::After)
}
pub fn applies_to(&self, t: &SystemTime) -> bool {
match self {
TimeFilter::Before(limit) => t <= limit,
TimeFilter::After(limit) => t >= limit,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn is_time_filter_applicable() {
use humantime;
let ref_time = humantime::parse_rfc3339("2010-10-10T10:10:10Z").unwrap();
assert!(TimeFilter::after(&ref_time, "1min")
.unwrap()
.applies_to(&ref_time));
assert!(!TimeFilter::before(&ref_time, "1min")
.unwrap()
.applies_to(&ref_time));
let t1m_ago = ref_time - Duration::from_secs(60);
assert!(!TimeFilter::after(&ref_time, "30sec")
.unwrap()
.applies_to(&t1m_ago));
assert!(TimeFilter::after(&ref_time, "2min")
.unwrap()
.applies_to(&t1m_ago));
assert!(TimeFilter::before(&ref_time, "30sec")
.unwrap()
.applies_to(&t1m_ago));
assert!(!TimeFilter::before(&ref_time, "2min")
.unwrap()
.applies_to(&t1m_ago));
let t10s_before = "2010-10-10 10:10:00";
assert!(!TimeFilter::before(&ref_time, t10s_before)
.unwrap()
.applies_to(&ref_time));
assert!(TimeFilter::before(&ref_time, t10s_before)
.unwrap()
.applies_to(&t1m_ago));
assert!(TimeFilter::after(&ref_time, t10s_before)
.unwrap()
.applies_to(&ref_time));
assert!(!TimeFilter::after(&ref_time, t10s_before)
.unwrap()
.applies_to(&t1m_ago));
}
}
+173
View File
@@ -0,0 +1,173 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use regex_syntax::hir::Hir;
use regex_syntax::Parser;
use std::ffi::OsString;
pub use self::file_types::FileTypes;
mod file_types;
pub mod filter;
pub mod opts;
macro_rules! print_error {
($($arg:tt)*) => (eprintln!("[fd error]: {}", format!($($arg)*)))
}
macro_rules! print_error_and_exit {
($($arg:tt)*) => {
print_error!($($arg)*);
::std::process::exit(1);
};
}
/// Determine if a regex pattern contains a literal uppercase character.
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
Parser::new()
.parse(pattern)
.map(|hir| hir_has_uppercase_char(&hir))
.unwrap_or(false)
}
/// Determine if a regex expression contains a literal uppercase character.
fn hir_has_uppercase_char(hir: &Hir) -> bool {
use regex_syntax::hir::*;
match *hir.kind() {
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
HirKind::Class(Class::Unicode(ref ranges)) => ranges
.iter()
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
hir_has_uppercase_char(hir)
}
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
hirs.iter().any(hir_has_uppercase_char)
}
_ => false,
}
}
/// Maximum size of the output buffer before flushing results to the console
pub const MAX_BUFFER_LENGTH: usize = 1000;
/// Traverse args_os, looking for -exec and replacing it with --exec.
///
/// # Returns
///
/// * The args, with substitution if required
pub fn transform_args_with_exec<I>(original: I) -> Vec<OsString>
where
I: Iterator<Item = OsString>,
{
let mut in_exec_opt = false;
let target = OsString::from("-exec");
let long_start = OsString::from("--exec");
let short_start = OsString::from("-x");
let exec_end = OsString::from(";");
original.fold(vec![], |mut args, curr| {
if in_exec_opt {
if curr == exec_end {
in_exec_opt = false;
}
args.push(curr);
return args;
}
if curr == target || curr == long_start || curr == short_start {
args.push(if curr == target {
OsString::from("--exec")
} else {
curr
});
in_exec_opt = true;
} else {
args.push(curr);
}
args
})
}
#[cfg(test)]
mod tests {
use super::*;
fn oss_vec(strs: &[&str]) -> Vec<OsString> {
strs.into_iter().map(OsString::from).collect()
}
/// Ensure that -exec gets transformed into --exec
#[test]
fn normal_exec_substitution() {
let original = oss_vec(&["fd", "foo", "-exec", "cmd"]);
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
/// Ensure that --exec is not touched
#[test]
fn passthru_of_original_exec() {
let original = oss_vec(&["fd", "foo", "--exec", "cmd"]);
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
#[test]
fn temp_check_that_exec_context_observed() {
let original = oss_vec(&[
"fd",
"foo",
"-exec",
"cmd",
"-exec",
"ls",
";",
"-exec",
"rm",
";",
"--exec",
"find",
"-exec",
"rm",
";",
"-x",
"foo",
"-exec",
"something",
";",
"-exec",
]);
let expected = oss_vec(&[
"fd",
"foo",
"--exec",
"cmd",
"-exec",
"ls",
";",
"--exec",
"rm",
";",
"--exec",
"find",
"-exec",
"rm",
";",
"-x",
"foo",
"-exec",
"something",
";",
"--exec",
]);
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
}
+78
View File
@@ -0,0 +1,78 @@
use exec::CommandTemplate;
use internal::{
filter::{SizeFilter, TimeFilter},
FileTypes,
};
use lscolors::LsColors;
use regex::RegexSet;
use std::{path::PathBuf, time::Duration};
/// Configuration options for *fd*.
pub struct FdOptions {
/// Whether the search is case-sensitive or case-insensitive.
pub case_sensitive: bool,
/// Whether to search within the full file path or just the base name (filename or directory
/// name).
pub search_full_path: bool,
/// Whether to ignore hidden files and directories (or not).
pub ignore_hidden: bool,
/// Whether to respect `.fdignore` files or not.
pub read_fdignore: bool,
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
pub read_vcsignore: bool,
/// Whether to follow symlinks or not.
pub follow_links: bool,
/// Whether elements of output should be separated by a null character
pub null_separator: bool,
/// The maximum search depth, or `None` if no maximum search depth should be set.
///
/// A depth of `1` includes all files under the current directory, a depth of `2` also includes
/// all files under subdirectories of the current directory, etc.
pub max_depth: Option<usize>,
/// The number of threads to use.
pub threads: usize,
/// Time to buffer results internally before streaming to the console. This is useful to
/// provide a sorted output, in case the total execution time is shorter than
/// `max_buffer_time`.
pub max_buffer_time: Option<Duration>,
/// `None` if the output should not be colorized. Otherwise, a `LsColors` instance that defines
/// how to style different filetypes.
pub ls_colors: Option<LsColors>,
/// The type of file to search for. If set to `None`, all file types are displayed. If
/// set to `Some(..)`, only the types that are specified are shown.
pub file_types: Option<FileTypes>,
/// The extension to search for. Only entries matching the extension will be included.
///
/// The value (if present) will be a lowercase string without leading dots.
pub extensions: Option<RegexSet>,
/// If a value is supplied, each item found will be used to generate and execute commands.
pub command: Option<CommandTemplate>,
/// A list of glob patterns that should be excluded from the search.
pub exclude_patterns: Vec<String>,
/// A list of custom ignore files.
pub ignore_files: Vec<PathBuf>,
/// The given constraints on the size of returned files
pub size_constraints: Vec<SizeFilter>,
/// Constraints on last modification time of files
pub time_constraints: Vec<TimeFilter>,
/// Whether or not to display filesystem errors
pub show_filesystem_errors: bool,
}
+71 -101
View File
@@ -6,10 +6,9 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
/// A parser for the `LS_COLORS` environment variable.
use std::collections::HashMap;
use ansi_term::{Colour, Style};
/// A parser for the `LS_COLORS` environment variable.
use std::collections::HashMap;
/// Maps file extensions to ANSI colors / styles.
pub type ExtensionStyles = HashMap<String, Style>;
@@ -18,43 +17,9 @@ pub type ExtensionStyles = HashMap<String, Style>;
pub type FilenameStyles = HashMap<String, Style>;
const LS_CODES: &[&str] = &[
"no",
"no",
"fi",
"rs",
"di",
"ln",
"ln",
"ln",
"or",
"mi",
"pi",
"pi",
"so",
"bd",
"bd",
"cd",
"cd",
"do",
"ex",
"lc",
"lc",
"rc",
"rc",
"ec",
"ec",
"su",
"su",
"sg",
"sg",
"st",
"ow",
"ow",
"tw",
"tw",
"ca",
"mh",
"cl",
"no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi", "so", "bd", "bd", "cd",
"cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec", "su", "su", "sg", "sg", "st", "ow", "ow",
"tw", "tw", "ca", "mh", "cl",
];
/// Defines how different file system entries should be colorized / styled.
@@ -202,65 +167,70 @@ impl LsColors {
}
}
#[test]
fn test_parse_simple() {
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31"));
}
#[test]
fn test_parse_decoration() {
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("00;31"));
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("03;34"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("01;36"));
}
#[test]
fn test_parse_decoration_backwards() {
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("34;03"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("36;01"));
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31;00"));
}
#[test]
fn test_parse_256() {
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("38;5;115")
);
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("00;38;5;115")
);
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("01;38;5;119")
);
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("38;5;119;01")
);
}
#[test]
fn test_from_string() {
assert_eq!(LsColors::default(), LsColors::from_string(&String::new()));
let result = LsColors::from_string(&String::from(
"rs=0:di=03;34:ln=01;36:*.foo=01;35:*README=33",
));
assert_eq!(Colour::Blue.italic(), result.directory);
assert_eq!(Colour::Cyan.bold(), result.symlink);
assert_eq!(Some(&Colour::Purple.bold()), result.extensions.get("foo"));
assert_eq!(
Some(&Colour::Yellow.normal()),
result.filenames.get("README")
);
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_simple() {
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31"));
}
#[test]
fn test_parse_decoration() {
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("00;31"));
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("03;34"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("01;36"));
}
#[test]
fn test_parse_decoration_backwards() {
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("34;03"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("36;01"));
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31;00"));
}
#[test]
fn test_parse_256() {
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("38;5;115")
);
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("00;38;5;115")
);
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("01;38;5;119")
);
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("38;5;119;01")
);
}
#[test]
fn test_from_string() {
assert_eq!(LsColors::default(), LsColors::from_string(&String::new()));
let result = LsColors::from_string(&String::from(
"rs=0:di=03;34:ln=01;36:*.foo=01;35:*README=33",
));
assert_eq!(Colour::Blue.italic(), result.directory);
assert_eq!(Colour::Cyan.bold(), result.symlink);
assert_eq!(Some(&Colour::Purple.bold()), result.extensions.get("foo"));
assert_eq!(
Some(&Colour::Yellow.normal()),
result.filenames.get("README")
);
}
}
+163 -54
View File
@@ -13,23 +13,24 @@ extern crate clap;
extern crate ignore;
#[macro_use]
extern crate lazy_static;
extern crate humantime;
#[cfg(all(unix, not(target_os = "redox")))]
extern crate libc;
extern crate num_cpus;
extern crate regex;
extern crate regex_syntax;
pub mod fshelper;
pub mod lscolors;
#[macro_use]
mod internal;
mod app;
mod exec;
mod internal;
mod exit_codes;
pub mod fshelper;
pub mod lscolors;
mod output;
mod walk;
#[cfg(windows)]
mod windows;
use std::env;
use std::error::Error;
use std::path::{Path, PathBuf};
@@ -37,15 +38,19 @@ use std::sync::Arc;
use std::time;
use atty::Stream;
use regex::RegexBuilder;
use regex::{RegexBuilder, RegexSetBuilder};
use exec::CommandTemplate;
use internal::{error, pattern_has_uppercase_char, FdOptions};
use internal::{
filter::{SizeFilter, TimeFilter},
opts::FdOptions,
pattern_has_uppercase_char, transform_args_with_exec, FileTypes,
};
use lscolors::LsColors;
use walk::FileType;
fn main() {
let matches = app::build_app().get_matches();
let checked_args = transform_args_with_exec(env::args_os());
let matches = app::build_app().get_matches_from(checked_args);
// Get the search pattern
let pattern = matches.value_of("pattern").unwrap_or("");
@@ -53,39 +58,69 @@ fn main() {
// Get the current working directory
let current_dir = Path::new(".");
if !fshelper::is_dir(current_dir) {
error("Error: could not get current directory.");
print_error_and_exit!("Could not get current directory.");
}
//Get one or more root directories to search.
let mut dir_vec: Vec<_> = match matches.values_of("path") {
Some(paths) => {
paths
.map(|path| {
let path_buffer = PathBuf::from(path);
if !fshelper::is_dir(&path_buffer) {
error(&format!(
"Error: '{}' is not a directory.",
path_buffer.to_string_lossy()
));
}
path_buffer
})
.collect::<Vec<_>>()
}
// Get one or more root directories to search.
let mut dir_vec: Vec<_> = match matches
.values_of("path")
.or(matches.values_of("search-path"))
{
Some(paths) => paths
.map(|path| {
let path_buffer = PathBuf::from(path);
if !fshelper::is_dir(&path_buffer) {
print_error_and_exit!(
"'{}' is not a directory.",
path_buffer.to_string_lossy()
);
}
path_buffer
})
.collect::<Vec<_>>(),
None => vec![current_dir.to_path_buf()],
};
if matches.is_present("absolute-path") {
dir_vec = dir_vec
.iter()
.map(|path_buffer| fshelper::absolute_path(path_buffer).unwrap())
.map(|path_buffer| {
path_buffer
.canonicalize()
.and_then(|pb| fshelper::absolute_path(pb.as_path()))
.unwrap()
})
.collect();
}
// Detect if the user accidentally supplied a path instead of a search pattern
if !matches.is_present("full-path")
&& pattern.contains(std::path::MAIN_SEPARATOR)
&& fshelper::is_dir(Path::new(pattern))
{
print_error_and_exit!(
"The search pattern '{pattern}' contains a path-separation character ('{sep}') \
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\
Instead, if you want to search for the pattern in the full path, use:\n\n \
fd --full-path '{pattern}'",
pattern = pattern,
sep = std::path::MAIN_SEPARATOR,
);
}
// Treat pattern as literal string if '--fixed-strings' is used
let pattern_regex = if matches.is_present("fixed-strings") {
regex::escape(pattern)
} else {
String::from(pattern)
};
// The search will be case-sensitive if the command line flag is set or
// if the pattern has an uppercase character (smart case).
let case_sensitive = !matches.is_present("ignore-case") &&
(matches.is_present("case-sensitive") || pattern_has_uppercase_char(pattern));
let case_sensitive = !matches.is_present("ignore-case")
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
let colored_output = match matches.value_of("color") {
Some("always") => true,
@@ -94,7 +129,7 @@ fn main() {
};
#[cfg(windows)]
let colored_output = colored_output && windows::enable_colored_output();
let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok();
let ls_colors = if colored_output {
Some(
@@ -109,21 +144,52 @@ fn main() {
let command = matches.values_of("exec").map(CommandTemplate::new);
let size_limits: Vec<SizeFilter> = matches
.values_of("size")
.map(|v| {
v.map(|sf| {
if let Some(f) = SizeFilter::from_string(sf) {
return f;
}
print_error_and_exit!("'{}' is not a valid size constraint. See 'fd --help'.", sf);
})
.collect()
})
.unwrap_or_else(|| vec![]);
let now = time::SystemTime::now();
let mut time_constraints: Vec<TimeFilter> = Vec::new();
if let Some(t) = matches.value_of("changed-within") {
if let Some(f) = TimeFilter::after(&now, t) {
time_constraints.push(f);
} else {
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
}
}
if let Some(t) = matches.value_of("changed-before") {
if let Some(f) = TimeFilter::before(&now, t) {
time_constraints.push(f);
} else {
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
}
}
let config = FdOptions {
case_sensitive,
search_full_path: matches.is_present("full-path"),
ignore_hidden: !(matches.is_present("hidden") ||
matches.occurrences_of("rg-alias-hidden-ignore") >= 2),
read_ignore: !(matches.is_present("no-ignore") ||
matches.is_present("rg-alias-hidden-ignore")),
read_gitignore: !(matches.is_present("no-ignore") ||
matches.is_present("rg-alias-hidden-ignore") ||
matches.is_present("no-ignore-vcs")),
ignore_hidden: !(matches.is_present("hidden")
|| matches.occurrences_of("rg-alias-hidden-ignore") >= 2),
read_fdignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")),
read_vcsignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")
|| matches.is_present("no-ignore-vcs")),
follow_links: matches.is_present("follow"),
null_separator: matches.is_present("null_separator"),
max_depth: matches.value_of("depth").and_then(|n| {
usize::from_str_radix(n, 10).ok()
}),
max_depth: matches
.value_of("depth")
.or_else(|| matches.value_of("rg-depth"))
.and_then(|n| usize::from_str_radix(n, 10).ok()),
threads: std::cmp::max(
matches
.value_of("threads")
@@ -136,29 +202,72 @@ fn main() {
.and_then(|n| u64::from_str_radix(n, 10).ok())
.map(time::Duration::from_millis),
ls_colors,
file_type: match matches.value_of("file-type") {
Some("f") | Some("file") => FileType::RegularFile,
Some("d") |
Some("directory") => FileType::Directory,
Some("l") | Some("symlink") => FileType::SymLink,
_ => FileType::Any,
},
extension: matches.value_of("extension").map(|e| {
e.trim_left_matches('.').to_lowercase()
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,
"x" | "executable" => {
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| {
let patterns = exts
.map(|e| e.trim_left_matches('.'))
.map(|e| format!(r".\.{}$", regex::escape(e)));
match RegexSetBuilder::new(patterns)
.case_insensitive(true)
.build()
{
Ok(re) => re,
Err(err) => {
print_error_and_exit!("{}", err.description());
}
}
}),
command,
exclude_patterns: matches
.values_of("exclude")
.map(|v| v.map(|p| String::from("!") + p).collect())
.unwrap_or_else(|| vec![]),
ignore_files: matches
.values_of("ignore-file")
.map(|vs| vs.map(PathBuf::from).collect())
.unwrap_or_else(|| vec![]),
size_constraints: size_limits,
time_constraints,
show_filesystem_errors: matches.is_present("show-errors"),
};
match RegexBuilder::new(pattern)
match RegexBuilder::new(&pattern_regex)
.case_insensitive(!config.case_sensitive)
.dot_matches_new_line(true)
.build() {
.build()
{
Ok(re) => walk::scan(&dir_vec, Arc::new(re), Arc::new(config)),
Err(err) => error(err.description()),
Err(err) => {
print_error_and_exit!(
"{}\nHint: You can use the '--fixed-strings' option to search for a \
literal string instead of a regular expression",
err.description()
);
}
}
}
+33 -28
View File
@@ -6,22 +6,36 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
use internal::FdOptions;
use exit_codes::ExitCode;
use fshelper::is_executable;
use internal::opts::FdOptions;
use lscolors::LsColors;
use std::{fs, process};
use std::io::{self, Write};
use std::ops::Deref;
use std::path::{self, Path, PathBuf, Component};
use std::sync::Arc;
use std::path::{self, Component, Path, PathBuf};
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::PermissionsExt;
use std::sync::Arc;
use ansi_term;
/// Remove the `./` prefix from a path.
fn strip_current_dir<'a>(pathbuf: &'a PathBuf) -> &'a Path {
let mut iter = pathbuf.components();
let mut iter_next = iter.clone();
if iter_next.next() == Some(Component::CurDir) {
iter.next();
}
iter.as_path()
}
pub fn print_entry(entry: &PathBuf, config: &FdOptions, wants_to_quit: &Arc<AtomicBool>) {
let path = entry.strip_prefix(".").unwrap_or(entry);
let path = if entry.is_absolute() {
entry.as_path()
} else {
strip_current_dir(entry)
};
let r = if let Some(ref ls_colors) = config.ls_colors {
print_entry_colorized(path, config, ls_colors, &wants_to_quit)
@@ -31,7 +45,7 @@ pub fn print_entry(entry: &PathBuf, config: &FdOptions, wants_to_quit: &Arc<Atom
if r.is_err() {
// Probably a broken pipe. Exit gracefully.
process::exit(0);
process::exit(ExitCode::GeneralError.into());
}
}
@@ -73,7 +87,7 @@ fn print_entry_colorized(
if wants_to_quit.load(Ordering::Relaxed) {
write!(handle, "\n")?;
process::exit(0);
process::exit(ExitCode::KilledBySigint.into());
}
}
@@ -92,7 +106,8 @@ fn print_entry_uncolorized(path: &Path, config: &FdOptions) -> io::Result<()> {
}
fn get_path_style<'a>(path: &Path, ls_colors: &'a LsColors) -> Option<&'a ansi_term::Style> {
if path.symlink_metadata()
if path
.symlink_metadata()
.map(|md| md.file_type().is_symlink())
.unwrap_or(false)
{
@@ -105,29 +120,19 @@ fn get_path_style<'a>(path: &Path, ls_colors: &'a LsColors) -> Option<&'a ansi_t
Some(&ls_colors.directory)
} else if metadata.map(|md| is_executable(&md)).unwrap_or(false) {
Some(&ls_colors.executable)
} else if let Some(filename_style) =
path.file_name().and_then(|n| n.to_str()).and_then(|n| {
ls_colors.filenames.get(n)
})
} else if let Some(filename_style) = path
.file_name()
.and_then(|n| n.to_str())
.and_then(|n| ls_colors.filenames.get(n))
{
Some(filename_style)
} else if let Some(extension_style) =
path.extension().and_then(|e| e.to_str()).and_then(|e| {
ls_colors.extensions.get(e)
})
} else if let Some(extension_style) = path
.extension()
.and_then(|e| e.to_str())
.and_then(|e| ls_colors.extensions.get(e))
{
Some(extension_style)
} else {
None
}
}
#[cfg(any(unix, target_os = "redox"))]
fn is_executable(md: &fs::Metadata) -> bool {
md.permissions().mode() & 0o111 != 0
}
#[cfg(windows)]
fn is_executable(_: &fs::Metadata) -> bool {
false
}
+170 -67
View File
@@ -9,19 +9,22 @@
extern crate ctrlc;
use exec;
use exit_codes::ExitCode;
use fshelper;
use internal::{error, FdOptions};
use internal::{opts::FdOptions, MAX_BUFFER_LENGTH};
use output;
use std::error::Error;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time;
use ignore::{self, WalkBuilder};
use ignore::overrides::OverrideBuilder;
use ignore::{self, WalkBuilder};
use regex::Regex;
/// The receiver thread can either be buffering results or directly streaming to the console.
@@ -34,13 +37,10 @@ enum ReceiverMode {
Streaming,
}
/// The type of file to search for.
#[derive(Copy, Clone)]
pub enum FileType {
Any,
RegularFile,
Directory,
SymLink,
/// The Worker threads can result in a valid entry having PathBuf or an error.
pub enum WorkerResult {
Entry(PathBuf),
Error(ignore::Error),
}
/// Recursively scan the given search path for files / pathnames matching the pattern.
@@ -50,36 +50,60 @@ pub enum FileType {
/// path will simply be written to standard output.
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
let mut path_iter = path_vec.iter();
let first_path_buf = path_iter.next().expect(
"Error: Path vector can not be empty",
);
let first_path_buf = path_iter
.next()
.expect("Error: Path vector can not be empty");
let (tx, rx) = channel();
let threads = config.threads;
let show_filesystem_errors = config.show_filesystem_errors;
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
for pattern in &config.exclude_patterns {
let res = override_builder.add(pattern);
if res.is_err() {
error(&format!("Error: malformed exclude pattern '{}'", pattern));
print_error_and_exit!("Malformed exclude pattern '{}'", pattern);
}
}
let overrides = override_builder.build().unwrap_or_else(|_| {
error("Mismatch in exclude patterns");
print_error_and_exit!("Mismatch in exclude patterns");
});
let mut walker = WalkBuilder::new(first_path_buf.as_path());
walker
.hidden(config.ignore_hidden)
.ignore(config.read_ignore)
.git_ignore(config.read_gitignore)
.parents(config.read_ignore || config.read_gitignore)
.git_global(config.read_gitignore)
.git_exclude(config.read_gitignore)
.ignore(config.read_fdignore)
.parents(config.read_fdignore || config.read_vcsignore)
.git_ignore(config.read_vcsignore)
.git_global(config.read_vcsignore)
.git_exclude(config.read_vcsignore)
.overrides(overrides)
.follow_links(config.follow_links)
.max_depth(config.max_depth);
if config.read_fdignore {
walker.add_custom_ignore_filename(".fdignore");
}
for ignore_file in &config.ignore_files {
let result = walker.add_ignore(ignore_file);
if let Some(err) = result {
match err {
ignore::Error::Partial(_) => (),
_ => {
print_error!(
"{}",
format!(
"Malformed pattern in custom ignore file '{}': {}.",
ignore_file.to_string_lossy(),
err.description()
)
);
}
}
}
}
for path_entry in path_iter {
walker.add(path_entry.as_path());
}
@@ -87,9 +111,14 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
let parallel_walker = walker.threads(threads).build_parallel();
let wants_to_quit = Arc::new(AtomicBool::new(false));
if config.ls_colors.is_some() {
let wq = Arc::clone(&wants_to_quit);
ctrlc::set_handler(move || { wq.store(true, Ordering::Relaxed); }).unwrap();
let receiver_wtq = Arc::clone(&wants_to_quit);
let sender_wtq = Arc::clone(&wants_to_quit);
if config.ls_colors.is_some() && config.command.is_none() {
let wq = Arc::clone(&receiver_wtq);
ctrlc::set_handler(move || {
wq.store(true, Ordering::Relaxed);
})
.unwrap();
}
// Spawn the thread that receives all results through the channel.
@@ -115,7 +144,8 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
let out_perm = Arc::clone(&out_perm);
// Spawn a job thread that will listen for and execute inputs.
let handle = thread::spawn(move || exec::job(rx, cmd, out_perm));
let handle =
thread::spawn(move || exec::job(rx, cmd, out_perm, show_filesystem_errors));
// Push the handle of the spawned thread into the vector for later joining.
handles.push(handle);
@@ -134,29 +164,40 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
let mut mode = ReceiverMode::Buffering;
// Maximum time to wait before we start streaming to the console.
let max_buffer_time = rx_config.max_buffer_time.unwrap_or_else(
|| time::Duration::from_millis(100),
);
let max_buffer_time = rx_config
.max_buffer_time
.unwrap_or_else(|| time::Duration::from_millis(100));
for value in rx {
match mode {
ReceiverMode::Buffering => {
buffer.push(value);
for worker_result in rx {
match worker_result {
WorkerResult::Entry(value) => {
match mode {
ReceiverMode::Buffering => {
buffer.push(value);
// Have we reached the maximum time?
if time::Instant::now() - start > max_buffer_time {
// Flush the buffer
for v in &buffer {
output::print_entry(v, &rx_config, &wants_to_quit);
// Have we reached the maximum buffer size or maximum buffering time?
if buffer.len() > MAX_BUFFER_LENGTH
|| time::Instant::now() - start > max_buffer_time
{
// Flush the buffer
for v in &buffer {
output::print_entry(v, &rx_config, &receiver_wtq);
}
buffer.clear();
// Start streaming
mode = ReceiverMode::Streaming;
}
}
ReceiverMode::Streaming => {
output::print_entry(&value, &rx_config, &receiver_wtq);
}
buffer.clear();
// Start streaming
mode = ReceiverMode::Streaming;
}
}
ReceiverMode::Streaming => {
output::print_entry(&value, &rx_config, &wants_to_quit);
WorkerResult::Error(err) => {
if show_filesystem_errors {
print_error!("{}", err);
}
}
}
}
@@ -166,7 +207,7 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
if !buffer.is_empty() {
buffer.sort();
for value in buffer {
output::print_entry(&value, &rx_config, &wants_to_quit);
output::print_entry(&value, &rx_config, &receiver_wtq);
}
}
}
@@ -177,45 +218,99 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
let config = Arc::clone(&config);
let pattern = Arc::clone(&pattern);
let tx_thread = tx.clone();
let wants_to_quit = Arc::clone(&sender_wtq);
Box::new(move |entry_o| {
if wants_to_quit.load(Ordering::Relaxed) {
return ignore::WalkState::Quit;
}
let entry = match entry_o {
Ok(e) => e,
Err(_) => return ignore::WalkState::Continue,
Err(err) => {
tx_thread.send(WorkerResult::Error(err)).unwrap();
return ignore::WalkState::Continue;
}
};
let entry_path = entry.path();
if entry.depth() == 0 {
return ignore::WalkState::Continue;
}
// Filter out unwanted file types.
match config.file_type {
FileType::Any => (),
FileType::RegularFile => {
if entry.file_type().map_or(true, |ft| !ft.is_file()) {
return ignore::WalkState::Continue;
}
}
FileType::Directory => {
if entry.file_type().map_or(true, |ft| !ft.is_dir()) {
return ignore::WalkState::Continue;
}
}
FileType::SymLink => {
if entry.file_type().map_or(true, |ft| !ft.is_symlink()) {
if let Some(ref file_types) = config.file_types {
if let Some(ref entry_type) = entry.file_type() {
if (!file_types.files && entry_type.is_file())
|| (!file_types.directories && entry_type.is_dir())
|| (!file_types.symlinks && entry_type.is_symlink())
|| (file_types.executables_only
&& !entry
.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()
|| entry_type.is_dir()
|| entry_type.is_symlink())
{
// This is probably a block device, char device, fifo or socket. Skip it.
return ignore::WalkState::Continue;
}
} else {
return ignore::WalkState::Continue;
}
}
let entry_path = entry.path();
// Filter out unwanted extensions.
if let Some(ref filter_ext) = config.extension {
let entry_ext = entry_path.extension().map(
|e| e.to_string_lossy().to_lowercase(),
);
if entry_ext.map_or(true, |ext| ext != *filter_ext) {
if let Some(ref exts_regex) = config.extensions {
if let Some(path_str) = entry_path.file_name().map_or(None, |s| s.to_str()) {
if !exts_regex.is_match(path_str) {
return ignore::WalkState::Continue;
}
} else {
return ignore::WalkState::Continue;
}
}
// Filter out unwanted sizes if it is a file and we have been given size constraints.
if config.size_constraints.len() > 0 {
if entry_path.is_file() {
if let Ok(metadata) = entry_path.metadata() {
let file_size = metadata.len();
if config
.size_constraints
.iter()
.any(|sc| !sc.is_within(file_size))
{
return ignore::WalkState::Continue;
}
} else {
return ignore::WalkState::Continue;
}
} else {
return ignore::WalkState::Continue;
}
}
// Filter out unwanted modification times
if !config.time_constraints.is_empty() {
let mut matched = false;
if entry_path.is_file() {
if let Ok(metadata) = entry_path.metadata() {
if let Ok(modified) = metadata.modified() {
matched = config
.time_constraints
.iter()
.all(|tf| tf.applies_to(&modified));
}
}
}
if !matched {
return ignore::WalkState::Continue;
}
}
@@ -223,7 +318,9 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
let search_str_o = if config.search_full_path {
match fshelper::path_absolute_form(entry_path) {
Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),
Err(_) => error("Error: unable to get full path."),
Err(_) => {
print_error_and_exit!("Unable to retrieve absolute path.");
}
}
} else {
entry_path.file_name().map(|f| f.to_string_lossy())
@@ -232,7 +329,9 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
if let Some(search_str) = search_str_o {
if pattern.is_match(&*search_str) {
// TODO: take care of the unwrap call
tx_thread.send(entry_path.to_owned()).unwrap()
tx_thread
.send(WorkerResult::Entry(entry_path.to_owned()))
.unwrap()
}
}
@@ -246,4 +345,8 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
// Wait for the receiver thread to print out all results.
receiver_thread.join().unwrap();
if wants_to_quit.load(Ordering::Relaxed) {
process::exit(ExitCode::KilledBySigint.into());
}
}
-39
View File
@@ -1,39 +0,0 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
extern crate kernel32;
extern crate winapi;
use self::kernel32::{GetStdHandle, GetConsoleMode, SetConsoleMode};
use self::winapi::{STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE};
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example
pub fn enable_colored_output() -> bool {
unsafe {
let handle = GetStdHandle(STD_OUTPUT_HANDLE);
if handle == INVALID_HANDLE_VALUE {
return false;
}
// https://docs.microsoft.com/en-us/windows/console/getconsolemode
let mut mode = 0;
if GetConsoleMode(handle, &mut mode) == 0 {
return false;
}
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
//
// A console consists of an input buffer and one or more screen buffers. ... Setting the
// output modes of one screen buffer does not affect the output modes of other screen
// buffers.
SetConsoleMode(handle, mode) != 0
}
}
+11 -9
View File
@@ -44,6 +44,9 @@ fn create_working_directory(
{
let root = temp_dir.path();
// Pretend that this is a Git repository in order for `.gitignore` files to be respected
fs::create_dir_all(root.join(".git"))?;
for directory in directories {
fs::create_dir_all(root.join(directory))?;
}
@@ -52,19 +55,17 @@ fn create_working_directory(
fs::File::create(root.join(file))?;
}
#[cfg(unix)] unix::fs::symlink(root.join("one/two"), root.join("symlink"))?;
#[cfg(unix)]
unix::fs::symlink(root.join("one/two"), root.join("symlink"))?;
// Note: creating symlinks on Windows requires the `SeCreateSymbolicLinkPrivilege` which
// is by default only granted for administrators.
#[cfg(windows)] windows::fs::symlink_dir(root.join("one/two"), root.join("symlink"))?;
#[cfg(windows)]
windows::fs::symlink_dir(root.join("one/two"), root.join("symlink"))?;
fs::File::create(root.join(".ignore"))?.write_all(
b"ignored.foo",
)?;
fs::File::create(root.join(".fdignore"))?.write_all(b"fdignored.foo")?;
fs::File::create(root.join(".gitignore"))?.write_all(
b"gitignored.foo",
)?;
fs::File::create(root.join(".gitignore"))?.write_all(b"gitignored.foo")?;
}
Ok(temp_dir)
@@ -122,7 +123,8 @@ fn format_output_error(args: &[&str], expected: &str, actual: &str) -> String {
/// Normalize the output for comparison.
fn normalize_output(s: &str, trim_left: bool) -> String {
// Split into lines and normalize separators.
let mut lines = s.replace('\0', "NULL\n")
let mut lines = s
.replace('\0', "NULL\n")
.lines()
.map(|line| {
let line = if trim_left { line.trim_left() } else { line };
+479 -33
View File
@@ -8,12 +8,18 @@
//! Integration tests for the CLI interface of fd.
extern crate filetime;
extern crate humantime;
extern crate regex;
mod testenv;
use testenv::TestEnv;
use regex::escape;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::time::{Duration, SystemTime};
use testenv::TestEnv;
static DEFAULT_DIRS: &'static [&'static str] = &["one/two/three", "one/two/three/directory_foo"];
@@ -23,14 +29,15 @@ static DEFAULT_FILES: &'static [&'static str] = &[
"one/two/c.foo",
"one/two/C.Foo2",
"one/two/three/d.foo",
"ignored.foo",
"fdignored.foo",
"gitignored.foo",
".hidden.foo",
"e1 e2",
];
fn get_absolute_root_path(env: &TestEnv) -> String {
let path = env.test_root()
let path = env
.test_root()
.canonicalize()
.expect("absolute path")
.to_str()
@@ -43,6 +50,20 @@ fn get_absolute_root_path(env: &TestEnv) -> String {
path
}
#[cfg(test)]
fn get_test_env_with_abs_path(dirs: &[&'static str], files: &[&'static str]) -> (TestEnv, String) {
let env = TestEnv::new(dirs, files);
let root_path = get_absolute_root_path(&env);
(env, root_path)
}
#[cfg(test)]
fn create_file_with_size<P: AsRef<Path>>(path: P, size_in_bytes: usize) {
let content = "#".repeat(size_in_bytes);
let mut f = fs::File::create::<P>(path).unwrap();
f.write(content.as_bytes()).unwrap();
}
/// Simple tests
#[test]
fn test_simple() {
@@ -265,7 +286,7 @@ fn test_no_ignore() {
te.assert_output(
&["--no-ignore", "foo"],
"a.foo
ignored.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
@@ -278,7 +299,7 @@ fn test_no_ignore() {
&["--hidden", "--no-ignore", "foo"],
".hidden.foo
a.foo
ignored.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
@@ -288,6 +309,70 @@ fn test_no_ignore() {
);
}
/// .gitignore and .fdignore
#[test]
fn test_gitignore_and_fdignore() {
let files = &[
"ignored-by-nothing",
"ignored-by-fdignore",
"ignored-by-gitignore",
"ignored-by-both",
];
let te = TestEnv::new(&[], files);
fs::File::create(te.test_root().join(".fdignore"))
.unwrap()
.write_all(b"ignored-by-fdignore\nignored-by-both")
.unwrap();
fs::File::create(te.test_root().join(".gitignore"))
.unwrap()
.write_all(b"ignored-by-gitignore\nignored-by-both")
.unwrap();
te.assert_output(&["ignored"], "ignored-by-nothing");
te.assert_output(
&["--no-ignore-vcs", "ignored"],
"ignored-by-nothing
ignored-by-gitignore",
);
te.assert_output(
&["--no-ignore", "ignored"],
"ignored-by-nothing
ignored-by-fdignore
ignored-by-gitignore
ignored-by-both",
);
}
/// Precedence of .fdignore files
#[test]
fn test_custom_ignore_precedence() {
let dirs = &["inner"];
let files = &["inner/foo"];
let te = TestEnv::new(dirs, files);
// Ignore 'foo' via .gitignore
fs::File::create(te.test_root().join("inner/.gitignore"))
.unwrap()
.write_all(b"foo")
.unwrap();
// Whitelist 'foo' via .fdignore
fs::File::create(te.test_root().join(".fdignore"))
.unwrap()
.write_all(b"!foo")
.unwrap();
te.assert_output(&["foo"], "inner/foo");
te.assert_output(&["--no-ignore-vcs", "foo"], "inner/foo");
te.assert_output(&["--no-ignore", "foo"], "inner/foo");
}
/// VCS ignored files (--no-ignore-vcs)
#[test]
fn test_no_ignore_vcs() {
@@ -305,6 +390,25 @@ fn test_no_ignore_vcs() {
);
}
/// Custom ignore files (--ignore-file)
#[test]
fn test_custom_ignore_files() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
// Ignore 'C.Foo2' and everything in 'three'.
fs::File::create(te.test_root().join("custom.ignore"))
.unwrap()
.write_all(b"C.Foo2\nthree")
.unwrap();
te.assert_output(
&["--ignore-file", "custom.ignore", "foo"],
"a.foo
one/b.foo
one/two/c.foo",
);
}
/// Ignored files with ripgrep aliases (-u / -uu)
#[test]
fn test_no_ignore_aliases() {
@@ -313,7 +417,7 @@ fn test_no_ignore_aliases() {
te.assert_output(
&["-u", "foo"],
"a.foo
ignored.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
@@ -326,7 +430,7 @@ fn test_no_ignore_aliases() {
&["-uu", "foo"],
".hidden.foo
a.foo
ignored.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
@@ -406,9 +510,7 @@ fn test_max_depth() {
/// Absolute paths (--absolute-path)
#[test]
fn test_absolute_path() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let abs_path = get_absolute_root_path(&te);
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--absolute-path"],
@@ -440,6 +542,12 @@ fn test_absolute_path() {
abs_path = &abs_path
),
);
}
/// Show absolute paths if the path argument is absolute
#[test]
fn test_implicit_absolute_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["foo", &abs_path],
@@ -455,6 +563,26 @@ fn test_absolute_path() {
);
}
/// Absolute paths should be normalized
#[test]
fn test_normalized_absolute_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output_subdirectory(
"one",
&["--absolute-path", "foo", ".."],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
/// File type filter (--type)
#[test]
fn test_type() {
@@ -470,6 +598,8 @@ fn test_type() {
one/two/three/d.foo",
);
te.assert_output(&["--type", "f", "e1"], "e1 e2");
te.assert_output(
&["--type", "d"],
"one
@@ -478,9 +608,72 @@ fn test_type() {
one/two/three/directory_foo",
);
te.assert_output(
&["--type", "d", "--type", "l"],
"one
one/two
one/two/three
one/two/three/directory_foo
symlink",
);
te.assert_output(&["--type", "l"], "symlink");
}
/// Test `--type executable`
#[cfg(unix)]
#[test]
fn test_type_executable() {
use std::os::unix::fs::OpenOptionsExt;
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
fs::OpenOptions::new()
.create(true)
.write(true)
.mode(0o777)
.open(te.test_root().join("executable-file.sh"))
.unwrap();
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",
);
}
/// 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() {
@@ -502,16 +695,47 @@ fn test_extension() {
one/two/three/d.foo",
);
te.assert_output(
&["--extension", ".foo", "--extension", "foo2"],
"a.foo
one/b.foo
one/two/c.foo
one/two/three/d.foo
one/two/C.Foo2",
);
te.assert_output(&["--extension", ".foo", "a"], "a.foo");
te.assert_output(&["--extension", "foo2"], "one/two/C.Foo2");
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",
);
te2.assert_output(&["--extension", "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", ".e\u{301}xt"], "latin1.e\u{301}xt");
let te4 = TestEnv::new(&[], &[".hidden", "test.hidden"]);
te4.assert_output(&["--hidden", "--extension", ".hidden"], "test.hidden");
}
/// Symlinks misc
/// Symlink as search directory
#[test]
fn test_symlink() {
fn test_symlink_as_root() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let abs_path = get_absolute_root_path(&te);
// From: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html
// The getcwd() function shall place an absolute pathname of the current working directory in
// the array pointed to by buf, and return buf. The pathname shall contain no components that
@@ -544,20 +768,29 @@ fn test_symlink() {
dir = &parent_parent
),
);
}
#[test]
fn test_symlink_and_absolute_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output_subdirectory(
"symlink",
&["--absolute-path"],
&format!(
"{abs_path}/{dir}/c.foo
{abs_path}/{dir}/C.Foo2
{abs_path}/{dir}/three
{abs_path}/{dir}/three/d.foo
{abs_path}/{dir}/three/directory_foo",
dir = if cfg!(windows) { "symlink" } else { "one/two" },
"{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
#[test]
fn test_symlink_as_absolute_root() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["", &format!("{abs_path}/symlink", abs_path = abs_path)],
@@ -570,7 +803,11 @@ fn test_symlink() {
abs_path = &abs_path
),
);
}
#[test]
fn test_symlink_and_full_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
let root = te.system_root();
let prefix = escape(&root.to_string_lossy());
@@ -582,14 +819,19 @@ fn test_symlink() {
&format!("^{prefix}.*three", prefix = prefix),
],
&format!(
"{abs_path}/{dir}/three
{abs_path}/{dir}/three/d.foo
{abs_path}/{dir}/three/directory_foo",
dir = if cfg!(windows) { "symlink" } else { "one/two" },
"{abs_path}/one/two/three
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
#[test]
fn test_symlink_and_full_path_abs_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
let root = te.system_root();
let prefix = escape(&root.to_string_lossy());
te.assert_output(
&[
"--full-path",
@@ -658,14 +900,28 @@ fn test_excludes() {
/// Shell script execution (--exec)
#[test]
fn test_exec() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
assert_exec_output("--exec");
}
let abs_path = get_absolute_root_path(&te);
/// Shell script execution using -exec
#[test]
fn test_exec_substitution() {
assert_exec_output("-exec");
}
// Shell script execution using -x
#[test]
fn test_exec_short_arg() {
assert_exec_output("-x");
}
#[cfg(test)]
fn assert_exec_output(exec_style: &str) {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
// TODO Windows tests: D:file.txt \file.txt \\server\share\file.txt ...
if !cfg!(windows) {
te.assert_output(
&["--absolute-path", "foo", "--exec", "echo"],
&["--absolute-path", "foo", exec_style, "echo"],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
@@ -678,7 +934,7 @@ fn test_exec() {
);
te.assert_output(
&["foo", "--exec", "echo", "{}"],
&["foo", exec_style, "echo", "{}"],
"a.foo
one/b.foo
one/two/C.Foo2
@@ -688,7 +944,7 @@ fn test_exec() {
);
te.assert_output(
&["foo", "--exec", "echo", "{.}"],
&["foo", exec_style, "echo", "{.}"],
"a
one/b
one/two/C
@@ -698,7 +954,7 @@ fn test_exec() {
);
te.assert_output(
&["foo", "--exec", "echo", "{/}"],
&["foo", exec_style, "echo", "{/}"],
"a.foo
b.foo
C.Foo2
@@ -708,7 +964,7 @@ fn test_exec() {
);
te.assert_output(
&["foo", "--exec", "echo", "{/.}"],
&["foo", exec_style, "echo", "{/.}"],
"a
b
C
@@ -718,7 +974,7 @@ fn test_exec() {
);
te.assert_output(
&["foo", "--exec", "echo", "{//}"],
&["foo", exec_style, "echo", "{//}"],
".
one
one/two
@@ -727,6 +983,196 @@ fn test_exec() {
one/two/three",
);
te.assert_output(&["e1", "--exec", "printf", "%s.%s\n"], "e1 e2.");
te.assert_output(&["e1", exec_style, "printf", "%s.%s\n"], "e1 e2.");
}
}
/// Literal search (--fixed-strings)
#[test]
fn test_fixed_strings() {
let dirs = &["test1", "test2"];
let files = &["test1/a.foo", "test1/a_foo", "test2/Download (1).tar.gz"];
let te = TestEnv::new(dirs, files);
// Regex search, dot is treated as "any character"
te.assert_output(
&["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");
// Regex search, parens are treated as group
te.assert_output(&["download (1)"], "");
// Literal search, parens are treated as characters
te.assert_output(
&["--fixed-strings", "download (1)"],
"test2/Download (1).tar.gz",
);
// Combine with --case-sensitive
te.assert_output(&["--fixed-strings", "--case-sensitive", "download (1)"], "");
}
/// Filenames with invalid UTF-8 sequences
#[cfg(target_os = "linux")]
#[test]
fn test_invalid_utf8() {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
let dirs = &["test1"];
let files = &[];
let te = TestEnv::new(dirs, files);
fs::File::create(
te.test_root()
.join(OsStr::from_bytes(b"test1/test_\xFEinvalid.txt")),
)
.unwrap();
te.assert_output(&["", "test1/"], "test1/test_invalid.txt");
te.assert_output(&["invalid", "test1/"], "test1/test_invalid.txt");
// Should not be found under a different extension
te.assert_output(&["-e", "zip", "", "test1/"], "");
}
/// Filtering for file size (--size)
#[test]
fn test_size() {
let te = TestEnv::new(&[], &[]);
create_file_with_size(te.test_root().join("0_bytes.foo"), 0);
create_file_with_size(te.test_root().join("11_bytes.foo"), 11);
create_file_with_size(te.test_root().join("30_bytes.foo"), 30);
create_file_with_size(te.test_root().join("3_kilobytes.foo"), 3 * 1000);
create_file_with_size(te.test_root().join("4_kibibytes.foo"), 4 * 1024);
// 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",
);
// Zero sized files.
te.assert_output(&["", "--size", "-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",
);
// Files with 2 bytes or less.
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");
// Files with size between 1 byte and 30 bytes.
te.assert_output(
&["", "--size", "+1B", "--size", "-30B"],
"11_bytes.foo
30_bytes.foo",
);
// Combine with a search pattern
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");
// 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");
// Files with size between 3 kilobytes and 5 kilobytes.
te.assert_output(
&["", "--size", "+3k", "--size", "-5k"],
"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");
// Files with size equal 4 kibibytes.
te.assert_output(&["", "--size", "+4ki", "--size", "-4ki"], "4_kibibytes.foo");
}
#[cfg(test)]
fn create_file_with_modified<P: AsRef<Path>>(path: P, duration_in_secs: u64) {
let st = SystemTime::now() - Duration::from_secs(duration_in_secs);
let ft = filetime::FileTime::from_system_time(st);
fs::File::create(&path).expect("creation failed");
filetime::set_file_times(&path, ft, ft).expect("time modification failed");
}
#[test]
fn test_modified_relative() {
let te = TestEnv::new(&[], &[]);
create_file_with_modified(te.test_root().join("foo_0_now"), 0);
create_file_with_modified(te.test_root().join("bar_1_min"), 60);
create_file_with_modified(te.test_root().join("foo_10_min"), 600);
create_file_with_modified(te.test_root().join("bar_1_h"), 60 * 60);
create_file_with_modified(te.test_root().join("foo_2_h"), 2 * 60 * 60);
create_file_with_modified(te.test_root().join("bar_1_day"), 24 * 60 * 60);
te.assert_output(
&["", "--changed-within", "15min"],
"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",
);
te.assert_output(
&["foo", "--changed-within", "12h"],
"foo_0_now
foo_10_min
foo_2_h",
);
}
#[cfg(test)]
fn change_file_modified<P: AsRef<Path>>(path: P, iso_date: &str) {
let st = humantime::parse_rfc3339(iso_date).expect("invalid date");
let ft = filetime::FileTime::from_system_time(st);
filetime::set_file_times(path, ft, ft).expect("time modification failde");
}
#[test]
fn test_modified_asolute() {
let te = TestEnv::new(&[], &["15mar2018", "30dec2017"]);
change_file_modified(te.test_root().join("15mar2018"), "2018-03-15T12:00:00Z");
change_file_modified(te.test_root().join("30dec2017"), "2017-12-30T23:59:00Z");
te.assert_output(
&["", "--change-newer-than", "2018-01-01 00:00:00"],
"15mar2018",
);
te.assert_output(
&["", "--changed-before", "2018-01-01 00:00:00"],
"30dec2017",
);
}