Compare commits

...

216 Commits

Author SHA1 Message Date
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
sharkdp 7f58e8f706 Update man page 2019-02-13 20:04:37 +01:00
sharkdp f1ab443dd0 Update USAGE in README 2019-02-13 19:58:27 +01:00
sharkdp acd4910f2c Update dependencies 2019-02-13 19:53:48 +01:00
sharkdp c83bcfbdfc Bump version number 2019-02-13 19:50:50 +01:00
Alexandru Macovei c2b46f247f avoid cloning command, in the wake of 9d26b74 2019-01-30 20:42:43 +01:00
Alexandru Macovei fe53af064b fix most clippy lints 2019-01-26 16:15:48 +01:00
Alexandru Macovei 6aa87f3423 save one indent level in error handling for add_ignore 2019-01-26 16:15:48 +01:00
Alexandru Macovei 74e593c43c inline value used only once 2019-01-26 16:15:48 +01:00
Alexandru Macovei df4227c614 Uniform names for config and wants_to_quit. Pass Arc's by ref. 2019-01-26 16:15:48 +01:00
Alexandru Macovei 5ade72a5e1 split spawn_receiver(..) and spawn_senders(..) from scan(..).
This is just a split commit, refraining from renaming too much.

The drop(tx) call is no longer necessary, as the first sender
is dropped at the end of spawn_senders(..)
2019-01-26 16:15:48 +01:00
Alexandru Macovei d8bd5f9d51 macronize repetitive tests in exec/input 2019-01-26 16:15:48 +01:00
sharkdp 85f9db0565 Add troubleshooting section, closes #386 2019-01-09 23:06:54 +01:00
sharkdp 2672d84160 Add section about catch-all patterns, closes #385 2019-01-09 23:06:54 +01:00
sharkdp 8cfdcf43f6 Lock stdout only once 2019-01-09 08:20:20 +01:00
sharkdp 8c197d2866 Update to new lscolors version 2019-01-08 21:28:43 +01:00
Alexandru Macovei 051ff5987a [2018 edition] remove all extern crate lines from sources 2019-01-07 12:52:30 +01:00
Alexandru Macovei 630f173724 [2018 edition] enable 2018 edition 2019-01-07 12:52:30 +01:00
Alexandru Macovei 64e6ea9fe9 [2018 edition] run cargo fix edition and edition-idioms 2019-01-07 12:52:30 +01:00
sharkdp 33feb511f0 Formatting 2019-01-01 17:40:53 +01:00
sharkdp 985fab16bb Add note about short and long help text, closes #286 2019-01-01 17:40:30 +01:00
sharkdp a17a3fe5ef Add comment about hidden files/directories, see #377 2019-01-01 16:31:06 +01:00
sharkdp 04debe5c4e Adapt README 2019-01-01 16:23:22 +01:00
Sylvestre Ledru ec99e5d34c Update info about debian & ubuntu 2019-01-01 16:20:04 +01:00
sharkdp 047f1be792 Update dependencies 2018-12-14 23:20:18 +01:00
sharkdp ecd5ec6387 Bump lscolors version 2018-12-14 23:20:18 +01:00
sharkdp 3c87952c47 Bump minimum required Rust version 2018-12-14 23:20:18 +01:00
sharkdp 4b5efa3438 Use lscolors crate
Use my new [lscolors](https://github.com/sharkdp/lscolors) crate instead
of the internal `lscolors` module

- Speeds up `LS_COLORS` querying, leading to a nice 25% performance
  improvement when
- Adds support for 24-bit colors and background colors

closes #368
closes #363
2018-12-14 23:20:18 +01:00
sharkdp afcdaf2e3d Move snapcraft file 2018-11-29 21:21:00 +01:00
Alan Pope 1abdf47731 Remove statefile 2018-11-29 21:20:18 +01:00
Alan Pope 92b5c9f71e Add support for building a snap of fd 2018-11-29 21:20:18 +01:00
sharkdp f09f038bd2 Add .gitattributes files 2018-11-12 21:26:46 +01:00
kimsnj 6b40a075cd exec-batch: fix a panic with -X "echo {}" and pass stdio to child cmd 2018-11-12 21:11:40 +01:00
kimsnj 45d1b15cff Add support for batch execution of command 2018-11-12 21:11:40 +01:00
kimsnj 32ec7afc3a Add test with multiple tokens in an argument 2018-11-10 10:29:35 +01:00
teresy 34b8c0df73 refactor: use shorthand fields 2018-11-08 11:48:12 +01:00
Duncan Finney 831f517f5a Add ARM static binary #320 (arm-unknown-linux-musleabihf) 2018-11-04 11:47:44 +01:00
sharkdp f064ee5509 Properly use .ignore files, see #156 2018-10-27 18:11:50 +02:00
sharkdp 26c2371835 Bump version 2018-10-27 17:09:00 +02:00
sharkdp 8832e471bc Update man page 2018-10-27 17:07:09 +02:00
sharkdp daf5191aac Add note about .ignore files 2018-10-27 16:44:34 +02:00
sharkdp c0c38872c4 Update usage 2018-10-27 16:37:41 +02:00
sharkdp 81a27fa9bd Unify error messages, closes #342 2018-10-27 16:30:29 +02:00
sharkdp 6ff58abb6c Re-enable .ignore files, closes #156 2018-10-27 15:42:02 +02:00
sharkdp 974802e817 Hide a few options from short '-h' text 2018-10-27 15:38:26 +02:00
sharkdp deea31cbbc Add aliases, change help text 2018-10-27 15:34:10 +02:00
Josh Leeb-du Toit caad43fe6c Use oss_vec fn in internal/mod.rs tests 2018-10-24 16:31:16 +02:00
Park Juhyung 095bad550f Print errors when --verbose is set 2018-10-23 21:50:25 +02:00
Park Juhyung f7a96bd980 Fix typo at max-buffer-time's help message
"tothe" -> "to the"
2018-10-23 07:03:21 +02:00
Casey Primozic cdf737bf5c Add search-path option
* Multiple `--search-path` arguments can be supplied which replace the positional `path` argument for providing directories which will be searched.
 * When a `--search-path` argument is provided, positional `path` arguments will not be allowed
2018-10-22 19:26:36 +02:00
yobrave e2bb932f87 Doc: add chinese readme link (#347) 2018-10-19 22:05:15 +02:00
David Peter 437bd1188e Remove shell prompt 2018-10-14 19:27:03 +02:00
David Peter dae09ecf6d Add recommendation for "alias fd=fdfind" 2018-10-14 19:26:22 +02:00
Sylvestre Ledru d81cf34771 fd is now officially in Debian (#345)
* fd is now officially in Debian
2018-10-14 19:21:16 +02:00
Benjamin Fox b98f0783ff Enable link-time optimization in release builds
This gives a significant improvement to runtime performance, at the cost of somewhat worse compile times.
2018-10-14 18:47:25 +02:00
Josh Leeb-du Toit 8543ca645d Split internals.rs into module and multiple files
This PR splits `internals.rs` into the `internal` module with multiple
files, and moves `FdOptions` into `opts.rs`.

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

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

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

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

closes #288
2018-09-18 21:04:09 +02:00
sharkdp 8fb25f15c9 Just reference --max-depth in --maxdepth help 2018-09-13 22:33:18 +02:00
Mahmoud Al-Qudsi d375b5e1f5 Add --maxdepth as hidden alias for --max-depth
For muscle memory compatibility with `rg`. This variant will not show in
the help or in the program options, and is only checked if `--maxdepth`
is not specified.
2018-09-13 22:33:18 +02:00
sharkdp 2465cd1399 Update man page 2018-08-19 18:50:47 +02:00
sharkdp 1c7e647f61 Bump version 2018-08-19 18:50:41 +02:00
sharkdp 67e76b8b5b Update USAGE 2018-08-19 18:50:35 +02:00
sharkdp aa70c5a446 Add --type empty
Add a new `empty`/`e` type to search for empty files and/or directories.

To search for both empty files and directories, use one of the
following:

    fd --type empty
    fd -te

    fd --type empty --type file --type directory

To search for empty files, use

    fd --type empty --type file
    fd -te -tf

To search for empty directories, use

    fd --type empty --type directory
    fd -te -td

closes #273
2018-08-19 17:05:04 +02:00
sharkdp 50a2bab5cd Use short-circuiting for --type searches
Reverses the order of boolean checks for `--type` searches,
making them about 10% to 50% faster(!).
2018-08-19 16:27:23 +02:00
sharkdp 641976cf7a Remove duplicated lstat syscall
Removes a unnecessary `lstat` syscall by calling `.metadata()` only
once. This makes `--type executable` searches about 15% faster.
2018-08-19 16:23:06 +02:00
sharkdp 6fb5004b85 Enable ARM cross-compilation
closes #244
2018-08-19 15:30:59 +02:00
kpcyrd e4f61bc795 Remove unused windows dependencies 2018-08-18 16:40:08 +02:00
sharkdp c428f52209 Run cargo fmt 2018-08-03 21:20:48 +02:00
sharkdp 59c9901cde Update dependencies, fix #313 2018-08-03 21:20:48 +02:00
Alexandru Macovei 4dee8b7940 fix build for rust 1.20.0 2018-07-02 19:55:03 +02:00
Alexandru Macovei 9a45947234 simplify SizeFilter as enum with value instead of struct
Also move the touched cfg(test) code into cfg(test) module and trim test names
2018-07-02 19:55:03 +02:00
sharkdp c1ef68662c Update for new rustfmt 2018-05-14 21:00:00 +02:00
Josh Sleeper 9628d7fdcb chore(crate-updates): updated all crates to latest versions 2018-05-05 08:56:04 +02:00
David Peter cb47be6497 Fedora is now in the official repositories 2018-05-02 20:54:08 +02:00
sharkdp a3200204dc Simplifications to --size tests 2018-04-29 22:26:10 +02:00
Jonathan e2b2701965 Add helper method to create sized files, use meaningful names and add kibi and kilo tests. 2018-04-29 21:56:31 +02:00
Jonathan 409cf0c7a1 Remove use (which broke the build) 2018-04-29 21:56:31 +02:00
Jonathan f01685c63f Add tests for SizeFilter (--size) fixes sharkdp/fd#289 2018-04-29 21:56:31 +02:00
David Peter 060d14b5dc Update README.md 2018-04-26 19:53:04 +02:00
Rainer Müller fe922688b3 README: Add install instructions for MacPorts 2018-04-26 19:51:04 +02:00
sharkdp 4172ed03f0 Do not include non-files when using --size 2018-04-25 23:08:05 +02:00
Steve Pentland 0bbc7f5219 Update regex and match, update comments 2018-04-25 08:14:12 +02:00
Steve Pentland ace3f512d3 Make compatible with min rust version 2018-04-25 08:14:12 +02:00
Steve Pentland 2f3b472dfd Changes from review 2018-04-25 08:14:12 +02:00
Steve Pentland 0207c1371e Initial impl of size constraints.
This adds find-style size constraints with + or - indicating greater
or less than, a numerical size, and a unit
2018-04-25 08:14:12 +02:00
David Peter fea85dbfb3 Re-enable nightly 2018-04-21 12:54:00 +02:00
sharkdp b5e5b132c8 Temporarily disable nightly, rustc-ap-rustc_errors is broken 2018-04-20 08:28:18 +02:00
sharkdp 6a9f16e159 Run latest version of rustfmt 2018-04-13 23:13:22 +02:00
sharkdp f02b3cba6a Fix typos 2018-04-13 23:13:22 +02:00
Chen Bin 666f90db87 Re-org fzf part. Doc on using fd with Emacs 2018-04-13 23:13:22 +02:00
Avindra Goolcharan 2a1ee0b17d README: openSUSE package available
add software.opensuse.org link and `zypper in fd`
2018-04-01 17:25:23 +02:00
sharkdp 388b90e624 Update USAGE and man page 2018-03-26 10:25:33 +02:00
sharkdp 011e09840a Bump version number 2018-03-26 10:16:18 +02:00
sharkdp 37483036e0 Implement --ignore-file 2018-03-26 08:28:22 +02:00
David Peter 8c1b037279 Normalize absolute paths, fixes #268 (#270)
* Normalize absolute paths, fixes #268
* Split unit tests into several tests
2018-03-25 23:47:58 +02:00
sharkdp f9a32583a5 Move is_executable to fshelper module 2018-03-25 19:48:09 +02:00
sharkdp 9d840dd485 Separate unit tests 2018-03-25 19:00:46 +02:00
sharkdp 4f6c43a6dc Unit test for --type x 2018-03-25 18:37:50 +02:00
sharkdp 2cf8e7b8a5 Move is_executable to internal module 2018-03-25 18:37:50 +02:00
Pramod Bisht 67f6fdf6a7 Code formating 2018-03-25 16:53:12 +02:00
Pramod Bisht 8ce127e443 changed executable=> executable_only 2018-03-25 16:53:12 +02:00
Pramod Bisht b1e48efc4a Addresses #246
Some implementation to search by filetype `executables`
2018-03-25 16:53:12 +02:00
joachimschmidt557 ab3ded8d27 Update README.md 2018-03-16 06:11:32 +01:00
sharkdp 5397787824 Skip fifos, sockets, .. when using --type f/d/l 2018-03-15 21:38:52 +01:00
sharkdp ffd49c60bd Error if user supplied path instead of pattern 2018-03-15 07:53:00 +01:00
sharkdp 89a8f5573d Formatting change 2018-03-15 07:53:00 +01:00
sharkdp d49b0aa977 Fix small mistake in README 2018-03-14 22:40:49 +01:00
KmBKeef ddddc3f1db fd on Fedora copr (#255)
created fd rpm and added it to Fedora copr
2018-03-14 21:12:16 +01:00
David Peter 87d0ccd4c0 Documentation for -E and .fdignore, see #156 2018-03-13 22:54:08 +01:00
sharkdp c654159bf2 Update to ansi_term 0.11 2018-03-12 23:19:20 +01:00
Josh Stone 588b0dc7c9 Update to regex-syntax 0.5
The new regex 0.2.7 has updated to regex-syntax 0.5, so it seems prudent
to update fd's dependency too, if only to avoid duplication.
2018-03-12 22:11:00 +01:00
sharkdp 88fb9d73d1 Run updated version of rustfmt 2018-03-12 17:46:13 +01:00
sharkdp 3386b854b0 Run invalid-utf8 on Linux only 2018-02-26 08:20:31 +01:00
sharkdp 47d95284aa Skip invalid utf8 filenames, closes #250 2018-02-26 08:20:31 +01:00
sharkdp 631931b431 Only construct entry_path if needed 2018-02-25 18:17:11 +01:00
sharkdp 40d811c7be Rewrite of file-type filtering leading to 5% speed-up 2018-02-25 18:17:11 +01:00
sharkdp e5ee5eb7b3 Minor speedup for strip_prefix 2018-02-25 10:25:59 +01:00
David Peter 82eeeda504 Update README.md 2018-02-24 12:07:24 +01:00
Chawye Hsu f16552f29c Add a installation method for Windows 2018-02-24 12:06:46 +01:00
sharkdp e30bc16780 Fix symlink filetype shortcut in man page 2018-02-21 22:58:22 +01:00
sharkdp b4be1f161c Add support for .fdignore files 2018-02-21 22:55:26 +01:00
sharkdp 8805bd32e2 Update dependencies 2018-02-21 22:55:26 +01:00
Pramod Bisht 7a3994decb Changes as per the instruction of cargo fmt -- --write-mode=diff 2018-02-19 21:17:11 +01:00
Pramod Bisht 7e8c2248d0 Fixes: #234
Giving suggestion to use `--fixed-strings` in case user passes invalid metacharacters as a regex.
2018-02-19 21:17:11 +01:00
Georgy Yakovlev ad8256c217 README.md: gentoo now has fd ebuild, document it 2018-02-19 08:38:14 +01:00
David Peter 1bf193af60 Update links to license files 2018-02-13 20:41:04 +01:00
David Peter 1c1be76f35 Update README.md 2018-02-13 08:10:16 +01:00
David Peter 267598fba5 Arch Linux package was renamed to 'fd' 2018-02-11 11:41:54 +01:00
sharkdp 11b3e96d2d Install fakeroot when deploying 2018-02-10 17:23:26 +01:00
sharkdp a6086abe7f Updates for v6.3.0 2018-02-10 16:46:42 +01:00
David Peter 7f65e7a0d6 Update benchmark results 2018-02-10 16:43:36 +01:00
sharkdp 814d37030e Implement -F/--fixed-strings/--literal 2018-02-10 15:55:06 +01:00
Lewis Belcher 089c183d30 Update README.md
The suggested use of `--ignore` no longer exists (renamed to `--no-ignore`)
2018-02-10 14:19:54 +01:00
Martin Larralde 370d9f081f Use RegexSet instead of hand-written parser 2018-02-10 14:18:37 +01:00
Martin Larralde 86fe9977e8 Implement multiple suffixes extension support with tests (#214) 2018-02-10 14:18:37 +01:00
Martin Larralde b1bb60ebb9 Install rustfmt-nightly in debug mode in Travis (#231) 2018-02-07 19:50:10 +01:00
Steve Pentland b4d39270d6 Allow -exec to work as --exec (#226)
* Replace usage of `-exec` by `--exec`, except when it is used as a value that is given to `--exec/-exec/-x`.
* Limit usage of --exec option to single instance.
2018-01-29 20:32:46 +01:00
sharkdp 6d83eea32b Small screencast updates 2018-01-28 15:44:07 +01:00
Mario Nebl 26beaf97a6 Update screencast 2018-01-28 12:59:27 +01:00
Mario Nebl 33d37d7623 Avoid glitches on new prompt lines 2018-01-28 12:59:27 +01:00
Mario Nebl dce004afef Use updated screencast 2018-01-28 12:59:27 +01:00
Mario Nebl c1941cc7ed Change to working char to signify carriage returns 2018-01-28 12:59:27 +01:00
Mario Nebl 0a1c65b9ce Delay command with output before them 2018-01-28 12:59:27 +01:00
Mario Nebl 4fb9d84ffc Use new screencast
* Might not require rawgit as per https://github.com/marionebl/svg-term-cli/issues/5#issuecomment-360037548
2018-01-28 12:59:27 +01:00
Mario Nebl 2a84eb5cea Update screencast 2018-01-28 12:59:27 +01:00
Mario Nebl 6dd59b33b1 Document svg-terminal command 2018-01-28 12:59:27 +01:00
Mario Nebl 4fea5f3a3c Ensure -h output can be read at end of cast 2018-01-28 12:59:27 +01:00
Mario Nebl b269f4902e Demonstrate -e and --exec flags 2018-01-28 12:59:27 +01:00
Mario Nebl 33791d8400 Use ▶ prompt 2018-01-28 12:59:27 +01:00
Mario Nebl 4e737b4b44 Use upstream src 2018-01-28 12:59:27 +01:00
Mario Nebl c985ba2e6f Use local svg demo 2018-01-28 12:59:27 +01:00
Mario Nebl 658a97a94a Add svg screencast 2018-01-28 12:59:27 +01:00
Dock O'Neal bc2b7a33ae Fixing exec hang by disabling ctrl-c handling for exec 2018-01-25 07:47:04 +01:00
Vinaya 1045930dd9 Fix typos (#218)
This fixes few typos: "distributions", "want to" instead of "want so".
2018-01-10 13:02:25 +01:00
David Peter cc36543da2 Add GA
I'm curious :-)
2018-01-09 17:41:02 +01:00
sharkdp 050fa9c3b1 Fix file owner in deb package, closes #213
Also, fix a few lintian warnings / errors.
2018-01-04 19:48:59 +01:00
36 changed files with 3214 additions and 1177 deletions
+29
View File
@@ -0,0 +1,29 @@
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
+23 -15
View File
@@ -20,6 +20,17 @@ matrix:
- os: osx
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
env:
- TARGET=arm-unknown-linux-musleabihf
- CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
# Beta channel.
- os: linux
@@ -59,19 +70,19 @@ matrix:
# Minimum Rust supported channel.
- os: linux
rust: 1.20.0
rust: 1.31.0
env: TARGET=x86_64-unknown-linux-gnu
- os: linux
rust: 1.20.0
rust: 1.31.0
env: TARGET=x86_64-unknown-linux-musl
- os: linux
rust: 1.20.0
rust: 1.31.0
env: TARGET=i686-unknown-linux-gnu
- os: linux
rust: 1.20.0
rust: 1.31.0
env: TARGET=i686-unknown-linux-musl
- os: osx
rust: 1.20.0
rust: 1.31.0
env: TARGET=x86_64-apple-darwin
# Code formatting check
@@ -79,14 +90,13 @@ matrix:
rust: nightly
# skip the global install step
install:
- cargo install --force rustfmt-nightly
script: cargo fmt -- --write-mode=diff
- cargo install --debug --force rustfmt-nightly
script: cargo fmt -- --check
addons:
apt:
packages:
# needed for i686-unknown-linux-gnu target
- gcc-multilib
sudo: required
before_install:
- ci/before_install.bash
env:
global:
@@ -101,9 +111,7 @@ install:
- if [[ $TRAVIS_OS_NAME = linux && $HOST != $TARGET ]]; then rustup target add $TARGET; fi
script:
# Incorporate TARGET env var to the build and test process
- cargo build --target $TARGET --verbose
- cargo test --target $TARGET --verbose
- ci/script.bash
before_deploy:
- bash ci/before_deploy.bash
+1 -1
View File
@@ -16,5 +16,5 @@ give us the chance to discuss any potential changes first.
* [Open pull requests](https://github.com/sharkdp/fd/pulls)
* [Development section in the README](https://github.com/sharkdp/fd#development)
* [fd on crates.io](https://crates.io/crates/fd-find)
* [LICENSE](https://github.com/sharkdp/fd/blob/master/LICENSE)
* [LICENSE-APACHE](https://github.com/sharkdp/fd/blob/master/LICENSE-APACHE) and [LICENSE-MIT](https://github.com/sharkdp/fd/blob/master/LICENSE-MIT)
Generated
+276 -208
View File
@@ -1,74 +1,100 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.6.4"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.9.0"
version = "0.11.0"
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 = "ansi_term"
version = "0.10.2"
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 = "atty"
version = "0.2.6"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (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.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (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 = "0.7.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.0.1"
name = "bstr"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cc"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.2"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.29.0"
version = "2.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (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.1 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam"
version = "0.2.10"
name = "crossbeam-channel"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-utils"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (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.0.3"
version = "3.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -78,23 +104,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fd-find"
version = "6.2.0"
version = "7.4.0"
dependencies = [
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"filetime 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.4.10 (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.62 (registry+https://github.com/rust-lang/crates.io-index)",
"lscolors 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.12 (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.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "filetime"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (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]]
@@ -103,44 +143,69 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-zircon"
version = "0.3.2"
name = "fs_extra"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.3.2"
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "globset"
version = "0.2.1"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
"bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.5 (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.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "humantime"
version = "1.3.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.2.2"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.4.4 (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.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 1.0.5 (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.9 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "jemalloc-sys"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.45 (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.62 (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.62 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -154,129 +219,142 @@ dependencies = [
[[package]]
name = "lazy_static"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.0.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.34"
version = "0.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.3.9"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "log"
version = "0.4.1"
name = "lscolors"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.2 (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 = "1.0.2"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nix"
version = "0.8.1"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (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.8.0"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quick-error"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand"
version = "0.3.19"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (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.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_termios"
version = "0.1.1"
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.33 (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 = "regex"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "same-file"
version = "0.1.3"
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "regex"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "remove_dir_all"
version = "0.5.2"
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 = "same-file"
version = "1.0.5"
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 = "strsim"
version = "0.6.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "tempdir"
version = "0.3.5"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.6 (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]]
@@ -285,64 +363,40 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (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.34 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.9.0"
version = "0.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.4 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-width"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unreachable"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "utf8-ranges"
version = "1.0.0"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "version_check"
version = "0.1.3"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -352,12 +406,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "walkdir"
version = "1.0.7"
version = "2.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 1.0.5 (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.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -367,11 +421,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.2"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"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]]
@@ -381,62 +435,76 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.3.2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-util"
version = "0.1.2"
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 = "winapi-x86_64-pc-windows-gnu"
version = "0.3.2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
"checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f"
"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97"
"checksum ctrlc 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "df391ea008fca636e41e40863a0b39a850e2ab26b0cdeed0c3657fd05a66d44c"
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
"checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245"
"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa"
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
"checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f"
"checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
"checksum filetime 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd7380b54ced79dda72ecc35cc4fbbd1da6bba54afaa37e96fd1c2a308cd469"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum fuchsia-zircon 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd510087c325af53ba24f3be8f1c081b0982319adcb8b03cad764512923ccc19"
"checksum fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "08b3a6f13ad6b96572b53ce7af74543132f1a7055ccceb6d073dd36c54481859"
"checksum globset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "464627f948c3190ae3d04b1bc6d7dca2f785bda0ac01278e6db129ad383dbeb6"
"checksum ignore 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3fcaf2365eb14b28ec7603c98c06cc531f19de9eb283d89a3dff8417c8c99f5"
"checksum 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.4 (registry+https://github.com/rust-lang/crates.io-index)" = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2"
"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
"checksum ignore 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0ec16832258409d571aaef8273f3c3cc5b060d784e159d1a0f3b0017308f84a7"
"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 kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
"checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
"checksum nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47e49f6982987135c5e9620ab317623e723bd06738fd85377e8d55f57c8b6487"
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
"checksum rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "9e7944d95d25ace8f377da3ac7068ce517e4c646754c43a1b1849177bbf72e59"
"checksum redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "07b8f011e3254d5a9b318fde596d409a0001c9ae4c6e7907520c2eaa4d988c99"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa"
"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e"
"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum lscolors 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea3b3414b2d015c4fd689815f2551797f3c2296bb241dd709c7da233ec7cba4b"
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"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.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.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
"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.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"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 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "890b38836c01d72fdb636d15c9cfc52ec7fd783b330abc93cd1686f4308dfccc"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc"
"checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668"
"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-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+22 -14
View File
@@ -16,7 +16,8 @@ license = "MIT/Apache-2.0"
name = "fd-find"
readme = "README.md"
repository = "https://github.com/sharkdp/fd"
version = "6.2.0"
version = "7.4.0"
edition= "2018"
[[bin]]
name = "fd"
@@ -28,30 +29,37 @@ repository = "sharkdp/fd"
repository = "sharkdp/fd"
[build-dependencies]
clap = "2.26.0"
version_check = "0.1.3"
clap = "2.31.2"
version_check = "0.9"
[dependencies]
ansi_term = "0.9"
ansi_term = "0.12"
atty = "0.2"
ignore = "0.2"
lazy_static = "1"
num_cpus = "1.6.2"
regex = "0.2"
regex-syntax = "0.4"
ctrlc = "3.0"
ignore = "0.4.3"
lazy_static = "1.1.0"
num_cpus = "1.8"
regex = "1.0.0"
regex-syntax = "0.6"
ctrlc = "3.1"
humantime = "1.1.1"
lscolors = "0.6"
globset = "0.4"
[dependencies.clap]
version = "2.26.0"
version = "2.31.2"
features = ["suggestions", "color", "wrap_help"]
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
libc = "0.2"
[target.'cfg(windows)'.dependencies]
kernel32-sys = "0.2"
winapi = "0.2"
[target.'cfg(all(not(windows), not(target_env = "musl")))'.dependencies]
jemallocator = "0.3.0"
[dev-dependencies]
diff = "0.1"
tempdir = "0.3"
filetime = "0.2.1"
[profile.release]
lto = true
codegen-units = 1
+251 -52
View File
@@ -2,6 +2,7 @@
[![Build Status](https://travis-ci.org/sharkdp/fd.svg?branch=master)](https://travis-ci.org/sharkdp/fd)
[![Build status](https://ci.appveyor.com/api/projects/status/21c4p5fwggc5gy3j?svg=true)](https://ci.appveyor.com/project/sharkdp/fd)
[![Version info](https://img.shields.io/crates/v/fd-find.svg)](https://crates.io/crates/fd-find)
[中文](https://github.com/chinanf-boy/fd-zh)
*fd* is a simple, fast and user-friendly alternative to
[*find*](https://www.gnu.org/software/findutils/).
@@ -26,63 +27,62 @@ While it does not seek to mirror all of *find*'s powerful functionality, it prov
## Demo
![Demo](http://i.imgur.com/kTMFSVU.gif)
![Demo](doc/screencast.svg)
## Benchmark
Let's search my home folder for files that end in `[0-9].jpg`. It contains ~150.000
Let's search my home folder for files that end in `[0-9].jpg`. It contains ~190.000
subdirectories and about a million files. For averaging and statistical analysis, I'm using
[bench](https://github.com/Gabriel439/bench). All benchmarks are performed for a "warm
cache". Results for a cold cache are similar.
[hyperfine](https://github.com/sharkdp/hyperfine). The following benchmarks are performed
with a "warm"/pre-filled disk-cache (results for a "cold" disk-cache show the same trends).
Let's start with `find`:
```
find ~ -iregex '.*[0-9]\.jpg$'
Benchmark #1: find ~ -iregex '.*[0-9]\.jpg$'
time 6.265 s (6.127 s .. NaN s)
1.000 R² (1.000 R² .. 1.000 R²)
mean 6.162 s (6.140 s .. 6.181 s)
std dev 31.73 ms (0.0 s .. 33.48 ms)
Time (mean ± σ): 7.236 s ± 0.090 s
Range (min … max): 7.133 s 7.385 s
```
`find` is much faster if it does not need to perform a regular-expression search:
```
find ~ -iname '*[0-9].jpg'
Benchmark #2: find ~ -iname '*[0-9].jpg'
time 2.866 s (2.754 s .. 2.964 s)
1.000 R² (0.999 R² .. 1.000 R²)
mean 2.860 s (2.834 s .. 2.875 s)
std dev 23.11 ms (0.0 s .. 25.09 ms)
Time (mean ± σ): 3.914 s ± 0.027 s
Range (min … max): 3.876 s 3.964 s
```
Now let's try the same for `fd`. Note that `fd` *always* performs a regular expression
search. The options `--hidden` and `--no-ignore` are needed for a fair comparison,
otherwise `fd` does not have to traverse hidden folders and ignored paths (see below):
```
fd --hidden --no-ignore '.*[0-9]\.jpg$' ~
Benchmark #3: fd -HI '.*[0-9]\.jpg$' ~
time 892.6 ms (839.0 ms .. 915.4 ms)
0.999 R² (0.997 R² .. 1.000 R²)
mean 871.2 ms (857.9 ms .. 881.3 ms)
std dev 15.50 ms (0.0 s .. 17.49 ms)
Time (mean ± σ): 811.6 ms ± 26.9 ms
Range (min … max): 786.0 ms … 870.7 ms
```
For this particular example, `fd` is approximately seven times faster than `find -iregex`
and about three times faster than `find -iname`. By the way, both tools found the exact
same 14030 files :smile:.
For this particular example, `fd` is approximately nine times faster than `find -iregex`
and about five times faster than `find -iname`. By the way, both tools found the exact
same 20880 files :smile:.
Finally, let's run `fd` without `--hidden` and `--no-ignore` (this can lead to different
search results, of course):
search results, of course). If *fd* does not have to traverse the hidden and git-ignored
folders, it is almost an order of magnitude faster:
```
fd '[0-9]\.jpg$' ~
Benchmark #4: fd '[0-9]\.jpg$' ~
time 159.5 ms (155.8 ms .. 165.3 ms)
0.999 R² (0.996 R² .. 1.000 R²)
mean 158.7 ms (156.5 ms .. 161.6 ms)
std dev 3.263 ms (2.401 ms .. 4.298 ms)
Time (mean ± σ): 123.7 ms ± 6.0 ms
Range (min … max): 118.8 ms … 140.0 ms
```
**Note**: This is *one particular* benchmark on *one particular* machine. While I have
performed quite a lot of different tests (and found consistent results), things might
be different for you! I encourage everyone to try it out on their own.
be different for you! I encourage everyone to try it out on their own. See
[this repository](https://github.com/sharkdp/fd-benchmarks) for all necessary scripts.
Concerning *fd*'s speed, the main credit goes to the `regex` and `ignore` crates that are also used
in [ripgrep](https://github.com/BurntSushi/ripgrep) (check it out!).
@@ -123,21 +123,78 @@ 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
### On Ubuntu
*... and other Debian-based Linux distrutions.*
*... 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:
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.
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_6.2.0_amd64.deb # adapt version number and architecture
sudo dpkg -i fd_7.4.0_amd64.deb # adapt version number and architecture
```
### On Debian
If you run Debian Buster or newer, you can install the
[officially maintained Debian package](https://tracker.debian.org/pkg/rust-fd-find):
```
sudo apt-get 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.
### On Fedora
Starting with Fedora 28, you can install `fd` from the official package sources:
``` bash
dnf install fd-find
```
For older versions, you can use this [Fedora copr](https://copr.fedorainfracloud.org/coprs/keefle/fd/) to install `fd`:
``` bash
dnf copr enable keefle/fd
dnf install fd
```
### On 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-rs package](https://www.archlinux.org/packages/community/x86_64/fd-rs/) from the official repos:
You can install [the fd package](https://www.archlinux.org/packages/community/x86_64/fd/) from the official repos:
```
pacman -S fd-rs
pacman -S fd
```
### On Gentoo Linux
You can use [the fd ebuild](https://packages.gentoo.org/packages/sys-apps/fd) from the official repo:
```
emerge -av fd
```
### On openSUSE Linux
You can install [the fd package](https://software.opensuse.org/package/fd) from the official repo:
```
zypper in fd
```
### On Void Linux
@@ -149,15 +206,30 @@ xbps-install -S fd
### On macOS
You can install [this Homebrew package](http://braumeister.org/formula/fd):
You can install `fd` with [Homebrew](http://braumeister.org/formula/fd):
```
brew install fd
```
… or with MacPorts:
```
sudo port install fd
```
### On Windows
You can download pre-built binaries from the [release page](https://github.com/sharkdp/fd/releases).
Alternatively, you can install `fd` via [Scoop](http://scoop.sh):
```
scoop install fd
```
Or via [Chocolatey](https://chocolatey.org):
```
choco install fd
```
### On NixOS / via Nix
You can use the [Nix package manager](https://nixos.org/nix/) to install `fd`:
@@ -167,9 +239,25 @@ 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
```
### On OpenBSD
You can install the [fd-find](https://gitlab.com/epbsd/ports/tree/master/sysutils/fd) package from the [EPBSD](https://epbsd.org/) repo:
```
ftp -o /etc/signify/epbsd-pkg.pub https://epbsd.org/epbsd-pkg.pub
export PKG_PATH=https://repo.epbsd.org/pub/OpenBSD/$(arch -s):${PKG_PATH}
pkg_add fd
```
### 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
@@ -178,7 +266,7 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can
```
cargo install fd-find
```
Note that rust version *1.20.0* or later is required.
Note that rust version *1.31.0* or later is required.
### From binaries
@@ -206,10 +294,12 @@ USAGE:
FLAGS:
-H, --hidden Search hidden files and directories
-I, --no-ignore Do not respect .(git)ignore files
-I, --no-ignore Do not respect .(git|fd)ignore files
--no-ignore-vcs Do not respect .gitignore files
-s, --case-sensitive Case-sensitive search (default: smart case)
-i, --ignore-case Case-insensitive search (default: smart case)
-g, --glob Glob-based search (default: regular expression)
-F, --fixed-strings Treat the pattern as a literal string
-a, --absolute-path Show absolute instead of relative paths
-L, --follow Follow symbolic links
-p, --full-path Search full path (default: file-/dirname only)
@@ -218,19 +308,26 @@ FLAGS:
-V, --version Prints version information
OPTIONS:
-d, --max-depth <depth> Set maximum search depth (default: none)
-t, --type <filetype>... Filter by type: f(ile), d(irectory), (sym)l(ink)
-e, --extension <ext>... Filter by file extension
-x, --exec <cmd>... Execute a command for each search result
-E, --exclude <pattern>... Exclude entries that match the given glob pattern
-c, --color <when> When to use colors: never, *auto*, always
-j, --threads <num> Set number of threads to use for searching & executing
-d, --max-depth <depth> Set maximum search depth (default: none)
-t, --type <filetype>... Filter by type: file (f), directory (d), symlink (l),
executable (x), empty (e)
-e, --extension <ext>... Filter by file extension
-x, --exec <cmd> Execute a command for each search result
-X, --exec-batch <cmd> Execute a command with all search results at once
-E, --exclude <pattern>... Exclude entries that match the given glob pattern
-c, --color <when> When to use colors: never, *auto*, always
-S, --size <size>... Limit results based on the size of files.
--changed-within <date|dur> Filter by file modification time (newer than)
--changed-before <date|dur> Filter by file modification time (older than)
ARGS:
<pattern> the search pattern, a regular expression (optional)
<pattern> the search pattern: a regular expression unless '--glob' is used (optional)
<path>... the root directory for the filesystem search (optional)
```
This is the output of `fd -h`. To see the full set of command-line options, use `fd --help` which
also includes a much more detailed help text.
## Tutorial
First, to get an overview of all available command line options, you can either run
@@ -262,7 +359,7 @@ X11/xinit/xserverrc
### Specifying the root directory
If we want so search a specific directory, it can be given as a second argument to *fd*:
If we want to search a specific directory, it can be given as a second argument to *fd*:
``` bash
> fd passwd /etc
/etc/default/passwd
@@ -282,6 +379,15 @@ testenv/mod.rs
tests.rs
```
If you want to use this functionality to list all files in a given directory, you have to use
a catch-all pattern such as `.` or `^`:
``` bash
> fd . fd/tests/
testenv
testenv/mod.rs
tests.rs
```
### Searching for a particular file extension
Often, we are interested in all files of a particular type. This can be done with the `-e` (or
@@ -312,7 +418,7 @@ search results. To disable this behavior, we can use the `-H` (or `--hidden`) op
If we work in a directory that is a Git repository (or includes Git repositories), *fd* does not
search folders (and does not show files) that match one of the `.gitignore` patterns. To disable
this behavior, we can use the `-I` (or `--ignore`) option:
this behavior, we can use the `-I` (or `--no-ignore`) option:
``` bash
> fd num_cpu
> fd -I num_cpu
@@ -322,16 +428,98 @@ target/debug/deps/libnum_cpus-f5ce7ef99006aa05.rlib
To really search *all* files and directories, simply combine the hidden and ignore features to show
everything (`-HI`).
### Excluding specific files or directories
Sometimes we want to ignore search results from a specific subdirectory. For example, we might
want to search all hidden files and directories (`-H`) but exclude all matches from `.git`
directories. We can use the `-E` (or `--exclude`) option for this. It takes an arbitrary glob
pattern as an argument:
``` bash
> fd -H -E .git …
```
We can also use this to skip mounted directories:
``` bash
> fd -E /mnt/external-drive …
```
.. or to skip certain file types:
``` bash
> fd -E '*.bak' …
```
To make exclude-patterns like these permanent, you can create a `.fdignore` file. They work like
`.gitignore` files, but are specific to `fd`. For example:
``` bash
> cat ~/.fdignore
/mnt/external-drive
*.bak
```
Note: `fd` also supports `.ignore` files that are used by other programs such as `rg` or `ag`.
### Using fd with `xargs` or `parallel`
If we want to run a command on all search results, we can pipe the output to `xargs`:
``` bash
> fd -0 -e rs | xargs -0 wc -l
```
Here, the `-0` option tells *fd* to separate search results by the NULL character (instead of .
newlines) In the same way, the `-0` option of `xargs` tells it to read the input in this way .
Here, the `-0` option tells *fd* to separate search results by the NULL character (instead of
newlines). In the same way, the `-0` option of `xargs` tells it to read the input in this way.
### Using fd with `fzf`
### 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!
Remember that `fd` ignores hidden directories and files by default. It also ignores patterns
from `.gitignore` files. If you want to make sure to find absolutely every possible file, always
use the options `-H` and `-I` to disable these two features:
``` bash
> fd -HI …
```
#### `fd` doesn't seem to interpret my regex pattern correctly
A lot of special regex characters (like `[]`, `^`, `$`, ..) are also special characters in your
shell. If in doubt, always make sure to put single quotes around the regex pattern:
``` bash
> fd '^[A-Z][0-9]+$'
```
If your pattern starts with a dash, you have to add `--` to signal the end of command line
options. Otherwise, the pattern will be interpreted as a command-line option. Alternatively,
use a character class with a single hyphen character:
``` bash
> fd -- '-pattern'
> fd '[-]pattern'
```
### Integration with other programs
#### Using fd with `fzf`
You can use *fd* to generate input for the command-line fuzzy finder [fzf](https://github.com/junegunn/fzf):
``` bash
@@ -353,3 +541,14 @@ export FZF_DEFAULT_OPTS="--ansi"
```
For more details, see the [Tips section](https://github.com/junegunn/fzf#tips) of the fzf README.
#### Using fd with `emacs`
The emacs package [find-file-in-project](https://github.com/technomancy/find-file-in-project) can
use *fd* to find files.
After installing `find-file-in-project`, add the line `(setq ffip-use-rust-fd t)` to your
`~/.emacs` or `~/.emacs.d/init.el` file.
In emacs, run `M-x find-file-in-project-by-selected` to find matching files. Alternatively, run
`M-x find-file-in-project` to list all available files in the project.
+5 -10
View File
@@ -6,24 +6,19 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
#[macro_use]
extern crate clap;
extern crate version_check;
use clap::Shell;
use std::fs;
use std::io::{self, Write};
use std::process::exit;
use std::fs;
include!("src/app.rs");
fn main() {
match version_check::is_min_version("1.20") {
// rustc >= 1.20
Some((true, _)) => {}
// rustc < 1.20 or can't figure it out
match version_check::is_min_version("1.31") {
Some(true) => {}
// rustc version too small or can't figure it out
_ => {
writeln!(&mut io::stderr(), "This crate requires rustc >= 1.20").unwrap();
writeln!(&mut io::stderr(), "'fd' requires rustc >= 1.31").unwrap();
exit(1);
}
}
+1
View File
@@ -0,0 +1 @@
* linguist-vendored
+67 -6
View File
@@ -11,18 +11,25 @@ pack() {
local tempdir
local out_dir
local package_name
local gcc_prefix
tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
out_dir=$(pwd)
package_name="$PROJECT_NAME-$TRAVIS_TAG-$TARGET"
if [[ $TARGET == arm-unknown-linux-* ]]; then
gcc_prefix="arm-linux-gnueabihf-"
else
gcc_prefix=""
fi
# create a "staging" directory
mkdir "$tempdir/$package_name"
mkdir "$tempdir/$package_name/autocomplete"
# copying the main binary
cp "target/$TARGET/release/$PROJECT_NAME" "$tempdir/$package_name/"
strip "$tempdir/$package_name/$PROJECT_NAME"
"${gcc_prefix}"strip "$tempdir/$package_name/$PROJECT_NAME"
# manpage, readme and license
cp "doc/$PROJECT_NAME.1" "$tempdir/$package_name"
@@ -48,17 +55,29 @@ 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 "ERROR: unknown target" >&2
return 1
echo "make_deb: skipping target '${TARGET}'" >&2
return 0
;;
esac
version=${TRAVIS_TAG#v}
@@ -74,15 +93,54 @@ 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"
gzip --best "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
# readme and license
install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md"
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}"
@@ -96,14 +154,17 @@ Package: $dpkgname
Version: $version
Section: utils
Priority: optional
Maintainer: David Peter
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.
EOF
dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
fakeroot dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
}
Vendored Executable
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -ex
if [ "$TRAVIS_OS_NAME" != linux ]; then
exit 0
fi
sudo apt-get update
# needed to build deb packages
sudo apt-get install -y fakeroot
# needed for i686 linux gnu target
if [[ $TARGET == i686-unknown-linux-gnu ]]; then
sudo apt-get install -y gcc-multilib
fi
# needed for cross-compiling for arm
if [[ $TARGET == arm-unknown-linux-* ]]; then
sudo apt-get install -y \
gcc-4.8-arm-linux-gnueabihf \
binutils-arm-linux-gnueabihf \
libc6-armhf-cross \
libc6-dev-armhf-cross
fi
Vendored Executable
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -ex
# Incorporate TARGET env var to the build and test process
cargo build --target "$TARGET" --verbose
# We cannot run arm executables on linux
if [[ $TARGET != arm-unknown-linux-* ]]; then
cargo test --target "$TARGET" --verbose
fi
+1
View File
@@ -0,0 +1 @@
* linguist-vendored
Vendored
+123 -19
View File
@@ -27,39 +27,59 @@ is a simple, fast and user-friendly alternative to
.SH OPTIONS
.TP
.B \-H, \-\-hidden
Include hidden files and directories in the search results.
Include hidden files and directories in the search results
(default: hidden files and directories are skipped).
.TP
.B \-I, \-\-no\-ignore
Do not respect files like
.I .gitignore
and
Show search results from files and directories that would otherwise be ignored by
.IR .gitignore ,
.I .ignore
and include ignored files in the search results.
or
.I .fdignore
files.
.TP
.B \-\-no\-ignore\-vcs
Do not respect version control ignore files like
.I ".gitignore"
and include the respective entries in the search results.
Show search results from files and directories that would otherwise be ignored by
.I .gitignore
files.
.TP
.B \-s, \-\-case\-sensitive
Perform a case-sensitive search (default: smart case).
Perform a case-sensitive search. By default, fd uses case-insensitive searches, unless the
pattern contains an uppercase character (smart case).
.TP
.B \-i, \-\-ignore\-case
Perform a case-insensitive search (default: smart case).
Perform a case-insensitive search. By default, fd uses case-insensitive searches, unless the
pattern contains an uppercase character (smart case).
.TP
.B \-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
Show absolute instead of relative paths.
Shows the full path starting from the root as opposed to relative paths.
.TP
.B \-L, \-\-follow
Dereference all symbolic links encountered.
By default, fd does not descend into symlinked directories. Using this flag, symbolic links are
also traversed.
.TP
.B \-p, \-\-full\-path
Match the
By default, the search pattern is only matched against the filename (or directory name). Using
this flag, the
.I pattern
against the full path instead of just the file or directory name.
is matched against the full path.
.TP
.B \-0, \-\-print0
Separate search results by null characters instead of newlines.
Separate search results by the null character (instead of newlines). Useful for piping results to
.IR xargs .
.TP
.B \-\-show-errors
Enable the display of filesystem errors for situations such as insufficient
permissions or dead symlinks.
.TP
.B \-h, \-\-help
Print help information.
@@ -77,10 +97,14 @@ Filter search by type:
.RS
.IP "f, file"
regular files
.IP "d, directories"
.IP "d, directory"
directories
.IP "s, symlink"
.IP "l, symlink"
symbolic links
.IP "x, executable"
executable (files)
.IP "e, empty"
empty files or directories
.RE
.RS
@@ -97,6 +121,10 @@ Exclude files/directories that match the given glob pattern.
This overrides any other ignore logic.
Multiple exclude patterns can be specified.
.TP
.BI "\-\-ignore-file " path
Add a custom ignore-file in '.gitignore' format.
These files have a low precedence.
.TP
.BI "\-c, \-\-color " when
Declare
.I when
@@ -111,9 +139,64 @@ 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 "\-x, \-\-exec " command "\fR [args...] ;"
.BI "\-S, \-\-size " size
Limit results based on the size of files using the format
.I <+-><NUM><UNIT>
.RS
.IP '+'
file size must be greater than or equal to this
.IP '-'
file size must be less than or equal to this
.IP 'NUM'
The numeric size (e.g. 500)
.IP 'UNIT'
The units for NUM. They are not case-sensitive.
Allowed unit values:
.RS
.IP 'b'
bytes
.IP 'k'
kilobytes
.IP 'm'
megabytes
.IP 'g'
gigabytes
.IP 't'
terabytes
.IP 'ki'
kibibytes
.IP 'mi'
mebibytes
.IP 'gi'
gibibytes
.IP 'ti'
tebibytes
.RE
.RE
.TP
.BI "\-\-changed-within " date|duration
Filter results based on the file modification time. The argument can be provided as a specific
point in time (\fIYYYY-MM-DD HH:MM:SS\fR) or as a duration (\fI10h, 1d, 35min\fR).
.B --change-newer-than
can be used as an alias.
Examples:
\-\-changed-within 2weeks
\-\-change-newer-than "2018-10-27 10:00:00"
.TP
.BI "\-\-changed-before " date|duration
Filter results based on the file modification time. The argument can be provided as a specific
point in time (\fIYYYY-MM-DD HH:MM:SS\fR) or as a duration (\fI10h, 1d, 35min\fR).
.B --change-older-than
can be used as an alias.
Examples:
\-\-changed-before "2018-10-27 10:00:00"
\-\-change-older-than 2weeks
.TP
.BI "\-x, \-\-exec " command
Execute
.I command
for each search result. The following placeholders are substituted by a path derived from the current search result:
@@ -129,6 +212,24 @@ path without file extension
.IP {/.}
basename without file extension
.RE
.TP
.BI "\-X, \-\-exec-batch " command
Execute
.I command
with all search results at once.
A single occurence of the following placeholders is authorized and substituted by the paths derived from the search results before the command is executed:
.RS
.IP {}
path
.IP {/}
basename
.IP {//}
parent directory
.IP {.}
path without file extension
.IP {/.}
basename without file extension
.RE
.SH ENVIRONMENT
.TP
.B LS_COLORS
@@ -144,5 +245,8 @@ $ fd nginx /var/log
.TP
.RI "Find all Python files (all files with the extention " .py ") in the current directory:"
$ fd -e py
.TP
.RI "Open all search results with vim:"
$ fd pattern -X vim
.SH SEE ALSO
.BR find (1)
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# Designed to be executed via svg-term from the fd root directory:
# svg-term --command="bash doc/screencast.sh" --out doc/screencast.svg --padding=10
set -e
set -u
PROMPT="▶"
enter() {
INPUT=$1
DELAY=1
prompt
sleep "$DELAY"
type "$INPUT"
sleep 0.5
printf '%b' "\\n"
eval "$INPUT"
type "\\n"
}
prompt() {
printf '%b ' $PROMPT | pv -q
}
type() {
printf '%b' "$1" | pv -qL $((10+(-2 + RANDOM%5)))
}
main() {
IFS='%'
enter "fd"
enter "fd app"
enter "fd sh"
enter "fd sh --type f"
enter "fd -e md"
enter "fd -e md --exec wc -l"
enter "fd '^[A-Z]'"
enter "fd --exclude src"
enter "fd --hidden sample"
prompt
sleep 3
echo ""
unset IFS
}
main
+1
View File
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 115 KiB

+199 -18
View File
@@ -5,10 +5,9 @@
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::collections::HashMap;
use clap::{App, AppSettings, Arg};
use clap::{crate_version, App, AppSettings, Arg};
struct Help {
short: &'static str,
@@ -20,10 +19,13 @@ macro_rules! doc {
doc!($map, $name, $short, $short)
};
($map:expr, $name:expr, $short:expr, $long:expr) => {
$map.insert($name, Help {
short: $short,
long: concat!($long, "\n ")
});
$map.insert(
$name,
Help {
short: $short,
long: concat!($long, "\n "),
},
);
};
}
@@ -40,6 +42,10 @@ pub fn build_app() -> App<'static, 'static> {
.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.",
)
.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"))
@@ -61,11 +67,36 @@ pub fn build_app() -> App<'static, 'static> {
.short("i")
.overrides_with("case-sensitive"),
)
.arg(
arg("glob")
.long("glob")
.short("g")
.conflicts_with("fixed-strings"),
)
.arg(
arg("regex")
.long("regex")
.overrides_with("glob")
.hidden_short_help(true),
)
.arg(
arg("fixed-strings")
.long("fixed-strings")
.short("F")
.alias("literal"),
)
.arg(arg("absolute-path").long("absolute-path").short("a"))
.arg(arg("follow").long("follow").short("L").alias("dereference"))
.arg(arg("full-path").long("full-path").short("p"))
.arg(arg("null_separator").long("print0").short("0"))
.arg(arg("depth").long("max-depth").short("d").takes_value(true))
// support --maxdepth as well, for compatibility with rg
.arg(
arg("rg-depth")
.long("maxdepth")
.hidden(true)
.takes_value(true),
)
.arg(
arg("file-type")
.long("type")
@@ -74,7 +105,18 @@ pub fn build_app() -> App<'static, 'static> {
.number_of_values(1)
.takes_value(true)
.value_name("filetype")
.possible_values(&["f", "file", "d", "directory", "l", "symlink"])
.possible_values(&[
"f",
"file",
"d",
"directory",
"l",
"symlink",
"x",
"executable",
"e",
"empty",
])
.hide_possible_values(true),
)
.arg(
@@ -90,12 +132,21 @@ pub fn build_app() -> App<'static, 'static> {
arg("exec")
.long("exec")
.short("x")
.multiple(true)
.min_values(1)
.allow_hyphen_values(true)
.value_terminator(";")
.value_name("cmd"),
)
.arg(
arg("exec-batch")
.long("exec-batch")
.short("X")
.min_values(1)
.allow_hyphen_values(true)
.value_terminator(";")
.value_name("cmd")
.conflicts_with("exec"),
)
.arg(
arg("exclude")
.long("exclude")
@@ -105,6 +156,15 @@ pub fn build_app() -> App<'static, 'static> {
.number_of_values(1)
.multiple(true),
)
.arg(
arg("ignore-file")
.long("ignore-file")
.takes_value(true)
.value_name("path")
.number_of_values(1)
.multiple(true)
.hidden_short_help(true),
)
.arg(
arg("color")
.long("color")
@@ -119,7 +179,17 @@ pub fn build_app() -> App<'static, 'static> {
.long("threads")
.short("j")
.takes_value(true)
.value_name("num"),
.value_name("num")
.hidden_short_help(true),
)
.arg(
arg("size")
.long("size")
.short("S")
.takes_value(true)
.number_of_values(1)
.allow_hyphen_values(true)
.multiple(true),
)
.arg(
arg("max-buffer-time")
@@ -127,21 +197,59 @@ pub fn build_app() -> App<'static, 'static> {
.takes_value(true)
.hidden(true),
)
.arg(
arg("changed-within")
.long("changed-within")
.alias("change-newer-than")
.takes_value(true)
.value_name("date|dur")
.number_of_values(1),
)
.arg(
arg("changed-before")
.long("changed-before")
.alias("change-older-than")
.takes_value(true)
.value_name("date|dur")
.number_of_values(1),
)
.arg(
arg("show-errors")
.long("show-errors")
.hidden_short_help(true),
)
.arg(arg("pattern"))
.arg(
arg("path-separator")
.takes_value(true)
.value_name("separator")
.long("path-separator")
.hidden_short_help(true),
)
.arg(arg("path").multiple(true))
.arg(
arg("search-path")
.long("search-path")
.takes_value(true)
.conflicts_with("path")
.multiple(true)
.hidden_short_help(true)
.number_of_values(1),
)
}
#[cfg_attr(rustfmt, rustfmt_skip)]
#[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).");
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)ignore files"
, "Do not respect .(git|fd)ignore files"
, "Show search results from files and directories that would otherwise be ignored by \
'.*ignore' files.");
'.gitignore', '.ignore' or '.fdignore' files.");
doc!(h, "no-ignore-vcs"
, "Do not respect .gitignore files"
, "Show search results from files and directories that would otherwise be ignored by \
@@ -154,9 +262,22 @@ fn usage() -> HashMap<&'static str, Help> {
, "Case-insensitive search (default: smart case)"
, "Perform a case-insensitive search. By default, fd uses case-insensitive searches, \
unless the pattern contains an uppercase character (smart case).");
doc!(h, "glob"
, "Glob-based search (default: regular expression)"
, "Perform a glob-based search instead of a regular expression search.");
doc!(h, "regex"
, "Perform a regex-based search"
, "Perform a regular-expression based seach (default). This can be used to override --glob.");
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, "path-separator"
, "Set the path separator to use when printing file paths."
, "Set the path separator to use when printing file paths. The default is the OS-specific \
separator ('/' on Unix, '\\' on Windows).");
doc!(h, "follow"
, "Follow symbolic links"
, "By default, fd does not descend into symlinked directories. Using this flag, symbolic \
@@ -173,12 +294,17 @@ fn usage() -> HashMap<&'static str, Help> {
, "Set maximum search depth (default: none)"
, "Limit the directory traversal to a given depth. By default, there is no limit \
on the search depth.");
doc!(h, "rg-depth"
, "See --max-depth"
, "See --max-depth");
doc!(h, "file-type"
, "Filter by type: f(ile), d(irectory), (sym)l(ink)"
, "Filter by type: file (f), directory (d), symlink (l),\nexecutable (x), empty (e)"
, "Filter the search by type (multiple allowable filetypes can be specified):\n \
'f' or 'file': regular files\n \
'd' or 'directory': directories\n \
'l' or 'symlink': symbolic links");
'l' or 'symlink': symbolic links\n \
'x' or 'executable': executables\n \
'e' or 'empty': empty files or directories");
doc!(h, "extension"
, "Filter by file extension"
, "(Additionally) filter search results by their file extension. Multiple allowable file \
@@ -195,10 +321,25 @@ fn usage() -> HashMap<&'static str, Help> {
'{//}': 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 \
@@ -211,10 +352,10 @@ fn usage() -> HashMap<&'static str, Help> {
CPU cores)");
doc!(h, "max-buffer-time"
, "the time (in ms) to buffer, before streaming to the console"
, "Amount of time in milliseconds to buffer, before streaming the search results to\
, "Amount of time in milliseconds to buffer, before streaming the search results to \
the console.");
doc!(h, "pattern"
, "the search pattern, a regular expression (optional)");
, "the search pattern: a regular expression unless '--glob' is used (optional)");
doc!(h, "path"
, "the root directory for the filesystem search (optional)"
, "The directory where the filesystem search is rooted (optional). \
@@ -222,6 +363,46 @@ fn usage() -> HashMap<&'static str, Help> {
doc!(h, "rg-alias-hidden-ignore"
, "Alias for no-ignore and/or hidden"
, "Alias for no-ignore ('u') and no-ignore and hidden ('uu')");
doc!(h, "size"
, "Limit results based on the size of files."
, "Limit results based on the size of files using the format <+-><NUM><UNIT>.\n \
'+': file size must be greater than or equal to this\n \
'-': file size must be less than or equal to this\n \
'NUM': The numeric size (e.g. 500)\n \
'UNIT': The units for NUM. They are not case-sensitive.\n\
Allowed unit values:\n \
'b': bytes\n \
'k': kilobytes\n \
'm': megabytes\n \
'g': gigabytes\n \
't': terabytes\n \
'ki': kibibytes\n \
'mi': mebibytes\n \
'gi': gibibytes\n \
'ti': tebibytes");
doc!(h, "changed-within"
, "Filter by file modification time (newer than)"
, "Filter results based on the file modification time. The argument can be provided \
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
'--change-newer-than' can be used as an alias.\n\
Examples:\n \
--changed-within 2weeks\n \
--change-newer-than '2018-10-27 10:00:00'");
doc!(h, "changed-before"
, "Filter by file modification time (older than)"
, "Filter results based on the file modification time. The argument can be provided \
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
'--change-older-than' can be used as an alias.\n\
Examples:\n \
--changed-before '2018-10-27 10:00:00'\n \
--change-older-than 2weeks");
doc!(h, "show-errors"
, "Enable display of filesystem errors"
, "Enable the display of filesystem errors for situations such as insufficient permissions \
or dead symlinks.");
doc!(h, "search-path"
, "(hidden)"
, "Provide paths to search as an alternative to the positional <path> argument. \
Changes the usage to `fd [FLAGS/OPTIONS] --search-path <path> --search-path <path2> [<pattern>]`");
h
}
+17 -8
View File
@@ -6,13 +6,15 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::io;
use std::io::Write;
use std::process::Command;
use std::sync::Mutex;
use crate::exit_codes::ExitCode;
/// Executes a command.
pub fn execute_command(mut cmd: Command, out_perm: Arc<Mutex<()>>) {
pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) -> ExitCode {
// Spawn the supplied command.
let output = cmd.output();
@@ -28,13 +30,20 @@ pub fn execute_command(mut cmd: Command, out_perm: Arc<Mutex<()>>) {
let _ = stdout.lock().write_all(&output.stdout);
let _ = stderr.lock().write_all(&output.stderr);
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);
ExitCode::GeneralError
}
Err(why) => {
if why.kind() == io::ErrorKind::NotFound {
eprintln!("fd: execution error: command not found");
} else {
eprintln!("fd: execution error: {}", why);
}
print_error!("Problem while executing command: {}", why);
ExitCode::GeneralError
}
}
}
+32 -76
View File
@@ -71,90 +71,46 @@ pub fn dirname(path: &str) -> &str {
}
#[cfg(test)]
mod tests {
use super::{basename, dirname, remove_extension, MAIN_SEPARATOR};
mod path_tests {
use super::*;
fn correct(input: &str) -> String {
input.replace('/', &MAIN_SEPARATOR.to_string())
}
#[test]
fn path_remove_ext_simple() {
assert_eq!(remove_extension("foo.txt"), "foo");
macro_rules! func_tests {
($($name:ident: $func:ident for $input:expr => $output:expr)+) => {
$(
#[test]
fn $name() {
assert_eq!($func(&correct($input)), correct($output));
}
)+
}
}
func_tests! {
remove_ext_simple: remove_extension for "foo.txt" => "foo"
remove_ext_dir: remove_extension for "dir/foo.txt" => "dir/foo"
hidden: remove_extension for ".foo" => ".foo"
remove_ext_utf8: remove_extension for "💖.txt" => "💖"
remove_ext_empty: remove_extension for "" => ""
basename_simple: basename for "foo.txt" => "foo.txt"
basename_dir: basename for "dir/foo.txt" => "foo.txt"
basename_empty: basename for "" => ""
basename_utf8_0: basename for "💖/foo.txt" => "foo.txt"
basename_utf8_1: basename for "dir/💖.txt" => "💖.txt"
dirname_simple: dirname for "foo.txt" => "."
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]
fn path_remove_ext_dir() {
assert_eq!(
remove_extension(&correct("dir/foo.txt")),
correct("dir/foo")
);
}
#[test]
fn path_hidden() {
assert_eq!(remove_extension(".foo"), ".foo")
}
#[test]
fn path_remove_ext_utf8() {
assert_eq!(remove_extension("💖.txt"), "💖");
}
#[test]
fn path_remove_ext_empty() {
assert_eq!(remove_extension(""), "");
}
#[test]
fn path_basename_simple() {
assert_eq!(basename("foo.txt"), "foo.txt");
}
#[test]
fn path_basename_no_ext() {
assert_eq!(remove_extension(basename("foo.txt")), "foo");
}
#[test]
fn path_basename_dir() {
assert_eq!(basename(&correct("dir/foo.txt")), "foo.txt");
}
#[test]
fn path_basename_empty() {
assert_eq!(basename(""), "");
}
#[test]
fn path_basename_utf8() {
assert_eq!(basename(&correct("💖/foo.txt")), "foo.txt");
assert_eq!(basename(&correct("dir/💖.txt")), "💖.txt");
}
#[test]
fn path_dirname_simple() {
assert_eq!(dirname("foo.txt"), ".");
}
#[test]
fn path_dirname_dir() {
assert_eq!(dirname(&correct("dir/foo.txt")), "dir");
}
#[test]
fn path_dirname_utf8() {
assert_eq!(dirname(&correct("💖/foo.txt")), "💖");
assert_eq!(dirname(&correct("dir/💖.txt")), "dir");
}
#[test]
fn path_dirname_empty() {
assert_eq!(dirname(""), ".");
}
#[test]
fn path_dirname_root() {
fn dirname_root() {
#[cfg(windows)]
assert_eq!(dirname("C:\\"), "C:");
#[cfg(windows)]
+36 -7
View File
@@ -6,24 +6,36 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::Receiver;
use super::CommandTemplate;
use crate::exit_codes::ExitCode;
use crate::walk::WorkerResult;
use std::path::PathBuf;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex};
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
/// generate a command with the supplied command template. The generated command will then
/// be executed, and this process will continue until the receiver's sender has closed.
pub fn job(rx: Arc<Mutex<Receiver<PathBuf>>>, cmd: Arc<CommandTemplate>, out_perm: Arc<Mutex<()>>) {
pub fn job(
rx: Arc<Mutex<Receiver<WorkerResult>>>,
cmd: Arc<CommandTemplate>,
out_perm: Arc<Mutex<()>>,
show_filesystem_errors: bool,
) {
loop {
// Create a lock on the shared receiver for this thread.
let lock = rx.lock().unwrap();
// Obtain the next path from the receiver, else if the channel
// Obtain the next result from the receiver, else if the channel
// has closed, exit from the loop
let value: PathBuf = match lock.recv() {
Ok(value) => value,
Ok(WorkerResult::Entry(val)) => val,
Ok(WorkerResult::Error(err)) => {
if show_filesystem_errors {
print_error!("{}", err);
}
continue;
}
Err(_) => break,
};
@@ -33,3 +45,20 @@ pub fn job(rx: Arc<Mutex<Receiver<PathBuf>>>, cmd: Arc<CommandTemplate>, out_per
cmd.generate_and_execute(&value, Arc::clone(&out_perm));
}
}
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);
}
None
}
});
cmd.generate_and_execute_batch(paths)
}
+196 -24
View File
@@ -8,21 +8,33 @@
// TODO: Possible optimization could avoid pushing characters on a buffer.
mod command;
mod token;
mod job;
mod input;
mod job;
mod token;
use std::borrow::Cow;
use std::path::Path;
use std::process::Command;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use lazy_static::lazy_static;
use regex::Regex;
use self::input::{basename, dirname, remove_extension};
use crate::exit_codes::ExitCode;
use self::command::execute_command;
use self::input::{basename, dirname, remove_extension};
pub use self::job::{batch, job};
use self::token::Token;
pub use self::job::job;
/// Execution mode of the command
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ExecutionMode {
/// Command is executed for each search result
OneByOne,
/// Command is run for a batch of results at once
Batch,
}
/// Represents a template that is utilized to generate command strings.
///
@@ -31,10 +43,34 @@ pub use self::job::job;
#[derive(Debug, Clone, PartialEq)]
pub struct CommandTemplate {
args: Vec<ArgumentTemplate>,
mode: ExecutionMode,
}
impl CommandTemplate {
pub fn new<I, S>(input: I) -> CommandTemplate
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
Self::build(input, ExecutionMode::OneByOne)
}
pub fn new_batch<I, S>(input: I) -> Result<CommandTemplate, &'static str>
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");
}
if cmd.args[0].has_tokens() {
return Err("First argument of exec-batch is expected to be a fixed executable");
}
Ok(cmd)
}
fn build<I, S>(input: I, mode: ExecutionMode) -> CommandTemplate
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
@@ -91,7 +127,19 @@ impl CommandTemplate {
args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder]));
}
CommandTemplate { args: args }
CommandTemplate { args, mode }
}
fn number_of_tokens(&self) -> usize {
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.
@@ -99,18 +147,50 @@ impl CommandTemplate {
/// 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 = input
.strip_prefix(".")
.unwrap_or(input)
.to_string_lossy()
.into_owned();
let input = Self::prepare_path(input);
let mut cmd = Command::new(self.args[0].generate(&input).as_ref());
for arg in &self.args[1..] {
cmd.arg(arg.generate(&input).as_ref());
}
execute_command(cmd, out_perm)
execute_command(cmd, &out_perm);
}
pub fn in_batch_mode(&self) -> bool {
self.mode == ExecutionMode::Batch
}
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());
cmd.stdin(Stdio::inherit());
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
let mut paths = paths.map(|p| Self::prepare_path(&p));
let mut has_path = false;
for arg in &self.args[1..] {
if arg.has_tokens() {
// A single `Tokens` is expected
// So we can directy consume the iterator once and for all
for path in &mut paths {
cmd.arg(arg.generate(&path).as_ref());
has_path = true;
}
} else {
cmd.arg(arg.generate("").as_ref());
}
}
if has_path {
execute_command(cmd, &Mutex::new(()))
} else {
ExitCode::Success
}
}
}
@@ -125,6 +205,13 @@ enum ArgumentTemplate {
}
impl ArgumentTemplate {
pub fn has_tokens(&self) -> bool {
match self {
ArgumentTemplate::Tokens(_) => true,
_ => false,
}
}
pub fn generate<'a>(&'a self, path: &str) -> Cow<'a, str> {
use self::Token::*;
@@ -150,20 +237,25 @@ impl ArgumentTemplate {
#[cfg(test)]
mod tests {
use super::{ArgumentTemplate, CommandTemplate, Token};
use super::*;
#[test]
fn tokens() {
let expected = CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Text("${SHELL}:".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
],
};
assert_eq!(CommandTemplate::new(&[&"echo", &"${SHELL}:"]), expected);
fn tokens_with_placeholder() {
assert_eq!(
CommandTemplate::new(&[&"echo", &"${SHELL}:"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Text("${SHELL}:".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
],
mode: ExecutionMode::OneByOne,
}
);
}
#[test]
fn tokens_with_no_extension() {
assert_eq!(
CommandTemplate::new(&["echo", "{.}"]),
CommandTemplate {
@@ -171,7 +263,87 @@ mod tests {
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::NoExt]),
],
mode: ExecutionMode::OneByOne,
}
);
}
#[test]
fn tokens_with_basename() {
assert_eq!(
CommandTemplate::new(&["echo", "{/}"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Basename]),
],
mode: ExecutionMode::OneByOne,
}
);
}
#[test]
fn tokens_with_parent() {
assert_eq!(
CommandTemplate::new(&["echo", "{//}"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Parent]),
],
mode: ExecutionMode::OneByOne,
}
);
}
#[test]
fn tokens_with_basename_no_extension() {
assert_eq!(
CommandTemplate::new(&["echo", "{/.}"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
],
mode: ExecutionMode::OneByOne,
}
);
}
#[test]
fn tokens_multiple() {
assert_eq!(
CommandTemplate::new(&["cp", "{}", "{/.}.ext"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("cp".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
ArgumentTemplate::Tokens(vec![
Token::BasenameNoExt,
Token::Text(".ext".into())
]),
],
mode: ExecutionMode::OneByOne,
}
);
}
#[test]
fn tokens_single_batch() {
assert_eq!(
CommandTemplate::new_batch(&["echo", "{.}"]).unwrap(),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::NoExt]),
],
mode: ExecutionMode::Batch,
}
);
}
#[test]
fn tokens_multiple_batch() {
assert!(CommandTemplate::new_batch(&["echo", "{.}", "{}"]).is_err());
}
}
+15 -4
View File
@@ -1,4 +1,15 @@
/// exit code 1 represents a general error
pub const ERROR: i32 = 1;
/// exit code 130 represents a process killed by signal SIGINT
pub const SIGINT: i32 = 130;
pub enum ExitCode {
Success,
GeneralError,
KilledBySigint,
}
impl Into<i32> for ExitCode {
fn into(self) -> i32 {
match self {
ExitCode::Success => 0,
ExitCode::GeneralError => 1,
ExitCode::KilledBySigint => 130,
}
}
}
+41 -11
View File
@@ -7,16 +7,21 @@
// according to those terms.
use std::env::current_dir;
use std::path::{Path, PathBuf};
use std::fs;
use std::io;
#[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use ignore::DirEntry;
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
if path.is_absolute() {
Ok(path.to_path_buf())
} else {
let path = path.strip_prefix(".").unwrap_or(path);
current_dir().map(|path_buf| path_buf.join(path))
return Ok(path.to_path_buf());
}
let path = path.strip_prefix(".").unwrap_or(path);
current_dir().map(|path_buf| path_buf.join(path))
}
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
@@ -27,18 +32,43 @@ pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
path_buf
.as_path()
.to_string_lossy()
.trim_left_matches(r"\\?\"),
).to_path_buf();
.trim_start_matches(r"\\?\"),
)
.to_path_buf();
Ok(path_buf)
}
// Path::is_dir() is not guarandteed to be intuitively correct for "." and ".."
// Path::is_dir() is not guaranteed to be intuitively correct for "." and ".."
// See: https://github.com/rust-lang/rust/issues/45302
pub fn is_dir(path: &Path) -> bool {
if path.file_name().is_some() {
path.is_dir()
path.is_dir() && (path.file_name().is_some() || path.canonicalize().is_ok())
}
#[cfg(any(unix, target_os = "redox"))]
pub fn is_executable(md: &fs::Metadata) -> bool {
md.permissions().mode() & 0o111 != 0
}
#[cfg(windows)]
pub fn is_executable(_: &fs::Metadata) -> bool {
false
}
pub fn is_empty(entry: &DirEntry) -> bool {
if let Some(file_type) = entry.file_type() {
if file_type.is_dir() {
if let Ok(mut entries) = fs::read_dir(entry.path()) {
entries.next().is_none()
} else {
false
}
} else if file_type.is_file() {
entry.metadata().map(|m| m.len() == 0).unwrap_or(false)
} else {
false
}
} else {
path.is_dir() && path.canonicalize().is_ok()
false
}
}
-111
View File
@@ -1,111 +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::collections::HashSet;
use std::process;
use std::time;
use std::io::Write;
use exec::CommandTemplate;
use lscolors::LsColors;
use walk::FileType;
use regex_syntax::{Expr, ExprBuilder};
/// Configuration options for *fd*.
pub struct FdOptions {
/// Whether the search is case-sensitive or case-insensitive.
pub case_sensitive: bool,
/// Whether to search within the full file path or just the base name (filename or directory
/// name).
pub search_full_path: bool,
/// Whether to ignore hidden files and directories (or not).
pub ignore_hidden: bool,
/// Whether to respect ignore files (`.gitignore`, `.ignore`, ..) or not.
pub read_ignore: bool,
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
pub read_gitignore: bool,
/// Whether to follow symlinks or not.
pub follow_links: bool,
/// Whether elements of output should be separated by a null character
pub null_separator: bool,
/// The maximum search depth, or `None` if no maximum search depth should be set.
///
/// A depth of `1` includes all files under the current directory, a depth of `2` also includes
/// all files under subdirectories of the current directory, etc.
pub max_depth: Option<usize>,
/// The number of threads to use.
pub threads: usize,
/// Time to buffer results internally before streaming to the console. This is useful to
/// provide a sorted output, in case the total execution time is shorter than
/// `max_buffer_time`.
pub max_buffer_time: Option<time::Duration>,
/// `None` if the output should not be colorized. Otherwise, a `LsColors` instance that defines
/// how to style different filetypes.
pub ls_colors: Option<LsColors>,
/// The type of file to search for. All files other than the specified type will be ignored.
pub file_types: HashSet<FileType>,
/// The extension to search for. Only entries matching the extension will be included.
///
/// The value (if present) will be a lowercase string without leading dots.
pub extensions: Option<HashSet<String>>,
/// If a value is supplied, each item found will be used to generate and execute commands.
pub command: Option<CommandTemplate>,
/// A list of glob patterns that should be excluded from the search.
pub exclude_patterns: Vec<String>,
}
/// Print error message to stderr and exit with status `1`.
pub fn error(message: &str) -> ! {
writeln!(&mut ::std::io::stderr(), "{}", message).expect("Failed writing to stderr");
process::exit(1);
}
/// Determine if a regex pattern contains a literal uppercase character.
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
ExprBuilder::new()
.parse(pattern)
.map(|expr| expr_has_uppercase_char(&expr))
.unwrap_or(false)
}
/// Determine if a regex expression contains a literal uppercase character.
fn expr_has_uppercase_char(expr: &Expr) -> bool {
match *expr {
Expr::Literal { ref chars, .. } => chars.iter().any(|c| c.is_uppercase()),
Expr::Class(ref ranges) => ranges
.iter()
.any(|r| r.start.is_uppercase() || r.end.is_uppercase()),
Expr::Group { ref e, .. } | Expr::Repeat { ref e, .. } => expr_has_uppercase_char(e),
Expr::Concat(ref es) => es.iter().any(expr_has_uppercase_char),
Expr::Alternate(ref es) => es.iter().any(expr_has_uppercase_char),
_ => false,
}
}
/// Maximum size of the output buffer before flushing results to the console
pub const MAX_BUFFER_LENGTH: usize = 1000;
/// Exit code representing a general error
pub const EXITCODE_ERROR: i32 = 1;
/// Exit code representing that the process was killed by SIGINT
pub const EXITCODE_SIGINT: i32 = 130;
+20
View File
@@ -0,0 +1,20 @@
/// Whether or not to show
pub struct FileTypes {
pub files: bool,
pub directories: bool,
pub symlinks: bool,
pub executables_only: bool,
pub empty_only: bool,
}
impl Default for FileTypes {
fn default() -> FileTypes {
FileTypes {
files: false,
directories: false,
symlinks: false,
executables_only: false,
empty_only: false,
}
}
}
+5
View File
@@ -0,0 +1,5 @@
pub use self::size::SizeFilter;
pub use self::time::TimeFilter;
mod size;
mod time;
+209
View File
@@ -0,0 +1,209 @@
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() };
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SizeFilter {
Max(u64),
Min(u64),
}
// SI prefixes (powers of 10)
const KILO: u64 = 1000;
const MEGA: u64 = KILO * 1000;
const GIGA: u64 = MEGA * 1000;
const TERA: u64 = GIGA * 1000;
// Binary prefixes (powers of 2)
const KIBI: u64 = 1024;
const MEBI: u64 = KIBI * 1024;
const GIBI: u64 = MEBI * 1024;
const TEBI: u64 = GIBI * 1024;
impl SizeFilter {
pub fn from_string(s: &str) -> Option<Self> {
if !SIZE_CAPTURES.is_match(s) {
return None;
}
let captures = SIZE_CAPTURES.captures(s)?;
let limit_kind = captures.get(1).map_or("+", |m| m.as_str());
let quantity = captures
.get(2)
.and_then(|v| v.as_str().parse::<u64>().ok())?;
let multiplier = match &captures.get(3).map_or("b", |m| m.as_str()).to_lowercase()[..] {
v if v.starts_with("ki") => KIBI,
v if v.starts_with("k") => KILO,
v if v.starts_with("mi") => MEBI,
v if v.starts_with("m") => MEGA,
v if v.starts_with("gi") => GIBI,
v if v.starts_with("g") => GIGA,
v if v.starts_with("ti") => TEBI,
v if v.starts_with("t") => TERA,
"b" => 1,
_ => return None,
};
let size = quantity * multiplier;
Some(match limit_kind {
"+" => SizeFilter::Min(size),
_ => SizeFilter::Max(size),
})
}
pub fn is_within(&self, size: u64) -> bool {
match *self {
SizeFilter::Max(limit) => size <= limit,
SizeFilter::Min(limit) => size >= limit,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! gen_size_filter_parse_test {
($($name: ident: $val: expr,)*) => {
$(
#[test]
fn $name() {
let (txt, expected) = $val;
let actual = SizeFilter::from_string(txt).unwrap();
assert_eq!(actual, expected);
}
)*
};
}
// Parsing and size conversion tests data. Ensure that each type gets properly interpreted.
// Call with higher base values to ensure expected multiplication (only need a couple)
gen_size_filter_parse_test! {
byte_plus: ("+1b", SizeFilter::Min(1)),
byte_plus_multiplier: ("+10b", SizeFilter::Min(10)),
byte_minus: ("-1b", SizeFilter::Max(1)),
kilo_plus: ("+1k", SizeFilter::Min(1000)),
kilo_plus_suffix: ("+1kb", SizeFilter::Min(1000)),
kilo_minus: ("-1k", SizeFilter::Max(1000)),
kilo_minus_multiplier: ("-100k", SizeFilter::Max(100000)),
kilo_minus_suffix: ("-1kb", SizeFilter::Max(1000)),
kilo_plus_upper: ("+1K", SizeFilter::Min(1000)),
kilo_plus_suffix_upper: ("+1KB", SizeFilter::Min(1000)),
kilo_minus_upper: ("-1K", SizeFilter::Max(1000)),
kilo_minus_suffix_upper: ("-1Kb", SizeFilter::Max(1000)),
kibi_plus: ("+1ki", SizeFilter::Min(1024)),
kibi_plus_multiplier: ("+10ki", SizeFilter::Min(10240)),
kibi_plus_suffix: ("+1kib", SizeFilter::Min(1024)),
kibi_minus: ("-1ki", SizeFilter::Max(1024)),
kibi_minus_multiplier: ("-100ki", SizeFilter::Max(102400)),
kibi_minus_suffix: ("-1kib", SizeFilter::Max(1024)),
kibi_plus_upper: ("+1KI", SizeFilter::Min(1024)),
kibi_plus_suffix_upper: ("+1KiB", SizeFilter::Min(1024)),
kibi_minus_upper: ("-1Ki", SizeFilter::Max(1024)),
kibi_minus_suffix_upper: ("-1KIB", SizeFilter::Max(1024)),
mega_plus: ("+1m", SizeFilter::Min(1000000)),
mega_plus_suffix: ("+1mb", SizeFilter::Min(1000000)),
mega_minus: ("-1m", SizeFilter::Max(1000000)),
mega_minus_suffix: ("-1mb", SizeFilter::Max(1000000)),
mega_plus_upper: ("+1M", SizeFilter::Min(1000000)),
mega_plus_suffix_upper: ("+1MB", SizeFilter::Min(1000000)),
mega_minus_upper: ("-1M", SizeFilter::Max(1000000)),
mega_minus_suffix_upper: ("-1Mb", SizeFilter::Max(1000000)),
mebi_plus: ("+1mi", SizeFilter::Min(1048576)),
mebi_plus_suffix: ("+1mib", SizeFilter::Min(1048576)),
mebi_minus: ("-1mi", SizeFilter::Max(1048576)),
mebi_minus_suffix: ("-1mib", SizeFilter::Max(1048576)),
mebi_plus_upper: ("+1MI", SizeFilter::Min(1048576)),
mebi_plus_suffix_upper: ("+1MiB", SizeFilter::Min(1048576)),
mebi_minus_upper: ("-1Mi", SizeFilter::Max(1048576)),
mebi_minus_suffix_upper: ("-1MIB", SizeFilter::Max(1048576)),
giga_plus: ("+1g", SizeFilter::Min(1000000000)),
giga_plus_suffix: ("+1gb", SizeFilter::Min(1000000000)),
giga_minus: ("-1g", SizeFilter::Max(1000000000)),
giga_minus_suffix: ("-1gb", SizeFilter::Max(1000000000)),
giga_plus_upper: ("+1G", SizeFilter::Min(1000000000)),
giga_plus_suffix_upper: ("+1GB", SizeFilter::Min(1000000000)),
giga_minus_upper: ("-1G", SizeFilter::Max(1000000000)),
giga_minus_suffix_upper: ("-1Gb", SizeFilter::Max(1000000000)),
gibi_plus: ("+1gi", SizeFilter::Min(1073741824)),
gibi_plus_suffix: ("+1gib", SizeFilter::Min(1073741824)),
gibi_minus: ("-1gi", SizeFilter::Max(1073741824)),
gibi_minus_suffix: ("-1gib", SizeFilter::Max(1073741824)),
gibi_plus_upper: ("+1GI", SizeFilter::Min(1073741824)),
gibi_plus_suffix_upper: ("+1GiB", SizeFilter::Min(1073741824)),
gibi_minus_upper: ("-1Gi", SizeFilter::Max(1073741824)),
gibi_minus_suffix_upper: ("-1GIB", SizeFilter::Max(1073741824)),
tera_plus: ("+1t", SizeFilter::Min(1000000000000)),
tera_plus_suffix: ("+1tb", SizeFilter::Min(1000000000000)),
tera_minus: ("-1t", SizeFilter::Max(1000000000000)),
tera_minus_suffix: ("-1tb", SizeFilter::Max(1000000000000)),
tera_plus_upper: ("+1T", SizeFilter::Min(1000000000000)),
tera_plus_suffix_upper: ("+1TB", SizeFilter::Min(1000000000000)),
tera_minus_upper: ("-1T", SizeFilter::Max(1000000000000)),
tera_minus_suffix_upper: ("-1Tb", SizeFilter::Max(1000000000000)),
tebi_plus: ("+1ti", SizeFilter::Min(1099511627776)),
tebi_plus_suffix: ("+1tib", SizeFilter::Min(1099511627776)),
tebi_minus: ("-1ti", SizeFilter::Max(1099511627776)),
tebi_minus_suffix: ("-1tib", SizeFilter::Max(1099511627776)),
tebi_plus_upper: ("+1TI", SizeFilter::Min(1099511627776)),
tebi_plus_suffix_upper: ("+1TiB", SizeFilter::Min(1099511627776)),
tebi_minus_upper: ("-1Ti", SizeFilter::Max(1099511627776)),
tebi_minus_suffix_upper: ("-1TIB", SizeFilter::Max(1099511627776)),
}
/// Invalid parse testing
macro_rules! gen_size_filter_failure {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
let i = SizeFilter::from_string($value);
assert!(i.is_none());
}
)*
};
}
// Invalid parse data
gen_size_filter_failure! {
ensure_missing_symbol_returns_none: "10M",
ensure_missing_number_returns_none: "+g",
ensure_missing_unit_returns_none: "+18",
ensure_bad_format_returns_none_1: "$10M",
ensure_bad_format_returns_none_2: "badval",
ensure_bad_format_returns_none_3: "9999",
ensure_invalid_unit_returns_none_1: "+50a",
ensure_invalid_unit_returns_none_2: "-10v",
ensure_invalid_unit_returns_none_3: "+1Mv",
ensure_bib_format_returns_none: "+1bib",
ensure_bb_format_returns_none: "+1bb",
}
#[test]
fn is_within_less_than() {
let f = SizeFilter::from_string("-1k").unwrap();
assert!(f.is_within(999));
}
#[test]
fn is_within_less_than_equal() {
let f = SizeFilter::from_string("-1k").unwrap();
assert!(f.is_within(1000));
}
#[test]
fn is_within_greater_than() {
let f = SizeFilter::from_string("+1k").unwrap();
assert!(f.is_within(1001));
}
#[test]
fn is_within_greater_than_equal() {
let f = SizeFilter::from_string("+1K").unwrap();
assert!(f.is_within(1000));
}
}
+79
View File
@@ -0,0 +1,79 @@
use std::time::SystemTime;
/// Filter based on time ranges.
#[derive(Debug, PartialEq)]
pub enum TimeFilter {
Before(SystemTime),
After(SystemTime),
}
impl TimeFilter {
fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
humantime::parse_duration(s)
.map(|duration| *ref_time - duration)
.or_else(|_| humantime::parse_rfc3339_weak(s))
.ok()
}
pub fn before(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
TimeFilter::from_str(ref_time, s).map(TimeFilter::Before)
}
pub fn after(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
TimeFilter::from_str(ref_time, s).map(TimeFilter::After)
}
pub fn applies_to(&self, t: &SystemTime) -> bool {
match self {
TimeFilter::Before(limit) => t <= limit,
TimeFilter::After(limit) => t >= limit,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn is_time_filter_applicable() {
let ref_time = humantime::parse_rfc3339("2010-10-10T10:10:10Z").unwrap();
assert!(TimeFilter::after(&ref_time, "1min")
.unwrap()
.applies_to(&ref_time));
assert!(!TimeFilter::before(&ref_time, "1min")
.unwrap()
.applies_to(&ref_time));
let t1m_ago = ref_time - Duration::from_secs(60);
assert!(!TimeFilter::after(&ref_time, "30sec")
.unwrap()
.applies_to(&t1m_ago));
assert!(TimeFilter::after(&ref_time, "2min")
.unwrap()
.applies_to(&t1m_ago));
assert!(TimeFilter::before(&ref_time, "30sec")
.unwrap()
.applies_to(&t1m_ago));
assert!(!TimeFilter::before(&ref_time, "2min")
.unwrap()
.applies_to(&t1m_ago));
let t10s_before = "2010-10-10 10:10:00";
assert!(!TimeFilter::before(&ref_time, t10s_before)
.unwrap()
.applies_to(&ref_time));
assert!(TimeFilter::before(&ref_time, t10s_before)
.unwrap()
.applies_to(&t1m_ago));
assert!(TimeFilter::after(&ref_time, t10s_before)
.unwrap()
.applies_to(&ref_time));
assert!(!TimeFilter::after(&ref_time, t10s_before)
.unwrap()
.applies_to(&t1m_ago));
}
}
+197
View File
@@ -0,0 +1,197 @@
// 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::borrow::Cow;
use std::ffi::{OsStr, OsString};
use regex_syntax::hir::Hir;
use regex_syntax::ParserBuilder;
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);
};
}
#[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()),
}
}
/// 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,
}
}
/// 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);
}
}
+81
View File
@@ -0,0 +1,81 @@
use crate::exec::CommandTemplate;
use crate::internal::{
filter::{SizeFilter, TimeFilter},
FileTypes,
};
use lscolors::LsColors;
use regex::bytes::RegexSet;
use std::{path::PathBuf, sync::Arc, time::Duration};
/// Configuration options for *fd*.
pub struct FdOptions {
/// Whether the search is case-sensitive or case-insensitive.
pub case_sensitive: bool,
/// Whether to search within the full file path or just the base name (filename or directory
/// name).
pub search_full_path: bool,
/// Whether to ignore hidden files and directories (or not).
pub ignore_hidden: bool,
/// Whether to respect `.fdignore` files or not.
pub read_fdignore: bool,
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
pub read_vcsignore: bool,
/// Whether to follow symlinks or not.
pub follow_links: bool,
/// Whether elements of output should be separated by a null character
pub null_separator: bool,
/// The maximum search depth, or `None` if no maximum search depth should be set.
///
/// A depth of `1` includes all files under the current directory, a depth of `2` also includes
/// all files under subdirectories of the current directory, etc.
pub max_depth: Option<usize>,
/// The number of threads to use.
pub threads: usize,
/// Time to buffer results internally before streaming to the console. This is useful to
/// provide a sorted output, in case the total execution time is shorter than
/// `max_buffer_time`.
pub max_buffer_time: Option<Duration>,
/// `None` if the output should not be colorized. Otherwise, a `LsColors` instance that defines
/// how to style different filetypes.
pub ls_colors: Option<LsColors>,
/// The type of file to search for. If set to `None`, all file types are displayed. If
/// set to `Some(..)`, only the types that are specified are shown.
pub file_types: Option<FileTypes>,
/// The extension to search for. Only entries matching the extension will be included.
///
/// The value (if present) will be a lowercase string without leading dots.
pub extensions: Option<RegexSet>,
/// If a value is supplied, each item found will be used to generate and execute commands.
pub command: Option<Arc<CommandTemplate>>,
/// A list of glob patterns that should be excluded from the search.
pub exclude_patterns: Vec<String>,
/// A list of custom ignore files.
pub ignore_files: Vec<PathBuf>,
/// The given constraints on the size of returned files
pub size_constraints: Vec<SizeFilter>,
/// Constraints on last modification time of files
pub time_constraints: Vec<TimeFilter>,
/// Whether or not to display filesystem errors
pub show_filesystem_errors: bool,
/// The separator used to print file paths.
pub path_separator: Option<String>,
}
-232
View File
@@ -1,232 +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.
/// A parser for the `LS_COLORS` environment variable.
use std::collections::HashMap;
use ansi_term::{Colour, Style};
/// Maps file extensions to ANSI colors / styles.
pub type ExtensionStyles = HashMap<String, Style>;
/// Maps filenames to ANSI colors / styles.
pub type FilenameStyles = HashMap<String, Style>;
const LS_CODES: &[&str] = &[
"no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi", "so", "bd", "bd", "cd",
"cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec", "su", "su", "sg", "sg", "st", "ow", "ow",
"tw", "tw", "ca", "mh", "cl",
];
/// Defines how different file system entries should be colorized / styled.
#[derive(Debug, PartialEq)]
pub struct LsColors {
/// ANSI Style for directories.
pub directory: Style,
/// ANSI style for symbolic links.
pub symlink: Style,
/// ANSI style for executable files.
pub executable: Style,
/// A map that defines ANSI styles for different file extensions.
pub extensions: ExtensionStyles,
/// A map that defines ANSI styles for different specific filenames.
pub filenames: FilenameStyles,
}
impl Default for LsColors {
/// Get a default LsColors structure.
fn default() -> LsColors {
LsColors {
directory: Colour::Blue.bold(),
symlink: Colour::Cyan.normal(),
executable: Colour::Red.bold(),
extensions: HashMap::new(),
filenames: HashMap::new(),
}
}
}
impl LsColors {
/// Parse a single text-decoration code (normal, bold, italic, ...).
fn parse_decoration(code: &str) -> Option<fn(Colour) -> Style> {
match code {
"0" | "00" => Some(Colour::normal),
"1" | "01" => Some(Colour::bold),
"3" | "03" => Some(Colour::italic),
"4" | "04" => Some(Colour::underline),
_ => None,
}
}
/// Parse ANSI escape sequences like `38;5;10;1`.
fn parse_style(code: &str) -> Option<Style> {
let mut split = code.split(';');
if let Some(first) = split.next() {
// Try to match the first part as a text-decoration argument
let mut decoration = LsColors::parse_decoration(first);
let c1 = if decoration.is_none() {
Some(first)
} else {
split.next()
};
let c2 = split.next();
let c3 = split.next();
let color = if c1 == Some("38") && c2 == Some("5") {
let n_white = 7;
let n = if let Some(num) = c3 {
u8::from_str_radix(num, 10).unwrap_or(n_white)
} else {
n_white
};
Colour::Fixed(n)
} else if let Some(color_s) = c1 {
match color_s {
"30" => Colour::Black,
"31" => Colour::Red,
"32" => Colour::Green,
"33" => Colour::Yellow,
"34" => Colour::Blue,
"35" => Colour::Purple,
"36" => Colour::Cyan,
_ => Colour::White,
}
} else {
Colour::White
};
if decoration.is_none() {
// Try to find a decoration somewhere in the sequence
decoration = code.split(';').flat_map(LsColors::parse_decoration).next();
}
let ansi_style = decoration.unwrap_or(Colour::normal)(color);
Some(ansi_style)
} else {
None
}
}
/// Add a new `LS_COLORS` entry.
fn add_entry(&mut self, input: &str) {
let mut parts = input.trim().split('=');
if let Some(pattern) = parts.next() {
if let Some(style_code) = parts.next() {
// Ensure that the input was split into exactly two parts:
if !parts.next().is_none() {
return;
}
if let Some(style) = LsColors::parse_style(style_code) {
// Try to match against one of the known codes
let res = LS_CODES.iter().find(|&&c| c == pattern);
if let Some(code) = res {
match code.as_ref() {
"di" => self.directory = style,
"ln" => self.symlink = style,
"ex" => self.executable = style,
_ => return,
}
} else if pattern.starts_with("*.") {
let extension = String::from(pattern).split_off(2);
self.extensions.insert(extension, style);
} else if pattern.starts_with('*') {
let filename = String::from(pattern).split_off(1);
self.filenames.insert(filename, style);
} else {
// Unknown/corrupt pattern
return;
}
}
}
}
}
/// Generate a `LsColors` structure from a string.
pub fn from_string(input: &str) -> LsColors {
let mut lscolors = LsColors::default();
for s in input.split(':') {
lscolors.add_entry(s);
}
lscolors
}
}
#[test]
fn test_parse_simple() {
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31"));
}
#[test]
fn test_parse_decoration() {
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("00;31"));
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("03;34"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("01;36"));
}
#[test]
fn test_parse_decoration_backwards() {
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("34;03"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("36;01"));
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31;00"));
}
#[test]
fn test_parse_256() {
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("38;5;115")
);
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("00;38;5;115")
);
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("01;38;5;119")
);
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("38;5;119;01")
);
}
#[test]
fn test_from_string() {
assert_eq!(LsColors::default(), LsColors::from_string(&String::new()));
let result = LsColors::from_string(&String::from(
"rs=0:di=03;34:ln=01;36:*.foo=01;35:*README=33",
));
assert_eq!(Colour::Blue.italic(), result.directory);
assert_eq!(Colour::Cyan.bold(), result.symlink);
assert_eq!(Some(&Colour::Purple.bold()), result.extensions.get("foo"));
assert_eq!(
Some(&Colour::Yellow.normal()),
result.filenames.get("README")
);
}
+175 -64
View File
@@ -6,46 +6,43 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
extern crate ansi_term;
extern crate atty;
#[macro_use]
extern crate clap;
extern crate ignore;
#[macro_use]
extern crate lazy_static;
#[cfg(all(unix, not(target_os = "redox")))]
extern crate libc;
extern crate num_cpus;
extern crate regex;
extern crate regex_syntax;
mod internal;
pub mod fshelper;
pub mod lscolors;
mod app;
mod exec;
mod internal;
mod exit_codes;
pub mod fshelper;
mod output;
mod walk;
#[cfg(windows)]
mod windows;
use std::env;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::process;
use std::sync::Arc;
use std::time;
use atty::Stream;
use regex::RegexBuilder;
use exec::CommandTemplate;
use internal::{error, pattern_has_uppercase_char, FdOptions};
use globset::Glob;
use lscolors::LsColors;
use walk::FileType;
use regex::bytes::{RegexBuilder, RegexSetBuilder};
use crate::exec::CommandTemplate;
use crate::internal::{
filter::{SizeFilter, TimeFilter},
opts::FdOptions,
pattern_has_uppercase_char, transform_args_with_exec, FileTypes,
};
// We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481
#[cfg(all(not(windows), not(target_env = "musl")))]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
fn main() {
let matches = app::build_app().get_matches();
let checked_args = transform_args_with_exec(env::args_os());
let matches = app::build_app().get_matches_from(checked_args);
// Get the search pattern
let pattern = matches.value_of("pattern").unwrap_or("");
@@ -53,19 +50,22 @@ fn main() {
// Get the current working directory
let current_dir = Path::new(".");
if !fshelper::is_dir(current_dir) {
error("Error: could not get current directory.");
print_error_and_exit!("Could not get current directory.");
}
//Get one or more root directories to search.
let mut dir_vec: Vec<_> = match matches.values_of("path") {
// 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) {
error(&format!(
"Error: '{}' is not a directory.",
print_error_and_exit!(
"'{}' is not a directory.",
path_buffer.to_string_lossy()
));
);
}
path_buffer
})
@@ -76,14 +76,51 @@ fn main() {
if matches.is_present("absolute-path") {
dir_vec = dir_vec
.iter()
.map(|path_buffer| fshelper::absolute_path(path_buffer).unwrap())
.map(|path_buffer| {
path_buffer
.canonicalize()
.and_then(|pb| fshelper::absolute_path(pb.as_path()))
.unwrap()
})
.collect();
}
// Detect if the user accidentally supplied a path instead of a search pattern
if !matches.is_present("full-path")
&& pattern.contains(std::path::MAIN_SEPARATOR)
&& fshelper::is_dir(Path::new(pattern))
{
print_error_and_exit!(
"The search pattern '{pattern}' contains a path-separation character ('{sep}') \
and will not lead to any search results.\n\n\
If you want to search for all files inside the '{pattern}' directory, use a match-all pattern:\n\n \
fd . '{pattern}'\n\n\
Instead, if you want to search for the pattern in the full path, use:\n\n \
fd --full-path '{pattern}'",
pattern = pattern,
sep = std::path::MAIN_SEPARATOR,
);
}
let pattern_regex = if matches.is_present("glob") {
let glob = match Glob::new(pattern) {
Ok(glob) => glob,
Err(e) => {
print_error_and_exit!("{}", e);
}
};
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)
};
// The search will be case-sensitive if the command line flag is set or
// if the pattern has an uppercase character (smart case).
let case_sensitive = !matches.is_present("ignore-case")
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(pattern));
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
let colored_output = match matches.value_of("color") {
Some("always") => true,
@@ -91,36 +128,73 @@ fn main() {
_ => atty::is(Stream::Stdout),
};
let path_separator = matches.value_of("path-separator").map(|str| str.to_owned());
#[cfg(windows)]
let colored_output = colored_output && windows::enable_colored_output();
let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok();
let ls_colors = if colored_output {
Some(
env::var("LS_COLORS")
.ok()
.map(|val| LsColors::from_string(&val))
.unwrap_or_default(),
)
Some(LsColors::from_env().unwrap_or_default())
} else {
None
};
let command = matches.values_of("exec").map(CommandTemplate::new);
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 size_limits: Vec<SizeFilter> = matches
.values_of("size")
.map(|v| {
v.map(|sf| {
if let Some(f) = SizeFilter::from_string(sf) {
return f;
}
print_error_and_exit!("'{}' is not a valid size constraint. See 'fd --help'.", sf);
})
.collect()
})
.unwrap_or_else(|| vec![]);
let now = time::SystemTime::now();
let mut time_constraints: Vec<TimeFilter> = Vec::new();
if let Some(t) = matches.value_of("changed-within") {
if let Some(f) = TimeFilter::after(&now, t) {
time_constraints.push(f);
} else {
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
}
}
if let Some(t) = matches.value_of("changed-before") {
if let Some(f) = TimeFilter::before(&now, t) {
time_constraints.push(f);
} else {
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
}
}
let config = FdOptions {
case_sensitive,
search_full_path: matches.is_present("full-path"),
ignore_hidden: !(matches.is_present("hidden")
|| matches.occurrences_of("rg-alias-hidden-ignore") >= 2),
read_ignore: !(matches.is_present("no-ignore")
read_fdignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")),
read_gitignore: !(matches.is_present("no-ignore")
read_vcsignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")
|| matches.is_present("no-ignore-vcs")),
follow_links: matches.is_present("follow"),
null_separator: matches.is_present("null_separator"),
max_depth: matches
.value_of("depth")
.or_else(|| matches.value_of("rg-depth"))
.and_then(|n| usize::from_str_radix(n, 10).ok()),
threads: std::cmp::max(
matches
@@ -134,39 +208,76 @@ fn main() {
.and_then(|n| u64::from_str_radix(n, 10).ok())
.map(time::Duration::from_millis),
ls_colors,
file_types: match matches.values_of("file-type") {
None => vec![
FileType::RegularFile,
FileType::Directory,
FileType::SymLink,
].into_iter()
.collect(),
Some(values) => values
.map(|value| match value {
"f" | "file" => FileType::RegularFile,
"d" | "directory" => FileType::Directory,
"l" | "symlink" => FileType::SymLink,
_ => FileType::RegularFile,
})
.collect(),
},
extensions: matches.values_of("extension").map(|exts| {
exts.map(|e| e.trim_left_matches('.').to_lowercase())
.collect()
file_types: matches.values_of("file-type").map(|values| {
let mut file_types = FileTypes::default();
for value in values {
match value {
"f" | "file" => file_types.files = true,
"d" | "directory" => file_types.directories = true,
"l" | "symlink" => file_types.symlinks = true,
"x" | "executable" => {
file_types.executables_only = true;
file_types.files = true;
}
"e" | "empty" => {
file_types.empty_only = true;
}
_ => unreachable!(),
}
}
// If only 'empty' was specified, search for both files and directories:
if file_types.empty_only && !(file_types.files || file_types.directories) {
file_types.files = true;
file_types.directories = true;
}
file_types
}),
command,
extensions: matches.values_of("extension").map(|exts| {
let patterns = exts
.map(|e| e.trim_start_matches('.'))
.map(|e| format!(r".\.{}$", regex::escape(e)));
match RegexSetBuilder::new(patterns)
.case_insensitive(true)
.build()
{
Ok(re) => re,
Err(err) => {
print_error_and_exit!("{}", err.description());
}
}
}),
command: command.map(Arc::new),
exclude_patterns: matches
.values_of("exclude")
.map(|v| v.map(|p| String::from("!") + p).collect())
.unwrap_or_else(|| vec![]),
ignore_files: matches
.values_of("ignore-file")
.map(|vs| vs.map(PathBuf::from).collect())
.unwrap_or_else(|| vec![]),
size_constraints: size_limits,
time_constraints,
show_filesystem_errors: matches.is_present("show-errors"),
path_separator,
};
match RegexBuilder::new(pattern)
match 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)),
Err(err) => error(err.description()),
Ok(re) => {
let exit_code = walk::scan(&dir_vec, Arc::new(re), Arc::new(config));
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()
);
}
}
}
+61 -85
View File
@@ -6,36 +6,64 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
use internal::{FdOptions, EXITCODE_ERROR, EXITCODE_SIGINT};
use lscolors::LsColors;
use crate::exit_codes::ExitCode;
use crate::internal::opts::FdOptions;
use lscolors::{LsColors, Style};
use std::{fs, process};
use std::io::{self, Write};
use std::ops::Deref;
use std::path::{self, Component, Path, PathBuf};
use std::sync::Arc;
use std::borrow::Cow;
use std::io::{self, StdoutLock, Write};
use std::path::{Component, Path, PathBuf};
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::PermissionsExt;
use std::sync::Arc;
use ansi_term;
pub fn print_entry(entry: &PathBuf, config: &FdOptions, wants_to_quit: &Arc<AtomicBool>) {
let path = entry.strip_prefix(".").unwrap_or(entry);
/// 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()
}
pub fn print_entry(
stdout: &mut StdoutLock,
entry: &PathBuf,
config: &FdOptions,
wants_to_quit: &Arc<AtomicBool>,
) {
let path = if entry.is_absolute() {
entry.as_path()
} else {
strip_current_dir(entry)
};
let r = if let Some(ref ls_colors) = config.ls_colors {
print_entry_colorized(path, config, ls_colors, &wants_to_quit)
print_entry_colorized(stdout, path, config, ls_colors, &wants_to_quit)
} else {
print_entry_uncolorized(path, config)
print_entry_uncolorized(stdout, path, config)
};
if r.is_err() {
// Probably a broken pipe. Exit gracefully.
process::exit(EXITCODE_ERROR);
process::exit(ExitCode::GeneralError.into());
}
}
fn replace_path_separator<'a>(config: &FdOptions, path: &mut Cow<'a, str>) {
match &config.path_separator {
None => {}
Some(sep) => {
*path.to_mut() = path.replace(std::path::MAIN_SEPARATOR, &sep);
}
}
}
fn print_entry_colorized(
stdout: &mut StdoutLock,
path: &Path,
config: &FdOptions,
ls_colors: &LsColors,
@@ -43,89 +71,37 @@ fn print_entry_colorized(
) -> io::Result<()> {
let default_style = ansi_term::Style::default();
let stdout = io::stdout();
let mut handle = stdout.lock();
// Separator to use before the current component.
let mut separator = String::new();
// Full path to the current component.
let mut component_path = PathBuf::new();
// Traverse the path and colorize each component
for component in path.components() {
let comp_str = component.as_os_str().to_string_lossy();
component_path.push(Path::new(comp_str.deref()));
for (component, style) in ls_colors.style_for_path_components(path) {
let style = style
.map(Style::to_ansi_term_style)
.unwrap_or(default_style);
let style = get_path_style(&component_path, ls_colors).unwrap_or(&default_style);
write!(handle, "{}{}", separator, style.paint(comp_str))?;
// Determine separator to print before next component.
separator = match component {
// Prefix needs no separator, as it is always followed by RootDir.
Component::Prefix(_) => String::new(),
// RootDir is already a separator.
Component::RootDir => String::new(),
// Everything else uses a separator that is painted the same way as the component.
_ => style.paint(path::MAIN_SEPARATOR.to_string()).to_string(),
};
let mut path_string = component.to_string_lossy();
replace_path_separator(&config, &mut path_string);
write!(stdout, "{}", style.paint(path_string))?;
if wants_to_quit.load(Ordering::Relaxed) {
write!(handle, "\n")?;
process::exit(EXITCODE_SIGINT);
writeln!(stdout)?;
process::exit(ExitCode::KilledBySigint.into());
}
}
if config.null_separator {
write!(handle, "\0")
write!(stdout, "\0")
} else {
writeln!(handle, "")
writeln!(stdout)
}
}
fn print_entry_uncolorized(path: &Path, config: &FdOptions) -> io::Result<()> {
fn print_entry_uncolorized(
stdout: &mut StdoutLock,
path: &Path,
config: &FdOptions,
) -> io::Result<()> {
let separator = if config.null_separator { "\0" } else { "\n" };
let path_str = path.to_string_lossy();
write!(&mut io::stdout(), "{}{}", path_str, separator)
}
fn get_path_style<'a>(path: &Path, ls_colors: &'a LsColors) -> Option<&'a ansi_term::Style> {
if path.symlink_metadata()
.map(|md| md.file_type().is_symlink())
.unwrap_or(false)
{
return Some(&ls_colors.symlink);
}
let metadata = path.metadata();
if metadata.as_ref().map(|md| md.is_dir()).unwrap_or(false) {
Some(&ls_colors.directory)
} else if metadata.map(|md| is_executable(&md)).unwrap_or(false) {
Some(&ls_colors.executable)
} else if let Some(filename_style) = path.file_name()
.and_then(|n| n.to_str())
.and_then(|n| ls_colors.filenames.get(n))
{
Some(filename_style)
} else if let Some(extension_style) = path.extension()
.and_then(|e| e.to_str())
.and_then(|e| ls_colors.extensions.get(e))
{
Some(extension_style)
} else {
None
}
}
#[cfg(any(unix, target_os = "redox"))]
fn is_executable(md: &fs::Metadata) -> bool {
md.permissions().mode() & 0o111 != 0
}
#[cfg(windows)]
fn is_executable(_: &fs::Metadata) -> bool {
false
let mut path_str = path.to_string_lossy();
replace_path_separator(&config, &mut path_str);
write!(stdout, "{}{}", path_str, separator)
}
+255 -121
View File
@@ -6,24 +6,27 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
extern crate ctrlc;
use crate::exec;
use crate::exit_codes::ExitCode;
use crate::fshelper;
use crate::internal::{opts::FdOptions, osstr_to_bytes, MAX_BUFFER_LENGTH};
use crate::output;
use exec;
use fshelper;
use internal::{error, FdOptions, EXITCODE_SIGINT, MAX_BUFFER_LENGTH};
use output;
use std::process;
use std::borrow::Cow;
use std::error::Error;
use std::ffi::OsStr;
use std::io;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::channel;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time;
use ignore::{self, WalkBuilder};
use ignore::overrides::OverrideBuilder;
use regex::Regex;
use ignore::{self, WalkBuilder};
use regex::bytes::Regex;
/// The receiver thread can either be buffering results or directly streaming to the console.
enum ReceiverMode {
@@ -35,12 +38,10 @@ enum ReceiverMode {
Streaming,
}
/// The types of file to search for.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum FileType {
RegularFile,
Directory,
SymLink,
/// The Worker threads can result in a valid entry having PathBuf or an error.
pub enum WorkerResult {
Entry(PathBuf),
Error(ignore::Error),
}
/// Recursively scan the given search path for files / pathnames matching the pattern.
@@ -48,86 +49,137 @@ pub enum FileType {
/// 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<FdOptions>) -> ExitCode {
let mut path_iter = path_vec.iter();
let first_path_buf = path_iter
.next()
.expect("Error: Path vector can not be empty");
let (tx, rx) = channel();
let threads = config.threads;
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
for pattern in &config.exclude_patterns {
let res = override_builder.add(pattern);
if res.is_err() {
error(&format!("Error: malformed exclude pattern '{}'", pattern));
print_error_and_exit!("Malformed exclude pattern '{}'", pattern);
}
}
let overrides = override_builder.build().unwrap_or_else(|_| {
error("Mismatch in exclude patterns");
print_error_and_exit!("Mismatch in exclude patterns");
});
let mut walker = WalkBuilder::new(first_path_buf.as_path());
walker
.hidden(config.ignore_hidden)
.ignore(config.read_ignore)
.git_ignore(config.read_gitignore)
.parents(config.read_ignore || config.read_gitignore)
.git_global(config.read_gitignore)
.git_exclude(config.read_gitignore)
.ignore(config.read_fdignore)
.parents(config.read_fdignore || config.read_vcsignore)
.git_ignore(config.read_vcsignore)
.git_global(config.read_vcsignore)
.git_exclude(config.read_vcsignore)
.overrides(overrides)
.follow_links(config.follow_links)
.max_depth(config.max_depth);
if config.read_fdignore {
walker.add_custom_ignore_filename(".fdignore");
}
for ignore_file in &config.ignore_files {
let result = walker.add_ignore(ignore_file);
match result {
Some(ignore::Error::Partial(_)) => (),
Some(err) => {
print_error!(
"{}",
format!(
"Malformed pattern in custom ignore file '{}': {}.",
ignore_file.to_string_lossy(),
err.description()
)
);
}
None => (),
}
}
for path_entry in path_iter {
walker.add(path_entry.as_path());
}
let parallel_walker = walker.threads(threads).build_parallel();
let parallel_walker = walker.threads(config.threads).build_parallel();
let wants_to_quit = Arc::new(AtomicBool::new(false));
let receiver_wtq = Arc::clone(&wants_to_quit);
let sender_wtq = Arc::clone(&wants_to_quit);
if config.ls_colors.is_some() {
let wq = Arc::clone(&receiver_wtq);
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);
}).unwrap();
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();
}
// Spawn the thread that receives all results through the channel.
let rx_config = Arc::clone(&config);
let receiver_thread = thread::spawn(move || {
let receiver_thread = spawn_receiver(&config, &wants_to_quit, rx);
// Spawn the sender threads.
spawn_senders(&config, &wants_to_quit, pattern, parallel_walker, tx);
// Wait for the receiver thread to print out all results.
let exit_code = receiver_thread.join().unwrap();
if wants_to_quit.load(Ordering::Relaxed) {
process::exit(ExitCode::KilledBySigint.into());
}
exit_code
}
fn spawn_receiver(
config: &Arc<FdOptions>,
wants_to_quit: &Arc<AtomicBool>,
rx: Receiver<WorkerResult>,
) -> thread::JoinHandle<ExitCode> {
let config = Arc::clone(config);
let wants_to_quit = Arc::clone(wants_to_quit);
let show_filesystem_errors = config.show_filesystem_errors;
let threads = config.threads;
thread::spawn(move || {
// This will be set to `Some` if the `--exec` argument was supplied.
if let Some(ref cmd) = rx_config.command {
let shared_rx = Arc::new(Mutex::new(rx));
if let Some(ref cmd) = config.command {
if cmd.in_batch_mode() {
exec::batch(rx, cmd, show_filesystem_errors)
} else {
let shared_rx = Arc::new(Mutex::new(rx));
let out_perm = Arc::new(Mutex::new(()));
let out_perm = Arc::new(Mutex::new(()));
// TODO: the following line is a workaround to replace the `unsafe` block that was
// previously used here to avoid the (unnecessary?) cloning of the command. The
// `unsafe` block caused problems on some platforms (SIGILL instructions on Linux) and
// therefore had to be removed.
let cmd = Arc::new(cmd.clone());
// Each spawned job will store it's thread handle in here.
let mut handles = Vec::with_capacity(threads);
for _ in 0..threads {
let rx = Arc::clone(&shared_rx);
let cmd = Arc::clone(cmd);
let out_perm = Arc::clone(&out_perm);
// Each spawned job will store it's thread handle in here.
let mut handles = Vec::with_capacity(threads);
for _ in 0..threads {
let rx = Arc::clone(&shared_rx);
let cmd = Arc::clone(&cmd);
let out_perm = Arc::clone(&out_perm);
// Spawn a job thread that will listen for and execute inputs.
let handle =
thread::spawn(move || exec::job(rx, cmd, out_perm, show_filesystem_errors));
// Spawn a job thread that will listen for and execute inputs.
let handle = thread::spawn(move || exec::job(rx, cmd, out_perm));
// Push the handle of the spawned thread into the vector for later joining.
handles.push(handle);
}
// Push the handle of the spawned thread into the vector for later joining.
handles.push(handle);
}
// Wait for all threads to exit before exiting the program.
for h in handles {
h.join().unwrap();
}
// Wait for all threads to exit before exiting the program.
for h in handles {
h.join().unwrap();
ExitCode::Success
}
} else {
let start = time::Instant::now();
@@ -138,31 +190,48 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
let mut mode = ReceiverMode::Buffering;
// Maximum time to wait before we start streaming to the console.
let max_buffer_time = rx_config
let max_buffer_time = config
.max_buffer_time
.unwrap_or_else(|| time::Duration::from_millis(100));
for value in rx {
match mode {
ReceiverMode::Buffering => {
buffer.push(value);
let stdout = io::stdout();
let mut stdout = stdout.lock();
// Have we reached the maximum buffer size or maximum buffering time?
if buffer.len() > MAX_BUFFER_LENGTH
|| time::Instant::now() - start > max_buffer_time
{
// Flush the buffer
for v in &buffer {
output::print_entry(v, &rx_config, &receiver_wtq);
for worker_result in rx {
match worker_result {
WorkerResult::Entry(value) => {
match mode {
ReceiverMode::Buffering => {
buffer.push(value);
// Have we reached the maximum buffer size or maximum buffering time?
if buffer.len() > MAX_BUFFER_LENGTH
|| time::Instant::now() - start > max_buffer_time
{
// Flush the buffer
for v in &buffer {
output::print_entry(
&mut stdout,
v,
&config,
&wants_to_quit,
);
}
buffer.clear();
// Start streaming
mode = ReceiverMode::Streaming;
}
}
ReceiverMode::Streaming => {
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
}
buffer.clear();
// Start streaming
mode = ReceiverMode::Streaming;
}
}
ReceiverMode::Streaming => {
output::print_entry(&value, &rx_config, &receiver_wtq);
WorkerResult::Error(err) => {
if show_filesystem_errors {
print_error!("{}", err);
}
}
}
}
@@ -172,18 +241,27 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
if !buffer.is_empty() {
buffer.sort();
for value in buffer {
output::print_entry(&value, &rx_config, &receiver_wtq);
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
}
}
}
});
// Spawn the sender threads.
ExitCode::Success
}
})
}
fn spawn_senders(
config: &Arc<FdOptions>,
wants_to_quit: &Arc<AtomicBool>,
pattern: Arc<Regex>,
parallel_walker: ignore::WalkParallel,
tx: Sender<WorkerResult>,
) {
parallel_walker.run(|| {
let config = Arc::clone(&config);
let config = Arc::clone(config);
let pattern = Arc::clone(&pattern);
let tx_thread = tx.clone();
let wants_to_quit = Arc::clone(&sender_wtq);
let wants_to_quit = Arc::clone(wants_to_quit);
Box::new(move |entry_o| {
if wants_to_quit.load(Ordering::Relaxed) {
@@ -192,64 +270,120 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
let entry = match entry_o {
Ok(e) => e,
Err(_) => return ignore::WalkState::Continue,
Err(err) => {
tx_thread.send(WorkerResult::Error(err)).unwrap();
return ignore::WalkState::Continue;
}
};
let entry_path = entry.path();
if entry.depth() == 0 {
return ignore::WalkState::Continue;
}
// Filter out unwanted file types.
if (entry.file_type().map_or(false, |ft| ft.is_file())
&& !config.file_types.contains(&FileType::RegularFile))
|| (entry.file_type().map_or(false, |ft| ft.is_dir())
&& !config.file_types.contains(&FileType::Directory))
|| (entry.file_type().map_or(false, |ft| ft.is_symlink())
&& !config.file_types.contains(&FileType::SymLink))
{
// 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 {
match fshelper::path_absolute_form(entry_path) {
Ok(path_abs_buf) => Cow::Owned(path_abs_buf.as_os_str().to_os_string()),
Err(_) => {
print_error_and_exit!("Unable to retrieve absolute path.");
}
}
} 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(&osstr_to_bytes(search_str.as_ref())) {
return ignore::WalkState::Continue;
}
// Filter out unwanted extensions.
if let Some(ref filter_exts) = config.extensions {
let entry_ext = entry_path
.extension()
.map(|e| e.to_string_lossy().to_lowercase());
if entry_ext.map_or(true, |ext| !filter_exts.contains(&ext)) {
if let Some(ref exts_regex) = config.extensions {
if let Some(path_str) = entry_path.file_name() {
if !exts_regex.is_match(&osstr_to_bytes(path_str)) {
return ignore::WalkState::Continue;
}
} else {
return ignore::WalkState::Continue;
}
}
let search_str_o = if config.search_full_path {
match fshelper::path_absolute_form(entry_path) {
Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),
Err(_) => error("Error: unable to get full path."),
}
} else {
entry_path.file_name().map(|f| f.to_string_lossy())
};
if let Some(search_str) = search_str_o {
if pattern.is_match(&*search_str) {
// TODO: take care of the unwrap call
tx_thread.send(entry_path.to_owned()).unwrap()
// 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.
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() {
if let Ok(metadata) = entry_path.metadata() {
let file_size = metadata.len();
if config
.size_constraints
.iter()
.any(|sc| !sc.is_within(file_size))
{
return ignore::WalkState::Continue;
}
} else {
return ignore::WalkState::Continue;
}
} else {
return ignore::WalkState::Continue;
}
}
// Filter out unwanted modification times
if !config.time_constraints.is_empty() {
let mut matched = false;
if let Ok(metadata) = entry_path.metadata() {
if let Ok(modified) = metadata.modified() {
matched = config
.time_constraints
.iter()
.all(|tf| tf.applies_to(&modified));
}
}
if !matched {
return ignore::WalkState::Continue;
}
}
// TODO: take care of the unwrap call
tx_thread
.send(WorkerResult::Entry(entry_path.to_owned()))
.unwrap();
ignore::WalkState::Continue
})
});
// Drop the initial sender. If we don't do this, the receiver will block even
// if all threads have finished, since there is still one sender around.
drop(tx);
// Wait for the receiver thread to print out all results.
receiver_thread.join().unwrap();
if wants_to_quit.load(Ordering::Relaxed) {
process::exit(EXITCODE_SIGINT);
}
}
-39
View File
@@ -1,39 +0,0 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
extern crate kernel32;
extern crate winapi;
use self::kernel32::{GetConsoleMode, GetStdHandle, SetConsoleMode};
use self::winapi::{INVALID_HANDLE_VALUE, STD_OUTPUT_HANDLE};
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example
pub fn enable_colored_output() -> bool {
unsafe {
let handle = GetStdHandle(STD_OUTPUT_HANDLE);
if handle == INVALID_HANDLE_VALUE {
return false;
}
// https://docs.microsoft.com/en-us/windows/console/getconsolemode
let mut mode = 0;
if GetConsoleMode(handle, &mut mode) == 0 {
return false;
}
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
//
// A console consists of an input buffer and one or more screen buffers. ... Setting the
// output modes of one screen buffer does not affect the output modes of other screen
// buffers.
SetConsoleMode(handle, mode) != 0
}
}
+65 -16
View File
@@ -20,10 +20,7 @@ use std::os::unix;
#[cfg(windows)]
use std::os::windows;
extern crate diff;
extern crate tempdir;
use self::tempdir::TempDir;
use tempdir::TempDir;
/// Environment for the integration tests.
pub struct TestEnv {
@@ -32,6 +29,9 @@ pub struct TestEnv {
/// Path to the *fd* executable.
fd_exe: PathBuf,
/// Normalize each line by sorting the whitespace-separated words
normalize_line: bool,
}
/// Create the working directory and the test files.
@@ -44,6 +44,9 @@ fn create_working_directory(
{
let root = temp_dir.path();
// Pretend that this is a Git repository in order for `.gitignore` files to be respected
fs::create_dir_all(root.join(".git"))?;
for directory in directories {
fs::create_dir_all(root.join(directory))?;
}
@@ -60,7 +63,7 @@ fn create_working_directory(
#[cfg(windows)]
windows::fs::symlink_dir(root.join("one/two"), root.join("symlink"))?;
fs::File::create(root.join(".ignore"))?.write_all(b"ignored.foo")?;
fs::File::create(root.join(".fdignore"))?.write_all(b"fdignored.foo")?;
fs::File::create(root.join(".gitignore"))?.write_all(b"gitignored.foo")?;
}
@@ -118,19 +121,24 @@ fn format_output_error(args: &[&str], expected: &str, actual: &str) -> String {
}
/// Normalize the output for comparison.
fn normalize_output(s: &str, trim_left: bool) -> String {
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")
let mut lines = s
.replace('\0', "NULL\n")
.lines()
.map(|line| {
let line =
if trim_left { line.trim_left() } else { line };
line.replace('/', &std::path::MAIN_SEPARATOR.to_string())
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();
words.sort();
return words.join(" ");
}
line
})
.collect::<Vec<_>>();
lines.sort_by_key(|s| s.clone());
lines.sort();
lines.join("\n")
}
@@ -140,8 +148,17 @@ impl TestEnv {
let fd_exe = find_fd_exe();
TestEnv {
temp_dir: temp_dir,
fd_exe: fd_exe,
temp_dir,
fd_exe,
normalize_line: false,
}
}
pub fn normalize_line(self, normalize: bool) -> TestEnv {
TestEnv {
temp_dir: self.temp_dir,
fd_exe: self.fd_exe,
normalize_line: normalize,
}
}
@@ -183,12 +200,44 @@ impl TestEnv {
}
// Normalize both expected and actual output.
let expected = normalize_output(expected, true);
let actual = normalize_output(&String::from_utf8_lossy(&output.stdout), false);
let expected = normalize_output(expected, true, self.normalize_line);
let actual = normalize_output(
&String::from_utf8_lossy(&output.stdout),
false,
self.normalize_line,
);
// Compare actual output to expected output.
if expected != actual {
panic!(format_output_error(args, &expected, &actual));
}
}
/// Assert that calling *fd* with the specified arguments produces the expected error.
pub fn assert_error(&self, args: &[&str], expected: &str) {
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) {
// 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!("error '{}' did not occur.", expected);
}
// 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));
}
}
}
+634 -36
View File
@@ -8,12 +8,14 @@
//! Integration tests for the CLI interface of fd.
extern crate regex;
mod testenv;
use testenv::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"];
@@ -23,14 +25,15 @@ static DEFAULT_FILES: &'static [&'static str] = &[
"one/two/c.foo",
"one/two/C.Foo2",
"one/two/three/d.foo",
"ignored.foo",
"fdignored.foo",
"gitignored.foo",
".hidden.foo",
"e1 e2",
];
fn get_absolute_root_path(env: &TestEnv) -> String {
let path = env.test_root()
let path = env
.test_root()
.canonicalize()
.expect("absolute path")
.to_str()
@@ -38,11 +41,25 @@ 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
}
#[cfg(test)]
fn get_test_env_with_abs_path(dirs: &[&'static str], files: &[&'static str]) -> (TestEnv, String) {
let env = TestEnv::new(dirs, files);
let root_path = get_absolute_root_path(&env);
(env, root_path)
}
#[cfg(test)]
fn create_file_with_size<P: AsRef<Path>>(path: P, size_in_bytes: usize) {
let content = "#".repeat(size_in_bytes);
let mut f = fs::File::create::<P>(path).unwrap();
f.write(content.as_bytes()).unwrap();
}
/// Simple tests
#[test]
fn test_simple() {
@@ -222,6 +239,89 @@ 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",
);
}
#[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() {
@@ -257,6 +357,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() {
@@ -265,7 +387,7 @@ fn test_no_ignore() {
te.assert_output(
&["--no-ignore", "foo"],
"a.foo
ignored.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
@@ -278,7 +400,7 @@ fn test_no_ignore() {
&["--hidden", "--no-ignore", "foo"],
".hidden.foo
a.foo
ignored.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
@@ -288,6 +410,70 @@ fn test_no_ignore() {
);
}
/// .gitignore and .fdignore
#[test]
fn test_gitignore_and_fdignore() {
let files = &[
"ignored-by-nothing",
"ignored-by-fdignore",
"ignored-by-gitignore",
"ignored-by-both",
];
let te = TestEnv::new(&[], files);
fs::File::create(te.test_root().join(".fdignore"))
.unwrap()
.write_all(b"ignored-by-fdignore\nignored-by-both")
.unwrap();
fs::File::create(te.test_root().join(".gitignore"))
.unwrap()
.write_all(b"ignored-by-gitignore\nignored-by-both")
.unwrap();
te.assert_output(&["ignored"], "ignored-by-nothing");
te.assert_output(
&["--no-ignore-vcs", "ignored"],
"ignored-by-nothing
ignored-by-gitignore",
);
te.assert_output(
&["--no-ignore", "ignored"],
"ignored-by-nothing
ignored-by-fdignore
ignored-by-gitignore
ignored-by-both",
);
}
/// Precedence of .fdignore files
#[test]
fn test_custom_ignore_precedence() {
let dirs = &["inner"];
let files = &["inner/foo"];
let te = TestEnv::new(dirs, files);
// Ignore 'foo' via .gitignore
fs::File::create(te.test_root().join("inner/.gitignore"))
.unwrap()
.write_all(b"foo")
.unwrap();
// Whitelist 'foo' via .fdignore
fs::File::create(te.test_root().join(".fdignore"))
.unwrap()
.write_all(b"!foo")
.unwrap();
te.assert_output(&["foo"], "inner/foo");
te.assert_output(&["--no-ignore-vcs", "foo"], "inner/foo");
te.assert_output(&["--no-ignore", "foo"], "inner/foo");
}
/// VCS ignored files (--no-ignore-vcs)
#[test]
fn test_no_ignore_vcs() {
@@ -305,6 +491,25 @@ fn test_no_ignore_vcs() {
);
}
/// Custom ignore files (--ignore-file)
#[test]
fn test_custom_ignore_files() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
// Ignore 'C.Foo2' and everything in 'three'.
fs::File::create(te.test_root().join("custom.ignore"))
.unwrap()
.write_all(b"C.Foo2\nthree")
.unwrap();
te.assert_output(
&["--ignore-file", "custom.ignore", "foo"],
"a.foo
one/b.foo
one/two/c.foo",
);
}
/// Ignored files with ripgrep aliases (-u / -uu)
#[test]
fn test_no_ignore_aliases() {
@@ -313,7 +518,7 @@ fn test_no_ignore_aliases() {
te.assert_output(
&["-u", "foo"],
"a.foo
ignored.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
@@ -326,7 +531,7 @@ fn test_no_ignore_aliases() {
&["-uu", "foo"],
".hidden.foo
a.foo
ignored.foo
fdignored.foo
gitignored.foo
one/b.foo
one/two/c.foo
@@ -406,9 +611,7 @@ fn test_max_depth() {
/// Absolute paths (--absolute-path)
#[test]
fn test_absolute_path() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let abs_path = get_absolute_root_path(&te);
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--absolute-path"],
@@ -440,6 +643,12 @@ fn test_absolute_path() {
abs_path = &abs_path
),
);
}
/// Show absolute paths if the path argument is absolute
#[test]
fn test_implicit_absolute_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["foo", &abs_path],
@@ -455,6 +664,26 @@ fn test_absolute_path() {
);
}
/// Absolute paths should be normalized
#[test]
fn test_normalized_absolute_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output_subdirectory(
"one",
&["--absolute-path", "foo", ".."],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
/// File type filter (--type)
#[test]
fn test_type() {
@@ -492,6 +721,60 @@ fn test_type() {
te.assert_output(&["--type", "l"], "symlink");
}
/// Test `--type executable`
#[cfg(unix)]
#[test]
fn test_type_executable() {
use std::os::unix::fs::OpenOptionsExt;
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
fs::OpenOptions::new()
.create(true)
.write(true)
.mode(0o777)
.open(te.test_root().join("executable-file.sh"))
.unwrap();
te.assert_output(&["--type", "executable"], "executable-file.sh");
te.assert_output(
&["--type", "executable", "--type", "directory"],
"executable-file.sh
one
one/two
one/two/three
one/two/three/directory_foo",
);
}
/// Test `--type empty`
#[test]
fn test_type_empty() {
let te = TestEnv::new(&["dir_empty", "dir_nonempty"], &[]);
create_file_with_size(te.test_root().join("0_bytes.foo"), 0);
create_file_with_size(te.test_root().join("5_bytes.foo"), 5);
create_file_with_size(te.test_root().join("dir_nonempty").join("2_bytes.foo"), 2);
te.assert_output(
&["--type", "empty"],
"0_bytes.foo
dir_empty",
);
te.assert_output(
&["--type", "empty", "--type", "file", "--type", "directory"],
"0_bytes.foo
dir_empty",
);
te.assert_output(&["--type", "empty", "--type", "file"], "0_bytes.foo");
te.assert_output(&["--type", "empty", "--type", "directory"], "dir_empty");
}
/// File extension (--extension)
#[test]
fn test_extension() {
@@ -525,15 +808,35 @@ fn test_extension() {
te.assert_output(&["--extension", ".foo", "a"], "a.foo");
te.assert_output(&["--extension", "foo2"], "one/two/C.Foo2");
let te2 = TestEnv::new(&[], &["spam.bar.baz", "egg.bar.baz", "yolk.bar.baz.sig"]);
te2.assert_output(
&["--extension", ".bar.baz"],
"spam.bar.baz
egg.bar.baz",
);
te2.assert_output(&["--extension", "sig"], "yolk.bar.baz.sig");
te2.assert_output(&["--extension", "bar.baz.sig"], "yolk.bar.baz.sig");
let te3 = TestEnv::new(&[], &["latin1.e\u{301}xt", "smiley.☻"]);
te3.assert_output(&["--extension", ""], "smiley.☻");
te3.assert_output(&["--extension", ".e\u{301}xt"], "latin1.e\u{301}xt");
let te4 = TestEnv::new(&[], &[".hidden", "test.hidden"]);
te4.assert_output(&["--hidden", "--extension", ".hidden"], "test.hidden");
}
/// Symlinks misc
/// Symlink as search directory
#[test]
fn test_symlink() {
fn test_symlink_as_root() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let abs_path = get_absolute_root_path(&te);
// From: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html
// The getcwd() function shall place an absolute pathname of the current working directory in
// the array pointed to by buf, and return buf. The pathname shall contain no components that
@@ -566,20 +869,29 @@ fn test_symlink() {
dir = &parent_parent
),
);
}
#[test]
fn test_symlink_and_absolute_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output_subdirectory(
"symlink",
&["--absolute-path"],
&format!(
"{abs_path}/{dir}/c.foo
{abs_path}/{dir}/C.Foo2
{abs_path}/{dir}/three
{abs_path}/{dir}/three/d.foo
{abs_path}/{dir}/three/directory_foo",
dir = if cfg!(windows) { "symlink" } else { "one/two" },
"{abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2
{abs_path}/one/two/three
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
#[test]
fn test_symlink_as_absolute_root() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["", &format!("{abs_path}/symlink", abs_path = abs_path)],
@@ -592,7 +904,11 @@ fn test_symlink() {
abs_path = &abs_path
),
);
}
#[test]
fn test_symlink_and_full_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
let root = te.system_root();
let prefix = escape(&root.to_string_lossy());
@@ -604,14 +920,19 @@ fn test_symlink() {
&format!("^{prefix}.*three", prefix = prefix),
],
&format!(
"{abs_path}/{dir}/three
{abs_path}/{dir}/three/d.foo
{abs_path}/{dir}/three/directory_foo",
dir = if cfg!(windows) { "symlink" } else { "one/two" },
"{abs_path}/one/two/three
{abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo",
abs_path = &abs_path
),
);
}
#[test]
fn test_symlink_and_full_path_abs_path() {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
let root = te.system_root();
let prefix = escape(&root.to_string_lossy());
te.assert_output(
&[
"--full-path",
@@ -680,14 +1001,28 @@ fn test_excludes() {
/// Shell script execution (--exec)
#[test]
fn test_exec() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
assert_exec_output("--exec");
}
let abs_path = get_absolute_root_path(&te);
/// Shell script execution using -exec
#[test]
fn test_exec_substitution() {
assert_exec_output("-exec");
}
// Shell script execution using -x
#[test]
fn test_exec_short_arg() {
assert_exec_output("-x");
}
#[cfg(test)]
fn assert_exec_output(exec_style: &str) {
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
// TODO Windows tests: D:file.txt \file.txt \\server\share\file.txt ...
if !cfg!(windows) {
te.assert_output(
&["--absolute-path", "foo", "--exec", "echo"],
&["--absolute-path", "foo", exec_style, "echo"],
&format!(
"{abs_path}/a.foo
{abs_path}/one/b.foo
@@ -700,7 +1035,7 @@ fn test_exec() {
);
te.assert_output(
&["foo", "--exec", "echo", "{}"],
&["foo", exec_style, "echo", "{}"],
"a.foo
one/b.foo
one/two/C.Foo2
@@ -710,7 +1045,7 @@ fn test_exec() {
);
te.assert_output(
&["foo", "--exec", "echo", "{.}"],
&["foo", exec_style, "echo", "{.}"],
"a
one/b
one/two/C
@@ -720,7 +1055,7 @@ fn test_exec() {
);
te.assert_output(
&["foo", "--exec", "echo", "{/}"],
&["foo", exec_style, "echo", "{/}"],
"a.foo
b.foo
C.Foo2
@@ -730,7 +1065,7 @@ fn test_exec() {
);
te.assert_output(
&["foo", "--exec", "echo", "{/.}"],
&["foo", exec_style, "echo", "{/.}"],
"a
b
C
@@ -740,7 +1075,7 @@ fn test_exec() {
);
te.assert_output(
&["foo", "--exec", "echo", "{//}"],
&["foo", exec_style, "echo", "{//}"],
".
one
one/two
@@ -749,6 +1084,269 @@ fn test_exec() {
one/two/three",
);
te.assert_output(&["e1", "--exec", "printf", "%s.%s\n"], "e1 e2.");
te.assert_output(&["e1", exec_style, "printf", "%s.%s\n"], "e1 e2.");
}
}
#[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"],
&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
),
);
te.assert_output(
&["foo", exec_style, "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", "{/}"],
"a.foo b.foo C.Foo2 c.foo d.foo directory_foo",
);
te.assert_output(&["no_match", exec_style, "echo", "Matched: ", "{/}"], "");
te.assert_error(
&["foo", exec_style, "echo", "{}", "{}"],
"[fd error]: Only one placeholder allowed for batch commands",
);
te.assert_error(
&["foo", exec_style, "echo", "{/}", ";", "-x", "echo"],
"error: The argument '--exec <cmd>' cannot be used with '--exec-batch <cmd>'",
);
te.assert_error(
&["foo", exec_style],
"error: The argument '--exec-batch <cmd>' requires a value but none was supplied",
);
te.assert_error(
&["foo", exec_style, "echo {}"],
"[fd error]: First argument of exec-batch is expected to be a fixed executable",
);
}
}
/// Literal search (--fixed-strings)
#[test]
fn test_fixed_strings() {
let dirs = &["test1", "test2"];
let files = &["test1/a.foo", "test1/a_foo", "test2/Download (1).tar.gz"];
let te = TestEnv::new(dirs, files);
// Regex search, dot is treated as "any character"
te.assert_output(
&["a.foo"],
"test1/a.foo
test1/a_foo",
);
// Literal search, dot is treated as character
te.assert_output(&["--fixed-strings", "a.foo"], "test1/a.foo");
// Regex search, parens are treated as group
te.assert_output(&["download (1)"], "");
// Literal search, parens are treated as characters
te.assert_output(
&["--fixed-strings", "download (1)"],
"test2/Download (1).tar.gz",
);
// Combine with --case-sensitive
te.assert_output(&["--fixed-strings", "--case-sensitive", "download (1)"], "");
}
/// Filenames with invalid UTF-8 sequences
#[cfg(target_os = "linux")]
#[test]
fn test_invalid_utf8() {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
let dirs = &["test1"];
let files = &[];
let te = TestEnv::new(dirs, files);
fs::File::create(
te.test_root()
.join(OsStr::from_bytes(b"test1/test_\xFEinvalid.txt")),
)
.unwrap();
te.assert_output(&["", "test1/"], "test1/test_invalid.txt");
te.assert_output(&["invalid", "test1/"], "test1/test_invalid.txt");
// Should not be found under a different extension
te.assert_output(&["-e", "zip", "", "test1/"], "");
}
/// Filtering for file size (--size)
#[test]
fn test_size() {
let te = TestEnv::new(&[], &[]);
create_file_with_size(te.test_root().join("0_bytes.foo"), 0);
create_file_with_size(te.test_root().join("11_bytes.foo"), 11);
create_file_with_size(te.test_root().join("30_bytes.foo"), 30);
create_file_with_size(te.test_root().join("3_kilobytes.foo"), 3 * 1000);
create_file_with_size(te.test_root().join("4_kibibytes.foo"), 4 * 1024);
// Zero and non-zero sized files.
te.assert_output(
&["", "--size", "+0B"],
"0_bytes.foo
11_bytes.foo
30_bytes.foo
3_kilobytes.foo
4_kibibytes.foo",
);
// Zero sized files.
te.assert_output(&["", "--size", "-0B"], "0_bytes.foo");
// Files with 2 bytes or more.
te.assert_output(
&["", "--size", "+2B"],
"11_bytes.foo
30_bytes.foo
3_kilobytes.foo
4_kibibytes.foo",
);
// Files with 2 bytes or less.
te.assert_output(&["", "--size", "-2B"], "0_bytes.foo");
// Files with size between 1 byte and 11 bytes.
te.assert_output(&["", "--size", "+1B", "--size", "-11B"], "11_bytes.foo");
// Files with size between 1 byte and 30 bytes.
te.assert_output(
&["", "--size", "+1B", "--size", "-30B"],
"11_bytes.foo
30_bytes.foo",
);
// Combine with a search pattern
te.assert_output(&["^11_", "--size", "+1B", "--size", "-30B"], "11_bytes.foo");
// Files with size between 12 and 30 bytes.
te.assert_output(&["", "--size", "+12B", "--size", "-30B"], "30_bytes.foo");
// Files with size between 31 and 100 bytes.
te.assert_output(&["", "--size", "+31B", "--size", "-100B"], "");
// Files with size between 3 kibibytes and 5 kibibytes.
te.assert_output(&["", "--size", "+3ki", "--size", "-5ki"], "4_kibibytes.foo");
// Files with size between 3 kilobytes and 5 kilobytes.
te.assert_output(
&["", "--size", "+3k", "--size", "-5k"],
"3_kilobytes.foo
4_kibibytes.foo",
);
// Files with size greater than 3 kilobytes and less than 3 kibibytes.
te.assert_output(&["", "--size", "+3k", "--size", "-3ki"], "3_kilobytes.foo");
// Files with size equal 4 kibibytes.
te.assert_output(&["", "--size", "+4ki", "--size", "-4ki"], "4_kibibytes.foo");
}
#[cfg(test)]
fn create_file_with_modified<P: AsRef<Path>>(path: P, duration_in_secs: u64) {
let st = SystemTime::now() - Duration::from_secs(duration_in_secs);
let ft = filetime::FileTime::from_system_time(st);
fs::File::create(&path).expect("creation failed");
filetime::set_file_times(&path, ft, ft).expect("time modification failed");
}
#[test]
fn test_modified_relative() {
let te = TestEnv::new(&[], &[]);
create_file_with_modified(te.test_root().join("foo_0_now"), 0);
create_file_with_modified(te.test_root().join("bar_1_min"), 60);
create_file_with_modified(te.test_root().join("foo_10_min"), 600);
create_file_with_modified(te.test_root().join("bar_1_h"), 60 * 60);
create_file_with_modified(te.test_root().join("foo_2_h"), 2 * 60 * 60);
create_file_with_modified(te.test_root().join("bar_1_day"), 24 * 60 * 60);
te.assert_output(
&["", "--changed-within", "15min"],
"foo_0_now
bar_1_min
foo_10_min",
);
te.assert_output(
&["", "--change-older-than", "15min"],
"bar_1_h
foo_2_h
bar_1_day",
);
te.assert_output(
&["foo", "--changed-within", "12h"],
"foo_0_now
foo_10_min
foo_2_h",
);
}
#[cfg(test)]
fn change_file_modified<P: AsRef<Path>>(path: P, iso_date: &str) {
let st = humantime::parse_rfc3339(iso_date).expect("invalid date");
let ft = filetime::FileTime::from_system_time(st);
filetime::set_file_times(path, ft, ft).expect("time modification failde");
}
#[test]
fn test_modified_asolute() {
let te = TestEnv::new(&[], &["15mar2018", "30dec2017"]);
change_file_modified(te.test_root().join("15mar2018"), "2018-03-15T12:00:00Z");
change_file_modified(te.test_root().join("30dec2017"), "2017-12-30T23:59:00Z");
te.assert_output(
&["", "--change-newer-than", "2018-01-01 00:00:00"],
"15mar2018",
);
te.assert_output(
&["", "--changed-before", "2018-01-01 00:00:00"],
"30dec2017",
);
}
#[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",
);
}