Compare commits

...

228 Commits

Author SHA1 Message Date
sharkdp 5648597a61 Bump version to 8.1.1 2020-05-25 15:38:30 +02:00
sharkdp b3258e00df Update CHANGELOG 2020-05-25 15:37:03 +02:00
sharkdp 41d7cc33ca Add CHANGELOG entry 2020-05-25 12:17:32 +02:00
sharkdp a81fef9992 Windows: Check for xterm-256color environment variable as a proxy for color support 2020-05-25 12:17:32 +02:00
sharkdp 53c338d71f Windows: Always enable colors if --color=always is set 2020-05-25 12:17:32 +02:00
Dmitry Marakasov d77daa89a2 Merged into single project in repology 2020-05-24 17:25:52 +02:00
sharkdp 78cde856b1 Update dependencies 2020-05-19 18:01:51 +02:00
sharkdp a954944d69 Prepare 8.1.0 release 2020-05-19 17:58:07 +02:00
sharkdp 27162c2729 Update CHANGELOG 2020-05-19 17:57:14 +02:00
sharkdp 1d1eefdb7b Disable jemalloc on macOS
This is a workaround for #498. See issue for details.

closes #498
2020-05-19 17:55:21 +02:00
sharkdp c8746f6216 Use macOS 10.15.4 (Catalina) for Travis build 2020-05-19 17:55:21 +02:00
sharkdp c761d51dfb Add CHANGELOG entry 2020-05-19 17:09:27 +02:00
sharkdp ddee6aa8f3 Revert "Always enable colors on Windows, closes #469"
This reverts commit a4e78519c4.
2020-05-19 17:09:27 +02:00
sharkdp e3c77ef7f5 Update --size documentation
closes #584
2020-05-19 17:06:32 +02:00
sharkdp c0592e276e Disable caching on Travis 2020-05-19 16:24:06 +02:00
sharkdp d41a5a9e24 Update CHANGELOG 2020-05-19 15:57:47 +02:00
sharkdp 726fede7cc Code formatting 2020-05-19 15:57:47 +02:00
sharkdp 9d3cb69935 Minor refactoring 2020-05-19 15:57:47 +02:00
DJRHails da9af8c1eb Improve consistency of linting 2020-05-19 15:57:47 +02:00
DJRHails dc120c547d Add error checking on missing test case 2020-05-19 15:57:47 +02:00
DJRHails bbf3d0d430 Update testenv to support errors that don't fail 2020-05-19 15:57:47 +02:00
DJRHails 4f4591bfdc Correct Err return to prevent panic 2020-05-19 15:57:47 +02:00
DJRHails 28d6a4f518 Check for no valid search paths to prevent panic 2020-05-19 15:57:47 +02:00
DJRHails 42719624a8 Refactor to for-loop 2020-05-19 15:57:47 +02:00
DJRHails fef1b91feb Remove test assertion covered by existing tests 2020-05-19 15:57:47 +02:00
DJRHails c5a311d3cc Allow for missing directories 2020-05-19 15:57:47 +02:00
DJRHails ca2ef7ba82 Add a test for a single missing search path 2020-05-19 15:57:47 +02:00
sharkdp 65b65b32be Update lockfile 2020-05-19 14:01:22 +02:00
sharkdp 3283a7b345 Fix warning 2020-05-19 14:01:00 +02:00
sharkdp 4cafb28951 Update CHANGELOG 2020-05-18 21:20:23 +02:00
Bobbie Soedirgo 79d5a5bdc5 Add global fdignore support 2020-05-18 21:19:09 +02:00
Alexandru Macovei a9dc45ecb1 [ownership] document --owner in manpage and changelog 2020-05-18 21:11:33 +02:00
Alexandru Macovei 1a16003cca [ownership][errs] ':' and '' args to --owner result in noop; reject strings with more than one ':' 2020-05-18 21:11:33 +02:00
Alexandru Macovei 6a68ac8c27 [ownership] add help text for --owner 2020-05-18 21:11:33 +02:00
Alexandru Macovei 09cdf9831d [ownership] parse leading exclamation mark as negation of ownership component 2020-05-18 21:11:33 +02:00
Alexandru Macovei 22a55361e0 [ownership] change uid/gid storage to tristate enum for negation 2020-05-18 21:11:33 +02:00
Alexandru Macovei d7cc34e36f [ownership] depend on users 0.10.0 on unix; parse user and group names 2020-05-18 21:11:33 +02:00
Alexandru Macovei 4f9a02400a [ownership] add --owner arg parsing and storage 2020-05-18 21:11:33 +02:00
Alexandru Macovei 30cb4adc61 [ownership] initial struct, parsing, and test framework 2020-05-18 21:11:33 +02:00
Jake Zimmerman daf3046a35 Add as-tree to the README
Suggested in this comment:

    https://github.com/sharkdp/fd/issues/283#issuecomment-627465260
2020-05-13 08:16:02 +02:00
Alex Kitchens 151eaad043 Fix "unnecessary braces" warning
Original warning:
```
warning: unnecessary braces around block return value
 --> src/filter/size.rs:5:39
  |
5 |     static ref SIZE_CAPTURES: Regex = { Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap() };
  |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove these braces
  |
  = note: `#[warn(unused_braces)]` on by default

warning: 1 warning emitted
```
2020-05-10 21:27:55 +02:00
Alexandru Macovei 4f4330167a Fix some clippy lints, and format 2020-05-01 14:08:53 +02:00
sharkdp f31fb9bf68 Update CHANGELOG 2020-04-26 22:02:24 +02:00
Seamus Connor b22285ffe4 Fix --glob behavior with empty pattern
With an empty pattern, --glob results in no matches. This does not
align with the behavior of --fixed-strings or --regex. Update the
initialization code to handle globs with empty patterns.
2020-04-26 22:01:29 +02:00
David Peter db6fc44d05 Add packaging status to README 2020-04-24 12:40:55 +02:00
sharkdp 90de7242c7 Update CHANGELOG 2020-04-17 21:39:09 +02:00
Tobias Kortkamp 04bcd546b2 Also add NetBSD and OpenBSD 2020-04-16 20:33:32 +02:00
Tobias Kortkamp 4b5e8941a7 Fix --list-details on FreeBSD 2020-04-16 20:33:32 +02:00
sharkdp fe00860b87 Add issue templates 2020-04-16 11:24:24 +02:00
sharkdp 0335cc362b Do not allow list-details in combination with -1 2020-04-16 10:11:05 +02:00
sharkdp a06efe1418 Update USAGE section 2020-04-16 10:02:29 +02:00
sharkdp 1a96544b29 Remove two options from short -h help text 2020-04-16 10:02:29 +02:00
sharkdp ee1a9f567f Bump version to 8.0 2020-04-16 10:02:29 +02:00
sharkdp 17fbc2dc52 Update CHANGELOG 2020-04-16 10:02:29 +02:00
sharkdp 074de44caf Update dependencies 2020-04-16 10:02:29 +02:00
sharkdp cb56add1d8 Add new --type socket and --type pipe filters
closes #511
2020-04-16 09:59:45 +02:00
sharkdp 4287cddc00 Add test 2020-04-15 23:32:28 +02:00
sharkdp 0b7febc3b6 Windows support (if GNU ls is installed) 2020-04-15 23:32:28 +02:00
sharkdp d91100e354 Add comment why we prefer gls 2020-04-15 23:32:28 +02:00
sharkdp 0f5500adfc Do not show output of 'gls --version' 2020-04-15 23:32:28 +02:00
sharkdp 2685728695 Add MacOS support for -l/--list-details 2020-04-15 23:32:28 +02:00
sharkdp 789706c3ab Update CHANGELOG 2020-04-15 19:33:02 +02:00
sharkdp 4a0730a5fa Update CHANGELOG 2020-04-15 19:23:59 +02:00
sharkdp 3b42898a69 Always enable colors on Windows, closes #469 2020-04-15 19:23:59 +02:00
sharkdp d63c63be8c Add --min-depth and --exact-depth
Add new `--min-depth <depth>` and `--exact-depth <depth>` options in addition to the existing
option to limit the maximum depth.

closes #404
2020-04-15 17:02:41 +02:00
sharkdp 47974b6479 --glob: '*' does not match on path separators 2020-04-15 16:41:56 +02:00
Danny Mösch 2bab4a2249 Add alias -1 for --max-results=1 2020-04-10 21:39:30 +02:00
sharkdp 95eae00126 Fix test on Windows 2020-04-04 18:46:08 +02:00
sharkdp ed024ceb54 Remove code duplication, add feature gate 2020-04-04 18:46:08 +02:00
sharkdp 6c9e743d43 Print path as raw bytes, allowing invalid UTF-8 filenames to be passed to other processes 2020-04-04 18:46:08 +02:00
sharkdp ea21df3f76 Add tests originally written by @alexmaco in #309 2020-04-04 18:46:08 +02:00
sharkdp 7e3c69c096 Reimplement dirname, basename, remove_extension for OsStr 2020-04-04 18:46:08 +02:00
sharkdp 439548c25a panic => unreachable 2020-04-04 18:46:08 +02:00
sharkdp ed45d1c5af Remove duplicate 'strip prefix' function 2020-04-04 18:46:08 +02:00
sharkdp e44f2f8540 Rewrite replace_path_separator function 2020-04-03 23:01:57 +02:00
sharkdp 2ea77e7cdc Add TODOs, move function 2020-04-03 22:51:41 +02:00
sharkdp a85f33cf98 Clean up build.rs 2020-04-03 22:36:32 +02:00
sharkdp 4815ee416e Remove process::exit instance 2020-04-03 22:36:32 +02:00
sharkdp 53557ef471 Clean up include order 2020-04-03 22:36:32 +02:00
sharkdp f9372b55d5 Remove irrelevant TODO 2020-04-03 22:36:32 +02:00
sharkdp 4590ae8535 Proper error handling within walk.rs 2020-04-03 22:36:32 +02:00
sharkdp bce95274e3 Proper error handling in main.rs 2020-04-03 22:36:32 +02:00
sharkdp af9d8082a1 Better error message 2020-04-03 22:36:32 +02:00
sharkdp 094516aec3 Refactor directory handling 2020-04-03 22:36:32 +02:00
sharkdp 33eb857a81 Cleanup 2020-04-03 22:36:32 +02:00
sharkdp 6e96154d86 Add tests for strip_current_dir 2020-04-03 22:36:32 +02:00
sharkdp 556c40e1f4 Move strip_current_dir 2020-04-03 22:36:32 +02:00
sharkdp b415d7234b Rename fshelper to filesystem 2020-04-03 22:36:32 +02:00
sharkdp e0cba4b700 Remove 'internal' module 2020-04-03 22:36:32 +02:00
sharkdp 342d12db3f Remove possibility to use '--exec <cmd>' with a single dash 2020-04-03 22:36:32 +02:00
sharkdp c79d1638f5 Move MAX_BUFFER_LENGTH 2020-04-03 22:36:32 +02:00
sharkdp 9ef9bf7b80 Add tests for regex_helper 2020-04-03 22:36:32 +02:00
sharkdp 809fd8fb78 Move regex helpers to new module 2020-04-03 22:36:32 +02:00
sharkdp a3060f952e Move error macros to error.rs 2020-04-03 22:36:32 +02:00
sharkdp 65096a653e Move FdOptions to Options 2020-04-03 22:36:32 +02:00
sharkdp 9f738ad995 Move filter up from internal 2020-04-03 22:36:32 +02:00
sharkdp 5c27ab1a06 Move filetypes up from 'internal' 2020-04-03 22:36:32 +02:00
sharkdp 2cfb1a2bb2 Adapt --list-details help message 2020-04-03 11:28:01 +02:00
sharkdp 938dafda28 Clean up unused help texts 2020-04-03 11:26:20 +02:00
sharkdp e2a7d3446b Inline the help texts 2020-04-03 11:24:47 +02:00
sharkdp e42ddda5a6 Clean up exit code handling 2020-04-03 10:48:27 +02:00
sharkdp 289d6443a7 Use .write_all 2020-04-03 10:37:53 +02:00
sharkdp 3066445e6b Remove empty line 2020-04-03 10:36:05 +02:00
sharkdp 364555fd71 Clean up Cargo.toml 2020-04-03 10:35:30 +02:00
sharkdp 80b042ad47 Clean up CI files 2020-04-03 10:26:15 +02:00
sharkdp 4274075f6f Remove outdated license headers (not strictly required by Apache-2.0) 2020-04-03 10:19:13 +02:00
sharkdp b9d0e72f70 Solve clippy issues 2020-04-03 10:08:47 +02:00
sharkdp 33df48ebe4 Remove claim about unique search results 2020-04-03 10:07:46 +02:00
sharkdp c9dc617711 Rename --list to --list-details 2020-04-03 09:58:47 +02:00
sharkdp da5cd12b24 Add -l/--list option
Add a new `-l`/`--list` option to show more details about the search results. This is basically
an alias for `--exec-batch ls -l` with some additional `ls` options.
This can be used in order to:
    * see metadata like permissions, owner, file size, modification times (#491)
    * see symlink targets (#482)
    * achieve a deterministic output order (#324, #196, #159)
    * avoid duplicate search results when multiple search paths are given (#405)
2020-04-03 09:58:47 +02:00
sharkdp 1714d416e2 Fix test on Windows 2020-04-02 20:27:41 +02:00
sharkdp d43827fe57 Add --max-results=<count> option
This new option can be used instead of piping to `head -n <count>` for
improved performance:

| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `fd --max-buffer-time=0 flow.yaml` | 153.9 ± 2.5 | 151.3 | 170.3 | 4.21 ± 5.86 |
| `fd --max-buffer-time=0 flow.yaml \| head -n 1` | 145.3 ± 17.4 | 111.0 | 180.2 | 3.98 ± 5.55 |
| `fd --max-results=1 flow.yaml` | 36.5 ± 50.8 | 7.2 | 145.7 | 1.00 |

Note: there is a large standard deviation on the last result due to the
non-deterministic file system traversal. With `--max-results`, we don't
have to traverse the whole filesystem tree, so it's all about luck.

closes #472
closes #476
2020-04-02 20:27:41 +02:00
David Peter ee673c92d3 Added changes for upcoming release 2020-04-02 16:58:27 +02:00
David Peter decd7f2ea3 Update CHANGELOG.md 2020-04-02 16:54:19 +02:00
David Peter f3e1ea150c Add CHANGELOG 2020-04-02 16:53:12 +02:00
spearkkk 12bd321a27 Add link for Korean 2020-04-01 17:08:12 +02:00
sharkdp 5fc341832d Update lscolors dependency, closes #552 2020-04-01 11:25:18 +02:00
sharkdp dd07bc9ef1 Bump min. required Rust version to 1.36 2020-03-22 16:19:28 +01:00
sharkdp 2340bab32a Update README 2020-03-22 15:58:21 +01:00
sharkdp 4a237c591e Update humantime dependency 2020-03-22 15:56:52 +01:00
sharkdp 44605d55dd Use .to_string() instead of .description() 2020-03-22 15:54:43 +01:00
sharkdp e5e14f31c4 Bump version to 7.5.0 2020-03-22 15:48:55 +01:00
sharkdp f69b59195a Update dependencies, closes #437 2020-03-22 15:47:06 +01:00
David Peter 9c9bb12f23 Update README.md 2020-03-19 22:57:22 +01:00
Dave Parfitt 70448b0006 honor the NO_COLOR env var 2020-03-19 22:56:28 +01:00
sharkdp fc924d2e0a Remove custom .github folder 2020-03-16 07:53:49 +01:00
Caleb Webber cab4981b11 Add --one-file-system and aliases to man page (#548) 2020-03-15 22:03:51 +01:00
sharkdp 104600e642 Update links 2020-03-15 12:03:46 +01:00
Adam Smith 3c6fab3439 Fix homebrew formula link 2020-03-14 14:36:54 +01:00
sharkdp d05e7171d4 Fix for older versions of Rust 2020-02-28 20:42:14 +01:00
sharkdp 81dee25438 Add additional check for symlink 2020-02-28 20:42:14 +01:00
sharkdp bbf0f1cc1f New implementation of broken-symlink handling 2020-02-28 20:42:14 +01:00
sharkdp bfc8c42444 Revert back to master state 2020-02-28 20:42:14 +01:00
sharkdp 918dfc6ef0 Add expected behavior as unit tests 2020-02-28 20:42:14 +01:00
sharkdp d6034119ae Add comment for broken symlinks 2020-02-28 20:42:14 +01:00
sharkdp 82e6562cfc Further simplify the code 2020-02-28 20:42:14 +01:00
sharkdp 8cea65c1b8 Simplify match statement 2020-02-28 20:42:14 +01:00
Tom Milligan 9d73402ef2 walk: catch ignore NotFound error in the case of a broken symlink 2020-02-28 20:42:14 +01:00
Tom Milligan 06434f229b test: we should find broken symlink with and without --follow 2020-02-28 20:42:14 +01:00
fusillicode 0f2429cabc Add unit tests for merge_exitcodes 2020-02-22 12:32:35 +01:00
fusillicode 232e3937f2 Rename error_if_any_error to merge_exitcodes 2020-02-22 12:32:35 +01:00
fusillicode e23398e6d0 Extract error_if_any_error as free function 2020-02-22 12:32:35 +01:00
fusillicode a5fe138a25 Extract ExitCode::is_error helper 2020-02-22 12:32:35 +01:00
fusillicode b23cfc383f Switch back from Self to ExitCode 2020-02-22 12:32:35 +01:00
fusillicode e46998278a Fix typo in a job.rs comment 2020-02-22 12:32:35 +01:00
fusillicode 7213f5a88e Add collection of job & thread exit codes + default to ExitCode::Error if any ExitCode::Error 2020-02-22 12:32:35 +01:00
fusillicode fa7d49282b Restun exit code for generate_and_execute? 2020-02-22 12:32:35 +01:00
sharkdp 2f18903b29 Fix zsh autocompletion problem, closes #487 2020-02-06 21:52:09 +01:00
sharkdp b63e43b40f Remove .snapcraft.yaml. This never worked. 2020-02-06 21:38:50 +01:00
Shun Sakai 56289041a6 Fix LICENSE
- Add copyright notice to the MIT License.
- Add information about the license to README file.
2020-01-19 19:06:21 +01:00
David Peter 8196f79fa4 Show master build state 2020-01-12 11:06:04 +01:00
MarcoIeni 320d7fca0a Sort search results when using -X option 2020-01-02 21:52:26 +01:00
sharkdp f7d1938556 Formatting 2020-01-01 12:05:50 +01:00
sharkdp 328c6640be Formatting 2020-01-01 12:05:33 +01:00
sharkdp c001dd47a7 Hide --one-file-system from the short help text 2020-01-01 11:54:01 +01:00
sharkdp a9c7696ecc Small stylistic change 2020-01-01 11:54:01 +01:00
Simon Engmann 9a34ba6a0c Credit the source of the device_num() function 2020-01-01 11:54:01 +01:00
Simon Engmann cd2dc5714e Compare --one-file-system to find's -mount/-xdev 2020-01-01 11:54:01 +01:00
Simon Engmann 2828d90f99 Don't assume that /dev/null exists in test
If `/dev/null` doesn't exist or is not on a different partition during
the test for `--one-file-system`, the test is skipped instead of
mistakenly failing.
2020-01-01 11:54:01 +01:00
Simon Engmann ed6c184020 Shorten cfg!() checks 2020-01-01 11:54:01 +01:00
Simon Engmann 6f7d10870c Add aliases for --one-file-system
The functionality provided by `--one-file-system` is called
`-mount`/`-xdev` under `find`, so provide those aliases as a compromise.
2020-01-01 11:54:01 +01:00
Simon Engmann dea1fbe722 Restrict --one-file-system to supported systems
Instead of having the option do nothing at runtime on unsupported
platforms, it is now only available on the systems that support it in
the first place.
2020-01-01 11:54:01 +01:00
Simon Engmann 94993ca6c2 Rename --same-file-system to --one-file-system 2020-01-01 11:54:01 +01:00
Simon Engmann 3e0325cc60 Adjust tests to not time out on Travis OSX 2020-01-01 11:54:01 +01:00
Simon Engmann e5ba75f3c8 Add tests for --same-file-system 2020-01-01 11:54:01 +01:00
Simon Engmann 8796de57b5 Add same file system functionality
This adds a `--same-file-system` CLI option that instructs the walker to
not cross file system boundaries.
Due to the fact that the corresponding option of the `ignore` crate's
`WalkBuilder` does not support platforms other than Unix and Windows,
the option does nothing on platforms other than those.
Resolves #507
2020-01-01 11:54:01 +01:00
sharkdp 0f27485faf Quit immediately if the channel::send call failed 2020-01-01 11:21:52 +01:00
sharkdp 762f551ff4 Update --help text 2019-12-23 16:07:38 +01:00
Rafał Hajdacki fb205f5057 Allow changing current working directory of fd 2019-12-23 15:42:33 +01:00
EasiestSoft 32fdbb1eae Fixed typos, occurence to occurrence, seach to search 2019-12-19 21:52:43 +01:00
evitalis 19e5d6e36e Remove OpenBSD install instructions 2019-11-25 07:29:31 +01:00
sharkdp 0895027ed6 Add 'glob' syntax link as well 2019-11-25 00:14:26 +01:00
Deepansh c60cf9c1a8 Update man page 2019-11-25 00:14:26 +01:00
sharkdp c14a004367 Switch off nightly build 2019-10-22 22:18:31 +02:00
Deepansh a6ed0fc5ef Fix CI error
Fixes sharkdp/fd#496
2019-10-09 23:09:21 +02:00
Deepansh Sood 266499800b Fix idempotent flags
Add 'overrides_with' to format flags.
Resolves sharkdp/fd#488
2019-10-09 23:09:21 +02:00
David Peter 9491670974 Add sponsor button as an experiment :-) 2019-10-07 19:45:12 +02:00
sharkdp d48aeda6b2 Apply clippy suggestions 2019-09-23 20:23:11 +02:00
Nathan Henrie 59c27baf0f Expand help for -u, add to manpage 2019-09-17 22:21:37 +02:00
Nathan Henrie 33f385692d Add -u alias to long --help only
Fixed sharkdp/fd#92, specifically https://github.com/sharkdp/fd/issues/92#issuecomment-522756754
2019-09-17 22:21:37 +02:00
sharkdp b027b2c322 Update Cargo.lock 2019-09-15 19:29:15 +02:00
sharkdp 9350c8544d Clean up cfg line 2019-09-15 18:58:05 +02:00
sharkdp f55033818d Disable crate in Windows + musl builds 2019-09-15 18:58:05 +02:00
sharkdp 1ab4e87dda Disable jemalloc for musl builds 2019-09-15 18:58:05 +02:00
sharkdp 4e20803e7d Disable jemallocator on Windows 2019-09-15 18:58:05 +02:00
sharkdp 5a154866e3 Use jemalloc
Benchmark #1: ./fd-sys-alloc '[0-9]\.jpg$' /home/shark
  Time (mean ± σ):     246.8 ms ±   3.4 ms    [User: 960.1 ms, System: 810.0 ms]
  Range (min … max):   244.1 ms … 257.1 ms    12 runs

Benchmark #2: ./fd-jemalloc '[0-9]\.jpg$' /home/shark
  Time (mean ± σ):     201.0 ms ±   3.0 ms    [User: 833.9 ms, System: 666.9 ms]
  Range (min … max):   196.1 ms … 206.9 ms    14 runs

Summary
  './fd-jemalloc '[0-9]\.jpg$' /home/shark' ran
    1.23 ± 0.03 times faster than './fd-sys-alloc '[0-9]\.jpg$' /home/shark'
2019-09-15 18:58:05 +02:00
sharkdp ac3e0e18e4 Update lscolors for compatible ansi_term versions 2019-09-15 17:30:13 +02:00
sharkdp 9cc0c8ed57 Fix build script after update 2019-09-15 17:27:12 +02:00
sharkdp a991ed4488 Update dependencies 2019-09-15 17:26:19 +02:00
sharkdp fe79b8ce0a Update man page 2019-09-15 17:10:31 +02:00
sharkdp 25a728529d Bump version 2019-09-15 17:06:30 +02:00
sharkdp 1056220421 Update USAGE section 2019-09-15 17:05:34 +02:00
sharkdp 08fcd7ce59 Make --changed-within/before work for directories
closes #470
2019-09-15 17:03:23 +02:00
sharkdp 50fca2dca2 Disable full-path glob test on Windows 2019-09-15 16:47:38 +02:00
sharkdp 588888112a Adapt full-path glob test for Windows 2019-09-15 16:47:38 +02:00
sharkdp eac20a8132 Add --regex option to override --glob 2019-09-15 16:47:38 +02:00
sharkdp d5da615c17 Implement glob-based searches
closes #284
2019-09-15 16:47:38 +02:00
sharkdp 641594c2c6 Use regex::bytes::* instead of regex::* 2019-09-15 16:47:38 +02:00
sharkdp 2545aaabd2 Exit immediately when Ctrl-C has been pressed twice 2019-09-15 13:06:03 +02:00
David Peter 3599a30ee2 Support FILE_ATTRIBUTE_HIDDEN on Windows (#478) 2019-09-15 12:41:04 +02:00
sharkdp f14b854bc1 Use existing Cow pointer 2019-09-15 10:57:27 +02:00
sharkdp 8f86a06c1a Extend help text 2019-09-15 10:57:27 +02:00
Nathan Moreau 761c445263 Add a non regression test. 2019-09-15 10:57:27 +02:00
Nathan Moreau 588e344d5b Remove type annotation. 2019-09-15 10:57:27 +02:00
Nathan Moreau e3af9b0307 Factor into a helper function; adapt to print_entry_uncolorized. 2019-09-15 10:57:27 +02:00
Nathan Moreau 29bf9d731d Handle any string replacement. 2019-09-15 10:57:27 +02:00
Nathan Moreau 3857fa8f62 Amend clap configuration for path-separator. 2019-09-15 10:57:27 +02:00
Nathan Moreau 24e108e1ad Add path-separator option.
Example usage: `fd.exe --path-separator /` on windows.
2019-09-15 10:57:27 +02:00
sharkdp 1d16cd855e Fix warnings 2019-09-15 10:48:29 +02:00
sharkdp a0505bd4df Expose exit status from --exec-batch <cmd>
closes #333
2019-09-13 23:05:35 +02:00
David Peter 630749f706 Clarify statement about number of threads for parallell execution 2019-06-17 19:25:16 +02:00
Jason Heeris 1daa454615 Added detail on parallel execution job pool to man page and README. 2019-06-17 19:21:47 +02:00
Jason Heeris 040c57e007 Added gcc_prefix to make_deb() function. 2019-06-17 19:19:33 +02:00
Jason Heeris 1ce2add943 Added armhf deb target. 2019-06-17 19:19:33 +02:00
Jason Heeris 418233193a Added armhf build to CI. 2019-06-17 19:19:33 +02:00
Chloe Kudryavtsev 0b04bd19df Add Alpine Linux installation instructions
Resolves #451
2019-06-11 07:38:55 +02:00
David Peter ac2dae0427 Update Ubuntu install instructions 2019-05-31 21:26:00 +02:00
sharkdp 53afcd8fc3 Add a section about "removing files"
see #387
2019-05-31 14:30:54 +02:00
Pablo Varela d266fa3010 Remove npm on macOS 2019-05-10 11:43:57 -05:00
Pablo Varela e7d0a91986 ocd 2019-05-10 11:43:57 -05:00
Pablo Varela 801816ee6c Add instructions for npm 2019-05-10 11:43:57 -05:00
Tavian Barnes 5cbd8405ec Check the pattern before anything else, since it doesn't require metadata
This should partially address #432 by decreasing the number of stat() calls:

    $ strace -c -f ./fd-before '\.h$' /usr -j1 -S +1k >/dev/null
    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
     15.71    8.831948           7   1192279     46059 stat
    $ strace -c -f ./fd-after '\.h$' /usr -j1 -S +1k >/dev/null
    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
      7.92    1.972474          10    183907     46046 stat

Though it's not as few as possible:

    $ strace -c -f find /usr -iname '*.h' -size +1k >/dev/null
    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
     19.01    0.946500           5    161649           newfstatat
    $ strace -c -f bfs /usr -iname '*.h' -size +1k >/dev/null
    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
     13.73    0.406565           5     69005           statx

Performance is much better when metadata is required:

    $ hyperfine ./fd-{before,after}" '\.h$' /usr -j1 -S +1k"
    Benchmark #1: ./fd-before '\.h$' /usr -j1 -S +1k
      Time (mean ± σ):      4.623 s ±  0.154 s    [User: 1.465 s, System: 3.354 s]
      Range (min … max):    4.327 s …  4.815 s    10 runs

    Benchmark #2: ./fd-after '\.h$' /usr -j1 -S +1k
      Time (mean ± σ):      2.650 s ±  0.058 s    [User: 1.258 s, System: 1.592 s]
      Range (min … max):    2.568 s …  2.723 s    10 runs

    Summary
      './fd-after '\.h$' /usr -j1 -S +1k' ran
        1.74 ± 0.07 times faster than './fd-before '\.h$' /usr -j1 -S +1k'

While remaining the same when it's not:

    $ hyperfine ./fd-{before,after}" '\.h$' /usr -j1"
    Benchmark #1: ./fd-before '\.h$' /usr -j1
      Time (mean ± σ):      2.382 s ±  0.038 s    [User: 1.221 s, System: 1.286 s]
      Range (min … max):    2.325 s …  2.433 s    10 runs

    Benchmark #2: ./fd-after '\.h$' /usr -j1
      Time (mean ± σ):      2.362 s ±  0.034 s    [User: 1.193 s, System: 1.294 s]
      Range (min … max):    2.307 s …  2.422 s    10 runs

    Summary
      './fd-after '\.h$' /usr -j1' ran
        1.01 ± 0.02 times faster than './fd-before '\.h$' /usr -j1'
2019-05-08 07:28:47 -05:00
Laurent Arnoud 35945c4a62 Use dual license on Debian
cf https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2019-03-17 18:59:16 +01:00
Laurent Arnoud 97908beb97 Add copyright file for deb package
ref https://github.com/sharkdp/fd/issues/416
2019-03-17 18:59:16 +01:00
evitalis f15540e501 Add OpenBSD installation steps 2019-03-13 08:17:06 +01:00
Tim Holland 074cfad3d4 Remove deprecated trim_left(_matches)?
warning: use of deprecated item 'core::str::<impl str>::trim_left_matches': superseded by `trim_start_matches`
   --> src/main.rs:222:28
    |
222 |                 .map(|e| e.trim_left_matches('.'))
    |                            ^^^^^^^^^^^^^^^^^
    |
    = note: #[warn(deprecated)] on by default
2019-03-02 08:15:12 +01:00
Tobias Kortkamp f3ec3d9cf0 Update FreeBSD install instructions
Installing fd does not require using portmaster (it will also build
fd from source).  IMHO this is pretty misleading.  Let's update the
FreeBSD install instructions to recommend using the binary package
in the official repository like on other systems.
2019-02-27 07:21:45 +01:00
Alex Leferry 2 46e865f1a2 Fix documentation to filter search by directory type 2019-02-19 07:21:02 +01:00
40 changed files with 3182 additions and 1406 deletions
+23
View File
@@ -0,0 +1,23 @@
---
name: Bug Report
about: Report a bug.
title: ""
labels: bug
assignees: ''
---
**Describe the bug you encountered:**
**Describe what you expected to happen:**
**What version of `fd` are you using?**
<!-- paste the output of `fd --version` here -->
**Which operating system / distribution are you on?**
<!--
Unix: paste the output of `uname -srm` and `lsb_release -a` here.
Windows: please tell us your Windows version
-->
+2
View File
@@ -0,0 +1,2 @@
blank_issues_enabled: true
+10
View File
@@ -0,0 +1,10 @@
---
name: Feature Request
about: Suggest an idea for this project.
title: ''
labels: feature-request
assignees: ''
---
+13
View File
@@ -0,0 +1,13 @@
---
name: Question
about: Ask a question about 'fd'.
title: ''
labels: question
assignees: ''
---
**What version of `fd` are you using?**
[paste the output of `fd --version` here]
-29
View File
@@ -1,29 +0,0 @@
name: fd
version: git
summary: "A simple, fast and user-friendly alternative to 'find'"
description: |
fd is a simple, fast and user-friendly alternative to find.
- While it does not seek to mirror all of find's powerful functionality, it provides sensible (opinionated) defaults for 80% of the use cases.
Features
Convenient syntax: fd PATTERN instead of find -iname '*PATTERN*'.
Colorized terminal output (similar to ls).
It's fast (see benchmarks below).
Smart case: the search is case-insensitive by default. It switches to case-sensitive if the pattern contains an uppercase character*.
Ignores hidden directories and files, by default.
Ignores patterns from your .gitignore, by default.
Regular expressions.
Unicode-awareness.
The command name is 50% shorter* than find :-).
Parallel command execution with a syntax similar to GNU Parallel.
grade: stable
confinement: classic
parts:
fd:
plugin: rust
source: ./
apps:
fd:
command: bin/fd
+7 -47
View File
@@ -1,8 +1,5 @@
language: rust
cache: cargo
matrix:
# allow_failures:
# - rust: nightly
include:
# Stable channel.
- os: linux
@@ -18,12 +15,14 @@ matrix:
rust: stable
env: TARGET=i686-unknown-linux-musl
- os: osx
osx_image: xcode11.4 # Catalina
rust: stable
env: TARGET=x86_64-apple-darwin
- os: linux
rust: stable
env:
- TARGET=arm-unknown-linux-gnueabihf
- CC_arm_unknown_linux_gnueabihf=/usr/bin/arm-linux-gnueabihf-gcc-4.8
- CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
- os: linux
rust: stable
@@ -35,63 +34,24 @@ matrix:
- os: linux
rust: beta
env: TARGET=x86_64-unknown-linux-gnu
# Disabled to reduce total CI time
# - os: linux
# rust: beta
# env: TARGET=x86_64-unknown-linux-musl
# - os: linux
# rust: beta
# env: TARGET=i686-unknown-linux-gnu
# - os: linux
# rust: beta
# env: TARGET=i686-unknown-linux-musl
# - os: osx
# rust: beta
# env: TARGET=x86_64-apple-darwin
# Nightly channel.
- os: linux
rust: nightly
env: TARGET=x86_64-unknown-linux-gnu
# Disabled to reduce total CI time
# - os: linux
# rust: nightly
# env: TARGET=x86_64-unknown-linux-musl
# - os: linux
# rust: nightly
# env: TARGET=i686-unknown-linux-gnu
# - os: linux
# rust: nightly
# env: TARGET=i686-unknown-linux-musl
# - os: osx
# rust: nightly
# env: TARGET=x86_64-apple-darwin
# Minimum Rust supported channel.
- os: linux
rust: 1.31.0
rust: 1.36.0
env: TARGET=x86_64-unknown-linux-gnu
- os: linux
rust: 1.31.0
rust: 1.36.0
env: TARGET=x86_64-unknown-linux-musl
- os: linux
rust: 1.31.0
rust: 1.36.0
env: TARGET=i686-unknown-linux-gnu
- os: linux
rust: 1.31.0
rust: 1.36.0
env: TARGET=i686-unknown-linux-musl
- os: osx
rust: 1.31.0
rust: 1.36.0
env: TARGET=x86_64-apple-darwin
# Code formatting check
- os: linux
rust: nightly
# skip the global install step
install:
- cargo install --debug --force rustfmt-nightly
script: cargo fmt -- --check
sudo: required
before_install:
+481
View File
@@ -0,0 +1,481 @@
# Upcoming release
## Features
## Bugfixes
## Changes
## Other
# v8.1.1
## Bugfixes
- Support colored output on older Windows versions if either (1) `--color=always` is set or (2) the `TERM` environment variable is set. See #469
# v8.1.0
## Features
- Add new `--owner [user][:group]` filter. See #307 (pull #581) (@alexmaco)
- Add support for a global ignore file (`~/.config/fd/ignore` on Unix), see #575 (@soedirgo)
- Do not exit immediately if one of the search paths is missing, see #587 (@DJRHails)
## Bugfixes
- Reverted a change from fd 8.0 that enabled colors on all Windows terminals (see below) in order to support older Windows versions again, see #577. Unfortunately, this re-opens #469
- Fix segfault caused by jemalloc on macOS Catalina, see #498
- Fix `--glob` behavior with empty pattern, see #579 (@SeamusConnor)
- Fix `--list-details` on FreeBSD, DragonFly BSD, OpenBSD and NetBSD. See #573 (@t6)
## Changes
- Updated documentation for `--size`, see #584
# v8.0.0
## Features
- Add a new `-l`/`--list-details` option to show more details about the search results. This is
basically an alias for `--exec-batch ls -l` with some additional `ls` options.
This can be used in order to:
* see metadata like permissions, owner, file size, modification times (#491)
* see symlink targets (#482)
* achieve a deterministic output order (#324, #196, #159)
- Add a new `--max-results=<count>` option to limit the number of search results, see #472, #476 and #555
This can be useful to speed up searches in cases where you know that there are only N results.
Using this option is also (slightly) faster than piping to `head -n <count>` where `fd` can only
exit when it finds the search results `<count> + 1`.
- Add the alias `-1` for `--max-results=1`, see #561. (@SimplyDanny).
- Add new `--type socket` and `--type pipe` filters, see #511.
- Add new `--min-depth <depth>` and `--exact-depth <depth>` options in addition to the existing option
to limit the maximum depth. See #404.
- Support additional ANSI font styles in `LS_COLORS`: faint, slow blink, rapid blink, dimmed, hidden and strikethrough.
## Bugfixes
- Preserve non-UTF8 filenames: invalid UTF-8 filenames are now properly passed to child-processes
when using `--exec`, `--exec-batch` or `--list-details`. In `fd`'s output, we replace non-UTF-8
sequences with the "" character. However, if the output of `fd` goes to another process, we
print the actual bytes of the filename. For more details, see #558 and #295.
- `LS_COLORS` entries with unsupported font styles are not completely ignored, see #552
## Changes
- Colored output will now be enabled by default on older Windows versions.
This allows the use of colored output if the terminal supports it (e.g.
MinTTY, Git Bash). On the other hand, this will be a regression for users
on older Windows versions with terminals that do not support ANSI escape
sequences. Affected users can use an alias `fd="fd --color=never"` to
continue using `fd` without colors. There is no change of behavior for
Windows 10. See #469.
- When using `--glob` in combination with `--full-path`, a `*` character does not match a path
separation character (`/` or `\\`) anymore. You can use `**` for that. This allows things like
`fd -p -g '/some/base/path/*/*/*.txt'` which would previously match to arbitrary depths (instead
of exactly two folders below `/some/base/path`. See #404.
- "Legacy" support to use `fd -exec` (with a single dash) has been removed. Use `fd -x` or
`fd --exec` instead.
- Overall improved error handling and error messages.
## Other
- Korean translation of the README, see: [한국어](https://github.com/spearkkk/fd-kor) (@spearkkk)
# v7.5.0
## Features
- Added `--one-file-system` (aliases: `--mount`, `--xdev`) to not cross file system boundaries on Unix and Windows, see #507 (@FallenWarrior2k).
- Added `--base-directory` to change the working directory in which `fd` is run, see #509 and #475 (@hajdamak).
- `fd` will not use colored output if the `NO_COLOR` environment variable is set, see #550 and #551 (@metadave).
- `fd --exec` will return exit code 1 if one of the executed commands fails, see #526 and #531 (@fusillicode and @Giuffre)
## Bug Fixes
- Fixed 'command not found' error when using zsh completion, see #487 (@barskern).
- `fd -L` should include broken symlinks, see #357 and #497 (@tommilligan, @neersighted and @sharkdp)
- Display directories even if we don't have permission to enter, see #437 (@sharkdp)
## Changes
- A flag can now be passed multiple times without producing an error, see #488 and #496 (@rootbid).
- Search results are sorted when using the `-X` option to match the behaviour of piping to `xargs`, see #441 and #524 (@Marcoleni @crash-g).
# v7.4.0
## Performance improvements
- Reduce number of `stat` syscalls, improving the performance for searches where file metadata is
required (`--type`, `--size`, `--changed-within`, …), see #434 (@tavianator)
- Use jemalloc by default, improving the performance for almost all searches, see #481. Note that
Windows and `*musl*` builds do not profit from this.
## Features
- Added a new `-g`/`--glob` option to switch to glob-based searches (instead of regular expression
based searches). This is accompanied by a new `--regex` option that can be used to switch back,
if users want to `alias fd="fd --glob"`. See #284
- Added a new `--path-separator <sep>` option which can be useful for Windows users who
want/need `fd` to use `/` instead of `\`, see #428 and #153 (@mookid)
- Added support for hidden files on Windows, see #379
- When `fd` is run with the `--exec-batch`/`-X` option, it now exposes the exit status of the
command that was run, see #333.
- Exit immediately when Ctrl-C has been pressed twice, see #423
## Bugfixes
- Make `--changed-within`/`--changed-before` work for directories, see #470
## Other
- Pre-built `fd` binaries should now be available for `armhf` targets, see #457 (@detly)
- `fd` is now available on Alpine Linux, see #451 (@5paceToast)
- `fd` is now in the officla FreeBSD repositories, see #412 (@t6)
- Added OpenBSD install instructions, see #421 (@evitalis)
- Added metadata to the Debian package, see #416 (@cathalgarvey)
- `fd` can be installed via npm, see #438 (@pablopunk)
# v7.3.0
## Features
- New `--exec-batch <cmd>`/`-X <cmd>` option for batch execution of commands, see #360 (@kimsnj).
This allows you to do things like:
``` bash
fd … -X vim # open all search results in vim (or any other editor)
fd … -X ls -l # view detailed stats about the search results with 'ls'
fd -e svg -X inkscape # open all SVG files in Inkscape
```
- Support for 24-bit color codes (when specified via `LS_COLORS`) as well as
different font styles (bold, italic, underline).
## Changes
- A few performance improvements, in particular when printing lots of colorized
results to the console, see #370
- The `LS_COLORS` handling has been "outsourced" to a separate crate (https://github.com/sharkdp/lscolors) that is now being used by other tools as well: [fselect](https://github.com/jhspetersson/fselect), [lsd](https://github.com/Peltoche/lsd/pull/84). For details, see #363.
## Other
- `fd` will be available in Ubuntu Disco DIngo (19.04), see #373 (@sylvestre)
- This release should come with a static ARM binary (`arm-unknown-linux-musleabihf`), see #320 (@duncanfinney)
- Various documentation improvements, see #389
## Thanks
Special thanks to @alexmaco for his awesome work on refactoring and code improvements! (see #401, #398, and #383)
# v7.2.0
## Features
* Added support for filtering by file modification time by adding two new options `--changed-before <date|duration>` and `--changed-within <..>`. For more details, see the `--help` text, the man page, the relevant issue #165 and the PR #339 (@kimsnj)
* Added `--show-errors` option to enable the display of filesystem error messages such as "permission denied", see #311 (@psinghal20 and @majecty)
* Added `--maxdepth` as a (hidden) alias for `--max-depth`, see #323 (@mqudsi)
* Added `--search-path` option which can be supplied to replace the positional `path` argument at any position.
## Changes
* Loosen strict handling of missing `--ignore-file`, see #280 (@psinghal20)
* Re-enabled `.ignore` files, see #156.
## Bugfixes
* `fd` could previously get stuck when run from the root directory in the
presence of zombie processes. This curious bug has been fixed in Rust 1.29 and higher. For more details, see #288, [rust-lang/rust#50619](https://github.com/rust-lang/rust/issues/50619) and [the fix](https://github.com/rust-lang/rust/pull/50630)
## Other
* `fd` has officially landed in Debian! See #345 for details. Thanks goes to @sylvestre, @paride and possibly others I don't know about.
* Added Chinese translation of README (@chinanf-boy)
## Thanks
A special thanks goes to @joshleeb for his amazing improvements throughout
the code base (new tests, refactoring work and various other things)!
# v7.1.0
## Features
* Added `--size` filter option, see #276 (@stevepentland, @JonathanxD and @alexmaco)
* Added `--type empty` (or `-t e`) to search for empty files and/or directories, see #273
## Changes
* With the new version, `.gitignore` files will only be respected in Git repositories, not outside.
* A few performance improvements for `--type` searches, see 641976cf7ad311ba741571ca8b7f02b2654b6955 and 50a2bab5cd52d26d4a3bc786885a2c270ed3b227
## Other
* Starting with this release, we will offer pre-built ARM binaries, see #244
* Added instructions on how to use `fd` with `emacs`, see #282 (@redguardtoo)
* `fd` is now in the official openSUSE repositories, see #275 (@avindra)
* `fd` is now available via MacPorts, see #291 (@raimue)
# v7.0.0
## Features
* Added `--type executable` (or `-t x`) to search for executable files only, see #246 (@PramodBisht)
* Added support for `.fdignore` files, see #156 and #241.
* Added `--ignore-file` option to add custom ignore files, see #156.
* Suggest `--fixed-strings` on invalid regular expressions, see #234 (@PramodBisht)
* Detect when user supplied path instead of pattern, see #235.
## Changes
* `.ignore` and `.rgignore` files are not parsed anymore. Use `.fdignore` files
or add custom files via `--ignore-file` instead.
* Updated to `regex-syntax` 0.5 (@cuviper)
## Bugfixes
* Properly normalize absolute paths, see #268
* Invalid utf8 filenames displayed when `-e` is used, see #250
* If `--type` is used, fifos/sockets/etc. are always shown, see #260
## Other
* Packaging:
* The Arch Linux package is now simply called `fd`.
* There is now a `fd` ebuild for Gentoo Linux.
* There is a `scoop` package for `fd` (Windows).
* There is a `Chocolatey` package for `fd` (Windows).
* There is a Fedora `copr` package for `fd`.
# v6.3.0
## Features
* Files with multiple extensions can now be found via `--extension`/`-e`, see #214 (@althonos)
``` bash
> fd -e tar.gz
```
* Added new `-F`/`--fixed-strings`/`--literal` option that treats the pattern as a literal string instead of a regular expression, see #157
``` bash
> fd -F 'file(1).txt'
```
* Allow `-exec` to work as `--exec`, see #226 (@stevepentland)
## Bugfixes
* Fixed `Ctrl-C` handling when using `--exec`, see #224 (@Doxterpepper)
* Fixed wrong file owner for files in deb package, see #213
## Other
* Replaced old gif by a fancy new SVG screencast (@marionebl)
* Updated [benchmark results](https://github.com/sharkdp/fd#benchmark) (fd has become faster in the meantime!). There is a new repository that hosts several benchmarking scripts for fd: https://github.com/sharkdp/fd-benchmarks
# v6.2.0
## Features
* Support for filtering by multiple file extensions and multiple file types, see #199 and #177
(@tkadur).
For example, it's possible to search for C++ source or header files:
``` bash
> fd -e cpp -e c -e cxx -e h pattern
```
## Changes
* The size of the output buffer (for sorting search results) is now limited to 1000 entries. This
improves the search speed significantly if there are a lot of results, see #191 (@sharkdp).
## Bugfixes
* Fix a bug where long-running searches could not be killed via Ctrl-C, see #210 (@Doxterpepper)
* fd's exit codes are now in accordance with Unix standards, see #201 (@Doxterpepper)
## Other
* Bash, zsh and fish completion should now work with the Ubuntu `.deb` packages, see #195 and #209
(@tmccombs and @sharkdp)
* There is a new section on how to set up `fzf` to use `fd` in the
[README](https://github.com/sharkdp/fd#using-fd-with-fzf), see #168.
# v6.1.0
## Features
* Support for multiple search paths, see #166 (@Doxterpepper)
* Added `--no-ignore-vcs` option to disable `.gitignore` and other VCS ignore files,
without disabling `.ignore` files - see #156 (@ptzz).
## Bugfixes
* Handle terminal signals, see #128 (@Doxterpepper)
* Fixed hang on `--exec` when user input was required, see #178 and #193 (@reima)
## Other
* Debian packages are now created via Travis CI and should be available for this and all
future releases (@tmccombs).
* fd is now available on Void Linux (@maxice8)
* The minimum required Rust version is now 1.20
## Thanks
@Doxterpepper deserves a special mention for his great work that is included in this release and
for the support in ticket discussions and concerning Travis CI fixes. Thank you very much!
Thanks also go out to @tmccombs for the work on Debian packages and for reviewing a lot of pull requests!
# v6.0.0
## Changes
- The `--exec`/`-x` option does not spawn an intermediate shell anymore. This improves the
performance of parallel command execution and fixes a whole class of (present and potentially
future) problems with shell escaping. The drawback is that shell commands cannot directly be
called with `--exec`. See #155 for the full discussion. These changes have been implemented by
@reima (Thanks!).
## Bugfixes
- `--exec` does not escape cmd.exe metacharacters on Windows (see #155, as above).
## Other
* *fd* is now available in the FreeBSD ports (@andoriyu)
* The minimal `rustc` version is now checked when building with `cargo`, see #164 (@matematikaadit)
* The output directory for the shell completion files is created if it does not exist (@andoriyu)
# v5.0.0
## Features
* Added new `--exec`, `-x` option for parallel command execution (@mmstick, see #84 and #116). See the corresponding [README section](https://github.com/sharkdp/fd#parallel-command-execution) for an introduction.
* Auto-disable color output on unsupported Windows shells like `cmd.exe` (@iology, see #129)
* Added the `--exclude`, `-X` option to suppress certain files/directories in the search results
(see #89).
* Added ripgrep aliases `-u` and `-uu` for `--no-ignore` and `--no-ignore --hidden`, respectively
(@unsignedint, see #92)
* Added `-i`, `--ignore-case` (@iology, see #95)
* Made smart case really smart (@reima, see #103)
* Added RedoxOS support (@goyox86, see #131)
## Changes
* The dot `.` can now match newlines in file names (@iology, see #111)
* The short `--type` argument for symlinks has been changed from `s` to `l` (@jcpetkovich, see #83)
## Bugfixes
* Various improvements in root-path and symlink handling (@iology, see #82, #107, and #113)
* Fixed absolute path handling on Windows (@reima, #93)
* Fixed: current directory not included when using relative path (see #81)
* Fixed `--type` behavior for unknown file types (@iology, see #150)
* Some fixes around `--exec` (@iology, see #142)
## Other
* Major updates and bugfixes to our continuous integration and deployment tooling on Travis
(@matematikaadit, see #149, #145, #133)
* Code style improvements & automatic style checking via `rustfmt` on Travis (@Detegr, see #99)
* Added a man page (@pickfire, see #77)
* *fd* has been relicensed under the dual license MIT/Apache-2.0 (@Detegr, see #105)
* Major refactorings and code improvements (Big thanks to @gsquire, @reima, @iology)
* First version of [`CONTRIBUTING`](https://github.com/sharkdp/fd/blob/master/CONTRIBUTING.md) guidelines
* There is now a Nix package (@mehandes)
* *fd* is now in the official Arch Linux repos (@cassava)
* Improved tooling around shell completion files (@ImbaKnugel, see #124)
* Updated tutorial in the [`README`](https://github.com/sharkdp/fd/blob/master/README.md)
* The minimum required version of Rust has been bumped to 1.19.
## Thanks
A *lot* of things have happened since the last release and I'd like to thank all contributors for their great support. I'd also like to thank those that have contributed by reporting bugs and by posting feature requests.
I'd also like to take this chance to say a special Thank You to a few people that have stood out in one way or another: To @iology, for contributing a multitude of bugfixes, improvements and new features. To @reima and @Detegr for their continuing great support. To @mmstick, for implementing the most advanced new feature of *fd*. And to @matematikaadit for the CI/tooling upgrades.
# v4.0.0
## Features
* Added filtering by file extension, for example `fd -e txt`, see #56 (@reima)
* Add option to force colored output: `--color always`, see #49 (@Detegr)
* Generate Shell completions for Bash, ZSH, Fish and Powershell, see #64 (@ImbaKnugel)
* Better & extended `--help` text (@abaez and @Detegr)
* Proper Windows support, see #70
## Changes
* The integration tests have been re-written in Rust :sparkles:, making them platform-independent and easily callable via `cargo test` - see #65 (many thanks to @reima!)
* New tutorial in the README (@deg4uss3r)
* Reduced number of `stat` syscalls for each result from 3 to 1, see #36.
* Enabled Appveyor CI
# v3.1.0
## Features
- Added file type filtering, e.g. `find --type directory` or `find -t f` (@exitium)
# v3.0.0
## Features
- Directories are now traversed in parallel, leading to significant performance improvements (see [benchmarks](https://github.com/sharkdp/fd#benchmark))
- Added `--print0` option (@michaelmior)
- Added AUR packages (@wezm)
## Changes
- Changed short flag for `--follow` from `-f` to `-L` (consistency with `ripgrep`)
# v2.0.0
* Changed `--sensitive` to `--case-sensitive`
* Changed `--absolute` to `--absolute-path`
* Throw an error if root directory is not existent, see #39
* Use absolute paths if the root dir is an absolute path, see #40
* Handle invalid UTF-8, see #34 #38
* Support `-V`, `--version` by switching from `getopts` to `clap`.
Misc:
* It's now possible to install `fd` via homebrew on macOS: `brew install fd`.
# v1.1.0
- Windows compatibility (@sebasv), see #29 #35
- Safely exit on broken output pipes (e.g.: usage with `head`, `tail`, ..), see #24
- Backport for rust 1.16, see #23
# v1.0.0
* Respect `.(git)ignore` files
* Use `LS_COLORS` environment variable directly, instead of `~/.dir_colors` file.
* Added unit and integration tests
* Added optional second argument (search path)
# v0.3.0
- Parse dircolors files, closes #20
- Colorize each path component, closes #19
- Add short command line option for --hidden, see #18
# v0.2.0
- Option to follow symlinks, disable colors, closes #16, closes #17
- `--filename` instead of `--full-path`
- Option to search hidden directories, closes #12
- Configurable search depth, closes #13
- Detect interactive terminal, closes #11
# v0.1.0
Initial release
-1
View File
@@ -17,4 +17,3 @@ give us the chance to discuss any potential changes first.
* [Development section in the README](https://github.com/sharkdp/fd#development)
* [fd on crates.io](https://crates.io/crates/fd-find)
* [LICENSE-APACHE](https://github.com/sharkdp/fd/blob/master/LICENSE-APACHE) and [LICENSE-MIT](https://github.com/sharkdp/fd/blob/master/LICENSE-MIT)
Generated
+315 -228
View File
@@ -1,9 +1,11 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.6.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -11,116 +13,195 @@ name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "anyhow"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "arrayvec"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "atty"
version = "0.2.11"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.0.4"
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "base64"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "blake2b_simd"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bstr"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cc"
version = "1.0.29"
version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.6"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.32.0"
version = "2.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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)",
"bitflags 1.0.4 (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)",
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-channel"
version = "0.3.8"
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-utils"
version = "0.6.5"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ctrlc"
version = "3.1.1"
version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "diff"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fd-find"
version = "7.3.0"
name = "dirs"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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.4 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"lscolors 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dirs-sys"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fd-find"
version = "8.1.1"
dependencies = [
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"diff 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"filetime 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)",
"jemallocator 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
"lscolors 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.17 (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.5 (registry+https://github.com/rust-lang/crates.io-index)",
"users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "filetime"
version = "0.2.4"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fnv"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fs_extra"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -129,117 +210,138 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "globset"
version = "0.4.2"
name = "getrandom"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
"wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "globset"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
"bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hermit-abi"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "humantime"
version = "1.2.0"
version = "2.0.0"
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.4.6"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 1.0.4 (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.7 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
name = "jemalloc-sys"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
"fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "jemallocator"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"jemalloc-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "1.2.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.48"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.4.6"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lscolors"
version = "0.5.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "2.2.0"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nix"
version = "0.11.0"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.70 (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.10.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.70 (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.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -247,12 +349,12 @@ name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.4.0"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -265,64 +367,65 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.1.51"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_termios"
version = "0.1.1"
name = "redox_users"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "1.1.0"
version = "1.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.5 (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.2 (registry+https://github.com/rust-lang/crates.io-index)",
"aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.5"
version = "0.6.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "remove_dir_all"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rust-argon2"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)",
"constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "same-file"
version = "1.0.4"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "smallvec"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -331,77 +434,57 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "term_size"
version = "0.3.1"
version = "0.3.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)",
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termion"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.10.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
"term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.3.6"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ucd-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.5"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unreachable"
version = "1.0.0"
name = "users"
version = "0.10.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)",
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "utf8-ranges"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "version_check"
version = "0.1.5"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -411,33 +494,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "walkdir"
version = "2.2.7"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi"
version = "0.2.8"
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.6"
version = "0.3.8"
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"
@@ -445,10 +523,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-util"
version = "0.1.2"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -457,60 +535,69 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
"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.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)" = "4390a3b5f4f6bce9c1d0c00128379df433e53777fdd30e92f16a529332baec4e"
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
"checksum crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0ed1a4de2235cabda8558ff5840bffb97fcb64c97827f354a451307df5f72b"
"checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c"
"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.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a2df5c1a8c4be27e7707789dc42ae65976e60b394afd293d1419ab915833e646"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
"checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
"checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
"checksum bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931"
"checksum cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)" = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
"checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
"checksum ctrlc 3.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7a4ba686dff9fa4c1c9636ce1010b0cf98ceb421361b0bb3d6faeec43bd217a7"
"checksum diff 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
"checksum filetime 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695"
"checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
"checksum fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674"
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
"checksum globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4743617a7464bbda3c8aec8558ff2f9429047e025771037df561d383337ff865"
"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
"checksum ignore 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ad03ca67dc12474ecd91fdb94d758cbd20cb4e7a78ebe831df26a9b7511e1162"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
"checksum lscolors 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9938fd8c379393454f73ec4c9c5b40f3d8332d80b25a29da05e41ee0ecbb559"
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
"checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
"checksum globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120"
"checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71"
"checksum humantime 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9b6c53306532d3c8e8087b44e6580e10db51a023cf9b433cea2ac38066b92da"
"checksum ignore 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)" = "128b9e89d15a3faa642ee164c998fd4fae3d89d054463cddb2c25a7baad3a352"
"checksum jemalloc-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45"
"checksum jemallocator 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)" = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum lscolors 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f77452267149eac960ded529fe5f5460ddf792845a1d71b5d0cfcee5642e47e"
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
"checksum nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f"
"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861"
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
"checksum smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "88aea073965ab29f6edb5493faf96ad662fb18aa9eeb186a3b7057951605ed15"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431"
"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"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.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.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
"checksum term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486"
"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+21 -8
View File
@@ -16,24 +16,25 @@ license = "MIT/Apache-2.0"
name = "fd-find"
readme = "README.md"
repository = "https://github.com/sharkdp/fd"
version = "7.3.0"
version = "8.1.1"
edition= "2018"
[[bin]]
name = "fd"
path = "src/main.rs"
[badges.appveyor]
repository = "sharkdp/fd"
[badges.travis-ci]
repository = "sharkdp/fd"
[[bin]]
name = "fd"
path = "src/main.rs"
[build-dependencies]
clap = "2.31.2"
version_check = "0.1.3"
version_check = "0.9"
[dependencies]
ansi_term = "0.11"
ansi_term = "0.12"
atty = "0.2"
ignore = "0.4.3"
lazy_static = "1.1.0"
@@ -41,16 +42,28 @@ num_cpus = "1.8"
regex = "1.0.0"
regex-syntax = "0.6"
ctrlc = "3.1"
humantime = "1.1.1"
lscolors = "0.5"
humantime = "2.0"
lscolors = "0.7"
globset = "0.4"
anyhow = "1.0"
dirs = "2.0"
[dependencies.clap]
version = "2.31.2"
features = ["suggestions", "color", "wrap_help"]
[target.'cfg(unix)'.dependencies]
users = "0.10.0"
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
libc = "0.2"
# FIXME: Re-enable jemalloc on macOS
# jemalloc is currently disabled on macOS due to a bug in jemalloc in combination with macOS
# Catalina. See https://github.com/sharkdp/fd/issues/498 for details.
[target.'cfg(all(not(windows), not(target_os = "macos"), not(target_env = "musl")))'.dependencies]
jemallocator = "0.3.0"
[dev-dependencies]
diff = "0.1"
tempdir = "0.3"
+1 -1
View File
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 fd developers
Copyright 2017-2020 fd developers
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+19 -21
View File
@@ -1,23 +1,21 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
MIT License
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
Copyright (c) 2017-2020 The fd developers
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+105 -18
View File
@@ -1,8 +1,9 @@
# fd
[![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)
[![Build status](https://ci.appveyor.com/api/projects/status/21c4p5fwggc5gy3j/branch/master?svg=true)](https://ci.appveyor.com/project/sharkdp/fd/branch/master)
[![Version info](https://img.shields.io/crates/v/fd-find.svg)](https://crates.io/crates/fd-find)
[中文](https://github.com/chinanf-boy/fd-zh)
[한국어](https://github.com/spearkkk/fd-kor)
*fd* is a simple, fast and user-friendly alternative to
[*find*](https://www.gnu.org/software/findutils/).
@@ -41,7 +42,7 @@ Let's start with `find`:
Benchmark #1: find ~ -iregex '.*[0-9]\.jpg$'
Time (mean ± σ): 7.236 s ± 0.090 s
Range (min … max): 7.133 s … 7.385 s
```
@@ -50,7 +51,7 @@ Benchmark #1: find ~ -iregex '.*[0-9]\.jpg$'
Benchmark #2: find ~ -iname '*[0-9].jpg'
Time (mean ± σ): 3.914 s ± 0.027 s
Range (min … max): 3.876 s … 3.964 s
```
@@ -61,7 +62,7 @@ otherwise `fd` does not have to traverse hidden folders and ignored paths (see b
Benchmark #3: fd -HI '.*[0-9]\.jpg$' ~
Time (mean ± σ): 811.6 ms ± 26.9 ms
Range (min … max): 786.0 ms … 870.7 ms
```
For this particular example, `fd` is approximately nine times faster than `find -iregex`
@@ -75,13 +76,13 @@ folders, it is almost an order of magnitude faster:
Benchmark #4: fd '[0-9]\.jpg$' ~
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. See
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
@@ -97,6 +98,8 @@ complete (and more colorful) variants, see
[here](https://github.com/seebi/dircolors-solarized) or
[here](https://github.com/trapd00r/LS_COLORS).
`fd` also honors the [`NO_COLOR`](https://no-color.org/) environment variable.
## Parallel command execution
If the `-x`/`--exec` option is specified alongside a command template, a job pool will be created
for executing commands in parallel for each discovered path as the input. The syntax for generating
@@ -123,17 +126,29 @@ fd -e flac -x ffmpeg -i {} -c:a libopus {.}.opus
fd -x wc -l \; -e rs
```
The number of threads used for command execution can be set with the `--threads`/`-j` option.
## Installation
[![Packaging status](https://repology.org/badge/vertical-allrepos/fd-find.svg)](https://repology.org/project/fd-find/versions)
### On Ubuntu
*... and other Debian-based Linux distributions.*
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.3.0_amd64.deb # adapt version number and architecture
If you run Ubuntu 19.04 (Disco Dingo) or newer, you can install the
[officially maintained package](https://packages.ubuntu.com/disco/fd-find):
```
sudo apt install fd-find
```
Note that 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.
Note: `fd` will be officially available in Ubuntu Disco Dingo (19.04).
If you use an older version of Ubuntu, you can download the latest `.deb` package from the
[release page](https://github.com/sharkdp/fd/releases) and install it via:
``` bash
sudo dpkg -i fd_8.1.0_amd64.deb # adapt version number and architecture
```
### On Debian
@@ -159,6 +174,14 @@ dnf copr enable keefle/fd
dnf install fd
```
### On Alpine Linux
You can install [the fd package](https://pkgs.alpinelinux.org/packages?name=fd)
from the official sources, provided you have the appropriate repository enabled:
```
apk add fd
```
### On Arch Linux
You can install [the fd package](https://www.archlinux.org/packages/community/x86_64/fd/) from the official repos:
@@ -188,7 +211,7 @@ xbps-install -S fd
### On macOS
You can install `fd` with [Homebrew](http://braumeister.org/formula/fd):
You can install `fd` with [Homebrew](https://formulae.brew.sh/formula/fd):
```
brew install fd
```
@@ -221,9 +244,17 @@ nix-env -i fd
### On FreeBSD
You can install `sysutils/fd` via portmaster:
You can install [the fd-find package](https://www.freshports.org/sysutils/fd) from the official repo:
```
portmaster sysutils/fd
pkg install fd-find
```
### From NPM
On linux and macOS, you can install the [fd-find](https://npm.im/fd-find) package:
```
npm install -g fd-find
```
### From source
@@ -232,7 +263,7 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can
```
cargo install fd-find
```
Note that rust version *1.31.0* or later is required.
Note that rust version *1.36.0* or later is required.
### From binaries
@@ -261,11 +292,11 @@ USAGE:
FLAGS:
-H, --hidden Search hidden files and directories
-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
-g, --glob Glob-based search (default: regular expression)
-a, --absolute-path Show absolute instead of relative paths
-l, --list-details Use a long listing format with file metadata
-L, --follow Follow symbolic links
-p, --full-path Search full path (default: file-/dirname only)
-0, --print0 Separate results by the null character
@@ -275,7 +306,7 @@ FLAGS:
OPTIONS:
-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)
executable (x), empty (e), socket (s), pipe (p)
-e, --extension <ext>... Filter by file extension
-x, --exec <cmd> Execute a command for each search result
-X, --exec-batch <cmd> Execute a command with all search results at once
@@ -286,7 +317,7 @@ OPTIONS:
--changed-before <date|dur> Filter by file modification time (older than)
ARGS:
<pattern> the search pattern, a regular expression (optional)
<pattern> the search pattern - a regular expression unless '--glob' is used (optional)
<path>... the root directory for the filesystem search (optional)
```
@@ -422,6 +453,10 @@ To make exclude-patterns like these permanent, you can create a `.fdignore` file
```
Note: `fd` also supports `.ignore` files that are used by other programs such as `rg` or `ag`.
If you want `fd` to ignore these patterns globally, you can put them in `fd`'s global ignore file.
This is usually located in `~/.config/fd/ignore` in macOS or Linux, and `%APPDATA%\fd\ignore` in
Windows.
### Using fd with `xargs` or `parallel`
If we want to run a command on all search results, we can pipe the output to `xargs`:
@@ -431,6 +466,28 @@ If we want to run a command on all search results, we can pipe the output to `xa
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.
### Deleting files
You can use `fd` to remove all files and directories that are matched by your search pattern.
If you only want to remove files, you can use the `--exec-batch`/`-X` option to call `rm`. For
example, to recursively remove all `.DS_Store` files, run:
``` bash
> fd -H '^\.DS_Store$' -tf -X rm
```
If you are unsure, always call `fd` without `-X rm` first. Alternatively, use `rm`s "interactive"
option:
``` bash
> fd -H '^\.DS_Store$' -tf -X rm -i
```
If you also want to remove a certain class of directories, you can use the same technique. You will
have to use `rm`s `--recursive`/`-r` flag to remove directories.
Note: there are scenarios where using `fd … -X rm -r` can cause race conditions: if you have a
path like `…/foo/bar/foo/…` and want to remove all directories named `foo`, you can end up in a
situation where the outer `foo` directory is removed first, leading to (harmless) *"'foo/bar/foo':
No such file or directory"* errors in the `rm` call.
### Troubleshooting
#### `fd` does not find my file!
@@ -495,3 +552,33 @@ After installing `find-file-in-project`, add the line `(setq ffip-use-rust-fd t)
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.
#### Printing fd's output as a tree
To format the output of `fd` similar to the `tree` command, install [`as-tree`] and pipe the output
of `fd` to `as-tree`:
```bash
fd | as-tree
```
This can be more useful than running `tree` by itself because `tree` does not ignore any files by
default, nor does it support as rich a set of options as `fd` does to control what to print:
```bash
fd --extension rs | as-tree
.
├── build.rs
└── src
├── app.rs
└── error.rs
```
For more information about `as-tree`, see [the `as-tree` README][`as-tree`].
[`as-tree`]: https://github.com/jez/as-tree
## License
Copyright (c) 2017-2020 The fd developers
`fd` is distributed under the terms of both the MIT License and the Apache License 2.0.
See the [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) files for license details.
+1 -24
View File
@@ -13,24 +13,6 @@ environment:
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
# Beta channel (disabled for speed reasons)
#- TARGET: i686-pc-windows-gnu
# CHANNEL: beta
#- TARGET: i686-pc-windows-msvc
# CHANNEL: beta
#- TARGET: x86_64-pc-windows-gnu
# CHANNEL: beta
#- TARGET: x86_64-pc-windows-msvc
# CHANNEL: beta
# Nightly channel (disabled for speed reasons)
#- TARGET: i686-pc-windows-gnu
# CHANNEL: nightly
#- TARGET: i686-pc-windows-msvc
# CHANNEL: nightly
#- TARGET: x86_64-pc-windows-gnu
# CHANNEL: nightly
#- TARGET: x86_64-pc-windows-msvc
# CHANNEL: nightly
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
@@ -86,9 +68,4 @@ branches:
- master
# IMPORTANT Regex to match tags. Required, or appveyor may not trigger deploys when a new tag
# is pushed. This regex matches semantic versions like v1.2.3-rc4+2016.02.22
- /^v\d+\.\d+\.\d+.*$/
# Disable caching, for now
#cache:
# - '%USERPROFILE%\.cargo'
# - 'target -> Cargo.lock'
- /^v\d+\.\d+\.\d+.*$/
+7 -14
View File
@@ -1,25 +1,18 @@
// 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::fs;
use clap::Shell;
use std::fs;
use std::io::{self, Write};
use std::process::exit;
include!("src/app.rs");
fn main() {
match version_check::is_min_version("1.31") {
Some((true, _)) => {}
let min_version = "1.36";
match version_check::is_min_version(min_version) {
Some(true) => {}
// rustc version too small or can't figure it out
_ => {
writeln!(&mut io::stderr(), "'fd' requires rustc >= 1.31").unwrap();
exit(1);
eprintln!("'fd' requires rustc >= {}", min_version);
std::process::exit(1);
}
}
+53 -2
View File
@@ -55,13 +55,25 @@ make_deb() {
local version
local dpkgname
local conflictname
local homepage
local maintainer
local gcc_prefix
homepage="https://github.com/sharkdp/fd"
maintainer="David Peter <mail@david-peter.de>"
case $TARGET in
x86_64*)
architecture=amd64
gcc_prefix=""
;;
i686*)
architecture=i386
gcc_prefix=""
;;
arm*hf)
architecture=armhf
gcc_prefix="arm-linux-gnueabihf-"
;;
*)
echo "make_deb: skipping target '${TARGET}'" >&2
@@ -81,7 +93,7 @@ make_deb() {
# copy the main binary
install -Dm755 "target/$TARGET/release/$PROJECT_NAME" "$tempdir/usr/bin/$PROJECT_NAME"
strip "$tempdir/usr/bin/$PROJECT_NAME"
"${gcc_prefix}"strip "$tempdir/usr/bin/$PROJECT_NAME"
# manpage
install -Dm644 "doc/$PROJECT_NAME.1" "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
@@ -91,6 +103,44 @@ make_deb() {
install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md"
install -Dm644 LICENSE-MIT "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-MIT"
install -Dm644 LICENSE-APACHE "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-APACHE"
cat > "$tempdir/usr/share/doc/$PROJECT_NAME/copyright" <<EOF
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: $PROJECT_NAME
Source: $homepage
Files: *
Copyright: $maintainer
License: Apache-2.0 or MIT
License: Apache-2.0
On Debian systems, the complete text of the Apache-2.0 can be found in the
file /usr/share/common-licenses/Apache-2.0.
License: MIT
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
.
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
EOF
# completions
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.bash "$tempdir/usr/share/bash-completion/completions/${PROJECT_NAME}"
@@ -104,10 +154,11 @@ Package: $dpkgname
Version: $version
Section: utils
Priority: optional
Maintainer: David Peter <mail@david-peter.de>
Maintainer: $maintainer
Architecture: $architecture
Provides: $PROJECT_NAME
Conflicts: $conflictname
Homepage: $homepage
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.
Vendored
+68 -8
View File
@@ -33,10 +33,12 @@ Include hidden files and directories in the search results
.B \-I, \-\-no\-ignore
Show search results from files and directories that would otherwise be ignored by
.IR .gitignore ,
.I .ignore
or
.I .fdignore
files.
.IR .ignore ,
.IR .fdignore ,
or the global ignore file.
.TP
.B \-u, \-\-unrestricted
Alias for '--no-ignore'. Can be repeated; '-uu' is an alias for '--no-ignore --hidden'.
.TP
.B \-\-no\-ignore\-vcs
Show search results from files and directories that would otherwise be ignored by
@@ -51,12 +53,24 @@ pattern contains an uppercase character (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 \-g, \-\-glob
Perform a glob-based search instead of a regular expression search.
.TP
.B \-\-regex
Perform a regular-expression based seach (default). This can be used to override --glob.
.TP
.B \-F, \-\-fixed\-strings
Treat the pattern as a literal string instead of a regular expression.
.TP
.B \-a, \-\-absolute\-path
Shows the full path starting from the root as opposed to relative paths.
.TP
.B \-l, \-\-list\-details
Use a detailed listing format like 'ls -l'. This is basically an alias
for '--exec-batch ls -l' with some additional 'ls' options. This can be used
to see more metadata, to show symlink targets and to achieve a deterministic
sort order.
.TP
.B \-L, \-\-follow
By default, fd does not descend into symlinked directories. Using this flag, symbolic links are
also traversed.
@@ -71,10 +85,19 @@ is matched against the full path.
Separate search results by the null character (instead of newlines). Useful for piping results to
.IR xargs .
.TP
.B \-\-max\-results count
Limit the number of search results to 'count' and quit immediately.
.TP
.B \-1
Limit the search to a single result and quit immediately. This is an alias for '--max-results=1'.
.TP
.B \-\-show-errors
Enable the display of filesystem errors for situations such as insufficient
permissions or dead symlinks.
.TP
.B \-\-one\-file\-system, \-\-mount, \-\-xdev
By default, fd will traverse the file system tree as far as other options dictate. With this flag, fd ensures that it does not descend into a different file system than the one it started in. Comparable to the -mount or -xdev filters of find(1).
.TP
.B \-h, \-\-help
Print help information.
.TP
@@ -86,12 +109,18 @@ Limit directory traversal to at most
.I d
levels of depth. By default, there is no limit on the search depth.
.TP
.BI "\-\-min\-depth " d
Only show search results starting at the given depth. See also: '--max-depth' and '--exact-depth'.
.TP
.BI "\-\-exact\-depth " d
Only show search results at the exact given depth. This is an alias for '--min-depth <depth> --max-depth <depth>'.
.TP
.BI "\-t, \-\-type " filetype
Filter search by type:
.RS
.IP "f, file"
regular files
.IP "d, directories"
.IP "d, directory"
directories
.IP "l, symlink"
symbolic links
@@ -99,6 +128,10 @@ symbolic links
executable (files)
.IP "e, empty"
empty files or directories
.IP "s, socket"
sockets
.IP "p, pipe"
named pipes (FIFOs)
.RE
.RS
@@ -133,7 +166,7 @@ Always colorize output.
.RE
.TP
.BI "\-j, \-\-threads " num
Number of threads to use for searching (default: number of available CPUs).
Set number of threads to use for searching & executing (default: number of available CPU cores).
.TP
.BI "\-S, \-\-size " size
Limit results based on the size of files using the format
@@ -152,7 +185,7 @@ Allowed unit values:
.IP 'b'
bytes
.IP 'k'
kilobytes
kilobytes (base ten, 10^3 = 1000 bytes)
.IP 'm'
megabytes
.IP 'g'
@@ -160,7 +193,7 @@ gigabytes
.IP 't'
terabytes
.IP 'ki'
kibibytes
kibibytes (base two, 2^10 = 1024 bytes)
.IP 'mi'
mebibytes
.IP 'gi'
@@ -190,6 +223,15 @@ Examples:
\-\-changed-before "2018-10-27 10:00:00"
\-\-change-older-than 2weeks
.TP
.BI "-o, \-\-owner " [user][:group]
Filter files by their user and/or group. Format: [(user|uid)][:(group|gid)]. Either side
is optional. Precede either side with a '!' to exclude files instead.
Examples:
\-\-owner john
\-\-owner :students
\-\-owner "!john:students"
.TP
.BI "\-x, \-\-exec " command
Execute
.I command
@@ -224,11 +266,29 @@ path without file extension
.IP {/.}
basename without file extension
.RE
.SH PATTERN SYNTAX
The regular expression syntax used by fd is documented here:
.UR https://docs.rs/regex/1.0.0/regex/#syntax
.UE
The glob syntax is documented here:
.UR https://docs.rs/globset/#syntax
.UE
.SH ENVIRONMENT
.TP
.B LS_COLORS
Determines how to colorize search results, see
.BR dircolors (1) .
.TP
.B XDG_CONFIG_HOME, HOME
Used to locate the global ignore file. If
.B XDG_CONFIG_HOME
is set, use
.IR $XDG_CONFIG_HOME/fd/ignore .
Otherwise, use
.IR $HOME/.config/fd/ignore .
.SH EXAMPLES
.TP
.RI "Find files and directories that match the pattern '" needle "':"
+439 -246
View File
@@ -1,92 +1,222 @@
// 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::collections::HashMap;
use clap::{crate_version, App, AppSettings, Arg};
struct Help {
short: &'static str,
long: &'static str,
}
macro_rules! doc {
($map:expr, $name:expr, $short:expr) => {
doc!($map, $name, $short, $short)
};
($map:expr, $name:expr, $short:expr, $long:expr) => {
$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)
};
App::new("fd")
let mut app = App::new("fd")
.version(crate_version!())
.usage("fd [FLAGS/OPTIONS] [<pattern>] [<path>...]")
.setting(AppSettings::ColoredHelp)
.setting(AppSettings::DeriveDisplayOrder)
.after_help(
"Note: `fd -h` prints a short and concise overview while `fd --help` \
gives all details.",
"Note: `fd -h` prints a short and concise overview while `fd --help` gives all \
details.",
)
.arg(arg("hidden").long("hidden").short("H"))
.arg(arg("no-ignore").long("no-ignore").short("I"))
.arg(arg("no-ignore-vcs").long("no-ignore-vcs"))
.arg(
arg("rg-alias-hidden-ignore")
Arg::with_name("hidden")
.long("hidden")
.short("H")
.overrides_with("hidden")
.help("Search hidden files and directories")
.long_help(
"Include hidden directories and files in the search results (default: \
hidden files and directories are skipped). Files and directories are \
considered to be hidden if their name starts with a `.` sign (dot).",
),
)
.arg(
Arg::with_name("no-ignore")
.long("no-ignore")
.short("I")
.overrides_with("no-ignore")
.help("Do not respect .(git|fd)ignore files")
.long_help(
"Show search results from files and directories that would otherwise be \
ignored by '.gitignore', '.ignore', '.fdignore', or the global ignore file.",
),
)
.arg(
Arg::with_name("no-ignore-vcs")
.long("no-ignore-vcs")
.overrides_with("no-ignore-vcs")
.hidden_short_help(true)
.long_help(
"Show search results from files and directories that would otherwise be \
ignored by '.gitignore' files.",
),
)
.arg(
Arg::with_name("no-global-ignore-file")
.long("no-global-ignore-file")
.hidden(true)
.long_help("Do not respect the global ignore file."),
)
.arg(
Arg::with_name("rg-alias-hidden-ignore")
.short("u")
.long("unrestricted")
.multiple(true)
.hidden(true),
.hidden_short_help(true)
.long_help(
"Alias for '--no-ignore'. Can be repeated. '-uu' is an alias for \
'--no-ignore --hidden'.",
),
)
.arg(
arg("case-sensitive")
Arg::with_name("case-sensitive")
.long("case-sensitive")
.short("s")
.overrides_with("ignore-case"),
.overrides_with_all(&["ignore-case", "case-sensitive"])
.help("Case-sensitive search (default: smart case)")
.long_help(
"Perform a case-sensitive search. By default, fd uses case-insensitive \
searches, unless the pattern contains an uppercase character (smart \
case).",
),
)
.arg(
arg("ignore-case")
Arg::with_name("ignore-case")
.long("ignore-case")
.short("i")
.overrides_with("case-sensitive"),
.overrides_with_all(&["case-sensitive", "ignore-case"])
.help("Case-insensitive search (default: smart case)")
.long_help(
"Perform a case-insensitive search. By default, fd uses case-insensitive \
searches, unless the pattern contains an uppercase character (smart \
case).",
),
)
.arg(
arg("fixed-strings")
Arg::with_name("glob")
.long("glob")
.short("g")
.conflicts_with("fixed-strings")
.overrides_with("glob")
.help("Glob-based search (default: regular expression)")
.long_help("Perform a glob-based search instead of a regular expression search."),
)
.arg(
Arg::with_name("regex")
.long("regex")
.overrides_with_all(&["glob", "regex"])
.hidden_short_help(true)
.long_help(
"Perform a regular-expression based search (default). This can be used to \
override --glob.",
),
)
.arg(
Arg::with_name("fixed-strings")
.long("fixed-strings")
.short("F")
.alias("literal"),
.alias("literal")
.overrides_with("fixed-strings")
.hidden_short_help(true)
.long_help(
"Treat the pattern as a literal string instead of a regular expression.",
),
)
.arg(
Arg::with_name("absolute-path")
.long("absolute-path")
.short("a")
.overrides_with("absolute-path")
.help("Show absolute instead of relative paths")
.long_help(
"Shows the full path starting from the root as opposed to relative paths.",
),
)
.arg(
Arg::with_name("list-details")
.long("list-details")
.short("l")
.conflicts_with("absolute-path")
.help("Use a long listing format with file metadata")
.long_help(
"Use a detailed listing format like 'ls -l'. This is basically an alias \
for '--exec-batch ls -l' with some additional 'ls' options. This can be \
used to see more metadata, to show symlink targets and to achieve a \
deterministic sort order.",
),
)
.arg(
Arg::with_name("follow")
.long("follow")
.short("L")
.alias("dereference")
.overrides_with("follow")
.help("Follow symbolic links")
.long_help(
"By default, fd does not descend into symlinked directories. Using this \
flag, symbolic links are also traversed.",
),
)
.arg(
Arg::with_name("full-path")
.long("full-path")
.short("p")
.overrides_with("full-path")
.help("Search full path (default: file-/dirname only)")
.long_help(
"By default, the search pattern is only matched against the filename (or \
directory name). Using this flag, the pattern is matched against the \
full path.",
),
)
.arg(
Arg::with_name("null_separator")
.long("print0")
.short("0")
.overrides_with("print0")
.conflicts_with("list-details")
.help("Separate results by the null character")
.long_help(
"Separate search results by the null character (instead of newlines). \
Useful for piping results to 'xargs'.",
),
)
.arg(
Arg::with_name("max-depth")
.long("max-depth")
.short("d")
.takes_value(true)
.value_name("depth")
.help("Set maximum search depth (default: none)")
.long_help(
"Limit the directory traversal to a given depth. By default, there is no \
limit on the search depth.",
),
)
.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")
Arg::with_name("rg-depth")
.long("maxdepth")
.hidden(true)
.takes_value(true),
.takes_value(true)
)
.arg(
arg("file-type")
Arg::with_name("min-depth")
.long("min-depth")
.takes_value(true)
.value_name("depth")
.hidden_short_help(true)
.long_help(
"Only show search results starting at the given depth. \
See also: '--max-depth' and '--exact-depth'",
),
)
.arg(
Arg::with_name("exact-depth")
.long("exact-depth")
.takes_value(true)
.value_name("depth")
.hidden_short_help(true)
.conflicts_with_all(&["max-depth", "min-depth"])
.long_help(
"Only show search results at the exact given depth. This is an alias for \
'--min-depth <depth> --max-depth <depth>'.",
),
)
.arg(
Arg::with_name("file-type")
.long("type")
.short("t")
.multiple(true)
@@ -104,276 +234,339 @@ pub fn build_app() -> App<'static, 'static> {
"executable",
"e",
"empty",
"s",
"socket",
"p",
"pipe",
])
.hide_possible_values(true),
.hide_possible_values(true)
.help(
"Filter by type: file (f), directory (d), symlink (l),\nexecutable (x), \
empty (e), socket (s), pipe (p)",
)
.long_help(
"Filter the search by type (multiple allowable filetypes can be specified):\n \
'f' or 'file': regular files\n \
'd' or 'directory': directories\n \
'l' or 'symlink': symbolic links\n \
'x' or 'executable': executables\n \
'e' or 'empty': empty files or directories\n \
's' or 'socket': socket\n \
'p' or 'pipe': named pipe (FIFO)",
),
)
.arg(
arg("extension")
Arg::with_name("extension")
.long("extension")
.short("e")
.multiple(true)
.number_of_values(1)
.takes_value(true)
.value_name("ext"),
.value_name("ext")
.help("Filter by file extension")
.long_help(
"(Additionally) filter search results by their file extension. Multiple \
allowable file extensions can be specified.",
),
)
.arg(
arg("exec")
Arg::with_name("exec")
.long("exec")
.short("x")
.min_values(1)
.allow_hyphen_values(true)
.value_terminator(";")
.value_name("cmd"),
.value_name("cmd")
.conflicts_with("list-details")
.help("Execute a command for each search result")
.long_help(
"Execute a command for each search result.\n\
All arguments following --exec are taken to be arguments to the command until the \
argument ';' is encountered.\n\
Each occurrence of the following placeholders is substituted by a path derived from the \
current search result before the command is executed:\n \
'{}': path\n \
'{/}': basename\n \
'{//}': parent directory\n \
'{.}': path without file extension\n \
'{/.}': basename without file extension",
),
)
.arg(
arg("exec-batch")
Arg::with_name("exec-batch")
.long("exec-batch")
.short("X")
.min_values(1)
.allow_hyphen_values(true)
.value_terminator(";")
.value_name("cmd")
.conflicts_with("exec"),
.conflicts_with_all(&["exec", "list-details"])
.help("Execute a command with all search results at once")
.long_help(
"Execute a command with all search results at once.\n\
All arguments following --exec-batch are taken to be arguments to the command until the \
argument ';' is encountered.\n\
A single occurrence of the following placeholders is authorized and substituted by the paths derived from the \
search results before the command is executed:\n \
'{}': path\n \
'{/}': basename\n \
'{//}': parent directory\n \
'{.}': path without file extension\n \
'{/.}': basename without file extension",
),
)
.arg(
arg("exclude")
Arg::with_name("exclude")
.long("exclude")
.short("E")
.takes_value(true)
.value_name("pattern")
.number_of_values(1)
.multiple(true),
.multiple(true)
.help("Exclude entries that match the given glob pattern")
.long_help(
"Exclude files/directories that match the given glob pattern. This \
overrides any other ignore logic. Multiple exclude patterns can be \
specified.",
),
)
.arg(
arg("ignore-file")
Arg::with_name("ignore-file")
.long("ignore-file")
.takes_value(true)
.value_name("path")
.number_of_values(1)
.multiple(true)
.hidden_short_help(true),
.hidden_short_help(true)
.long_help(
"Add a custom ignore-file in '.gitignore' format. These files have a low \
precedence.",
),
)
.arg(
arg("color")
Arg::with_name("color")
.long("color")
.short("c")
.takes_value(true)
.value_name("when")
.possible_values(&["never", "auto", "always"])
.hide_possible_values(true),
.hide_possible_values(true)
.help("When to use colors: never, *auto*, always")
.long_help(
"Declare when to use color for the pattern match output:\n \
'auto': show colors if the output goes to an interactive console (default)\n \
'never': do not use colorized output\n \
'always': always use colorized output",
),
)
.arg(
arg("threads")
Arg::with_name("threads")
.long("threads")
.short("j")
.takes_value(true)
.value_name("num")
.hidden_short_help(true),
.hidden_short_help(true)
.long_help(
"Set number of threads to use for searching & executing (default: number \
of available CPU cores)",
),
)
.arg(
arg("size")
Arg::with_name("size")
.long("size")
.short("S")
.takes_value(true)
.number_of_values(1)
.allow_hyphen_values(true)
.multiple(true),
.multiple(true)
.help("Limit results based on the size of files.")
.long_help(
"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 (base ten, 10^3 = 1000 bytes)\n \
'm': megabytes\n \
'g': gigabytes\n \
't': terabytes\n \
'ki': kibibytes (base two, 2^10 = 1024 bytes)\n \
'mi': mebibytes\n \
'gi': gibibytes\n \
'ti': tebibytes",
),
)
.arg(
arg("max-buffer-time")
Arg::with_name("max-buffer-time")
.long("max-buffer-time")
.takes_value(true)
.hidden(true),
.hidden(true)
.long_help(
"Amount of time in milliseconds to buffer, before streaming the search \
results to the console.",
),
)
.arg(
arg("changed-within")
Arg::with_name("changed-within")
.long("changed-within")
.alias("change-newer-than")
.takes_value(true)
.value_name("date|dur")
.number_of_values(1),
.number_of_values(1)
.help("Filter by file modification time (newer than)")
.long_help(
"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'",
),
)
.arg(
arg("changed-before")
Arg::with_name("changed-before")
.long("changed-before")
.alias("change-older-than")
.takes_value(true)
.value_name("date|dur")
.number_of_values(1),
.number_of_values(1)
.help("Filter by file modification time (older than)")
.long_help(
"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",
),
)
.arg(
arg("show-errors")
Arg::with_name("max-results")
.long("max-results")
.takes_value(true)
.value_name("count")
// We currently do not support --max-results in combination with
// program execution because the results that come up in a --max-results
// search are non-deterministic. Users might think that they can run the
// same search with `--exec rm` attached and get a reliable removal of
// the files they saw in the previous search.
.conflicts_with_all(&["exec", "exec-batch", "list-details"])
.hidden_short_help(true)
.long_help("Limit the number of search results to 'count' and quit immediately."),
)
.arg(
Arg::with_name("max-one-result")
.short("1")
.hidden_short_help(true)
.overrides_with("max-results")
.conflicts_with_all(&["exec", "exec-batch", "list-details"])
.long_help("Limit the search to a single result and quit immediately. \
This is an alias for '--max-results=1'.")
)
.arg(
Arg::with_name("show-errors")
.long("show-errors")
.hidden_short_help(true),
.hidden_short_help(true)
.overrides_with("show-errors")
.long_help(
"Enable the display of filesystem errors for situations such as \
insufficient permissions or dead symlinks.",
),
)
.arg(arg("pattern"))
.arg(arg("path").multiple(true))
.arg(
arg("search-path")
Arg::with_name("base-directory")
.long("base-directory")
.takes_value(true)
.value_name("path")
.number_of_values(1)
.hidden_short_help(true)
.long_help(
"Change the current working directory of fd to the provided path. The \
means that search results will be shown with respect to the given base \
path. Note that relative paths which are passed to fd via the positional \
<path> argument or the '--search-path' option will also be resolved \
relative to this directory.",
),
)
.arg(
Arg::with_name("pattern").help(
"the search pattern - a regular expression unless '--glob' is used (optional)",
),
)
.arg(
Arg::with_name("path-separator")
.takes_value(true)
.value_name("separator")
.long("path-separator")
.hidden_short_help(true)
.long_help(
"Set the path separator to use when printing file paths. The default is \
the OS-specific separator ('/' on Unix, '\\' on Windows).",
),
)
.arg(
Arg::with_name("path")
.multiple(true)
.help("the root directory for the filesystem search (optional)")
.long_help(
"The directory where the filesystem search is rooted (optional). If \
omitted, search the current working directory.",
),
)
.arg(
Arg::with_name("search-path")
.long("search-path")
.takes_value(true)
.conflicts_with("path")
.multiple(true)
.hidden_short_help(true)
.number_of_values(1),
)
}
.number_of_values(1)
.long_help(
"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>]`",
),
);
#[rustfmt::skip]
fn usage() -> HashMap<&'static str, Help> {
let mut h = HashMap::new();
doc!(h, "hidden"
, "Search hidden files and directories"
, "Include hidden directories and files in the search results (default: hidden files \
and directories are skipped). Files and directories are considered to be hidden if \
their name starts with a `.` sign (dot).");
doc!(h, "no-ignore"
, "Do not respect .(git|fd)ignore files"
, "Show search results from files and directories that would otherwise be ignored by \
'.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 \
'.gitignore' files.");
doc!(h, "case-sensitive"
, "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).");
doc!(h, "ignore-case"
, "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.");
doc!(h, "follow"
, "Follow symbolic links"
, "By default, fd does not descend into symlinked directories. Using this flag, symbolic \
links are also traversed.");
doc!(h, "full-path"
, "Search full path (default: file-/dirname only)"
, "By default, the search pattern is only matched against the filename (or directory \
name). Using this flag, the pattern is matched against the full path.");
doc!(h, "null_separator"
, "Separate results by the null character"
, "Separate search results by the null character (instead of newlines). Useful for \
piping results to 'xargs'.");
doc!(h, "depth"
, "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: file (f), directory (d), symlink (l),\nexecutable (x), empty (e)"
, "Filter the search by type (multiple allowable filetypes can be specified):\n \
'f' or 'file': regular files\n \
'd' or 'directory': directories\n \
'l' or 'symlink': symbolic links\n \
'x' or 'executable': executables\n \
'e' or 'empty': empty files or directories");
doc!(h, "extension"
, "Filter by file extension"
, "(Additionally) filter search results by their file extension. Multiple allowable file \
extensions can be specified.");
doc!(h, "exec"
, "Execute a command for each search result"
, "Execute a command for each search result.\n\
All arguments following --exec are taken to be arguments to the command until the \
argument ';' is encountered.\n\
Each occurrence of the following placeholders is substituted by a path derived from the \
current search result before the command is executed:\n \
'{}': path\n \
'{/}': basename\n \
'{//}': parent directory\n \
'{.}': path without file extension\n \
'{/.}': basename without file extension");
doc!(h, "exec-batch"
, "Execute a command with all search results at once"
, "Execute a command with all search results at once.\n\
All arguments following --exec-batch are taken to be arguments to the command until the \
argument ';' is encountered.\n\
A single occurence of the following placeholders is authorized and substituted by the paths derived from the \
search results before the command is executed:\n \
'{}': path\n \
'{/}': basename\n \
'{//}': parent directory\n \
'{.}': path without file extension\n \
'{/.}': basename without file extension");
doc!(h, "exclude"
, "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 \
'auto': show colors if the output goes to an interactive console (default)\n \
'never': do not use colorized output\n \
'always': always use colorized output");
doc!(h, "threads"
, "Set number of threads to use for searching & executing"
, "Set number of threads to use for searching & executing (default: number of available \
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 \
the console.");
doc!(h, "pattern"
, "the search pattern, a regular expression (optional)");
doc!(h, "path"
, "the root directory for the filesystem search (optional)"
, "The directory where the filesystem search is rooted (optional). \
If omitted, search the current working directory.");
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
if cfg!(unix) {
app = app.arg(
Arg::with_name("owner")
.long("owner")
.short("o")
.takes_value(true)
.value_name("user:group")
.help("Filter by owning user and/or group")
.long_help(
"Filter files by their user and/or group. \
Format: [(user|uid)][:(group|gid)]. Either side is optional. \
Precede either side with a '!' to exclude files instead.\n\
Examples:\n \
--owner john\n \
--owner :students\n \
--owner '!john:students'",
),
);
}
// Make `--one-file-system` available only on Unix and Windows platforms, as per the
// restrictions on the corresponding option in the `ignore` crate.
// Provide aliases `mount` and `xdev` for people coming from `find`.
if cfg!(any(unix, windows)) {
app = app.arg(
Arg::with_name("one-file-system")
.long("one-file-system")
.aliases(&["mount", "xdev"])
.hidden_short_help(true)
.long_help(
"By default, fd will traverse the file system tree as far as other options \
dictate. With this flag, fd ensures that it does not descend into a \
different file system than the one it started in. Comparable to the -mount \
or -xdev filters of find(1).",
),
);
}
app
}
+3
View File
@@ -0,0 +1,3 @@
pub fn print_error(msg: impl Into<String>) {
eprintln!("[fd error]: {}", msg.into());
}
+14 -11
View File
@@ -1,18 +1,13 @@
// 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::io;
use std::io::Write;
use std::process::Command;
use std::sync::Mutex;
use crate::error::print_error;
use crate::exit_codes::ExitCode;
/// Executes a command.
pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) {
pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) -> ExitCode {
// Spawn the supplied command.
let output = cmd.output();
@@ -28,12 +23,20 @@ pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) {
let _ = stdout.lock().write_all(&output.stdout);
let _ = stderr.lock().write_all(&output.stderr);
if output.status.code() == Some(0) {
ExitCode::Success
} else {
ExitCode::GeneralError
}
}
Err(ref why) if why.kind() == io::ErrorKind::NotFound => {
print_error!("Command not found: {:?}", cmd);
print_error(format!("Command not found: {:?}", cmd));
ExitCode::GeneralError
}
Err(why) => {
print_error!("Problem while executing command: {}", why);
print_error(format!("Problem while executing command: {}", why));
ExitCode::GeneralError
}
}
}
+33 -67
View File
@@ -1,78 +1,40 @@
// 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::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::path::MAIN_SEPARATOR;
use crate::filesystem::strip_current_dir;
/// Removes the parent component of the path
pub fn basename(path: &str) -> &str {
let mut index = 0;
for (id, character) in path.char_indices() {
if character == MAIN_SEPARATOR {
index = id;
}
}
// FIXME: On Windows, should return what for C:file.txt D:file.txt and \\server\share ?
if index != 0 {
return &path[index + 1..];
}
path
pub fn basename(path: &Path) -> &OsStr {
path.file_name().unwrap_or_else(|| path.as_os_str())
}
/// Removes the extension from the path
pub fn remove_extension(path: &str) -> &str {
let mut has_dir = false;
let mut dir_index = 0;
let mut ext_index = 0;
pub fn remove_extension(path: &Path) -> OsString {
let dirname = dirname(path);
let stem = path.file_stem().unwrap_or_else(|| path.as_os_str());
for (id, character) in path.char_indices() {
if character == MAIN_SEPARATOR {
has_dir = true;
dir_index = id;
}
if character == '.' {
ext_index = id;
}
}
let path = PathBuf::from(dirname).join(stem);
// Account for hidden files and directories
if ext_index != 0 && (!has_dir || dir_index + 2 <= ext_index) {
return &path[0..ext_index];
}
path
strip_current_dir(&path).to_owned().into_os_string()
}
/// Removes the basename from the path.
pub fn dirname(path: &str) -> &str {
let mut has_dir = false;
let mut index = 0;
for (id, character) in path.char_indices() {
if character == MAIN_SEPARATOR {
has_dir = true;
index = id;
}
}
// FIXME: On Windows, return what for C:file.txt D:file.txt and \\server\share ?
if !has_dir {
"."
} else if index == 0 {
&path[..1]
} else {
&path[0..index]
}
pub fn dirname(path: &Path) -> OsString {
path.parent()
.map(|p| {
if p == OsStr::new("") {
OsString::from(".")
} else {
p.as_os_str().to_owned()
}
})
.unwrap_or_else(|| path.as_os_str().to_owned())
}
#[cfg(test)]
mod path_tests {
use super::*;
use std::path::MAIN_SEPARATOR;
fn correct(input: &str) -> String {
input.replace('/', &MAIN_SEPARATOR.to_string())
@@ -83,7 +45,9 @@ mod path_tests {
$(
#[test]
fn $name() {
assert_eq!($func(&correct($input)), correct($output));
let input_path = PathBuf::from(&correct($input));
let output_string = OsString::from(correct($output));
assert_eq!($func(&input_path), output_string);
}
)+
}
@@ -106,16 +70,18 @@ mod path_tests {
dirname_dir: dirname for "dir/foo.txt" => "dir"
dirname_utf8_0: dirname for "💖/foo.txt" => "💖"
dirname_utf8_1: dirname for "dir/💖.txt" => "dir"
dirname_empty: dirname for "" => "."
}
#[test]
#[cfg(windows)]
fn dirname_root() {
#[cfg(windows)]
assert_eq!(dirname("C:\\"), "C:");
#[cfg(windows)]
assert_eq!(dirname("\\"), "\\");
#[cfg(not(windows))]
assert_eq!(dirname("/"), "/");
assert_eq!(dirname(&PathBuf::from("C:")), OsString::from("C:"));
assert_eq!(dirname(&PathBuf::from("\\")), OsString::from("\\"));
}
#[test]
#[cfg(not(windows))]
fn dirname_root() {
assert_eq!(dirname(&PathBuf::from("/")), OsString::from("/"));
}
}
+21 -18
View File
@@ -1,17 +1,13 @@
// 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 super::CommandTemplate;
use crate::walk::WorkerResult;
use std::path::PathBuf;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex};
use crate::error::print_error;
use crate::exit_codes::{merge_exitcodes, ExitCode};
use crate::walk::WorkerResult;
use super::CommandTemplate;
/// 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.
@@ -20,7 +16,8 @@ pub fn job(
cmd: Arc<CommandTemplate>,
out_perm: Arc<Mutex<()>>,
show_filesystem_errors: bool,
) {
) -> ExitCode {
let mut results: Vec<ExitCode> = Vec::new();
loop {
// Create a lock on the shared receiver for this thread.
let lock = rx.lock().unwrap();
@@ -31,29 +28,35 @@ pub fn job(
Ok(WorkerResult::Entry(val)) => val,
Ok(WorkerResult::Error(err)) => {
if show_filesystem_errors {
print_error!("{}", err);
print_error(err.to_string());
}
continue;
}
Err(_) => break,
};
// Drop the lock so that other threads can read from the the receiver.
// Drop the lock so that other threads can read from the receiver.
drop(lock);
// Generate a command and execute it.
cmd.generate_and_execute(&value, Arc::clone(&out_perm));
// Generate a command, execute it and store its exit code.
results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm)))
}
// Returns error in case of any error.
merge_exitcodes(&results)
}
pub fn batch(rx: Receiver<WorkerResult>, cmd: &CommandTemplate, show_filesystem_errors: bool) {
pub fn batch(
rx: Receiver<WorkerResult>,
cmd: &CommandTemplate,
show_filesystem_errors: bool,
) -> ExitCode {
let paths = rx.iter().filter_map(|value| match value {
WorkerResult::Entry(val) => Some(val),
WorkerResult::Error(err) => {
if show_filesystem_errors {
print_error!("{}", err);
print_error(err.to_string());
}
None
}
});
cmd.generate_and_execute_batch(paths);
cmd.generate_and_execute_batch(paths)
}
+38 -43
View File
@@ -1,25 +1,20 @@
// 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.
// TODO: Possible optimization could avoid pushing characters on a buffer.
mod command;
mod input;
mod job;
mod token;
use std::borrow::Cow;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use anyhow::{anyhow, Result};
use lazy_static::lazy_static;
use regex::Regex;
use crate::exit_codes::ExitCode;
use crate::filesystem::strip_current_dir;
use self::command::execute_command;
use self::input::{basename, dirname, remove_extension};
pub use self::job::{batch, job};
@@ -53,17 +48,19 @@ impl CommandTemplate {
Self::build(input, ExecutionMode::OneByOne)
}
pub fn new_batch<I, S>(input: I) -> Result<CommandTemplate, &'static str>
pub fn new_batch<I, S>(input: I) -> Result<CommandTemplate>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let cmd = Self::build(input, ExecutionMode::Batch);
if cmd.number_of_tokens() > 1 {
return Err("Only one placeholder allowed for batch commands");
return Err(anyhow!("Only one placeholder allowed for batch commands"));
}
if cmd.args[0].has_tokens() {
return Err("First argument of exec-batch is expected to be a fixed executable");
return Err(anyhow!(
"First argument of exec-batch is expected to be a fixed executable"
));
}
Ok(cmd)
}
@@ -100,7 +97,7 @@ impl CommandTemplate {
"{/}" => tokens.push(Token::Basename),
"{//}" => tokens.push(Token::Parent),
"{/.}" => tokens.push(Token::BasenameNoExt),
_ => panic!("Unhandled placeholder"),
_ => unreachable!("Unhandled placeholder"),
}
has_placeholder = true;
@@ -132,24 +129,16 @@ impl CommandTemplate {
self.args.iter().filter(|arg| arg.has_tokens()).count()
}
fn prepare_path(input: &Path) -> String {
input
.strip_prefix(".")
.unwrap_or(input)
.to_string_lossy()
.into_owned()
}
/// Generates and executes a command.
///
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
/// build. Once all arguments have been processed, the command is executed.
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) {
let input = Self::prepare_path(input);
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) -> ExitCode {
let input = strip_current_dir(input);
let mut cmd = Command::new(self.args[0].generate(&input).as_ref());
let mut cmd = Command::new(self.args[0].generate(&input));
for arg in &self.args[1..] {
cmd.arg(arg.generate(&input).as_ref());
cmd.arg(arg.generate(&input));
}
execute_command(cmd, &out_perm)
@@ -159,33 +148,37 @@ impl CommandTemplate {
self.mode == ExecutionMode::Batch
}
pub fn generate_and_execute_batch<I>(&self, paths: I)
pub fn generate_and_execute_batch<I>(&self, paths: I) -> ExitCode
where
I: Iterator<Item = PathBuf>,
{
let mut cmd = Command::new(self.args[0].generate("").as_ref());
let mut cmd = Command::new(self.args[0].generate(""));
cmd.stdin(Stdio::inherit());
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
let mut paths = paths.map(|p| Self::prepare_path(&p));
let mut paths: Vec<_> = paths.collect();
let mut has_path = false;
for arg in &self.args[1..] {
if arg.has_tokens() {
paths.sort();
// A single `Tokens` is expected
// So we can directy consume the iterator once and for all
// So we can directly consume the iterator once and for all
for path in &mut paths {
cmd.arg(arg.generate(&path).as_ref());
cmd.arg(arg.generate(strip_current_dir(path)));
has_path = true;
}
} else {
cmd.arg(arg.generate("").as_ref());
cmd.arg(arg.generate(""));
}
}
if has_path {
execute_command(cmd, &Mutex::new(()));
execute_command(cmd, &Mutex::new(()))
} else {
ExitCode::Success
}
}
}
@@ -208,25 +201,27 @@ impl ArgumentTemplate {
}
}
pub fn generate<'a>(&'a self, path: &str) -> Cow<'a, str> {
pub fn generate(&self, path: impl AsRef<Path>) -> OsString {
use self::Token::*;
match *self {
ArgumentTemplate::Tokens(ref tokens) => {
let mut s = String::new();
let mut s = OsString::new();
for token in tokens {
match *token {
Basename => s += basename(path),
BasenameNoExt => s += remove_extension(basename(path)),
NoExt => s += remove_extension(path),
Parent => s += dirname(path),
Placeholder => s += path,
Text(ref string) => s += string,
Basename => s.push(basename(path.as_ref())),
BasenameNoExt => {
s.push(remove_extension(&PathBuf::from(basename(path.as_ref()))))
}
NoExt => s.push(remove_extension(path.as_ref())),
Parent => s.push(dirname(path.as_ref())),
Placeholder => s.push(path.as_ref()),
Text(ref string) => s.push(string),
}
}
Cow::Owned(s)
s
}
ArgumentTemplate::Text(ref text) => Cow::Borrowed(text),
ArgumentTemplate::Text(ref text) => OsString::from(text),
}
}
}
-8
View File
@@ -1,11 +1,3 @@
// 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::fmt::{self, Display, Formatter};
/// Designates what should be written to a buffer
+59
View File
@@ -1,4 +1,6 @@
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ExitCode {
Success,
GeneralError,
KilledBySigint,
}
@@ -6,8 +8,65 @@ pub enum ExitCode {
impl Into<i32> for ExitCode {
fn into(self) -> i32 {
match self {
ExitCode::Success => 0,
ExitCode::GeneralError => 1,
ExitCode::KilledBySigint => 130,
}
}
}
impl ExitCode {
fn is_error(&self) -> bool {
*self != ExitCode::Success
}
}
pub fn merge_exitcodes(results: &[ExitCode]) -> ExitCode {
if results.iter().any(ExitCode::is_error) {
return ExitCode::GeneralError;
}
ExitCode::Success
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn success_when_no_results() {
assert_eq!(merge_exitcodes(&[]), ExitCode::Success);
}
#[test]
fn general_error_if_at_least_one_error() {
assert_eq!(
merge_exitcodes(&[ExitCode::GeneralError]),
ExitCode::GeneralError
);
assert_eq!(
merge_exitcodes(&[ExitCode::KilledBySigint]),
ExitCode::GeneralError
);
assert_eq!(
merge_exitcodes(&[ExitCode::KilledBySigint, ExitCode::Success]),
ExitCode::GeneralError
);
assert_eq!(
merge_exitcodes(&[ExitCode::Success, ExitCode::GeneralError]),
ExitCode::GeneralError
);
assert_eq!(
merge_exitcodes(&[ExitCode::GeneralError, ExitCode::KilledBySigint]),
ExitCode::GeneralError
);
}
#[test]
fn success_if_no_error() {
assert_eq!(merge_exitcodes(&[ExitCode::Success]), ExitCode::Success);
assert_eq!(
merge_exitcodes(&[ExitCode::Success, ExitCode::Success]),
ExitCode::Success
);
}
}
+129
View File
@@ -0,0 +1,129 @@
use std::borrow::Cow;
use std::env::current_dir;
use std::ffi::OsStr;
use std::fs;
use std::io;
#[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::{FileTypeExt, PermissionsExt};
use std::path::{Path, PathBuf};
use crate::walk;
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
if path.is_absolute() {
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_start_matches(r"\\?\"),
)
.to_path_buf();
Ok(path_buf)
}
// 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 {
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: &walk::DirEntry) -> bool {
if let Some(file_type) = entry.file_type() {
if file_type.is_dir() {
if let Ok(mut entries) = fs::read_dir(entry.path()) {
entries.next().is_none()
} else {
false
}
} else if file_type.is_file() {
entry.metadata().map(|m| m.len() == 0).unwrap_or(false)
} else {
false
}
} else {
false
}
}
#[cfg(any(unix, target_os = "redox"))]
pub fn is_socket(ft: &fs::FileType) -> bool {
ft.is_socket()
}
#[cfg(windows)]
pub fn is_socket(_: &fs::FileType) -> bool {
false
}
#[cfg(any(unix, target_os = "redox"))]
pub fn is_pipe(ft: &fs::FileType) -> bool {
ft.is_fifo()
}
#[cfg(windows)]
pub fn is_pipe(_: &fs::FileType) -> bool {
false
}
#[cfg(any(unix, target_os = "redox"))]
pub fn osstr_to_bytes(input: &OsStr) -> Cow<[u8]> {
use std::os::unix::ffi::OsStrExt;
Cow::Borrowed(input.as_bytes())
}
#[cfg(windows)]
pub fn osstr_to_bytes(input: &OsStr) -> Cow<[u8]> {
let string = input.to_string_lossy();
match string {
Cow::Owned(string) => Cow::Owned(string.into_bytes()),
Cow::Borrowed(string) => Cow::Borrowed(string.as_bytes()),
}
}
/// Remove the `./` prefix from a path.
pub fn strip_current_dir(path: &Path) -> &Path {
path.strip_prefix(".").unwrap_or(path)
}
#[cfg(test)]
mod tests {
use super::strip_current_dir;
use std::path::Path;
#[test]
fn strip_current_dir_basic() {
assert_eq!(strip_current_dir(Path::new("./foo")), Path::new("foo"));
assert_eq!(strip_current_dir(Path::new("foo")), Path::new("foo"));
assert_eq!(
strip_current_dir(Path::new("./foo/bar/baz")),
Path::new("foo/bar/baz")
);
assert_eq!(
strip_current_dir(Path::new("foo/bar/baz")),
Path::new("foo/bar/baz")
);
}
}
@@ -3,6 +3,8 @@ pub struct FileTypes {
pub files: bool,
pub directories: bool,
pub symlinks: bool,
pub sockets: bool,
pub pipes: bool,
pub executables_only: bool,
pub empty_only: bool,
}
@@ -13,6 +15,8 @@ impl Default for FileTypes {
files: false,
directories: false,
symlinks: false,
sockets: false,
pipes: false,
executables_only: false,
empty_only: false,
}
@@ -1,5 +1,11 @@
pub use self::size::SizeFilter;
pub use self::time::TimeFilter;
#[cfg(unix)]
pub use self::owner::OwnerFilter;
mod size;
mod time;
#[cfg(unix)]
mod owner;
+123
View File
@@ -0,0 +1,123 @@
use anyhow::{anyhow, Result};
use std::fs;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OwnerFilter {
uid: Check<u32>,
gid: Check<u32>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Check<T> {
Equal(T),
NotEq(T),
Ignore,
}
impl OwnerFilter {
/// Parses an owner constraint
/// Returns an error if the string is invalid
/// Returns Ok(None) when string is acceptable but a noop (such as "" or ":")
pub fn from_string(input: &str) -> Result<Option<Self>> {
let mut it = input.split(':');
let (fst, snd) = (it.next(), it.next());
if it.next().is_some() {
return Err(anyhow!(
"more than one ':' present in owner string '{}'. See 'fd --help'.",
input
));
}
let uid = Check::parse(fst, |s| {
s.parse()
.ok()
.or_else(|| users::get_user_by_name(s).map(|user| user.uid()))
.ok_or_else(|| anyhow!("'{}' is not a recognized user name", s))
})?;
let gid = Check::parse(snd, |s| {
s.parse()
.ok()
.or_else(|| users::get_group_by_name(s).map(|group| group.gid()))
.ok_or_else(|| anyhow!("'{}' is not a recognized group name", s))
})?;
if let (Check::Ignore, Check::Ignore) = (uid, gid) {
Ok(None)
} else {
Ok(Some(OwnerFilter { uid, gid }))
}
}
pub fn matches(&self, md: &fs::Metadata) -> bool {
use std::os::unix::fs::MetadataExt;
self.uid.check(md.uid()) && self.gid.check(md.gid())
}
}
impl<T: PartialEq> Check<T> {
fn check(&self, v: T) -> bool {
match self {
Check::Equal(x) => v == *x,
Check::NotEq(x) => v != *x,
Check::Ignore => true,
}
}
fn parse<F>(s: Option<&str>, f: F) -> Result<Self>
where
F: Fn(&str) -> Result<T>,
{
let (s, equality) = match s {
Some("") | None => return Ok(Check::Ignore),
Some(s) if s.starts_with('!') => (&s[1..], false),
Some(s) => (s, true),
};
f(s).map(|x| {
if equality {
Check::Equal(x)
} else {
Check::NotEq(x)
}
})
}
}
#[cfg(test)]
mod owner_parsing {
use super::OwnerFilter;
macro_rules! owner_tests {
($($name:ident: $value:expr => $result:pat,)*) => {
$(
#[test]
fn $name() {
let o = OwnerFilter::from_string($value);
match o {
$result => {},
_ => panic!("{:?} does not match {}", o, stringify!($result)),
}
}
)*
};
}
use super::Check::*;
owner_tests! {
empty: "" => Ok(None),
uid_only: "5" => Ok(Some(OwnerFilter { uid: Equal(5), gid: Ignore })),
uid_gid: "9:3" => Ok(Some(OwnerFilter { uid: Equal(9), gid: Equal(3) })),
gid_only: ":8" => Ok(Some(OwnerFilter { uid: Ignore, gid: Equal(8) })),
colon_only: ":" => Ok(None),
trailing: "5:" => Ok(Some(OwnerFilter { uid: Equal(5), gid: Ignore })),
uid_negate: "!5" => Ok(Some(OwnerFilter { uid: NotEq(5), gid: Ignore })),
both_negate:"!4:!3" => Ok(Some(OwnerFilter { uid: NotEq(4), gid: NotEq(3) })),
uid_not_gid:"6:!8" => Ok(Some(OwnerFilter { uid: Equal(6), gid: NotEq(8) })),
more_colons:"3:5:" => Err(_),
only_colons:"::" => Err(_),
}
}
@@ -2,7 +2,7 @@ use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
static ref SIZE_CAPTURES: Regex = { Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap() };
static ref SIZE_CAPTURES: Regex = Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap();
}
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -37,13 +37,13 @@ impl SizeFilter {
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('k') => KILO,
v if v.starts_with("mi") => MEBI,
v if v.starts_with("m") => MEGA,
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('g') => GIGA,
v if v.starts_with("ti") => TEBI,
v if v.starts_with("t") => TERA,
v if v.starts_with('t') => TERA,
"b" => 1,
_ => return None,
};
@@ -80,8 +80,8 @@ mod tests {
};
}
/// 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)
// 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)),
@@ -168,7 +168,7 @@ mod tests {
};
}
/// Invalid parse data
// Invalid parse data
gen_size_filter_failure! {
ensure_missing_symbol_returns_none: "10M",
ensure_missing_number_returns_none: "+g",
@@ -9,7 +9,6 @@ pub enum TimeFilter {
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))
@@ -39,8 +38,6 @@ mod tests {
#[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()
-74
View File
@@ -1,74 +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::env::current_dir;
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() {
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();
Ok(path_buf)
}
// 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 {
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 {
false
}
}
-173
View File
@@ -1,173 +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 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);
}
}
+274 -105
View File
@@ -1,78 +1,116 @@
// 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.
#[macro_use]
mod internal;
mod app;
mod error;
mod exec;
mod exit_codes;
pub mod fshelper;
mod filesystem;
mod filetypes;
mod filter;
mod options;
mod output;
mod regex_helper;
mod walk;
use std::env;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::process;
use std::sync::Arc;
use std::time;
use anyhow::{anyhow, Context, Result};
use atty::Stream;
use globset::GlobBuilder;
use lscolors::LsColors;
use regex::{RegexBuilder, RegexSetBuilder};
use regex::bytes::{RegexBuilder, RegexSetBuilder};
use crate::error::print_error;
use crate::exec::CommandTemplate;
use crate::internal::{
filter::{SizeFilter, TimeFilter},
opts::FdOptions,
pattern_has_uppercase_char, transform_args_with_exec, FileTypes,
};
use crate::exit_codes::ExitCode;
use crate::filetypes::FileTypes;
#[cfg(unix)]
use crate::filter::OwnerFilter;
use crate::filter::{SizeFilter, TimeFilter};
use crate::options::Options;
use crate::regex_helper::pattern_has_uppercase_char;
fn main() {
let checked_args = transform_args_with_exec(env::args_os());
let matches = app::build_app().get_matches_from(checked_args);
// We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481
// FIXME: re-enable jemalloc on macOS, see comment in Cargo.toml file for more infos
#[cfg(all(not(windows), not(target_os = "macos"), not(target_env = "musl")))]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
// Get the search pattern
let pattern = matches.value_of("pattern").unwrap_or("");
fn run() -> Result<ExitCode> {
let matches = app::build_app().get_matches_from(env::args_os());
// Get the current working directory
let current_dir = Path::new(".");
if !fshelper::is_dir(current_dir) {
print_error_and_exit!("Could not get current directory.");
// Set the current working directory of the process
if let Some(base_directory) = matches.value_of_os("base-directory") {
let base_directory = Path::new(base_directory);
if !filesystem::is_dir(base_directory) {
return Err(anyhow!(
"The '--base-directory' path '{}' is not a directory.",
base_directory.to_string_lossy()
));
}
env::set_current_dir(base_directory).with_context(|| {
format!(
"Could not set '{}' as the current working directory.",
base_directory.to_string_lossy()
)
})?;
}
let current_directory = Path::new(".");
if !filesystem::is_dir(current_directory) {
return Err(anyhow!(
"Could not retrieve current directory (has it been deleted?)."
));
}
// Get the search pattern
let pattern = matches
.value_of_os("pattern")
.map(|p| {
p.to_str()
.ok_or_else(|| anyhow!("The search pattern includes invalid UTF-8 sequences."))
})
.transpose()?
.unwrap_or("");
// Get one or more root directories to search.
let mut dir_vec: Vec<_> = match matches
.values_of("path")
.or_else(|| 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()],
let passed_arguments = matches
.values_of_os("path")
.or_else(|| matches.values_of_os("search-path"));
let mut search_paths = if let Some(paths) = passed_arguments {
let mut directories = vec![];
for path in paths {
let path_buffer = PathBuf::from(path);
if filesystem::is_dir(&path_buffer) {
directories.push(path_buffer);
} else {
print_error(format!(
"Search path '{}' is not a directory.",
path_buffer.to_string_lossy()
));
}
}
directories
} else {
vec![current_directory.to_path_buf()]
};
// Check if we have no valid search paths.
if search_paths.is_empty() {
return Err(anyhow!("No valid search paths given."));
}
if matches.is_present("absolute-path") {
dir_vec = dir_vec
search_paths = search_paths
.iter()
.map(|path_buffer| {
path_buffer
.canonicalize()
.and_then(|pb| fshelper::absolute_path(pb.as_path()))
.and_then(|pb| filesystem::absolute_path(pb.as_path()))
.unwrap()
})
.collect();
@@ -81,22 +119,25 @@ fn main() {
// 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))
&& filesystem::is_dir(Path::new(pattern))
{
print_error_and_exit!(
return Err(anyhow!(
"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 \
Instead, if you want your pattern to match the full file 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") {
let pattern_regex = if matches.is_present("glob") && !pattern.is_empty() {
let glob = GlobBuilder::new(pattern).literal_separator(true).build()?;
glob.regex().to_owned()
} else if matches.is_present("fixed-strings") {
// Treat pattern as literal string if '--fixed-strings' is used
regex::escape(pattern)
} else {
String::from(pattern)
@@ -107,14 +148,21 @@ fn main() {
let case_sensitive = !matches.is_present("ignore-case")
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
#[cfg(windows)]
let ansi_colors_support =
ansi_term::enable_ansi_support().is_ok() || std::env::var_os("TERM").is_some();
#[cfg(not(windows))]
let ansi_colors_support = true;
let interactive_terminal = atty::is(Stream::Stdout);
let colored_output = match matches.value_of("color") {
Some("always") => true,
Some("never") => false,
_ => atty::is(Stream::Stdout),
_ => ansi_colors_support && env::var_os("NO_COLOR").is_none() && interactive_terminal,
};
#[cfg(windows)]
let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok();
let path_separator = matches.value_of("path-separator").map(|str| str.to_owned());
let ls_colors = if colored_output {
Some(LsColors::from_env().unwrap_or_default())
@@ -122,29 +170,103 @@ fn main() {
None
};
let command = matches
.values_of("exec")
.map(CommandTemplate::new)
.or_else(|| {
matches.values_of("exec-batch").map(|m| {
CommandTemplate::new_batch(m).unwrap_or_else(|e| {
print_error_and_exit!("{}", e);
})
})
});
let command = if let Some(args) = matches.values_of("exec") {
Some(CommandTemplate::new(args))
} else if let Some(args) = matches.values_of("exec-batch") {
Some(CommandTemplate::new_batch(args)?)
} else if matches.is_present("list-details") {
let color = matches.value_of("color").unwrap_or("auto");
let color_arg = ["--color=", color].concat();
let size_limits: Vec<SizeFilter> = matches
.values_of("size")
.map(|v| {
v.map(|sf| {
if let Some(f) = SizeFilter::from_string(sf) {
return f;
#[allow(unused)]
let gnu_ls = |command_name| {
vec![
command_name,
"-l", // long listing format
"--human-readable", // human readable file sizes
"--directory", // list directories themselves, not their contents
&color_arg,
]
};
let cmd: Vec<&str> = if cfg!(unix) {
if !cfg!(any(
target_os = "macos",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)) {
// Assume ls is GNU ls
gnu_ls("ls")
} else {
// MacOS, DragonFlyBSD, FreeBSD
use std::process::{Command, Stdio};
// Use GNU ls, if available (support for --color=auto, better LS_COLORS support)
let gnu_ls_exists = Command::new("gls")
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok();
if gnu_ls_exists {
gnu_ls("gls")
} else {
let mut cmd = vec![
"ls", // BSD version of ls
"-l", // long listing format
"-h", // '--human-readable' is not available, '-h' is
"-d", // '--directory' is not available, but '-d' is
];
if !cfg!(any(target_os = "netbsd", target_os = "openbsd")) && colored_output {
// -G is not available in NetBSD's and OpenBSD's ls
cmd.push("-G");
}
cmd
}
print_error_and_exit!("'{}' is not a valid size constraint. See 'fd --help'.", sf);
})
.collect()
}
} else if cfg!(windows) {
use std::process::{Command, Stdio};
// Use GNU ls, if available
let gnu_ls_exists = Command::new("ls")
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok();
if gnu_ls_exists {
gnu_ls("ls")
} else {
return Err(anyhow!(
"'fd --list-details' is not supported on Windows unless GNU 'ls' is installed."
));
}
} else {
return Err(anyhow!(
"'fd --list-details' is not supported on this platform."
));
};
Some(CommandTemplate::new_batch(&cmd).unwrap())
} else {
None
};
let size_limits = if let Some(vs) = matches.values_of("size") {
vs.map(|sf| {
SizeFilter::from_string(sf)
.ok_or_else(|| anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", sf))
})
.unwrap_or_else(|| vec![]);
.collect::<Result<Vec<_>>>()?
} else {
vec![]
};
let now = time::SystemTime::now();
let mut time_constraints: Vec<TimeFilter> = Vec::new();
@@ -152,18 +274,31 @@ fn main() {
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);
return Err(anyhow!(
"'{}' 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);
return Err(anyhow!(
"'{}' is not a valid date or duration. See 'fd --help'.",
t
));
}
}
let config = FdOptions {
#[cfg(unix)]
let owner_constraint = if let Some(s) = matches.value_of("owner") {
OwnerFilter::from_string(s)?
} else {
None
};
let config = Options {
case_sensitive,
search_full_path: matches.is_present("full-path"),
ignore_hidden: !(matches.is_present("hidden")
@@ -173,11 +308,20 @@ fn main() {
read_vcsignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")
|| matches.is_present("no-ignore-vcs")),
read_global_ignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")
|| matches.is_present("no-global-ignore-file")),
follow_links: matches.is_present("follow"),
one_file_system: matches.is_present("one-file-system"),
null_separator: matches.is_present("null_separator"),
max_depth: matches
.value_of("depth")
.value_of("max-depth")
.or_else(|| matches.value_of("rg-depth"))
.or_else(|| matches.value_of("exact-depth"))
.and_then(|n| usize::from_str_radix(n, 10).ok()),
min_depth: matches
.value_of("min-depth")
.or_else(|| matches.value_of("exact-depth"))
.and_then(|n| usize::from_str_radix(n, 10).ok()),
threads: std::cmp::max(
matches
@@ -191,6 +335,7 @@ fn main() {
.and_then(|n| u64::from_str_radix(n, 10).ok())
.map(time::Duration::from_millis),
ls_colors,
interactive_terminal,
file_types: matches.values_of("file-type").map(|values| {
let mut file_types = FileTypes::default();
for value in values {
@@ -202,9 +347,9 @@ fn main() {
file_types.executables_only = true;
file_types.files = true;
}
"e" | "empty" => {
file_types.empty_only = true;
}
"e" | "empty" => file_types.empty_only = true,
"s" | "socket" => file_types.sockets = true,
"p" | "pipe" => file_types.pipes = true,
_ => unreachable!(),
}
}
@@ -217,20 +362,17 @@ fn main() {
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());
}
}
}),
extensions: matches
.values_of("extension")
.map(|exts| {
let patterns = exts
.map(|e| e.trim_start_matches('.'))
.map(|e| format!(r".\.{}$", regex::escape(e)));
RegexSetBuilder::new(patterns)
.case_insensitive(true)
.build()
})
.transpose()?,
command: command.map(Arc::new),
exclude_patterns: matches
.values_of("exclude")
@@ -242,21 +384,48 @@ fn main() {
.unwrap_or_else(|| vec![]),
size_constraints: size_limits,
time_constraints,
#[cfg(unix)]
owner_constraint,
show_filesystem_errors: matches.is_present("show-errors"),
path_separator,
max_results: matches
.value_of("max-results")
.and_then(|n| usize::from_str_radix(n, 10).ok())
.filter(|&n| n != 0)
.or_else(|| {
if matches.is_present("max-one-result") {
Some(1)
} else {
None
}
}),
};
match RegexBuilder::new(&pattern_regex)
let re = RegexBuilder::new(&pattern_regex)
.case_insensitive(!config.case_sensitive)
.dot_matches_new_line(true)
.build()
{
Ok(re) => walk::scan(&dir_vec, Arc::new(re), Arc::new(config)),
.map_err(|e| {
anyhow!(
"{}\n\nNote: You can use the '--fixed-strings' option to search for a \
literal string instead of a regular expression. Alternatively, you can \
also use the '--glob' option to match on a glob pattern.",
e.to_string()
)
})?;
walk::scan(&search_paths, Arc::new(re), Arc::new(config))
}
fn main() {
let result = run();
match result {
Ok(exit_code) => {
process::exit(exit_code.into());
}
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()
);
eprintln!("[fd error]: {}", err);
process::exit(ExitCode::GeneralError.into());
}
}
}
+32 -8
View File
@@ -1,14 +1,16 @@
use crate::exec::CommandTemplate;
use crate::internal::{
filter::{SizeFilter, TimeFilter},
FileTypes,
};
use lscolors::LsColors;
use regex::RegexSet;
use std::{path::PathBuf, sync::Arc, time::Duration};
use lscolors::LsColors;
use regex::bytes::RegexSet;
use crate::exec::CommandTemplate;
use crate::filetypes::FileTypes;
#[cfg(unix)]
use crate::filter::OwnerFilter;
use crate::filter::{SizeFilter, TimeFilter};
/// Configuration options for *fd*.
pub struct FdOptions {
pub struct Options {
/// Whether the search is case-sensitive or case-insensitive.
pub case_sensitive: bool,
@@ -25,9 +27,15 @@ pub struct FdOptions {
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
pub read_vcsignore: bool,
/// Whether to respect the global ignore file or not.
pub read_global_ignore: bool,
/// Whether to follow symlinks or not.
pub follow_links: bool,
/// Whether to limit the search to starting file system or not.
pub one_file_system: bool,
/// Whether elements of output should be separated by a null character
pub null_separator: bool,
@@ -37,6 +45,9 @@ pub struct FdOptions {
/// all files under subdirectories of the current directory, etc.
pub max_depth: Option<usize>,
/// The minimum depth for reported entries, or `None`.
pub min_depth: Option<usize>,
/// The number of threads to use.
pub threads: usize,
@@ -49,6 +60,9 @@ pub struct FdOptions {
/// how to style different filetypes.
pub ls_colors: Option<LsColors>,
/// Whether or not we are writing to an interactive terminal
pub interactive_terminal: bool,
/// 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>,
@@ -73,6 +87,16 @@ pub struct FdOptions {
/// Constraints on last modification time of files
pub time_constraints: Vec<TimeFilter>,
#[cfg(unix)]
/// User/group ownership constraint
pub owner_constraint: Option<OwnerFilter>,
/// Whether or not to display filesystem errors
pub show_filesystem_errors: bool,
/// The separator used to print file paths.
pub path_separator: Option<String>,
/// The maximum number of search results
pub max_results: Option<usize>,
}
+54 -28
View File
@@ -1,37 +1,25 @@
// 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 crate::exit_codes::ExitCode;
use crate::internal::opts::FdOptions;
use lscolors::{LsColors, Style};
use std::io::{self, StdoutLock, Write};
use std::path::{Component, Path, PathBuf};
use std::path::{Path, PathBuf};
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use ansi_term;
use lscolors::{LsColors, Style};
/// Remove the `./` prefix from a path.
fn strip_current_dir(pathbuf: &PathBuf) -> &Path {
let mut iter = pathbuf.components();
let mut iter_next = iter.clone();
if iter_next.next() == Some(Component::CurDir) {
iter.next();
}
iter.as_path()
use crate::exit_codes::ExitCode;
use crate::filesystem::strip_current_dir;
use crate::options::Options;
pub fn replace_path_separator(path: &str, new_path_separator: &str) -> String {
path.replace(std::path::MAIN_SEPARATOR, &new_path_separator)
}
// TODO: this function is performance critical and can probably be optimized
pub fn print_entry(
stdout: &mut StdoutLock,
entry: &PathBuf,
config: &FdOptions,
config: &Options,
wants_to_quit: &Arc<AtomicBool>,
) {
let path = if entry.is_absolute() {
@@ -52,10 +40,11 @@ pub fn print_entry(
}
}
// TODO: this function is performance critical and can probably be optimized
fn print_entry_colorized(
stdout: &mut StdoutLock,
path: &Path,
config: &FdOptions,
config: &Options,
ls_colors: &LsColors,
wants_to_quit: &Arc<AtomicBool>,
) -> io::Result<()> {
@@ -67,8 +56,13 @@ fn print_entry_colorized(
.map(Style::to_ansi_term_style)
.unwrap_or(default_style);
write!(stdout, "{}", style.paint(component.to_string_lossy()))?;
let mut path_string = component.to_string_lossy();
if let Some(ref separator) = config.path_separator {
*path_string.to_mut() = replace_path_separator(&path_string, &separator);
}
write!(stdout, "{}", style.paint(path_string))?;
// TODO: can we move this out of the if-statement? Why do we call it that often?
if wants_to_quit.load(Ordering::Relaxed) {
writeln!(stdout)?;
process::exit(ExitCode::KilledBySigint.into());
@@ -82,13 +76,45 @@ fn print_entry_colorized(
}
}
fn print_entry_uncolorized(
// TODO: this function is performance critical and can probably be optimized
fn print_entry_uncolorized_base(
stdout: &mut StdoutLock,
path: &Path,
config: &FdOptions,
config: &Options,
) -> io::Result<()> {
let separator = if config.null_separator { "\0" } else { "\n" };
let path_str = path.to_string_lossy();
write!(stdout, "{}{}", path_str, separator)
let mut path_string = path.to_string_lossy();
if let Some(ref separator) = config.path_separator {
*path_string.to_mut() = replace_path_separator(&path_string, &separator);
}
write!(stdout, "{}{}", path_string, separator)
}
#[cfg(not(unix))]
fn print_entry_uncolorized(
stdout: &mut StdoutLock,
path: &Path,
config: &Options,
) -> io::Result<()> {
print_entry_uncolorized_base(stdout, path, config)
}
#[cfg(unix)]
fn print_entry_uncolorized(
stdout: &mut StdoutLock,
path: &Path,
config: &Options,
) -> io::Result<()> {
use std::os::unix::ffi::OsStrExt;
if config.interactive_terminal || config.path_separator.is_some() {
// Fall back to the base implementation
print_entry_uncolorized_base(stdout, path, config)
} else {
// Print path as raw bytes, allowing invalid UTF-8 filenames to be passed to other processes
let separator = if config.null_separator { b"\0" } else { b"\n" };
stdout.write_all(path.as_os_str().as_bytes())?;
stdout.write_all(separator)
}
}
+52
View File
@@ -0,0 +1,52 @@
use regex_syntax::hir::Hir;
use regex_syntax::ParserBuilder;
/// Determine if a regex pattern contains a literal uppercase character.
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
let mut parser = ParserBuilder::new().allow_invalid_utf8(true).build();
parser
.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::Literal(Literal::Byte(b)) => char::from(b).is_uppercase(),
HirKind::Class(Class::Unicode(ref ranges)) => ranges
.iter()
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
HirKind::Class(Class::Bytes(ref ranges)) => ranges
.iter()
.any(|r| char::from(r.start()).is_uppercase() || char::from(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,
}
}
#[test]
fn pattern_has_uppercase_char_simple() {
assert!(pattern_has_uppercase_char("A"));
assert!(pattern_has_uppercase_char("foo.EXE"));
assert!(!pattern_has_uppercase_char("a"));
assert!(!pattern_has_uppercase_char("foo.exe123"));
}
#[test]
fn pattern_has_uppercase_char_advanced() {
assert!(pattern_has_uppercase_char("foo.[a-zA-Z]"));
assert!(!pattern_has_uppercase_char(r"\Acargo"));
assert!(!pattern_has_uppercase_char(r"carg\x6F"));
}
+224 -95
View File
@@ -1,20 +1,8 @@
// 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 crate::exec;
use crate::exit_codes::ExitCode;
use crate::fshelper;
use crate::internal::{opts::FdOptions, MAX_BUFFER_LENGTH};
use crate::output;
use std::error::Error;
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fs::{FileType, Metadata};
use std::io;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
@@ -22,9 +10,17 @@ use std::sync::{Arc, Mutex};
use std::thread;
use std::time;
use anyhow::{anyhow, Result};
use ignore::overrides::OverrideBuilder;
use ignore::{self, WalkBuilder};
use regex::Regex;
use regex::bytes::Regex;
use crate::error::print_error;
use crate::exec;
use crate::exit_codes::{merge_exitcodes, ExitCode};
use crate::filesystem;
use crate::options::Options;
use crate::output;
/// The receiver thread can either be buffering results or directly streaming to the console.
enum ReceiverMode {
@@ -42,12 +38,15 @@ pub enum WorkerResult {
Error(ignore::Error),
}
/// Maximum size of the output buffer before flushing results to the console
pub const MAX_BUFFER_LENGTH: usize = 1000;
/// Recursively scan the given search path for files / pathnames matching the pattern.
///
/// If the `--exec` argument was supplied, this will create a thread pool for executing
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
/// path will simply be written to standard output.
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Options>) -> Result<ExitCode> {
let mut path_iter = path_vec.iter();
let first_path_buf = path_iter
.next()
@@ -57,14 +56,13 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
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() {
print_error_and_exit!("Malformed exclude pattern '{}'", pattern);
}
override_builder
.add(pattern)
.map_err(|e| anyhow!("Malformed exclude pattern: {}", e))?;
}
let overrides = override_builder.build().unwrap_or_else(|_| {
print_error_and_exit!("Mismatch in exclude patterns");
});
let overrides = override_builder
.build()
.map_err(|_| anyhow!("Mismatch in exclude patterns"))?;
let mut walker = WalkBuilder::new(first_path_buf.as_path());
walker
@@ -76,25 +74,51 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
.git_exclude(config.read_vcsignore)
.overrides(overrides)
.follow_links(config.follow_links)
// No need to check for supported platforms, option is unavailable on unsupported ones
.same_file_system(config.one_file_system)
.max_depth(config.max_depth);
if config.read_fdignore {
walker.add_custom_ignore_filename(".fdignore");
}
if config.read_global_ignore {
#[cfg(target_os = "macos")]
let config_dir_op = std::env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.filter(|p| p.is_absolute())
.or_else(|| dirs::home_dir().map(|d| d.join(".config")));
#[cfg(not(target_os = "macos"))]
let config_dir_op = dirs::config_dir();
if let Some(global_ignore_file) = config_dir_op
.map(|p| p.join("fd").join("ignore"))
.filter(|p| p.is_file())
{
let result = walker.add_ignore(global_ignore_file);
match result {
Some(ignore::Error::Partial(_)) => (),
Some(err) => {
print_error(format!(
"Malformed pattern in global ignore file. {}.",
err.to_string()
));
}
None => (),
}
}
}
for ignore_file in &config.ignore_files {
let result = walker.add_ignore(ignore_file);
match result {
Some(ignore::Error::Partial(_)) => (),
Some(err) => {
print_error!(
"{}",
format!(
"Malformed pattern in custom ignore file '{}': {}.",
ignore_file.to_string_lossy(),
err.description()
)
);
print_error(format!(
"Malformed pattern in custom ignore file. {}.",
err.to_string()
));
}
None => (),
}
@@ -110,7 +134,12 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
if config.ls_colors.is_some() && config.command.is_none() {
let wq = Arc::clone(&wants_to_quit);
ctrlc::set_handler(move || {
wq.store(true, Ordering::Relaxed);
if wq.load(Ordering::Relaxed) {
// Ctrl-C has been pressed twice, exit NOW
process::exit(ExitCode::KilledBySigint.into());
} else {
wq.store(true, Ordering::Relaxed);
}
})
.unwrap();
}
@@ -122,18 +151,20 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
spawn_senders(&config, &wants_to_quit, pattern, parallel_walker, tx);
// Wait for the receiver thread to print out all results.
receiver_thread.join().unwrap();
let exit_code = receiver_thread.join().unwrap();
if wants_to_quit.load(Ordering::Relaxed) {
process::exit(ExitCode::KilledBySigint.into());
Ok(ExitCode::KilledBySigint)
} else {
Ok(exit_code)
}
}
fn spawn_receiver(
config: &Arc<FdOptions>,
config: &Arc<Options>,
wants_to_quit: &Arc<AtomicBool>,
rx: Receiver<WorkerResult>,
) -> thread::JoinHandle<()> {
) -> thread::JoinHandle<ExitCode> {
let config = Arc::clone(config);
let wants_to_quit = Arc::clone(wants_to_quit);
@@ -144,7 +175,7 @@ fn spawn_receiver(
// This will be set to `Some` if the `--exec` argument was supplied.
if let Some(ref cmd) = config.command {
if cmd.in_batch_mode() {
exec::batch(rx, cmd, show_filesystem_errors);
exec::batch(rx, cmd, show_filesystem_errors)
} else {
let shared_rx = Arc::new(Mutex::new(rx));
@@ -166,9 +197,12 @@ fn spawn_receiver(
}
// Wait for all threads to exit before exiting the program.
let mut results: Vec<ExitCode> = Vec::new();
for h in handles {
h.join().unwrap();
results.push(h.join().unwrap());
}
merge_exitcodes(&results)
}
} else {
let start = time::Instant::now();
@@ -186,6 +220,8 @@ fn spawn_receiver(
let stdout = io::stdout();
let mut stdout = stdout.lock();
let mut num_results = 0;
for worker_result in rx {
match worker_result {
WorkerResult::Entry(value) => {
@@ -216,13 +252,21 @@ fn spawn_receiver(
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
}
}
num_results += 1;
}
WorkerResult::Error(err) => {
if show_filesystem_errors {
print_error!("{}", err);
print_error(err.to_string());
}
}
}
if let Some(max_results) = config.max_results {
if num_results >= max_results {
break;
}
}
}
// If we have finished fast enough (faster than max_buffer_time), we haven't streamed
@@ -233,12 +277,51 @@ fn spawn_receiver(
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
}
}
ExitCode::Success
}
})
}
pub enum DirEntry {
Normal(ignore::DirEntry),
BrokenSymlink(PathBuf),
}
impl DirEntry {
pub fn path(&self) -> &Path {
match self {
DirEntry::Normal(e) => e.path(),
DirEntry::BrokenSymlink(pathbuf) => pathbuf.as_path(),
}
}
pub fn file_type(&self) -> Option<FileType> {
match self {
DirEntry::Normal(e) => e.file_type(),
DirEntry::BrokenSymlink(pathbuf) => {
pathbuf.symlink_metadata().map(|m| m.file_type()).ok()
}
}
}
pub fn metadata(&self) -> Option<Metadata> {
match self {
DirEntry::Normal(e) => e.metadata().ok(),
DirEntry::BrokenSymlink(_) => None,
}
}
pub fn depth(&self) -> Option<usize> {
match self {
DirEntry::Normal(e) => Some(e.depth()),
DirEntry::BrokenSymlink(_) => None,
}
}
}
fn spawn_senders(
config: &Arc<FdOptions>,
config: &Arc<Options>,
wants_to_quit: &Arc<AtomicBool>,
pattern: Arc<Regex>,
parallel_walker: ignore::WalkParallel,
@@ -256,37 +339,72 @@ fn spawn_senders(
}
let entry = match entry_o {
Ok(e) => e,
Ok(ref e) if e.depth() == 0 => {
// Skip the root directory entry.
return ignore::WalkState::Continue;
}
Ok(e) => DirEntry::Normal(e),
Err(ignore::Error::WithPath {
path,
err: inner_err,
}) => match inner_err.as_ref() {
ignore::Error::Io(io_error)
if io_error.kind() == io::ErrorKind::NotFound
&& path
.symlink_metadata()
.ok()
.map_or(false, |m| m.file_type().is_symlink()) =>
{
DirEntry::BrokenSymlink(path)
}
_ => {
tx_thread
.send(WorkerResult::Error(ignore::Error::WithPath {
path,
err: inner_err,
}))
.unwrap();
return ignore::WalkState::Continue;
}
},
Err(err) => {
tx_thread.send(WorkerResult::Error(err)).unwrap();
return ignore::WalkState::Continue;
}
};
if entry.depth() == 0 {
if let Some(min_depth) = config.min_depth {
if entry.depth().map_or(true, |d| d < min_depth) {
return ignore::WalkState::Continue;
}
}
// Check the name first, since it doesn't require metadata
let entry_path = entry.path();
let search_str: Cow<OsStr> = if config.search_full_path {
let path_abs_buf = filesystem::path_absolute_form(entry_path)
.expect("Retrieving absolute path succeeds");
Cow::Owned(path_abs_buf.as_os_str().to_os_string())
} else {
match entry_path.file_name() {
Some(filename) => Cow::Borrowed(filename),
None => unreachable!(
"Encountered file system entry without a file name. This should only \
happen for paths like 'foo/bar/..' or '/' which are not supposed to \
appear in a file system traversal."
),
}
};
if !pattern.is_match(&filesystem::osstr_to_bytes(search_str.as_ref())) {
return ignore::WalkState::Continue;
}
// Filter out unwanted file types.
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.
// Filter out unwanted extensions.
if let Some(ref exts_regex) = config.extensions {
if let Some(path_str) = entry_path.file_name() {
if !exts_regex.is_match(&filesystem::osstr_to_bytes(path_str)) {
return ignore::WalkState::Continue;
}
} else {
@@ -294,12 +412,26 @@ fn spawn_senders(
}
}
let entry_path = entry.path();
// Filter out unwanted extensions.
if let Some(ref exts_regex) = config.extensions {
if let Some(path_str) = entry_path.file_name().and_then(|s| s.to_str()) {
if !exts_regex.is_match(path_str) {
// Filter out unwanted file types.
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.sockets && filesystem::is_socket(entry_type))
|| (!file_types.pipes && filesystem::is_pipe(entry_type))
|| (file_types.executables_only
&& !entry
.metadata()
.map(|m| filesystem::is_executable(&m))
.unwrap_or(false))
|| (file_types.empty_only && !filesystem::is_empty(&entry))
|| !(entry_type.is_file()
|| entry_type.is_dir()
|| entry_type.is_symlink()
|| filesystem::is_socket(entry_type)
|| filesystem::is_pipe(entry_type))
{
return ignore::WalkState::Continue;
}
} else {
@@ -307,6 +439,19 @@ fn spawn_senders(
}
}
#[cfg(unix)]
{
if let Some(ref owner_constraint) = config.owner_constraint {
if let Ok(ref metadata) = entry_path.metadata() {
if !owner_constraint.matches(&metadata) {
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.is_empty() {
if entry_path.is_file() {
@@ -330,14 +475,12 @@ fn spawn_senders(
// 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 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 {
@@ -345,24 +488,10 @@ fn spawn_senders(
}
}
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(_) => {
print_error_and_exit!("Unable to retrieve absolute path.");
}
}
} else {
entry_path.file_name().map(|f| f.to_string_lossy())
};
let send_result = tx_thread.send(WorkerResult::Entry(entry_path.to_owned()));
if let Some(search_str) = search_str_o {
if pattern.is_match(&*search_str) {
// TODO: take care of the unwrap call
tx_thread
.send(WorkerResult::Entry(entry_path.to_owned()))
.unwrap()
}
if send_result.is_err() {
return ignore::WalkState::Quit;
}
ignore::WalkState::Continue
+84 -39
View File
@@ -1,24 +1,12 @@
// 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;
use std::env;
use std::fs;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process;
use std::io::{self, Write};
#[cfg(unix)]
use std::os::unix;
#[cfg(windows)]
use std::os::windows;
use std::path::{Path, PathBuf};
use std::process;
use tempdir::TempDir;
@@ -121,13 +109,13 @@ fn format_output_error(args: &[&str], expected: &str, actual: &str) -> String {
}
/// Normalize the output for comparison.
fn normalize_output(s: &str, trim_left: bool, normalize_line: bool) -> String {
fn normalize_output(s: &str, trim_start: bool, normalize_line: bool) -> String {
// Split into lines and normalize separators.
let mut lines = s
.replace('\0', "NULL\n")
.lines()
.map(|line| {
let line = if trim_left { line.trim_left() } else { line };
let line = if trim_start { line.trim_start() } else { line };
let line = line.replace('/', &std::path::MAIN_SEPARATOR.to_string());
if normalize_line {
let mut words: Vec<_> = line.split_whitespace().collect();
@@ -162,6 +150,25 @@ impl TestEnv {
}
}
/// Create a broken symlink at the given path in the temp_dir.
pub fn create_broken_symlink<P: AsRef<Path>>(
&mut self,
link_path: P,
) -> Result<PathBuf, io::Error> {
let root = self.test_root();
let broken_symlink_link = root.join(link_path);
{
let temp_target_dir = TempDir::new("fd-tests-broken-symlink")?;
let broken_symlink_target = temp_target_dir.path().join("broken_symlink_target");
fs::File::create(&broken_symlink_target)?;
#[cfg(unix)]
unix::fs::symlink(&broken_symlink_target, &broken_symlink_link)?;
#[cfg(windows)]
windows::fs::symlink_file(&broken_symlink_target, &broken_symlink_link)?;
}
Ok(broken_symlink_link)
}
/// Get the root directory for the tests.
pub fn test_root(&self) -> PathBuf {
self.temp_dir.path().to_path_buf()
@@ -173,11 +180,42 @@ impl TestEnv {
PathBuf::from(components.next().expect("root directory").as_os_str())
}
/// Assert that calling *fd* in the specified path under the root working directory,
/// and with the specified arguments produces the expected output.
pub fn assert_success_and_get_output<P: AsRef<Path>>(
&self,
path: P,
args: &[&str],
) -> process::Output {
// Setup *fd* command.
let mut cmd = process::Command::new(&self.fd_exe);
cmd.current_dir(self.temp_dir.path().join(path));
cmd.arg("--no-global-ignore-file").args(args);
// Run *fd*.
let output = cmd.output().expect("fd output");
// Check for exit status.
if !output.status.success() {
panic!(format_exit_error(args, &output));
}
output
}
/// Assert that calling *fd* with the specified arguments produces the expected output.
pub fn assert_output(&self, args: &[&str], expected: &str) {
self.assert_output_subdirectory(".", args, expected)
}
/// Similar to assert_output, but able to handle non-utf8 output
#[cfg(all(unix, not(target_os = "macos")))]
pub fn assert_output_raw(&self, args: &[&str], expected: &[u8]) {
let output = self.assert_success_and_get_output(".", args);
assert_eq!(expected, &output.stdout[..]);
}
/// Assert that calling *fd* in the specified path under the root working directory,
/// and with the specified arguments produces the expected output.
pub fn assert_output_subdirectory<P: AsRef<Path>>(
@@ -186,18 +224,7 @@ impl TestEnv {
args: &[&str],
expected: &str,
) {
// Setup *fd* command.
let mut cmd = process::Command::new(&self.fd_exe);
cmd.current_dir(self.temp_dir.path().join(path));
cmd.args(args);
// Run *fd*.
let output = cmd.output().expect("fd output");
// Check for exit status.
if !output.status.success() {
panic!(format_exit_error(args, &output));
}
let output = self.assert_success_and_get_output(path, args);
// Normalize both expected and actual output.
let expected = normalize_output(expected, true, self.normalize_line);
@@ -213,31 +240,49 @@ impl TestEnv {
}
}
/// Assert that calling *fd* with the specified arguments produces the expected error,
/// and does not succeed.
pub fn assert_failure_with_error(&self, args: &[&str], expected: &str) {
let status = self.assert_error_subdirectory(".", args, expected);
if status.success() {
panic!("error '{}' did not occur.", expected);
}
}
/// Assert that calling *fd* with the specified arguments produces the expected error.
pub fn assert_error(&self, args: &[&str], expected: &str) {
pub fn assert_error(&self, args: &[&str], expected: &str) -> process::ExitStatus {
self.assert_error_subdirectory(".", args, expected)
}
/// Assert that calling *fd* in the specified path under the root working directory,
/// and with the specified arguments produces an error with the expected message.
fn assert_error_subdirectory<P: AsRef<Path>>(&self, path: P, args: &[&str], expected: &str) {
fn assert_error_subdirectory<P: AsRef<Path>>(
&self,
path: P,
args: &[&str],
expected: &str,
) -> process::ExitStatus {
// Setup *fd* command.
let mut cmd = process::Command::new(&self.fd_exe);
cmd.current_dir(self.temp_dir.path().join(path));
cmd.args(args);
cmd.arg("--no-global-ignore-file").args(args);
// Run *fd*.
let output = cmd.output().expect("fd output");
// Check for exit status.
if output.status.success() {
panic!("error '{}' did not occur.", expected);
}
// Normalize both expected and actual output.
let expected_error = normalize_output(expected, true, self.normalize_line);
let actual_err = normalize_output(
&String::from_utf8_lossy(&output.stderr),
false,
self.normalize_line,
);
// Compare actual output to expected output.
let actual = String::from_utf8_lossy(&output.stderr);
if !actual.starts_with(expected) {
panic!(format_output_error(args, &expected, &actual));
if !actual_err.trim_start().starts_with(&expected_error) {
panic!(format_output_error(args, &expected_error, &actual_err));
}
return output.status;
}
}
+459 -79
View File
@@ -1,25 +1,17 @@
// 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.
//! Integration tests for the CLI interface of fd.
mod testenv;
use crate::testenv::TestEnv;
use regex::escape;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::time::{Duration, SystemTime};
static DEFAULT_DIRS: &'static [&'static str] = &["one/two/three", "one/two/three/directory_foo"];
use regex::escape;
static DEFAULT_FILES: &'static [&'static str] = &[
use crate::testenv::TestEnv;
static DEFAULT_DIRS: &[&str] = &["one/two/three", "one/two/three/directory_foo"];
static DEFAULT_FILES: &[&str] = &[
"a.foo",
"one/b.foo",
"one/two/c.foo",
@@ -41,7 +33,7 @@ fn get_absolute_root_path(env: &TestEnv) -> String {
.to_string();
#[cfg(windows)]
let path = path.trim_left_matches(r"\\?\").to_string();
let path = path.trim_start_matches(r"\\?\").to_string();
path
}
@@ -57,10 +49,10 @@ fn get_test_env_with_abs_path(dirs: &[&'static str], files: &[&'static str]) ->
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();
f.write_all(content.as_bytes()).unwrap();
}
/// Simple tests
/// Simple test
#[test]
fn test_simple() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
@@ -78,21 +70,27 @@ fn test_simple() {
one/two/three/d.foo
one/two/three/directory_foo",
);
}
te.assert_output(
&[],
"a.foo
e1 e2
one
one/b.foo
one/two
one/two/c.foo
one/two/C.Foo2
one/two/three
one/two/three/d.foo
one/two/three/directory_foo
symlink",
);
/// Test each pattern type with an empty pattern.
#[test]
fn test_empty_pattern() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let expected = "a.foo
e1 e2
one
one/b.foo
one/two
one/two/c.foo
one/two/C.Foo2
one/two/three
one/two/three/d.foo
one/two/three/directory_foo
symlink";
te.assert_output(&["--regex"], expected);
te.assert_output(&["--fixed-strings"], expected);
te.assert_output(&["--glob"], expected);
}
/// Test multiple directory searches
@@ -119,6 +117,45 @@ fn test_multi_file() {
te.assert_output(&["b.foo", "test1", "test2"], "test1/b.foo");
}
/// Test search over multiple directory with missing
#[test]
fn test_multi_file_with_missing() {
let dirs = &["real"];
let files = &["real/a.foo", "real/b.foo"];
let te = TestEnv::new(dirs, files);
te.assert_output(&["a.foo", "real", "fake"], "real/a.foo");
te.assert_error(
&["a.foo", "real", "fake"],
"[fd error]: Search path 'fake' is not a directory.",
);
te.assert_output(
&["", "real", "fake"],
"real/a.foo
real/b.foo",
);
te.assert_output(
&["", "real", "fake1", "fake2"],
"real/a.foo
real/b.foo",
);
te.assert_error(
&["", "real", "fake1", "fake2"],
"[fd error]: Search path 'fake1' is not a directory.
[fd error]: Search path 'fake2' is not a directory.",
);
te.assert_failure_with_error(
&["", "fake1", "fake2"],
"[fd error]: Search path 'fake1' is not a directory.
[fd error]: Search path 'fake2' is not a directory.
[fd error]: No valid search paths given.",
);
}
/// Explicit root path
#[test]
fn test_explicit_root_path() {
@@ -239,6 +276,99 @@ fn test_case_insensitive() {
);
}
/// Glob-based searches (--glob)
#[test]
fn test_glob_searches() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--glob", "*.foo"],
"a.foo
one/b.foo
one/two/c.foo
one/two/three/d.foo",
);
te.assert_output(
&["--glob", "[a-c].foo"],
"a.foo
one/b.foo
one/two/c.foo",
);
te.assert_output(
&["--glob", "[a-c].foo*"],
"a.foo
one/b.foo
one/two/C.Foo2
one/two/c.foo",
);
}
/// Glob-based searches (--glob) in combination with full path searches (--full-path)
#[cfg(not(windows))] // TODO: make this work on Windows
#[test]
fn test_full_path_glob_searches() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--glob", "--full-path", "**/one/**/*.foo"],
"one/b.foo
one/two/c.foo
one/two/three/d.foo",
);
te.assert_output(
&["--glob", "--full-path", "**/one/*/*.foo"],
" one/two/c.foo",
);
te.assert_output(
&["--glob", "--full-path", "**/one/*/*/*.foo"],
" one/two/three/d.foo",
);
}
#[test]
fn test_smart_case_glob_searches() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--glob", "c.foo*"],
"one/two/C.Foo2
one/two/c.foo",
);
te.assert_output(&["--glob", "C.Foo*"], "one/two/C.Foo2");
}
/// Glob-based searches (--glob) in combination with --case-sensitive
#[test]
fn test_case_sensitive_glob_searches() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(&["--glob", "--case-sensitive", "c.foo*"], "one/two/c.foo");
}
/// Glob-based searches (--glob) in combination with --extension
#[test]
fn test_glob_searches_with_extension() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--glob", "--extension", "foo2", "[a-z].*"],
"one/two/C.Foo2",
);
}
/// Make sure that --regex overrides --glob
#[test]
fn test_regex_overrides_glob() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(&["--glob", "--regex", "Foo2$"], "one/two/C.Foo2");
}
/// Full path search (--full-path)
#[test]
fn test_full_path() {
@@ -274,6 +404,28 @@ fn test_hidden() {
);
}
/// Hidden file attribute on Windows
#[cfg(windows)]
#[test]
fn test_hidden_file_attribute() {
use std::os::windows::fs::OpenOptionsExt;
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesa
const FILE_ATTRIBUTE_HIDDEN: u32 = 2;
fs::OpenOptions::new()
.create(true)
.write(true)
.attributes(FILE_ATTRIBUTE_HIDDEN)
.open(te.test_root().join("hidden-file.txt"))
.unwrap();
te.assert_output(&["--hidden", "hidden-file.txt"], "hidden-file.txt");
te.assert_output(&["hidden-file.txt"], "");
}
/// Ignored files (--no-ignore)
#[test]
fn test_no_ignore() {
@@ -450,6 +602,75 @@ fn test_follow() {
);
}
// File system boundaries (--one-file-system)
// Limited to Unix because, to the best of my knowledge, there is no easy way to test a use case
// file systems mounted into the tree on Windows.
// Not limiting depth causes massive delay under Darwin, see BurntSushi/ripgrep#1429
#[test]
#[cfg(unix)]
fn test_file_system_boundaries() {
// Helper function to get the device ID for a given path
// Inspired by https://github.com/BurntSushi/ripgrep/blob/8892bf648cfec111e6e7ddd9f30e932b0371db68/ignore/src/walk.rs#L1693
fn device_num(path: impl AsRef<Path>) -> u64 {
use std::os::unix::fs::MetadataExt;
path.as_ref().metadata().map(|md| md.dev()).unwrap()
}
// Can't simulate file system boundaries
let te = TestEnv::new(&[], &[]);
let dev_null = Path::new("/dev/null");
// /dev/null should exist in all sane Unixes. Skip if it doesn't exist for some reason.
// Also skip should it be on the same device as the root partition for some reason.
if !dev_null.is_file() || device_num(dev_null) == device_num("/") {
return;
}
te.assert_output(
&["--full-path", "--max-depth", "2", "^/dev/null$", "/"],
"/dev/null",
);
te.assert_output(
&[
"--one-file-system",
"--full-path",
"--max-depth",
"2",
"^/dev/null$",
"/",
],
"",
);
}
#[test]
fn test_follow_broken_symlink() {
let mut te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.create_broken_symlink("broken_symlink")
.expect("Failed to create broken symlink.");
te.assert_output(
&["symlink"],
"broken_symlink
symlink",
);
te.assert_output(
&["--type", "symlink", "symlink"],
"broken_symlink
symlink",
);
te.assert_output(&["--type", "file", "symlink"], "");
te.assert_output(
&["--follow", "--type", "symlink", "symlink"],
"broken_symlink",
);
te.assert_output(&["--follow", "--type", "file", "symlink"], "");
}
/// Null separator (--print0)
#[test]
fn test_print0() {
@@ -503,6 +724,40 @@ fn test_max_depth() {
);
}
/// Minimum depth (--min-depth)
#[test]
fn test_min_depth() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--min-depth", "3"],
"one/two/c.foo
one/two/C.Foo2
one/two/three
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output(
&["--min-depth", "4"],
"one/two/three/d.foo
one/two/three/directory_foo",
);
}
/// Exact depth (--exact-depth)
#[test]
fn test_exact_depth() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--exact-depth", "3"],
"one/two/c.foo
one/two/C.Foo2
one/two/three",
);
}
/// Absolute paths (--absolute-path)
#[test]
fn test_absolute_path() {
@@ -730,7 +985,9 @@ fn test_extension() {
/// Symlink as search directory
#[test]
fn test_symlink_as_root() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let mut te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.create_broken_symlink("broken_symlink")
.expect("Failed to create broken symlink.");
// From: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html
// The getcwd() function shall place an absolute pathname of the current working directory in
@@ -751,6 +1008,7 @@ fn test_symlink_as_root() {
&["", parent_parent],
&format!(
"{dir}/a.foo
{dir}/broken_symlink
{dir}/e1 e2
{dir}/one
{dir}/one/b.foo
@@ -842,7 +1100,6 @@ fn test_symlink_and_full_path_abs_path() {
),
);
}
/// Exclude patterns (--exclude)
#[test]
fn test_excludes() {
@@ -896,28 +1153,11 @@ fn test_excludes() {
/// Shell script execution (--exec)
#[test]
fn test_exec() {
assert_exec_output("--exec");
}
/// 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_style, "echo"],
&["--absolute-path", "foo", "--exec", "echo"],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
@@ -930,7 +1170,7 @@ fn assert_exec_output(exec_style: &str) {
);
te.assert_output(
&["foo", exec_style, "echo", "{}"],
&["foo", "--exec", "echo", "{}"],
"a.foo
one/b.foo
one/two/C.Foo2
@@ -940,7 +1180,7 @@ fn assert_exec_output(exec_style: &str) {
);
te.assert_output(
&["foo", exec_style, "echo", "{.}"],
&["foo", "--exec", "echo", "{.}"],
"a
one/b
one/two/C
@@ -950,7 +1190,7 @@ fn assert_exec_output(exec_style: &str) {
);
te.assert_output(
&["foo", exec_style, "echo", "{/}"],
&["foo", "--exec", "echo", "{/}"],
"a.foo
b.foo
C.Foo2
@@ -960,7 +1200,7 @@ fn assert_exec_output(exec_style: &str) {
);
te.assert_output(
&["foo", exec_style, "echo", "{/.}"],
&["foo", "--exec", "echo", "{/.}"],
"a
b
C
@@ -970,7 +1210,7 @@ fn assert_exec_output(exec_style: &str) {
);
te.assert_output(
&["foo", exec_style, "echo", "{//}"],
&["foo", "--exec", "echo", "{//}"],
".
one
one/two
@@ -979,29 +1219,19 @@ fn assert_exec_output(exec_style: &str) {
one/two/three",
);
te.assert_output(&["e1", exec_style, "printf", "%s.%s\n"], "e1 e2.");
te.assert_output(&["e1", "--exec", "printf", "%s.%s\n"], "e1 e2.");
}
}
#[test]
fn test_exec_batch() {
assert_exec_batch_output("--exec-batch");
}
#[test]
fn test_exec_batch_short_arg() {
assert_exec_batch_output("-X");
}
#[cfg(test)]
fn assert_exec_batch_output(exec_style: &str) {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
let te = te.normalize_line(true);
// TODO Test for windows
if !cfg!(windows) {
te.assert_output(
&["--absolute-path", "foo", exec_style, "echo"],
&["--absolute-path", "foo", "--exec-batch", "echo"],
&format!(
"{abs_path}/a.foo {abs_path}/one/b.foo {abs_path}/one/two/C.Foo2 {abs_path}/one/two/c.foo {abs_path}/one/two/three/d.foo {abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
@@ -1009,34 +1239,37 @@ fn assert_exec_batch_output(exec_style: &str) {
);
te.assert_output(
&["foo", exec_style, "echo", "{}"],
&["foo", "--exec-batch", "echo", "{}"],
"a.foo one/b.foo one/two/C.Foo2 one/two/c.foo one/two/three/d.foo one/two/three/directory_foo",
);
te.assert_output(
&["foo", exec_style, "echo", "{/}"],
&["foo", "--exec-batch", "echo", "{/}"],
"a.foo b.foo C.Foo2 c.foo d.foo directory_foo",
);
te.assert_output(&["no_match", exec_style, "echo", "Matched: ", "{/}"], "");
te.assert_output(
&["no_match", "--exec-batch", "echo", "Matched: ", "{/}"],
"",
);
te.assert_error(
&["foo", exec_style, "echo", "{}", "{}"],
te.assert_failure_with_error(
&["foo", "--exec-batch", "echo", "{}", "{}"],
"[fd error]: Only one placeholder allowed for batch commands",
);
te.assert_error(
&["foo", exec_style, "echo", "{/}", ";", "-x", "echo"],
te.assert_failure_with_error(
&["foo", "--exec-batch", "echo", "{/}", ";", "-x", "echo"],
"error: The argument '--exec <cmd>' cannot be used with '--exec-batch <cmd>'",
);
te.assert_error(
&["foo", exec_style],
te.assert_failure_with_error(
&["foo", "--exec-batch"],
"error: The argument '--exec-batch <cmd>' requires a value but none was supplied",
);
te.assert_error(
&["foo", exec_style, "echo {}"],
te.assert_failure_with_error(
&["foo", "--exec-batch", "echo {}"],
"[fd error]: First argument of exec-batch is expected to be a fixed executable",
);
}
@@ -1231,3 +1464,150 @@ fn test_modified_asolute() {
"30dec2017",
);
}
#[test]
fn test_custom_path_separator() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["foo", "one", "--path-separator", "="],
"one=b.foo
one=two=c.foo
one=two=C.Foo2
one=two=three=d.foo
one=two=three=directory_foo",
);
}
#[test]
fn test_base_directory() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--base-directory", "one"],
"b.foo
two
two/c.foo
two/C.Foo2
two/three
two/three/d.foo
two/three/directory_foo",
);
te.assert_output(
&["--base-directory", "one/two", "foo"],
"c.foo
C.Foo2
three/d.foo
three/directory_foo",
);
// Explicit root path
te.assert_output(
&["--base-directory", "one", "foo", "two"],
"two/c.foo
two/C.Foo2
two/three/d.foo
two/three/directory_foo",
);
// Ignore base directory when absolute path is used
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
let abs_base_dir = &format!("{abs_path}/one/two", abs_path = &abs_path);
te.assert_output(
&["--base-directory", &abs_base_dir, "foo", &abs_path],
&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
),
);
}
#[test]
fn test_max_results() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
// Unrestricted
te.assert_output(
&["--max-results=0", "c.foo"],
"one/two/C.Foo2
one/two/c.foo",
);
// Limited to two results
te.assert_output(
&["--max-results=2", "c.foo"],
"one/two/C.Foo2
one/two/c.foo",
);
// Limited to one result. We could find either C.Foo2 or c.foo
let assert_just_one_result_with_option = |option| {
let output = te.assert_success_and_get_output(".", &[option, "c.foo"]);
let stdout = String::from_utf8_lossy(&output.stdout)
.trim()
.replace(&std::path::MAIN_SEPARATOR.to_string(), "/");
assert!(stdout == "one/two/C.Foo2" || stdout == "one/two/c.foo");
};
assert_just_one_result_with_option("--max-results=1");
assert_just_one_result_with_option("-1");
}
/// Filenames with non-utf8 paths are passed to the executed program unchanged
///
/// Note:
/// - the test is disabled on Darwin/OSX, since it coerces file names to UTF-8,
/// even when the requested file name is not valid UTF-8.
/// - the test is currently disabled on Windows because I'm not sure how to create
/// invalid UTF-8 files on Windows
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn test_exec_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_raw(
&["", "test1/", "--exec", "echo", "{}"],
b"test1/test_\xFEinvalid.txt\n",
);
te.assert_output_raw(
&["", "test1/", "--exec", "echo", "{/}"],
b"test_\xFEinvalid.txt\n",
);
te.assert_output_raw(&["", "test1/", "--exec", "echo", "{//}"], b"test1\n");
te.assert_output_raw(
&["", "test1/", "--exec", "echo", "{.}"],
b"test1/test_\xFEinvalid\n",
);
te.assert_output_raw(
&["", "test1/", "--exec", "echo", "{/.}"],
b"test_\xFEinvalid\n",
);
}
#[test]
fn test_list_details() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
// Make sure we can execute 'fd --list-details' without any errors.
te.assert_success_and_get_output(".", &["--list-details"]);
}