Compare commits

..

519 Commits

Author SHA1 Message Date
David Peter 9f5ed8534e Add temporary version of logo 2022-01-29 16:53:13 +01:00
David Peter c48d02e0a9 Bump version to v8.3.2 2022-01-29 16:49:09 +01:00
David Peter b3399c239b Add clippy run in CI/CD 2022-01-29 13:01:46 +01:00
David Peter 4377954cf9 Add tavianator sponsoring link 2022-01-29 12:51:16 +01:00
Thayne McCombs 125cb81a5b Merge pull request #929 from tmccombs/clap-upgrade
Upgrade to clap 3.0
2022-01-08 00:24:43 -07:00
Thayne McCombs 2e9be3e3f5 Bump MSRV to 1.54
Because that is needed by clap 3.0
2022-01-07 23:56:03 -07:00
Thayne McCombs 43f276e073 Upgrade to clap 3.0
This wasn't backwards compatible so required some more substantial
changes.
2022-01-07 23:56:03 -07:00
David Peter ec38e23d58 Add CHANGELOG entry 2022-01-07 17:39:24 +01:00
Tavian Barnes 56060a5b6b Merge pull request #936 from gbarta/fix_fd_issue_931
Update normpath to 0.3.2 to fix issue #931
2022-01-07 10:14:08 -05:00
Tavian Barnes 03548a847f Merge pull request #937 from tmccombs/less-flaky-tests
Less flaky tests
2022-01-07 10:11:54 -05:00
Thayne McCombs 06cd391845 Change test_opposing to be less flaky.
Make it resilient to the different invocations returning results in
different orders.
2022-01-07 03:10:09 -07:00
Gabriel Barta ef4abfc1d2 Update normpath to 0.3.2 to fix issue #931 2022-01-07 10:53:03 +11:00
David Peter 72895675ef Bump version to v8.3.1 2022-01-06 14:20:02 +01:00
David Peter ccc6cc54b2 Merge pull request #916 from tavianator/revert-crossbeam
Revert "Switch from std::sync::mpsc to crossbeam-channel"
2022-01-06 13:59:16 +01:00
David Peter aa1c0250b7 Update CHANGELOG 2022-01-06 13:59:04 +01:00
Thayne McCombs d5f740c9d3 Add a couple of options to zsh completion
--no-ignore-parent and --strip-cwd-prefix
2022-01-06 13:52:49 +01:00
David Peter aa6933d9e3 Merge branch 'master' into revert-crossbeam 2022-01-06 13:49:58 +01:00
dependabot[bot] c800c79976 Bump anyhow from 1.0.51 to 1.0.52
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.51 to 1.0.52.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.51...1.0.52)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 21:45:55 +01:00
dependabot[bot] 075c1af7d8 Bump num_cpus from 1.13.0 to 1.13.1 (#927)
Bumps [num_cpus](https://github.com/seanmonstar/num_cpus) from 1.13.0 to 1.13.1.
- [Release notes](https://github.com/seanmonstar/num_cpus/releases)
- [Changelog](https://github.com/seanmonstar/num_cpus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/num_cpus/compare/v1.13.0...v1.13.1)

---
updated-dependencies:
- dependency-name: num_cpus
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-03 09:47:20 -07:00
dependabot[bot] fbebaa299d Bump version_check from 0.9.3 to 0.9.4 (#921)
Bumps [version_check](https://github.com/SergioBenitez/version_check) from 0.9.3 to 0.9.4.
- [Release notes](https://github.com/SergioBenitez/version_check/releases)
- [Commits](https://github.com/SergioBenitez/version_check/compare/v0.9.3...v0.9.4)

---
updated-dependencies:
- dependency-name: version_check
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-03 09:26:25 -07:00
dependabot[bot] f6bbeb35f5 Bump nix from 0.23.0 to 0.23.1 (#922)
Bumps [nix](https://github.com/nix-rust/nix) from 0.23.0 to 0.23.1.
- [Release notes](https://github.com/nix-rust/nix/releases)
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.23.0...v0.23.1)

---
updated-dependencies:
- dependency-name: nix
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-03 09:26:00 -07:00
dependabot[bot] 0dc49b07a0 Bump libc from 0.2.108 to 0.2.112 (#924)
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.108 to 0.2.112.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.108...0.2.112)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-03 09:25:33 -07:00
dependabot[bot] c2e5075316 Bump once_cell from 1.8.0 to 1.9.0 (#925)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-03 09:25:06 -07:00
Tavian Barnes e7f192f1c6 Revert "Switch from std::sync::mpsc to crossbeam-channel"
@sharkdp [noticed][1] a quite severe performance degredation due to this
change.  Switch back to std::sync::mpsc until we can fix the performance
regression.

This reverts commit a4bb734482.

[1]: https://github.com/sharkdp/fd/pull/895#issuecomment-1002099369
2021-12-28 12:11:32 -05:00
Yujia Qiao e5145ffb98 fix: disable jemalloc on risc-v target
`jemallocator` does not support risc-v and fails the compilation.
This fixes build for risc-v.
2021-12-28 17:38:28 +01:00
Ryan Lue 30add71233 Update manpage to match --help output
Three command line options were added in late 2021
that never made their way into the manpage:

* f8ae334 introduced --no-ignore-parent;
* bf9e6fd introduced --strip-prefix
  (later renamed strip-cwd-prefix); and
* 17dd2a6 introduced --batch-size.

This commit updates the manpage content to reflect
the auto-generated output of `fd --help`.
2021-12-28 14:21:10 +01:00
Thayne McCombs dade9736ac Add entry to changelog for no-ignore-vcs fix
I forgot to add it when I made the PR.

See commit 8bdbbd8e0cbe8ba010ce5c7f863e23af44386741
2021-12-28 14:20:25 +01:00
eatradish 639ed34791 Allow use to disable jemalloc
jemalloc will require special flags on devices where page size is not 4K. With default options fd will not work on a Mac Mini M1 where hardware only allow 16K paging size. This commit will allow the user to turn off jemalloc (using --no-default-features) under special circumstances where jemalloc is not usable.
2021-12-28 14:15:45 +01:00
David Peter da7ea79034 Use correct description for Debian package, closes #915 2021-12-28 14:00:36 +01:00
Thayne McCombs b7e077320d Fix logic for --no-ignore-parent (#908)
Make sure that using `--no-ignore-vcs` or `--no-ignore` don't also
enable `--no-ignore-parent`. So that if `--no-ignore-vcs` is enabled, it
continues to respect .fdignore and .ignore in the parent directories.

Fixes: #907
Fixes: #901
2021-12-22 23:38:00 -07:00
Tavian Barnes 81669f4c10 Merge pull request #902 from tavianator/quit-senders
Quit senders more aggressively
2021-12-10 13:56:28 -05:00
Thayne McCombs 5ea7cb7a05 A couple of minor recommendations from the Code Quality action (#903)
use derive for a Default impl, and use a function directly instead of
needlessly wrapping it in a closure
2021-12-07 10:04:17 -07:00
Tavian Barnes 4baefb2f5a walk: Split wants_to_quit into two separate flags
quit_flag is now used to quit the sender threads for any reason, either
due to an interrupt or because the receiver is done.

interrupt_flag is used specifically for ^C interrupts, and causes the
receiver to stop between printing paths, to avoid unfinished escape
sequences when colors are being used.

Fixes #867.
2021-12-05 12:06:49 -05:00
Tavian Barnes 97a8825b00 output: Remove wants_to_quit handling from print_entry()
Since we only check it once per path now, we might as well just check it
before printing anything.
2021-12-05 12:06:49 -05:00
Tavian Barnes 7fe4bfaacb Merge pull request #895 from tavianator/receiver-buffer
walk: Encapsulate the buffering behavior in a struct
2021-12-05 11:56:22 -05:00
dependabot[bot] 1a6638ba23 Bump anyhow from 1.0.48 to 1.0.51 (#899)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.48 to 1.0.51.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.48...1.0.51)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-01 13:58:36 -07:00
dependabot[bot] 8772708aa7 Bump clap from 2.33.3 to 2.34.0 (#900)
Bumps [clap](https://github.com/clap-rs/clap) from 2.33.3 to 2.34.0.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/commits)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-01 13:58:05 -07:00
Tavian Barnes a4bb734482 Switch from std::sync::mpsc to crossbeam-channel
This lets us avoid https://github.com/rust-lang/rust/issues/39364, which
could potentially be seen now that we're using recv_timeout().
2021-12-01 10:24:58 -05:00
Alexander Sieg 5fbfdfadb8 fix: disable jemalloc on FreeBSD
As jemalloc is the default system allocator on FreeBSD and the
jemalloc-sys crate failes to compile on FreeBSD, this fixes the build on
FreeBSD.
2021-11-28 17:42:15 +01:00
Tavian Barnes 16ae03c3b4 walk: Encapsulate the buffering behavior in a struct
The new ReceiverBuffer struct allows us to factor out the receiver
implementation into a number of helper methods.  The new implementation
uses rx.{recv,recv_timeout} instead of a for loop, which enables us to
switch to streaming mode at the right time without waiting for more
results.

Fixes #868.
2021-11-27 12:56:22 -05:00
David Peter 52f22557a5 Update usage section 2021-11-26 22:18:50 +01:00
David Peter 72cc9a876c Bump version 2021-11-26 22:18:50 +01:00
David Peter 5d06491370 Update CHANGELOG for v8.3 2021-11-26 22:18:50 +01:00
David Peter 67aaf82d37 Update dependencies 2021-11-26 22:18:50 +01:00
David Peter f219da4b3a Use non-sync channel 2021-11-26 21:23:47 +01:00
Thayne McCombs e990a13405 squash! Add buffering to stdout when it's not a terminal
Co-authored-by: sourlemon207 <jw1756@protonmail.com>
2021-11-26 21:23:47 +01:00
Thayne McCombs 7b7876e701 Add entry for buffering to CHANGELOG 2021-11-26 21:23:47 +01:00
Thayne McCombs feac240eb5 Add buffering to stdout when it's not a terminal
This is based on the work of #736 by @sourlemon207.

I've added the suggestion I recommended on that PR.
2021-11-26 21:23:47 +01:00
David Peter fba6db5096 Modify example to a realistic use case 2021-11-26 19:31:06 +01:00
tacoda f2eb1be678 Add an example of the -X option using rg 2021-11-26 19:31:06 +01:00
David Peter b211ded5de Merge pull request #861 from jcaplan/no-strip/760
show "./" prefix when search path provided
2021-11-26 19:27:44 +01:00
David Peter 92bd7850d0 Merge remote-tracking branch 'origin/master' into no-strip/760 2021-11-26 18:19:28 +01:00
David Peter 06c58b99f5 Update CHANGELOG 2021-11-26 17:58:18 +01:00
David Peter 776f8d0f11 Shorten short help text 2021-11-26 17:58:18 +01:00
David Peter 47421a49b8 Remove strip-prefix placeholders 2021-11-26 17:58:18 +01:00
David Peter f347379fb0 Adapt help text 2021-11-26 17:48:31 +01:00
David Peter 4d7e34ad59 Rename option to strip_cwd_prefix, rename CLI option 2021-11-26 17:46:04 +01:00
David Peter 359c0269ba Remove example from man page 2021-11-26 17:40:42 +01:00
David Peter 13cdfb32f8 Remove outdated comment 2021-11-26 17:40:07 +01:00
Shun Sakai 0484486f3f Remove lazy_static from dependencies 2021-11-25 20:06:43 +01:00
Jacob Mischka f32060b0de Change time comparisons to exclusive
Closes #794
2021-11-17 23:38:28 +01:00
Tavian Barnes fe992706ae Raise SIGINT when dying by ^C
Fixes #854.
2021-11-15 07:53:57 +01:00
Tavian Barnes 2b1bf471b1 Add an exit() method to ExitCode 2021-11-15 07:53:57 +01:00
David Peter cab31e280b Add missing single-tick in issue template 2021-11-15 07:38:48 +01:00
David Peter 088e42deef Improve --type help text
closes #823
2021-11-14 21:17:57 +01:00
David Peter ecdaf58e7f Update to latest lscolors dependency 2021-11-14 18:29:59 +01:00
David Peter fc2a972082 Add CHANGELOG entry 2021-11-14 17:42:16 +01:00
Tavian Barnes 66e3ccc5e1 Don't color each path component separately
It can be expensive to color each path component separately, requiring a
stat() call on each component.  For deep hierarchies this can result in
quadratic overhead.  Instead, just color the path up to the basename as
a directory.

Fixes #720.
2021-11-14 17:42:16 +01:00
David Peter 21fd013073 Improve --full-path documentation, closes #839 2021-11-14 17:16:44 +01:00
David Peter a539181f1f Merge pull request #822 from Asha20/pr/opposing-options
Add opposing CLI options
2021-11-14 16:55:13 +01:00
David Peter cdc6a37ed6 Merge remote-tracking branch 'origin/master' into pr/opposing-options 2021-11-14 16:47:53 +01:00
David Peter 2570fbd04e Add longer help text for --prune, closes #727 2021-11-14 16:29:10 +01:00
David Peter 690976380d Add section concerning aliases, shell functions
closes #870
2021-11-14 16:21:26 +01:00
David Peter 828649a30d Revert "Add pemistahl as a maintainer"
This reverts commit c06c9952b6.
2021-11-14 13:58:57 +01:00
Tavian Barnes 20dc7a984d Merge pull request #881 from cgjosephlee/master
fix zsh completion
2021-11-12 09:45:53 -05:00
Joseph Lee 653bc0e55d fix zsh completion 2021-11-11 23:30:01 +08:00
Tavian Barnes e8fe1508e2 Merge pull request #878 from sharkdp/bug-form
Change bug_report from an issue template to an issue form
2021-11-03 11:34:24 -04:00
Thayne McCombs fd493eb709 Change bug_report from an issue template to an issue form 2021-11-03 02:33:46 -06:00
Tavian Barnes f1b39d49c1 Merge pull request #877 from tmccombs/dep-update
Update some dependencies
2021-11-02 22:12:43 -04:00
Thayne McCombs 1236b1dbcf Update some dependencies
Combination of dependabot prs.
2021-11-01 22:41:56 -06:00
Devon Hollowood 17dd2a6dfe Implement --batch-size (#866) 2021-10-22 00:05:13 -06:00
Jonah Caplan bf9e6fd36e add -P option to strip prefix to tty 2021-10-17 00:29:51 -04:00
Jonah Caplan 46db1c4ef3 add {-} format for exec 2021-10-16 23:52:04 -04:00
Jonah Caplan b9cb5d54a4 fixup tests 2021-10-16 23:13:27 -04:00
Jonah Caplan a41021b0d2 use interactive_terminal 2021-10-16 14:34:58 -04:00
Jonah Caplan 82f04755bf hopefully fix build error on windows 2021-10-16 14:21:11 -04:00
Jonah Caplan c42bf4b904 don't strip paths for -X/x, and for tty output 2021-10-16 14:15:37 -04:00
Jonah Caplan fbc836b553 Revert -X stuff
This reverts commit 91860bf682.
2021-10-16 14:03:23 -04:00
Jonah Caplan 91860bf682 fmt 2021-10-16 00:02:37 -04:00
Jonah Caplan a4a4709320 rename Option to Config 2021-10-15 23:59:47 -04:00
Jonah Caplan b6f8bc8ff8 also handle -X and -x with no_strip 2021-10-15 23:57:03 -04:00
Jonah Caplan 782fc278aa add changelog entry 2021-10-15 23:57:03 -04:00
Jonah Caplan 12650a0913 add example to man page and correct test comment 2021-10-15 23:57:03 -04:00
Jonah Caplan 2e115df9e7 simplify check in prent_entry 2021-10-15 23:57:03 -04:00
Jonah Caplan 953f586f26 replace -N option with specifying '.' path 2021-10-15 23:57:03 -04:00
Jonah Caplan 08d913f167 cargo fmt fixup 2021-10-15 23:56:36 -04:00
Jonah Caplan 6b92c5db90 add -N/--no-strip option to show "./" prefix on relative paths 2021-10-15 23:56:36 -04:00
Tavian Barnes 7b5b3ec47b walk: Add a cache for DirEntry metadata 2021-10-13 09:13:44 +02:00
Niklas Mohrin 02e9850112 Refactor: Rename Options to Config 2021-10-12 18:49:07 +02:00
Niklas Mohrin b8c575cc8f Refactor: extract some methods out of run and reorder main.rs
Now, the top method is `main`, then comes `run`, then the methods used
in `run` follow. Generally, a method is always declared somewhere after
its first use. This way, you can read the file from top to bottom with
a decreasing level of abstraction (you start with very high-level
processes like setting the current dir and logic for which ls command to
use only comes furher down).
2021-10-12 18:49:07 +02:00
Niklas Mohrin 3de948ae0d Refactor: use some nice Rust methods in buffering code 2021-10-12 18:49:07 +02:00
Niklas Mohrin 45a86459b2 Refactor: merge_exit_codes now takes an impl IntoIterator<Item = ExitCode>
This way, callers don't need to collect into a slice / vec.
2021-10-12 18:49:07 +02:00
Niklas Mohrin a64a607fd8 Bump MSRV to 1.53 2021-10-12 18:49:07 +02:00
Thayne McCombs 00eb6461cb Remove an unnecessary reference. (#864)
This was caught by Code Quality github action with the message:

> this expression borrows a reference (`&walk::DirEntry`) that is immediately dereferenced by the compiler
2021-10-12 00:46:15 -06:00
dependabot[bot] feb969881b Bump libc from 0.2.101 to 0.2.103
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.101 to 0.2.103.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.101...0.2.103)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-08 22:49:44 +02:00
dependabot[bot] c06efe1317 Bump anyhow from 1.0.43 to 1.0.44
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.43 to 1.0.44.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.43...1.0.44)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-08 22:49:38 +02:00
Tavian Barnes 04fac46c5c Merge pull request #857 from exploide/short-help-completion
Added missing help messages to various options
2021-10-07 12:52:31 -04:00
Tavian Barnes e0d03d170e Merge pull request #858 from friederbluemle/fix-typo
Fix typos
2021-10-07 12:52:12 -04:00
Frieder Bluemle fd1c3d376e Fix typos 2021-10-06 18:38:50 +02:00
exploide c3f786db43 added missing help messages to various options
despite they have hidden_short_help set to true, a short help message is still useful for auto-generated completions
2021-10-03 17:06:09 +02:00
Niklas Mohrin a5f17db53a CI: Run clippy on stable and use msrv setting in clippy.toml 2021-09-03 07:36:12 +02:00
Tavian Barnes f1a5aa0887 Merge pull request #846 from tmccombs/dep-update
Update dependencies
2021-09-02 14:40:47 -04:00
Thayne McCombs 3c619afe30 Update dependencies
in Cargo.lock and Cargo.toml
2021-09-01 17:32:57 -06:00
Tavian Barnes 476d404938 Merge pull request #817 from will459/add_no_ignore_parent
Add --no-ignore-parent flag
2021-09-01 13:22:01 -04:00
William Correia 43f5c8adc9 Add tests for --no-ignore-parent 2021-08-31 18:04:42 -04:00
Vukašin Stepanović c749c95136 Merge branch 'master' into pr/opposing-options 2021-08-23 15:55:17 +02:00
Vukašin Stepanović 3ebd78cf02 Use overrides_with to clean up opposing arg logic 2021-08-23 15:44:11 +02:00
Vukašin Stepanović 8da936abd8 Merge branch 'pr/opposing-options' of https://github.com/Asha20/fd into pr/opposing-options 2021-08-23 15:28:45 +02:00
Vukašin Stepanović 668af05dca Add opposing options to manpage 2021-08-23 15:27:52 +02:00
David Peter 3ba90dd768 Remove --lib option 2021-08-22 16:06:04 +02:00
David Peter 87caef9513 Use --locked for cargo, enable 'cargo fmt' check 2021-08-22 16:06:04 +02:00
Thayne McCombs 78e21395b8 Make list of ignore files for --no-ignore more explicit 2021-08-15 12:52:10 +02:00
Thayne McCombs 1b52948e42 Add better documentation of vcs-ignore files.
Explicitly list which files are used for gitignore.

Fixes #354
2021-08-15 12:52:10 +02:00
Asha20 334488cab7 Add -q/--quiet/--has-match flag (#813)
Instead of printing to stdout, the program will return 1 as the exit
code if there are no matches and 0 otherwise.
2021-08-14 17:57:01 +02:00
a1346054 1c72f80ff5 fix whitespace 2021-08-14 16:21:02 +02:00
a1346054 5b4869a940 fix spelling 2021-08-14 16:21:02 +02:00
Thayne McCombs 9bc8fabba0 Update Cargo.lock 2021-08-11 01:48:42 -06:00
Thayne McCombs 8984e57dd6 Merge branch 'master' into pr/opposing-options 2021-08-11 01:44:51 -06:00
Thayne McCombs 049232439a Add item to CHANGELOG for no buffer with single thread 2021-08-11 09:38:55 +02:00
Thayne McCombs e4730603d0 Address feedback for removing buffering when running on a single thread 2021-08-11 09:38:55 +02:00
Max Triano 154cd6e329 Clarifying comments 2021-08-11 09:38:55 +02:00
Max Triano 394e967b5f No buffering cmd output when running on 1 thread 2021-08-11 09:38:55 +02:00
Max Triano 0f0fa76c81 Just trying some things out, initial functionality 2021-08-11 09:38:55 +02:00
Thayne McCombs 1795e0e9dd Add documentation and examples of ** in glob mode
And how it should be used with the `--full-path` option.

Fixes #764.
2021-08-10 20:53:56 +02:00
Thayne McCombs 500f404c4f Mention make as a build dependency.
Fixes #743
2021-08-10 20:41:02 +02:00
Klim Tsoutsman 6a18b36037 Support --list-details on more platforms
Implement `--list-details` by using short options for `ls` to support more platforms (like BusyBox)

closes #783
2021-08-10 20:39:10 +02:00
Thayne McCombs 115ae93df9 Refactor file types check to be on impl of FileTypes
Relates to #382
2021-08-10 20:30:23 +02:00
David Peter c06c9952b6 Add pemistahl as a maintainer 2021-08-09 23:06:00 +02:00
Peter Newman 42dce35dd7 Drop the 18.04 runs entirely 2021-08-09 16:48:06 +02:00
Peter Newman c2e4be3d12 Add 20.04 Ubuntu builds 2021-08-09 16:48:06 +02:00
Vukašin Stepanović 37852aa388 Add 5 opposing CLI options
Those are:
  - `--no-hidden`, which overrides `--hidden`
  - `--ignore`, which overrides `--no-ignore`
  - `--ignore-vcs`, which overrides `--no-ignore-vcs`
  - `--no-follow`, which overrides `--follow`
  - `--relative-path`, which overrides `--absolute-path`
2021-08-09 15:49:48 +02:00
Sean Joseph 25d7c4801f Changed to better whole word example 2021-08-09 13:42:45 +02:00
Sean Joseph d34e18a58c Added example for searching exactly a filename 2021-08-09 13:42:45 +02:00
Jacob Mischka ba60a163fe Use local times for time functions
This patch uses Chrono for explicit date or datetime parsing, only using
humantime for its relative time parsing. The following formats are accepted:

1. Full RFC3339 parsing, requiring an explicit timezone
2. `YY-MM-DD`, defaulting to time `00:00:00` for the given date in the
   local time zone
3. `YY-MM-DD HH:MM:SS` in the local time zone

Fixes #631, #794
2021-08-09 13:40:55 +02:00
himself65 4e04d13215 Fix return out of match pattern 2021-08-09 13:36:09 +02:00
David Peter c37592b0b7 Add tavianator as a maintainer 2021-08-09 07:34:26 +02:00
David Peter 224b7f2354 Silence wrong clippy warning 2021-08-08 23:48:20 +02:00
William Correia f8ae334ca9 Add --no-ignore-parent flag
- Flag toggles parent checking in the `ignore` crate.  This should
  affect both git and non-git ignore files.
- Updated Changelog.
2021-08-08 17:38:24 -04:00
David Peter b5344dac30 Improve -x/-X help text and man page description
closes #605
2021-08-08 22:58:13 +02:00
David Peter 018556a367 Add CHANGELOG entries 2021-08-08 15:04:28 +02:00
David Peter aeff525c30 Attempt to fix #365 2021-08-08 15:02:01 +02:00
David Peter 2d398dc4a7 Fix directory-existence check on Windows
This fixes a bug on Windows where `fd` could not be used on ram disks
and encrypted folders.

closes #752
2021-08-08 15:02:01 +02:00
David Peter 6e44828cc8 Add CHANGELOG entry 2021-08-08 13:13:58 +02:00
David Peter b507449146 Handle IO errors based on type, closes #737 2021-08-08 13:10:16 +02:00
David Peter d9697d1486 Make the path-separator check Windows-only 2021-08-08 13:03:31 +02:00
David Peter 515e0ee469 Do not use .clone() 2021-08-08 13:03:31 +02:00
David Peter a0ca460901 Adjust error message 2021-08-08 13:03:31 +02:00
Kaleb Olson 693325bcf2 Adds path separator checking and hint for automatic / expansion for some windows shells, eg., msys 2021-08-08 13:03:31 +02:00
dependabot[bot] ab81cad3b5 Bump regex from 1.4.6 to 1.5.4
Bumps [regex](https://github.com/rust-lang/regex) from 1.4.6 to 1.5.4.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.4.6...1.5.4)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-08 12:14:50 +02:00
dependabot[bot] 70850629e9 Bump globset from 0.4.6 to 0.4.8
Bumps [globset](https://github.com/BurntSushi/ripgrep) from 0.4.6 to 0.4.8.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.6...globset-0.4.8)

---
updated-dependencies:
- dependency-name: globset
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-08 12:14:41 +02:00
dependabot[bot] 8b0db189a1 Bump ctrlc from 3.1.7 to 3.1.9
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.1.7 to 3.1.9.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.1.7...3.1.9)

---
updated-dependencies:
- dependency-name: ctrlc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-08 11:48:54 +02:00
dependabot[bot] ad1b267601 Bump filetime from 0.2.13 to 0.2.14
Bumps [filetime](https://github.com/alexcrichton/filetime) from 0.2.13 to 0.2.14.
- [Release notes](https://github.com/alexcrichton/filetime/releases)
- [Commits](https://github.com/alexcrichton/filetime/compare/0.2.13...0.2.14)

---
updated-dependencies:
- dependency-name: filetime
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-08 11:48:47 +02:00
dependabot[bot] 04829c287d Bump anyhow from 1.0.35 to 1.0.42
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.35 to 1.0.42.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.35...1.0.42)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-08 11:43:15 +02:00
dependabot[bot] ca92bea0d7 Bump version_check from 0.9.2 to 0.9.3
Bumps [version_check](https://github.com/SergioBenitez/version_check) from 0.9.2 to 0.9.3.
- [Release notes](https://github.com/SergioBenitez/version_check/releases)
- [Commits](https://github.com/SergioBenitez/version_check/compare/v0.9.2...v0.9.3)

---
updated-dependencies:
- dependency-name: version_check
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-08 11:43:01 +02:00
dependabot[bot] 252beb8df7 Bump libc from 0.2.80 to 0.2.98
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.80 to 0.2.98.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.80...0.2.98)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-08 11:42:55 +02:00
dependabot[bot] b80764b19a Bump regex from 1.4.2 to 1.4.6
Bumps [regex](https://github.com/rust-lang/regex) from 1.4.2 to 1.4.6.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.4.2...1.4.6)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-08 11:27:36 +02:00
Daniel Bast 148fa64a73 Change dependabot interval to monthly 2021-08-08 11:15:18 +02:00
Daniel Bast 6ce55624f7 Add dependabot config for cargo updates 2021-08-08 11:15:18 +02:00
David Peter 708efaa812 Literal separators 2021-07-27 09:05:23 +02:00
David Peter a7a4499d1d Fix unneeded references 2021-07-27 09:05:23 +02:00
David Peter 2a2126c40a Fix clippy warnings 2021-07-27 09:05:23 +02:00
David Peter db30bfc2b7 Update README.md 2021-07-26 22:34:03 +02:00
David Peter 81a051f07c Fix shell completion path 2021-07-26 22:33:27 +02:00
David Peter 95d6470956 Fix stripping on arm musl target 2021-07-26 22:33:27 +02:00
David Peter e59a2001cc Fix shell completion path 2021-07-26 22:33:27 +02:00
David Peter c60338a28b Adapt test options 2021-07-26 22:33:27 +02:00
David Peter 16464a35b1 Fix man page path 2021-07-26 22:33:27 +02:00
David Peter 1d85e31260 Migrate to GitHub actions 2021-07-26 22:33:27 +02:00
Thayne McCombs 024309f378 Add comment to README about making sure .local/bin is on PATH (#797)
To make it more clear how to set up the symlin on debian/ubuntu.

Fixes #791
2021-07-20 09:05:01 -06:00
Luca Poldelmengo ac4ded5b27 Add explanation for musl releases 2021-06-20 10:26:58 +02:00
Sudeshna Sur c9873b4b82 A few spell-checks is required in the readme file
fd is a program to find entries in your 'filesytem'. It must be filesystem instead :)
2021-05-25 06:35:46 +02:00
Kaspar 07ebce9419 Add rofi to integration with other programs in README.md
As discussed in ISSUE #753
2021-05-13 11:40:10 +02:00
Delapouite f60b7687a2 doc: add link to GNU parallel 2021-03-14 20:10:47 +01:00
Andrew Mitchell cb385a4822 Issue 624 newer older help text (#733)
* Document the newer and older aliases in the help text

* Documentation for new older aliases, remove commas

* Adds better clarification on alias usage for newer and older

* Fixes inconsistencies in help text for newer and older aliases
2021-02-25 07:39:44 +01:00
David Peter efc71bc00b Update CHANGELOG 2021-02-15 22:53:50 +01:00
Allen Wild 1a3615df9c set default path separator to '/' in MSYS
MSYS and MSYS2 environments (such as Git Bash) have a UNIX like
filesystem which uses '/' as the path separator rather than '\', but
Rust doesn't know about this by default.

On Windows, check the MSYSTEM environment variable and set the default
value of the --path-separator option to '/' for convenience.

There is no similar detection of Cygwin because there seems to be no way
for Rust (and any native Win32) programs to detect that they're being
called from a Cygwin environment. Cygwin users can use a shell
alias/function/script to wrap fd.

Fixes: https://github.com/sharkdp/fd/issues/537
2021-02-15 20:08:59 +01:00
David Peter cf7dd43f80 Hide --print0 from the short help text 2021-02-14 20:02:37 +01:00
David Peter 4ebb3bbc81 Explain what will be executed 2021-02-14 19:59:53 +01:00
David Peter d283aba52e Move command execution section 2021-02-14 19:51:10 +01:00
David Peter 5423c45660 Update command execution section 2021-02-14 19:50:50 +01:00
David Peter 98738d2ac1 Remove xargs section from How-to 2021-02-14 11:27:44 +01:00
David Peter 371794047a Update features section 2021-02-14 11:22:10 +01:00
David Peter c43c931985 Update intro 2021-02-14 11:17:04 +01:00
David Peter 41dc622fb5 Update features section 2021-02-14 11:07:44 +01:00
David Peter 266ba7893a Add quick links 2021-02-14 11:03:24 +01:00
David Peter 194dbe2033 Move benchmark and CLI sections 2021-02-14 10:57:09 +01:00
David Peter 97f1558daf Move colorized output section to 'Troubleshooting' 2021-02-14 10:55:30 +01:00
David Peter f00af363d1 Fix section heading levels 2021-02-14 10:52:53 +01:00
David Peter 2155d19be0 Change section header 2021-02-14 10:51:40 +01:00
David Peter 3e728f3edd Update LICENSE terms => 2021 2021-02-14 10:51:30 +01:00
David Peter 6645131234 Integrate regex link into text 2021-02-14 10:47:34 +01:00
Travis Stewart 189d6a907a Move and update Parallel command execution section 2021-02-14 10:45:26 +01:00
David Peter 8a83ad41c8 Remove alias suggestions 2021-02-14 10:44:52 +01:00
Chinmay 5feb89aa99 README update: Use soft link instead of alias for Debian.
As export command will not work with fd because fd is just an alias to
fdfind, create a soft link instead of alias.

Addresses https://github.com/sharkdp/fd/issues/728
2021-02-14 10:44:52 +01:00
Allen Wild 8a532e5196 use custom --path-separator in arguments to --exec
When --path-separator is used, pass it on to commands run with
--exec(-batch) and --list-details.

On Windows, paths with drive letters (C:\foo) and UNC paths
(\\server\share\path) are handled as expected, though they're unlikely
to be found in normal usage of fd.

Fixes: https://github.com/sharkdp/fd/issues/697
2021-02-14 10:24:41 +01:00
johnwdjiang b8089f8d58 hide private func 2021-02-13 21:55:38 +01:00
David Peter 85fb7389af Shorter help text for --exec 2021-02-13 21:25:50 +01:00
Archer Stéphane eb28030f87 include similar change to src/app.rs after feeback 2021-02-13 21:24:15 +01:00
Archer Stéphane 5d2e011e90 change documentation message after feedbacks 2021-02-13 21:24:15 +01:00
Archer Stéphane 64bde11887 Try to clarify the documentation for -x command
see issue #712
2021-02-13 21:24:15 +01:00
Richard Taityr b928af7d9c fix typo (#723) 2021-02-02 22:37:36 -07:00
Chathika Weerasuriya 9bc20e38b1 fixed: typographic error in help
- Changed "extention" to "extension"
2021-01-15 19:33:34 +01:00
sharkdp 6a3aae52fd Bump version to v8.2.1 2020-12-07 19:01:36 +01:00
Travis Stewart 7dc81381b0 Move Installation and Development below Tutorial (#701) 2020-12-07 06:54:00 +01:00
sharkdp c7cf2d1104 Revert using 'cleanup', we are still using dpl v1 2020-12-06 19:06:33 +01:00
sharkdp 91eaff056f Update CHANGELOG 2020-12-06 16:04:37 +01:00
sharkdp cadaef3f07 Show error if pattern matches leading dot but --hidden is not given, closes #615 2020-12-06 15:57:33 +01:00
sharkdp 17bd256ae6 Improve --fixed-strings help text, closes #627 2020-12-06 14:57:47 +01:00
sharkdp ecfd8cea41 Update --help text regarding pattern, closes #684 2020-12-06 12:31:31 +01:00
sharkdp d2659de782 Improve --extension help text, closes #673 2020-12-06 12:20:50 +01:00
sharkdp d205a7ff9e Improve --exclude help text, closes #661 2020-12-06 12:15:01 +01:00
sharkdp 296300ce46 Fix clippy suggestions 2020-12-06 12:04:47 +01:00
sharkdp ea48a96945 Run cargo fmt 2020-12-06 12:02:22 +01:00
sharkdp f2fec45099 Update dirs-next and users dependency 2020-12-06 12:02:04 +01:00
sharkdp 4faa201c89 Update dependencies 2020-12-06 12:00:11 +01:00
sharkdp 54603501ec Bump version 2020-12-06 11:56:36 +01:00
sharkdp 45c66e7f1b Update CHANGELOG 2020-12-06 11:54:54 +01:00
xanonid 441a235a0f Simplify code a bit (suggestions by clippy) 2020-12-06 11:52:10 +01:00
xanonid 2cd3de8b94 Use NO_COLOR env variable check also for help screen 2020-12-06 11:52:10 +01:00
Marco Sirabella 6830b3dee9 Better defaults for LsColors 2020-12-06 11:48:19 +01:00
Platon Pronko f3edaf0e4d add changelog entry for exact file sizes in --size filter 2020-12-06 11:47:27 +01:00
Platon Pronko e97a1e90cb Add exact size support to the --size filter (#669) 2020-12-06 11:47:27 +01:00
Andreas Lappe 2338b3bc81 Fix typo in fd.1 2020-12-04 12:04:19 +01:00
sharkdp 9412648ee9 Add link to troubleshooting section 2020-12-01 19:44:13 +01:00
sharkdp 0a22dcc044 Add CHANGELOG section to CONTRIBUTING.md 2020-12-01 19:38:05 +01:00
Nick Flueckiger 24402dda7c Remove .UE and .UR to fix url display on macos 2020-11-06 21:44:21 +01:00
Nicholas Bailey 4f8697c407 Update README to have updated instructions. 2020-10-30 08:09:06 +01:00
sharkdp b2fa188029 Avoid panic when sending errors after receiver shutdown
fixes #678
2020-10-28 13:10:25 +01:00
David Peter e0adb45d08 Fail with error message if numeric arguments can not be parsed
closes #675
2020-10-25 21:18:53 +01:00
Matthias Reitinger ec4cc981fc Add --prune flag
closes #535
2020-10-25 08:16:01 +01:00
Amrit Rathie 06eb231fbd Update lockfile 2020-10-24 15:25:07 +02:00
Amrit Rathie fd1ddc9d08 Change dependency dirs to dirs-next 2020-10-24 15:25:07 +02:00
Matthias Reitinger e852009876 Add NO_COLOR to the man page 2020-10-24 12:15:06 +02:00
Allen Wild 99934ab98c add --base-directory, --path-separator, and --search-path to the man page
These options are in `fd --help` but missing from the man page.
Also fix a typo in the help text for --base-directory.
2020-10-14 08:35:09 +02:00
Craig Hills 633d486ac1 Adjust the regex syntax reference wording 2020-10-14 08:34:06 +02:00
Craig Hills 4d2472bfab Add documentation around regex syntax
This is to address https://github.com/sharkdp/fd/issues/622
2020-10-14 08:34:06 +02:00
Fredrik Fornwall a851570b15 Disable jemalloc on Android (fixes #636 and #642)
Disabling jemalloc for Android fixes a build failure since jemalloc does
not build out of the box for that platform on older API versions.

Android started using jemalloc as the system allocator around 2014 for
most devices. In the latest Android version (11) the system allocator
has been switched to Scudo, which while not being jemalloc is modern and
actively maintained and should be used for most software on Android.

See:
- https://android-developers.googleblog.com/2020/06/system-hardening-in-android-11.html
- https://blog.nsogroup.com/a-tale-of-two-mallocs-on-android-libc-allocators-part-2-jemalloc/
- https://stackoverflow.com/questions/2266762/android-libc-version-and-malloc-implementation
2020-10-08 12:45:48 +02:00
sharkdp f064e41bf1 Update CHANGELOG 2020-10-05 23:30:00 +02:00
Sebastián Mancilla ef1bfc7508 Add native Zsh completion
Replace the auto-generated Zsh completion with a full completion script.
This script improves completion support for options and arguments,
which is hard to obtain from an auto-generated completion.

- Full completion support for external commands and options when using
  `--exec`/`--exec-batch`.

- The completion for `--type` and `--color` adds descriptions instead of
  just values.

- Complete files when using `--ignore-file`.

- Stop completing files for the pattern argument.

- Improve completion for path arguments. Only directories are
  completed.

- Single-use options are not offered again.

- Updated exclusions lists for mutual-exclusive options.

- Add support for hidden options (as defined in `app.rs`). They are not
  offered for completion, but if present are considered (and their value
  may be completed).

Full support for exclusion lists depends of options groups, which was
added in Zsh 5.4. Older Zsh versions, as commented in the completion
script, will just offer most options.

The format of the completion script was taken from ripgrep completion
script. Command completion for `--exec`/`--exec-batch` was taken from
Zsh's `_find` completion.
2020-10-05 22:39:17 +02:00
Matthias Reitinger a38da63a34 Replace aliases in Travis config 2020-10-04 21:50:49 +02:00
Matthias Reitinger 9a49d0d6d0 Fix deprecated Travis config keys
* root: deprecated key sudo (The key `sudo` has no effect anymore.)
* deploy: deprecated key skip_cleanup (not supported in dpl v2, use
  cleanup)
2020-10-04 21:50:49 +02:00
gorogoroumaru 74c3431a2b improve time option usage (#645)
* improve time option usage
2020-09-10 00:34:27 -06:00
yuuji.yaginuma 1b4e1e9c1a Fix a link to ubuntu official page
Currently, https://packages.ubuntu.com/disco/fd-find shows an error.
This is because of the package page that unsupported version was removed.

So I removed the version from the link. That link shows a search result of `fd-find`
and can find OSs that can install `fd-find`.
2020-08-08 07:46:45 +02:00
David Peter 6f2c8cdf91 Add tmccombs as maintainer 2020-06-10 07:42:49 +02:00
David Peter e2f90dba44 Update LS_COLORS help text 2020-05-25 16:47:51 +02:00
sharkdp 5648597a61 Bump version to 8.1.1 2020-05-25 15:38:30 +02:00
sharkdp b3258e00df Update CHANGELOG 2020-05-25 15:37:03 +02:00
sharkdp 41d7cc33ca Add CHANGELOG entry 2020-05-25 12:17:32 +02:00
sharkdp a81fef9992 Windows: Check for xterm-256color environment variable as a proxy for color support 2020-05-25 12:17:32 +02:00
sharkdp 53c338d71f Windows: Always enable colors if --color=always is set 2020-05-25 12:17:32 +02:00
Dmitry Marakasov d77daa89a2 Merged into single project in repology 2020-05-24 17:25:52 +02:00
sharkdp 78cde856b1 Update dependencies 2020-05-19 18:01:51 +02:00
sharkdp a954944d69 Prepare 8.1.0 release 2020-05-19 17:58:07 +02:00
sharkdp 27162c2729 Update CHANGELOG 2020-05-19 17:57:14 +02:00
sharkdp 1d1eefdb7b Disable jemalloc on macOS
This is a workaround for #498. See issue for details.

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

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

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

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

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

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

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

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

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

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

Though it's not as few as possible:

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

Performance is much better when metadata is required:

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

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

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

While remaining the same when it's not:

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

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

    Summary
      './fd-after '\.h$' /usr -j1' ran
        1.01 ± 0.02 times faster than './fd-before '\.h$' /usr -j1'
2019-05-08 07:28:47 -05:00
Laurent Arnoud 35945c4a62 Use dual license on Debian
cf https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2019-03-17 18:59:16 +01:00
Laurent Arnoud 97908beb97 Add copyright file for deb package
ref https://github.com/sharkdp/fd/issues/416
2019-03-17 18:59:16 +01:00
evitalis f15540e501 Add OpenBSD installation steps 2019-03-13 08:17:06 +01:00
Tim Holland 074cfad3d4 Remove deprecated trim_left(_matches)?
warning: use of deprecated item 'core::str::<impl str>::trim_left_matches': superseded by `trim_start_matches`
   --> src/main.rs:222:28
    |
222 |                 .map(|e| e.trim_left_matches('.'))
    |                            ^^^^^^^^^^^^^^^^^
    |
    = note: #[warn(deprecated)] on by default
2019-03-02 08:15:12 +01:00
Tobias Kortkamp f3ec3d9cf0 Update FreeBSD install instructions
Installing fd does not require using portmaster (it will also build
fd from source).  IMHO this is pretty misleading.  Let's update the
FreeBSD install instructions to recommend using the binary package
in the official repository like on other systems.
2019-02-27 07:21:45 +01:00
Alex Leferry 2 46e865f1a2 Fix documentation to filter search by directory type 2019-02-19 07:21:02 +01:00
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
53 changed files with 7014 additions and 2995 deletions
+1
View File
@@ -0,0 +1 @@
github: [sharkdp, tavianator]
+41
View File
@@ -0,0 +1,41 @@
name: Bug Report
description: Report a bug.
title: "[BUG] "
labels: bug
body:
- type: markdown
attributes:
value: |
Please check out the [troubleshooting section](https://github.com/sharkdp/fd#troubleshooting) first.
- type: checkboxes
attributes:
options:
- label: I have read the troubleshooting section and still think this is a bug.
required: true
- type: textarea
id: bug
attributes:
label: "Describe the bug you encountered:"
validations:
required: true
- type: textarea
id: expected
attributes:
label: "Describe what you expected to happen:"
- type: input
id: version
attributes:
label: "What version of `fd` are you using?"
placeholder: "paste the output of `fd --version` here"
validations:
required: true
- type: textarea
id: os
attributes:
label: Which operating system / distribution are you on?
placeholder: |
Unix: paste the output of `uname -srm` and `lsb_release -a` here.
Windows: please tell us your Windows version
render: shell
validations:
required: true
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: true
@@ -0,0 +1,8 @@
---
name: Feature Request
about: Suggest an idea for this project.
title: ''
labels: feature-request
assignees: ''
---
+13
View File
@@ -0,0 +1,13 @@
---
name: Question
about: Ask a question about 'fd'.
title: ''
labels: question
assignees: ''
---
**What version of `fd` are you using?**
[paste the output of `fd --version` here]
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "monthly"
+364
View File
@@ -0,0 +1,364 @@
name: CICD
env:
MIN_SUPPORTED_RUST_VERSION: "1.54.0"
CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
on:
workflow_dispatch:
pull_request:
push:
branches:
- master
tags:
- '*'
jobs:
code_quality:
name: Code quality
runs-on: ubuntu-20.04
steps:
- name: Checkout source code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
default: true
profile: minimal # minimal component installation (ie, no documentation)
components: clippy, rustfmt
- name: Ensure `cargo fmt` has been run
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
- name: Ensure MSRV is set in `clippy.toml`
run: grep "^msrv = \"${{ env.MIN_SUPPORTED_RUST_VERSION }}\"\$" clippy.toml
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --locked --all-targets --all-features
min_version:
name: Minimum supported rust version
runs-on: ubuntu-20.04
steps:
- name: Checkout source code
uses: actions/checkout@v2
- name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }}
default: true
components: clippy
- name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
uses: actions-rs/cargo@v1
with:
command: clippy
args: --locked --all-targets --all-features
profile: minimal # minimal component installation (ie, no documentation)
- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
args: --locked
build:
name: ${{ matrix.job.os }} (${{ matrix.job.target }})
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-20.04, target: arm-unknown-linux-gnueabihf , use-cross: true }
- { os: ubuntu-20.04, target: arm-unknown-linux-musleabihf, use-cross: true }
- { os: ubuntu-20.04, target: aarch64-unknown-linux-gnu , use-cross: true }
- { os: ubuntu-20.04, target: i686-unknown-linux-gnu , use-cross: true }
- { os: ubuntu-20.04, target: i686-unknown-linux-musl , use-cross: true }
- { os: ubuntu-20.04, target: x86_64-unknown-linux-gnu }
- { os: ubuntu-20.04, target: x86_64-unknown-linux-musl , use-cross: true }
- { os: macos-10.15 , target: x86_64-apple-darwin }
# - { os: windows-2019, target: i686-pc-windows-gnu } ## disabled; error: linker `i686-w64-mingw32-gcc` not found
- { os: windows-2019, target: i686-pc-windows-msvc }
- { os: windows-2019, target: x86_64-pc-windows-gnu }
- { os: windows-2019, target: x86_64-pc-windows-msvc }
steps:
- name: Checkout source code
uses: actions/checkout@v2
- name: Install prerequisites
shell: bash
run: |
case ${{ matrix.job.target }} in
arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
esac
- name: Extract crate information
shell: bash
run: |
echo "PROJECT_NAME=fd" >> $GITHUB_ENV
echo "PROJECT_VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV
echo "PROJECT_MAINTAINER=$(sed -n 's/^authors = \["\(.*\)"\]/\1/p' Cargo.toml)" >> $GITHUB_ENV
echo "PROJECT_HOMEPAGE=$(sed -n 's/^homepage = "\(.*\)"/\1/p' Cargo.toml)" >> $GITHUB_ENV
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.job.target }}
override: true
profile: minimal # minimal component installation (ie, no documentation)
- name: Show version information (Rust, cargo, GCC)
shell: bash
run: |
gcc --version || true
rustup -V
rustup toolchain list
rustup default
cargo -V
rustc -V
- name: Build
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.job.use-cross }}
command: build
args: --locked --release --target=${{ matrix.job.target }}
- name: Strip debug information from executable
id: strip
shell: bash
run: |
# Figure out suffix of binary
EXE_suffix=""
case ${{ matrix.job.target }} in
*-pc-windows-*) EXE_suffix=".exe" ;;
esac;
# Figure out what strip tool to use if any
STRIP="strip"
case ${{ matrix.job.target }} in
arm-unknown-linux-*) STRIP="arm-linux-gnueabihf-strip" ;;
aarch64-unknown-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;;
*-pc-windows-msvc) STRIP="" ;;
esac;
# Setup paths
BIN_DIR="${{ env.CICD_INTERMEDIATES_DIR }}/stripped-release-bin/"
mkdir -p "${BIN_DIR}"
BIN_NAME="${{ env.PROJECT_NAME }}${EXE_suffix}"
BIN_PATH="${BIN_DIR}/${BIN_NAME}"
# Copy the release build binary to the result location
cp "target/${{ matrix.job.target }}/release/${BIN_NAME}" "${BIN_DIR}"
# Also strip if possible
if [ -n "${STRIP}" ]; then
"${STRIP}" "${BIN_PATH}"
fi
# Let subsequent steps know where to find the (stripped) bin
echo ::set-output name=BIN_PATH::${BIN_PATH}
echo ::set-output name=BIN_NAME::${BIN_NAME}
- name: Set testing options
id: test-options
shell: bash
run: |
# test only library unit tests and binary for arm-type targets
unset CARGO_TEST_OPTIONS
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac;
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
- name: Run tests
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.job.use-cross }}
command: test
args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
- name: Create tarball
id: package
shell: bash
run: |
PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
PKG_BASENAME=${PROJECT_NAME}-v${PROJECT_VERSION}-${{ matrix.job.target }}
PKG_NAME=${PKG_BASENAME}${PKG_suffix}
echo ::set-output name=PKG_NAME::${PKG_NAME}
PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package"
ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/"
mkdir -p "${ARCHIVE_DIR}"
mkdir -p "${ARCHIVE_DIR}/autocomplete"
# Binary
cp "${{ steps.strip.outputs.BIN_PATH }}" "$ARCHIVE_DIR"
# Man page
cp 'doc/${{ env.PROJECT_NAME }}.1' "$ARCHIVE_DIR"
# README, LICENSE and CHANGELOG files
cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR"
# Autocompletion files
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "$ARCHIVE_DIR/autocomplete/"
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "$ARCHIVE_DIR/autocomplete/"
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'_${{ env.PROJECT_NAME }}.ps1' "$ARCHIVE_DIR/autocomplete/"
cp 'contrib/completion/_fd' "$ARCHIVE_DIR/autocomplete/"
# base compressed package
pushd "${PKG_STAGING}/" >/dev/null
case ${{ matrix.job.target }} in
*-pc-windows-*) 7z -y a "${PKG_NAME}" "${PKG_BASENAME}"/* | tail -2 ;;
*) tar czf "${PKG_NAME}" "${PKG_BASENAME}"/* ;;
esac;
popd >/dev/null
# Let subsequent steps know where to find the compressed package
echo ::set-output name=PKG_PATH::"${PKG_STAGING}/${PKG_NAME}"
- name: Create Debian package
id: debian-package
shell: bash
if: startsWith(matrix.job.os, 'ubuntu')
run: |
COPYRIGHT_YEARS="2018 - "$(date "+%Y")
DPKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/debian-package"
DPKG_DIR="${DPKG_STAGING}/dpkg"
mkdir -p "${DPKG_DIR}"
DPKG_BASENAME=${PROJECT_NAME}
DPKG_CONFLICTS=${PROJECT_NAME}-musl
case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac;
DPKG_VERSION=${PROJECT_VERSION}
unset DPKG_ARCH
case ${{ matrix.job.target }} in
aarch64-*-linux-*) DPKG_ARCH=arm64 ;;
arm-*-linux-*hf) DPKG_ARCH=armhf ;;
i686-*-linux-*) DPKG_ARCH=i686 ;;
x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
*) DPKG_ARCH=notset ;;
esac;
DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
echo ::set-output name=DPKG_NAME::${DPKG_NAME}
# Binary
install -Dm755 "${{ steps.strip.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.strip.outputs.BIN_NAME }}"
# Man page
install -Dm644 'doc/${{ env.PROJECT_NAME }}.1' "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1"
gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1"
# Autocompletion files
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ env.PROJECT_NAME }}"
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ env.PROJECT_NAME }}.fish"
install -Dm644 'contrib/completion/_fd' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ env.PROJECT_NAME }}"
# README and LICENSE
install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md"
install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-MIT"
install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-APACHE"
install -Dm644 "CHANGELOG.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog"
gzip -n --best "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog"
cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" <<EOF
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: ${{ env.PROJECT_NAME }}
Source: ${{ env.PROJECT_HOMEPAGE }}
Files: *
Copyright: ${{ env.PROJECT_MAINTAINER }}
Copyright: $COPYRIGHT_YEARS ${{ env.PROJECT_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
chmod 644 "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright"
# control file
mkdir -p "${DPKG_DIR}/DEBIAN"
cat > "${DPKG_DIR}/DEBIAN/control" <<EOF
Package: ${DPKG_BASENAME}
Version: ${DPKG_VERSION}
Section: utils
Priority: optional
Maintainer: ${{ env.PROJECT_MAINTAINER }}
Homepage: ${{ env.PROJECT_HOMEPAGE }}
Architecture: ${DPKG_ARCH}
Provides: ${{ env.PROJECT_NAME }}
Conflicts: ${DPKG_CONFLICTS}
Description: simple, fast and user-friendly alternative to find
fd is a program to find entries in your filesystem.
It is a simple, fast and user-friendly alternative to find.
While it does not aim to support all of finds powerful functionality, it provides
sensible (opinionated) defaults for a majority of use cases.
EOF
DPKG_PATH="${DPKG_STAGING}/${DPKG_NAME}"
echo ::set-output name=DPKG_PATH::${DPKG_PATH}
# build dpkg
fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}"
- name: "Artifact upload: tarball"
uses: actions/upload-artifact@master
with:
name: ${{ steps.package.outputs.PKG_NAME }}
path: ${{ steps.package.outputs.PKG_PATH }}
- name: "Artifact upload: Debian package"
uses: actions/upload-artifact@master
if: steps.debian-package.outputs.DPKG_NAME
with:
name: ${{ steps.debian-package.outputs.DPKG_NAME }}
path: ${{ steps.debian-package.outputs.DPKG_PATH }}
- name: Check for release
id: is-release
shell: bash
run: |
unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi
echo ::set-output name=IS_RELEASE::${IS_RELEASE}
- name: Publish archives and packages
uses: softprops/action-gh-release@v1
if: steps.is-release.outputs.IS_RELEASE
with:
files: |
${{ steps.package.outputs.PKG_PATH }}
${{ steps.debian-package.outputs.DPKG_PATH }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-141
View File
@@ -1,141 +0,0 @@
language: rust
cache: cargo
matrix:
# allow_failures:
# - rust: nightly
include:
# Stable channel.
- os: linux
rust: stable
env: TARGET=x86_64-unknown-linux-gnu
- os: linux
rust: stable
env: TARGET=x86_64-unknown-linux-musl
- os: linux
rust: stable
env: TARGET=i686-unknown-linux-gnu
- os: linux
rust: stable
env: TARGET=i686-unknown-linux-musl
- os: osx
rust: stable
env: TARGET=x86_64-apple-darwin
- os: linux
rust: stable
env:
- TARGET=arm-unknown-linux-gnueabihf
- CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
# Beta channel.
- os: linux
rust: beta
env: TARGET=x86_64-unknown-linux-gnu
# Disabled to reduce total CI time
# - os: linux
# rust: beta
# env: TARGET=x86_64-unknown-linux-musl
# - os: linux
# rust: beta
# env: TARGET=i686-unknown-linux-gnu
# - os: linux
# rust: beta
# env: TARGET=i686-unknown-linux-musl
# - os: osx
# rust: beta
# env: TARGET=x86_64-apple-darwin
# Nightly channel.
- os: linux
rust: nightly
env: TARGET=x86_64-unknown-linux-gnu
# Disabled to reduce total CI time
# - os: linux
# rust: nightly
# env: TARGET=x86_64-unknown-linux-musl
# - os: linux
# rust: nightly
# env: TARGET=i686-unknown-linux-gnu
# - os: linux
# rust: nightly
# env: TARGET=i686-unknown-linux-musl
# - os: osx
# rust: nightly
# env: TARGET=x86_64-apple-darwin
# Minimum Rust supported channel.
- os: linux
rust: 1.29.0
env: TARGET=x86_64-unknown-linux-gnu
- os: linux
rust: 1.29.0
env: TARGET=x86_64-unknown-linux-musl
- os: linux
rust: 1.29.0
env: TARGET=i686-unknown-linux-gnu
- os: linux
rust: 1.29.0
env: TARGET=i686-unknown-linux-musl
- os: osx
rust: 1.29.0
env: TARGET=x86_64-apple-darwin
# Code formatting check
- os: linux
rust: nightly
# skip the global install step
install:
- cargo install --debug --force rustfmt-nightly
script: cargo fmt -- --check
sudo: required
before_install:
- ci/before_install.bash
env:
global:
# Default target on travis-ci.
# Used as conditional check in the install stage
- HOST=x86_64-unknown-linux-gnu
# Used on the deployment script
- PROJECT_NAME=fd
install:
# prevent target re-add error from rustup
- if [[ $TRAVIS_OS_NAME = linux && $HOST != $TARGET ]]; then rustup target add $TARGET; fi
script:
- ci/script.bash
before_deploy:
- bash ci/before_deploy.bash
deploy:
provider: releases
# NOTE updating the `api_key.secure`
# - go to: https://github.com/settings/tokens/new
# - generate new token using `public_repo` scope
# - encrypt it using: `travis encrypt API_KEY_HERE`
# - paste the output below
api_key:
secure: "RyFdh2lpDmaNhPar7ezsb18Xz+6XFM40y7cZCDRML+Sk+eYK1xtDNfEhDRJU5Qo1ReVsByds/QJTSXr2KmZPk3lXwG3SiN7UtrLUxCxFr6qrcM/iujlKTf5UxeRklkzPXxnH95DEyEgxvgbVhWTGVDWoyMnrVQXZKDy6z1iAiYB5h2Zl1rs+MRb/Enlt5q6XIKAlG0ppGtl8CfYudq5ZiqfJaMWTt9SWm2YskC8FeMc0S3IM6/EhTvaNYLdaarFqVWQEVql+6oCuL3ayPzmGyxLdxM37tIMNQ0f97zxqWodacXTG5ULdRD8if1l/SmTujrtjbZ0KWRjsjOq4vBtxBJKGdprcSiB0xH/hToqqtTSO0z5FPXi5cB8UlK6YLDDHcP3kXNer8CYMLI1VPaUDLTF57/0/RPi2DZiiGfZsIAS6PsICbHdTQVzxQckM4lN1vnAGgkhXIMbztml21pv+QrGy98OZJ0ubf5ztgQhpT0WPH4JXT8M6htsoo8dZf8lQ5aLfmW9RKePJDqixQwPqmimPIkrlxRDTDGII0ZAZws7l779eOLmEcM2tH2HbsUKUCZIG/pRHLSlP45Jn2bULGzuXZ2daq70z6zvIbom0CUzSXIvdTXEZI2AM5RBvPYGGaKI8YlxgRdQvJp3h0BzPdFOXI3RAxscCY7PJpa/RdIg="
# for uploading multiple files
file_glob: true
# NOTE explanation on each env variable
# - PROJECT_NAME: name of the project, set on the `env.global` above
# - TRAVIS_TAG: tag name that the build is being deployed for, usually the version number
# - TARGET: target triple of the build
file:
- $PROJECT_NAME-$TRAVIS_TAG-$TARGET.*
- $PROJECT_NAME*.deb
# don't delete artifacts from previous stage
skip_cleanup: true
on:
# deploy only if we push a tag
tags: true
# deploy only on stable channel that has TARGET env variable sets
condition: $TRAVIS_RUST_VERSION = stable && $TARGET != ""
notifications:
email:
on_success: never
+588
View File
@@ -0,0 +1,588 @@
# Upcoming release
## Performance improvements
## Features
## Bugfixes
## Changes
## Other
# v8.3.2
## Bugfixes
- Invalid absolute path on windows when searching from the drive root, see #931 and #936 (@gbarta)
# v8.3.1
## Bugfixes
- Stop implying `--no-ignore-parent` when `--no-vcs-ignore` is supplied, see #907, #901, #908 (@tmccombs)
- fd no longer waits for the whole traversal if the only matches arrive within max_buffer_time, see #868 and #895 (@tavianator)
- `--max-results=1` now immediately quits after the first result, see #867
- `fd -h` does not panic anymore when stdout is closed, see #897
## Changes
- Disable jemalloc on FreeBSD, see #896 (@xanderio)
- Updated man page, see #912 (@rlue)
- Updated zsh completions, see #932 (@tmccombs)
# v8.3.0
## Performance improvements
- Colorized output is now significantly faster, see #720 and #853 (@tavianator)
- Writing to stdout is now buffered if the output does not go to a TTY. This increases performance
when the output of `fd` is piped to another program or to a file, see #885 (@tmccombs, original
implementation by @sourlemon207)
- File metadata is now cached between the different filters that require it (e.g. `--owner`,
`--size`), reducing the number of `stat` syscalls when multiple filters are used; see #863
(@tavianator, original implementation by @alexmaco)
## Features
- Don't buffer command output from `--exec` when using a single thread. See #522
- Add new `-q, --quiet` flag, see #303 (@Asha20)
- Add new `--no-ignore-parent` flag, see #787 (@will459)
- Add new `--batch-size` flag, see #410 (@devonhollowood)
- Add opposing command-line options, see #595 (@Asha20)
- Add support for more filesystem indicators in `LS_COLORS`, see
https://github.com/sharkdp/lscolors/pull/35 (@tavianator)
## Bugfixes
- Always show the `./` prefix for search results unless the output is a TTY or `--strip-cwd-prefix` is set, see #760 and #861 (@jcaplan)
- Set default path separator to `/` in MSYS, see #537 and #730 (@aswild)
- fd cannot search files under a RAM disk, see #752
- fd doesn't show substituted drive on Windows, see #365
- Properly handle write errors to devices that are full, see #737
- Use local time zone for time functions (`--change-newer-than`, `--change-older-than`), see #631 (@jacobmischka)
- Support `--list-details` on more platforms (like BusyBox), see #783
- The filters `--owner`, `--size`, and `--changed-{within,before}` now apply to symbolic links
themselves, rather than the link target, except when `--follow` is specified; see #863
- Change time comparisons to be exclusive, see #794 (@jacobmischka)
## Changes
- Apply custom `--path-separator` to commands run with `--exec(-batch)` and `--list-details`, see #697 (@aswild)
## Other
- Many documentation updates
# v8.2.1
No functional changes with respect to v8.2.0. Bugfix in the release process.
# v8.2.0
## Features
- Add new `--prune` flag, see #535 (@reima)
- Improved the usability of the time-based options, see #624 and #645 (@gorogoroumaru)
- Add support for exact file sizes in the `--size` filter, see #669 and #696 (@Rogach)
- `fd` now prints an error message if the search pattern requires a leading dot but
`--hidden` is not enabled (Unix only), see #615
## Bugfixes
- Avoid panic when performing limited searches in directories with restricted permissions, see #678
- Invalid numeric command-line arguments are silently ignored, see #675
- Disable jemalloc on Android, see #662
- The `--help` text will be colorless if `NO_COLOR` has been set, see #600 (@xanonid)
## Changes
- If `LS_COLORS` is not set (e.g. on Windows), we now provide a more comprehensive default which
includes much more filetypes, see #604 and #682 (mjsir911).
## Other
- Added `zsh` completion files, see #654 and #189 (@smancill)
# v8.1.1
## Bugfixes
- Support colored output on older Windows versions if either (1) `--color=always` is set or (2) the `TERM` environment variable is set. See #469
# v8.1.0
## Features
- Add new `--owner [user][:group]` filter. See #307 (pull #581) (@alexmaco)
- Add support for a global ignore file (`~/.config/fd/ignore` on Unix), see #575 (@soedirgo)
- Do not exit immediately if one of the search paths is missing, see #587 (@DJRHails)
## Bugfixes
- Reverted a change from fd 8.0 that enabled colors on all Windows terminals (see below) in order to support older Windows versions again, see #577. Unfortunately, this re-opens #469
- Fix segfault caused by jemalloc on macOS Catalina, see #498
- Fix `--glob` behavior with empty pattern, see #579 (@SeamusConnor)
- Fix `--list-details` on FreeBSD, DragonFly BSD, OpenBSD and NetBSD. See #573 (@t6)
## Changes
- Updated documentation for `--size`, see #584
# v8.0.0
## Features
- Add a new `-l`/`--list-details` option to show more details about the search results. This is
basically an alias for `--exec-batch ls -l` with some additional `ls` options.
This can be used in order to:
* see metadata like permissions, owner, file size, modification times (#491)
* see symlink targets (#482)
* achieve a deterministic output order (#324, #196, #159)
- Add a new `--max-results=<count>` option to limit the number of search results, see #472, #476 and #555
This can be useful to speed up searches in cases where you know that there are only N results.
Using this option is also (slightly) faster than piping to `head -n <count>` where `fd` can only
exit when it finds the search results `<count> + 1`.
- Add the alias `-1` for `--max-results=1`, see #561. (@SimplyDanny).
- Add new `--type socket` and `--type pipe` filters, see #511.
- Add new `--min-depth <depth>` and `--exact-depth <depth>` options in addition to the existing option
to limit the maximum depth. See #404.
- Support additional ANSI font styles in `LS_COLORS`: faint, slow blink, rapid blink, dimmed, hidden and strikethrough.
## Bugfixes
- Preserve non-UTF8 filenames: invalid UTF-8 filenames are now properly passed to child-processes
when using `--exec`, `--exec-batch` or `--list-details`. In `fd`'s output, we replace non-UTF-8
sequences with the "" character. However, if the output of `fd` goes to another process, we
print the actual bytes of the filename. For more details, see #558 and #295.
- `LS_COLORS` entries with unsupported font styles are not completely ignored, see #552
## Changes
- Colored output will now be enabled by default on older Windows versions.
This allows the use of colored output if the terminal supports it (e.g.
MinTTY, Git Bash). On the other hand, this will be a regression for users
on older Windows versions with terminals that do not support ANSI escape
sequences. Affected users can use an alias `fd="fd --color=never"` to
continue using `fd` without colors. There is no change of behavior for
Windows 10. See #469.
- When using `--glob` in combination with `--full-path`, a `*` character does not match a path
separation character (`/` or `\\`) anymore. You can use `**` for that. This allows things like
`fd -p -g '/some/base/path/*/*/*.txt'` which would previously match to arbitrary depths (instead
of exactly two folders below `/some/base/path`. See #404.
- "Legacy" support to use `fd -exec` (with a single dash) has been removed. Use `fd -x` or
`fd --exec` instead.
- Overall improved error handling and error messages.
## Other
- Korean translation of the README, see: [한국어](https://github.com/spearkkk/fd-kor) (@spearkkk)
# v7.5.0
## Features
- Added `--one-file-system` (aliases: `--mount`, `--xdev`) to not cross file system boundaries on Unix and Windows, see #507 (@FallenWarrior2k).
- Added `--base-directory` to change the working directory in which `fd` is run, see #509 and #475 (@hajdamak).
- `fd` will not use colored output if the `NO_COLOR` environment variable is set, see #550 and #551 (@metadave).
- `fd --exec` will return exit code 1 if one of the executed commands fails, see #526 and #531 (@fusillicode and @Giuffre)
## Bug Fixes
- Fixed 'command not found' error when using zsh completion, see #487 (@barskern).
- `fd -L` should include broken symlinks, see #357 and #497 (@tommilligan, @neersighted and @sharkdp)
- Display directories even if we don't have permission to enter, see #437 (@sharkdp)
## Changes
- A flag can now be passed multiple times without producing an error, see #488 and #496 (@rootbid).
- Search results are sorted when using the `-X` option to match the behaviour of piping to `xargs`, see #441 and #524 (@Marcoleni @crash-g).
# v7.4.0
## Performance improvements
- Reduce number of `stat` syscalls, improving the performance for searches where file metadata is
required (`--type`, `--size`, `--changed-within`, …), see #434 (@tavianator)
- Use jemalloc by default, improving the performance for almost all searches, see #481. Note that
Windows and `*musl*` builds do not profit from this.
## Features
- Added a new `-g`/`--glob` option to switch to glob-based searches (instead of regular expression
based searches). This is accompanied by a new `--regex` option that can be used to switch back,
if users want to `alias fd="fd --glob"`. See #284
- Added a new `--path-separator <sep>` option which can be useful for Windows users who
want/need `fd` to use `/` instead of `\`, see #428 and #153 (@mookid)
- Added support for hidden files on Windows, see #379
- When `fd` is run with the `--exec-batch`/`-X` option, it now exposes the exit status of the
command that was run, see #333.
- Exit immediately when Ctrl-C has been pressed twice, see #423
## Bugfixes
- Make `--changed-within`/`--changed-before` work for directories, see #470
## Other
- Pre-built `fd` binaries should now be available for `armhf` targets, see #457 (@detly)
- `fd` is now available on Alpine Linux, see #451 (@5paceToast)
- `fd` is now in the officla FreeBSD repositories, see #412 (@t6)
- Added OpenBSD install instructions, see #421 (@evitalis)
- Added metadata to the Debian package, see #416 (@cathalgarvey)
- `fd` can be installed via npm, see #438 (@pablopunk)
# v7.3.0
## Features
- New `--exec-batch <cmd>`/`-X <cmd>` option for batch execution of commands, see #360 (@kimsnj).
This allows you to do things like:
``` bash
fd … -X vim # open all search results in vim (or any other editor)
fd … -X ls -l # view detailed stats about the search results with 'ls'
fd -e svg -X inkscape # open all SVG files in Inkscape
```
- Support for 24-bit color codes (when specified via `LS_COLORS`) as well as
different font styles (bold, italic, underline).
## Changes
- A few performance improvements, in particular when printing lots of colorized
results to the console, see #370
- The `LS_COLORS` handling has been "outsourced" to a separate crate (https://github.com/sharkdp/lscolors) that is now being used by other tools as well: [fselect](https://github.com/jhspetersson/fselect), [lsd](https://github.com/Peltoche/lsd/pull/84). For details, see #363.
## Other
- `fd` will be available in Ubuntu Disco DIngo (19.04), see #373 (@sylvestre)
- This release should come with a static ARM binary (`arm-unknown-linux-musleabihf`), see #320 (@duncanfinney)
- Various documentation improvements, see #389
## Thanks
Special thanks to @alexmaco for his awesome work on refactoring and code improvements! (see #401, #398, and #383)
# v7.2.0
## Features
* Added support for filtering by file modification time by adding two new options `--changed-before <date|duration>` and `--changed-within <..>`. For more details, see the `--help` text, the man page, the relevant issue #165 and the PR #339 (@kimsnj)
* Added `--show-errors` option to enable the display of filesystem error messages such as "permission denied", see #311 (@psinghal20 and @majecty)
* Added `--maxdepth` as a (hidden) alias for `--max-depth`, see #323 (@mqudsi)
* Added `--search-path` option which can be supplied to replace the positional `path` argument at any position.
## Changes
* Loosen strict handling of missing `--ignore-file`, see #280 (@psinghal20)
* Re-enabled `.ignore` files, see #156.
## Bugfixes
* `fd` could previously get stuck when run from the root directory in the
presence of zombie processes. This curious bug has been fixed in Rust 1.29 and higher. For more details, see #288, [rust-lang/rust#50619](https://github.com/rust-lang/rust/issues/50619) and [the fix](https://github.com/rust-lang/rust/pull/50630)
## Other
* `fd` has officially landed in Debian! See #345 for details. Thanks goes to @sylvestre, @paride and possibly others I don't know about.
* Added Chinese translation of README (@chinanf-boy)
## Thanks
A special thanks goes to @joshleeb for his amazing improvements throughout
the code base (new tests, refactoring work and various other things)!
# v7.1.0
## Features
* Added `--size` filter option, see #276 (@stevepentland, @JonathanxD and @alexmaco)
* Added `--type empty` (or `-t e`) to search for empty files and/or directories, see #273
## Changes
* With the new version, `.gitignore` files will only be respected in Git repositories, not outside.
* A few performance improvements for `--type` searches, see 641976cf7ad311ba741571ca8b7f02b2654b6955 and 50a2bab5cd52d26d4a3bc786885a2c270ed3b227
## Other
* Starting with this release, we will offer pre-built ARM binaries, see #244
* Added instructions on how to use `fd` with `emacs`, see #282 (@redguardtoo)
* `fd` is now in the official openSUSE repositories, see #275 (@avindra)
* `fd` is now available via MacPorts, see #291 (@raimue)
# v7.0.0
## Features
* Added `--type executable` (or `-t x`) to search for executable files only, see #246 (@PramodBisht)
* Added support for `.fdignore` files, see #156 and #241.
* Added `--ignore-file` option to add custom ignore files, see #156.
* Suggest `--fixed-strings` on invalid regular expressions, see #234 (@PramodBisht)
* Detect when user supplied path instead of pattern, see #235.
## Changes
* `.ignore` and `.rgignore` files are not parsed anymore. Use `.fdignore` files
or add custom files via `--ignore-file` instead.
* Updated to `regex-syntax` 0.5 (@cuviper)
## Bugfixes
* Properly normalize absolute paths, see #268
* Invalid utf8 filenames displayed when `-e` is used, see #250
* If `--type` is used, fifos/sockets/etc. are always shown, see #260
## Other
* Packaging:
* The Arch Linux package is now simply called `fd`.
* There is now a `fd` ebuild for Gentoo Linux.
* There is a `scoop` package for `fd` (Windows).
* There is a `Chocolatey` package for `fd` (Windows).
* There is a Fedora `copr` package for `fd`.
# v6.3.0
## Features
* Files with multiple extensions can now be found via `--extension`/`-e`, see #214 (@althonos)
``` bash
> fd -e tar.gz
```
* Added new `-F`/`--fixed-strings`/`--literal` option that treats the pattern as a literal string instead of a regular expression, see #157
``` bash
> fd -F 'file(1).txt'
```
* Allow `-exec` to work as `--exec`, see #226 (@stevepentland)
## Bugfixes
* Fixed `Ctrl-C` handling when using `--exec`, see #224 (@Doxterpepper)
* Fixed wrong file owner for files in deb package, see #213
## Other
* Replaced old gif by a fancy new SVG screencast (@marionebl)
* Updated [benchmark results](https://github.com/sharkdp/fd#benchmark) (fd has become faster in the meantime!). There is a new repository that hosts several benchmarking scripts for fd: https://github.com/sharkdp/fd-benchmarks
# v6.2.0
## Features
* Support for filtering by multiple file extensions and multiple file types, see #199 and #177
(@tkadur).
For example, it's possible to search for C++ source or header files:
``` bash
> fd -e cpp -e c -e cxx -e h pattern
```
## Changes
* The size of the output buffer (for sorting search results) is now limited to 1000 entries. This
improves the search speed significantly if there are a lot of results, see #191 (@sharkdp).
## Bugfixes
* Fix a bug where long-running searches could not be killed via Ctrl-C, see #210 (@Doxterpepper)
* fd's exit codes are now in accordance with Unix standards, see #201 (@Doxterpepper)
## Other
* Bash, zsh and fish completion should now work with the Ubuntu `.deb` packages, see #195 and #209
(@tmccombs and @sharkdp)
* There is a new section on how to set up `fzf` to use `fd` in the
[README](https://github.com/sharkdp/fd#using-fd-with-fzf), see #168.
# v6.1.0
## Features
* Support for multiple search paths, see #166 (@Doxterpepper)
* Added `--no-ignore-vcs` option to disable `.gitignore` and other VCS ignore files,
without disabling `.ignore` files - see #156 (@ptzz).
## Bugfixes
* Handle terminal signals, see #128 (@Doxterpepper)
* Fixed hang on `--exec` when user input was required, see #178 and #193 (@reima)
## Other
* Debian packages are now created via Travis CI and should be available for this and all
future releases (@tmccombs).
* fd is now available on Void Linux (@maxice8)
* The minimum required Rust version is now 1.20
## Thanks
@Doxterpepper deserves a special mention for his great work that is included in this release and
for the support in ticket discussions and concerning Travis CI fixes. Thank you very much!
Thanks also go out to @tmccombs for the work on Debian packages and for reviewing a lot of pull requests!
# v6.0.0
## Changes
- The `--exec`/`-x` option does not spawn an intermediate shell anymore. This improves the
performance of parallel command execution and fixes a whole class of (present and potentially
future) problems with shell escaping. The drawback is that shell commands cannot directly be
called with `--exec`. See #155 for the full discussion. These changes have been implemented by
@reima (Thanks!).
## Bugfixes
- `--exec` does not escape cmd.exe metacharacters on Windows (see #155, as above).
## Other
* *fd* is now available in the FreeBSD ports (@andoriyu)
* The minimal `rustc` version is now checked when building with `cargo`, see #164 (@matematikaadit)
* The output directory for the shell completion files is created if it does not exist (@andoriyu)
# v5.0.0
## Features
* Added new `--exec`, `-x` option for parallel command execution (@mmstick, see #84 and #116). See the corresponding [README section](https://github.com/sharkdp/fd#parallel-command-execution) for an introduction.
* Auto-disable color output on unsupported Windows shells like `cmd.exe` (@iology, see #129)
* Added the `--exclude`, `-X` option to suppress certain files/directories in the search results
(see #89).
* Added ripgrep aliases `-u` and `-uu` for `--no-ignore` and `--no-ignore --hidden`, respectively
(@unsignedint, see #92)
* Added `-i`, `--ignore-case` (@iology, see #95)
* Made smart case really smart (@reima, see #103)
* Added RedoxOS support (@goyox86, see #131)
## Changes
* The dot `.` can now match newlines in file names (@iology, see #111)
* The short `--type` argument for symlinks has been changed from `s` to `l` (@jcpetkovich, see #83)
## Bugfixes
* Various improvements in root-path and symlink handling (@iology, see #82, #107, and #113)
* Fixed absolute path handling on Windows (@reima, #93)
* Fixed: current directory not included when using relative path (see #81)
* Fixed `--type` behavior for unknown file types (@iology, see #150)
* Some fixes around `--exec` (@iology, see #142)
## Other
* Major updates and bugfixes to our continuous integration and deployment tooling on Travis
(@matematikaadit, see #149, #145, #133)
* Code style improvements & automatic style checking via `rustfmt` on Travis (@Detegr, see #99)
* Added a man page (@pickfire, see #77)
* *fd* has been relicensed under the dual license MIT/Apache-2.0 (@Detegr, see #105)
* Major refactorings and code improvements (Big thanks to @gsquire, @reima, @iology)
* First version of [`CONTRIBUTING`](https://github.com/sharkdp/fd/blob/master/CONTRIBUTING.md) guidelines
* There is now a Nix package (@mehandes)
* *fd* is now in the official Arch Linux repos (@cassava)
* Improved tooling around shell completion files (@ImbaKnugel, see #124)
* Updated tutorial in the [`README`](https://github.com/sharkdp/fd/blob/master/README.md)
* The minimum required version of Rust has been bumped to 1.19.
## Thanks
A *lot* of things have happened since the last release and I'd like to thank all contributors for their great support. I'd also like to thank those that have contributed by reporting bugs and by posting feature requests.
I'd also like to take this chance to say a special Thank You to a few people that have stood out in one way or another: To @iology, for contributing a multitude of bugfixes, improvements and new features. To @reima and @Detegr for their continuing great support. To @mmstick, for implementing the most advanced new feature of *fd*. And to @matematikaadit for the CI/tooling upgrades.
# v4.0.0
## Features
* Added filtering by file extension, for example `fd -e txt`, see #56 (@reima)
* Add option to force colored output: `--color always`, see #49 (@Detegr)
* Generate Shell completions for Bash, ZSH, Fish and Powershell, see #64 (@ImbaKnugel)
* Better & extended `--help` text (@abaez and @Detegr)
* Proper Windows support, see #70
## Changes
* The integration tests have been re-written in Rust :sparkles:, making them platform-independent and easily callable via `cargo test` - see #65 (many thanks to @reima!)
* New tutorial in the README (@deg4uss3r)
* Reduced number of `stat` syscalls for each result from 3 to 1, see #36.
* Enabled Appveyor CI
# v3.1.0
## Features
- Added file type filtering, e.g. `find --type directory` or `find -t f` (@exitium)
# v3.0.0
## Features
- Directories are now traversed in parallel, leading to significant performance improvements (see [benchmarks](https://github.com/sharkdp/fd#benchmark))
- Added `--print0` option (@michaelmior)
- Added AUR packages (@wezm)
## Changes
- Changed short flag for `--follow` from `-f` to `-L` (consistency with `ripgrep`)
# v2.0.0
* Changed `--sensitive` to `--case-sensitive`
* Changed `--absolute` to `--absolute-path`
* Throw an error if root directory is not existent, see #39
* Use absolute paths if the root dir is an absolute path, see #40
* Handle invalid UTF-8, see #34 #38
* Support `-V`, `--version` by switching from `getopts` to `clap`.
Misc:
* It's now possible to install `fd` via homebrew on macOS: `brew install fd`.
# v1.1.0
- Windows compatibility (@sebasv), see #29 #35
- Safely exit on broken output pipes (e.g.: usage with `head`, `tail`, ..), see #24
- Backport for rust 1.16, see #23
# v1.0.0
* Respect `.(git)ignore` files
* Use `LS_COLORS` environment variable directly, instead of `~/.dir_colors` file.
* Added unit and integration tests
* Added optional second argument (search path)
# v0.3.0
- Parse dircolors files, closes #20
- Colorize each path component, closes #19
- Add short command line option for --hidden, see #18
# v0.2.0
- Option to follow symlinks, disable colors, closes #16, closes #17
- `--filename` instead of `--full-path`
- Option to search hidden directories, closes #12
- Configurable search depth, closes #13
- Detect interactive terminal, closes #11
# v0.1.0
Initial release
+18 -1
View File
@@ -10,6 +10,24 @@ We welcome any form of contribution:
**Note**: Before you take the time to open a pull request, please open a ticket first. This will
give us the chance to discuss any potential changes first.
## Add an entry to the changelog
If your contribution changes the behavior of `fd` (as opposed to a typo-fix
in the documentation), please update the [`CHANGELOG.md`](CHANGELOG.md) file
and describe your changes. This makes the release process much easier and
therefore helps to get your changes into a new `fd` release faster.
The top of the `CHANGELOG` contains a *"unreleased"* section with a few
subsections (Features, Bugfixes, …). Please add your entry to the subsection
that best describes your change.
Entries follow this format:
```
- Short description of what has been changed, see #123 (@user)
```
Here, `#123` is the number of the original issue and/or your pull request.
Please replace `@user` by your GitHub username.
## Important links
* [Open issues](https://github.com/sharkdp/fd/issues)
@@ -17,4 +35,3 @@ give us the chance to discuss any potential changes first.
* [Development section in the README](https://github.com/sharkdp/fd#development)
* [fd on crates.io](https://crates.io/crates/fd-find)
* [LICENSE-APACHE](https://github.com/sharkdp/fd/blob/master/LICENSE-APACHE) and [LICENSE-MIT](https://github.com/sharkdp/fd/blob/master/LICENSE-MIT)
Generated
+465 -229
View File
@@ -1,454 +1,690 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.6.6"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
[[package]]
name = "atty"
version = "0.2.11"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.0.3"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.32.0"
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr",
]
[[package]]
name = "crossbeam"
version = "0.3.2"
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi",
]
[[package]]
name = "clap"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f34b09b9ee8c7c7b400fe2f8df39cafc9538b03d6ba7f4ae13e4cb90bfbb7d"
dependencies = [
"atty",
"bitflags",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim",
"termcolor",
"terminal_size",
"textwrap",
]
[[package]]
name = "clap_complete"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a394f7ec0715b42a4e52b294984c27c9a61f77c8d82f7774c5198350be143f19"
dependencies = [
"clap",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "ctrlc"
version = "3.1.1"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf"
dependencies = [
"nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"nix",
"winapi",
]
[[package]]
name = "diff"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "fd-find"
version = "7.2.0"
version = "8.3.2"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"filetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ansi_term",
"anyhow",
"atty",
"chrono",
"clap",
"clap_complete",
"ctrlc",
"diff",
"dirs-next",
"filetime",
"globset",
"humantime",
"ignore",
"jemallocator",
"libc",
"lscolors",
"nix",
"normpath",
"num_cpus",
"once_cell",
"regex",
"regex-syntax",
"tempdir",
"test-case",
"users",
"version_check",
]
[[package]]
name = "filetime"
version = "0.2.1"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
dependencies = [
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if",
"libc",
"redox_syscall",
"winapi",
]
[[package]]
name = "fnv"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
name = "fs_extra"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.3.3"
name = "globset"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
]
[[package]]
name = "globset"
version = "0.4.1"
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc",
]
[[package]]
name = "humantime"
version = "1.1.1"
version = "2.1.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)",
]
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "ignore"
version = "0.4.3"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
dependencies = [
"crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils",
"globset",
"lazy_static",
"log",
"memchr",
"regex",
"same-file",
"thread_local",
"walkdir",
"winapi-util",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
name = "indexmap"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg",
"hashbrown",
]
[[package]]
name = "jemalloc-sys"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45"
dependencies = [
"cc",
"fs_extra",
"libc",
]
[[package]]
name = "jemallocator"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69"
dependencies = [
"jemalloc-sys",
"libc",
]
[[package]]
name = "lazy_static"
version = "1.1.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.43"
version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
[[package]]
name = "log"
version = "0.4.4"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if",
]
[[package]]
name = "lscolors"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd58d8727f3035fa6d5272f16b519741fd4875936b99d8a7cde21291b7d9174"
dependencies = [
"ansi_term",
]
[[package]]
name = "memchr"
version = "2.0.1"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg",
]
[[package]]
name = "nix"
version = "0.11.0"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags",
"cc",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "normpath"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04aaf5e9cb0fbf883cc0423159eacdf96a9878022084b35c462c428cab73bcaf"
dependencies = [
"winapi",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.8.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"hermit-abi",
"libc",
]
[[package]]
name = "quick-error"
version = "1.2.2"
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
dependencies = [
"memchr",
]
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.4.3"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.1.40"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.1"
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"getrandom",
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.0.2"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.2"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "remove_dir_all"
version = "0.5.1"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi",
]
[[package]]
name = "same-file"
version = "1.0.2"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util",
]
[[package]]
name = "strsim"
version = "0.7.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
"rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand",
"remove_dir_all",
]
[[package]]
name = "term_size"
version = "0.3.1"
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util",
]
[[package]]
name = "termion"
version = "1.5.1"
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc",
"winapi",
]
[[package]]
name = "test-case"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7cad0a06f9a61e94355aa3b3dc92d85ab9c83406722b1ca5e918d4297c12c23"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "textwrap"
version = "0.10.0"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
dependencies = [
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"terminal_size",
]
[[package]]
name = "thread_local"
version = "0.3.6"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"once_cell",
]
[[package]]
name = "ucd-util"
version = "0.1.1"
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "unicode-width"
version = "0.1.5"
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "utf8-ranges"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.1"
name = "users"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
dependencies = [
"libc",
"log",
]
[[package]]
name = "version_check"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "void"
version = "1.0.2"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.2.0"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "winapi"
version = "0.2.8"
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.5"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c1c6d463cbe7ed28720b5b489e7c083eeb8f90d08be2a0d6bb9e1ffea9ce1afa"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
"checksum cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "2119ea4867bd2b8ed3aecab467709720b2d55b1bcfe09f772fd68066eaf15275"
"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3"
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
"checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19"
"checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e"
"checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
"checksum filetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "da4b9849e77b13195302c174324b5ba73eec9b236b24c221a61000daefb95c5f"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum globset 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8e49edbcc9c7fc5beb8c0a54e7319ff8bed353a2b55e85811c6281188c2a6c84"
"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e"
"checksum ignore 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3e9faa7c84064f07b40da27044af629f578bc7994b650d3e458d0c29183c1d91"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7"
"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d"
"checksum log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cba860f648db8e6f269df990180c2217f333472b4a6e901e97446858487971e2"
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd"
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5bbbea44c5490a1e84357ff28b7d518b4619a159fed5d25f6c1de2d19cc42814"
"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d"
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1b768ba943161a9226ccd59b26bcd901e5d60e6061f4fcad3034784e0c7372b"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+39 -15
View File
@@ -16,44 +16,68 @@ license = "MIT/Apache-2.0"
name = "fd-find"
readme = "README.md"
repository = "https://github.com/sharkdp/fd"
version = "7.2.0"
version = "8.3.2"
edition= "2018"
[[bin]]
name = "fd"
path = "src/main.rs"
[badges.appveyor]
repository = "sharkdp/fd"
[badges.travis-ci]
repository = "sharkdp/fd"
[[bin]]
name = "fd"
path = "src/main.rs"
[build-dependencies]
clap = "2.31.2"
version_check = "0.1.3"
clap = "3.0"
clap_complete = "3.0"
version_check = "0.9"
[dependencies]
ansi_term = "0.11"
ansi_term = "0.12"
atty = "0.2"
ignore = "0.4.3"
lazy_static = "1.1.0"
num_cpus = "1.8"
regex = "1.0.0"
num_cpus = "1.13"
regex = "1.5.4"
regex-syntax = "0.6"
ctrlc = "3.1"
humantime = "1.1.1"
ctrlc = "3.2"
humantime = "2.1"
lscolors = "0.8"
globset = "0.4"
anyhow = "1.0"
dirs-next = "2.0"
normpath = "0.3.2"
chrono = "0.4"
once_cell = "1.9.0"
[dependencies.clap]
version = "2.31.2"
features = ["suggestions", "color", "wrap_help"]
version = "3.0"
features = ["suggestions", "color", "wrap_help", "cargo"]
[target.'cfg(unix)'.dependencies]
users = "0.11.0"
nix = "0.23.1"
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
libc = "0.2"
# FIXME: Re-enable jemalloc on macOS
# jemalloc is currently disabled on macOS due to a bug in jemalloc in combination with macOS
# Catalina. See https://github.com/sharkdp/fd/issues/498 for details.
[target.'cfg(all(not(windows), not(target_os = "android"), not(target_os = "macos"), not(target_os = "freebsd"), not(target_env = "musl"), not(target_arch = "riscv64")))'.dependencies]
jemallocator = {version = "0.3.0", optional = true}
[dev-dependencies]
diff = "0.1"
tempdir = "0.3"
filetime = "0.2.1"
filetime = "0.2"
test-case = "1.2"
[profile.release]
lto = true
codegen-units = 1
[features]
use-jemalloc = ["jemallocator"]
default = ["use-jemalloc"]
+1 -1
View File
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 fd developers
Copyright 2017-2020 fd developers
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+19 -21
View File
@@ -1,23 +1,21 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
MIT License
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
Copyright (c) 2017-2021 The fd developers
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+487 -250
View File
@@ -1,34 +1,328 @@
# fd
[![Build Status](https://travis-ci.org/sharkdp/fd.svg?branch=master)](https://travis-ci.org/sharkdp/fd)
[![Build status](https://ci.appveyor.com/api/projects/status/21c4p5fwggc5gy3j?svg=true)](https://ci.appveyor.com/project/sharkdp/fd)
[![CICD](https://github.com/sharkdp/fd/actions/workflows/CICD.yml/badge.svg)](https://github.com/sharkdp/fd/actions/workflows/CICD.yml)
[![Version info](https://img.shields.io/crates/v/fd-find.svg)](https://crates.io/crates/fd-find)
[中文](https://github.com/chinanf-boy/fd-zh)
[[中文](https://github.com/chinanf-boy/fd-zh)]
[[한국어](https://github.com/spearkkk/fd-kor)]
*fd* is a simple, fast and user-friendly alternative to
[*find*](https://www.gnu.org/software/findutils/).
`fd` is a program to find entries in your filesystem.
It is a simple, fast and user-friendly alternative to [`find`](https://www.gnu.org/software/findutils/).
While it does not aim to support all of `find`'s powerful functionality, it provides sensible
(opinionated) defaults for a majority of use cases.
While it does not seek to mirror all of *find*'s powerful functionality, it provides sensible
(opinionated) defaults for [80%](https://en.wikipedia.org/wiki/Pareto_principle) of the use cases.
Quick links:
* [How to use](#how-to-use)
* [Installation](#installation)
* [Troubleshooting](#troubleshooting)
## Features
* Convenient syntax: `fd PATTERN` instead of `find -iname '*PATTERN*'`.
* Colorized terminal output (similar to *ls*).
* It's *fast* (see [benchmarks](#benchmark) below).
* Intuitive syntax: `fd PATTERN` instead of `find -iname '*PATTERN*'`.
* Regular expression (default) and glob-based patterns.
* [Very fast](#benchmark) due to parallelized directory traversal.
* Uses colors to highlight different file types (same as *ls*).
* Supports [parallel command execution](#command-execution)
* Smart case: the search is case-insensitive by default. It switches to
case-sensitive if the pattern contains an uppercase
character[\*](http://vimdoc.sourceforge.net/htmldoc/options.html#'smartcase').
* Ignores hidden directories and files, by default.
* Ignores patterns from your `.gitignore`, by default.
* Regular expressions.
* Unicode-awareness.
* The command name is *50%* shorter[\*](https://github.com/ggreer/the_silver_searcher) than
`find` :-).
* Parallel command execution with a syntax similar to GNU Parallel.
## Demo
![Demo](doc/screencast.svg)
## How to use
First, to get an overview of all available command line options, you can either run
[`fd -h`](#command-line-options) for a concise help message or `fd --help` for a more detailed
version.
### Simple search
*fd* is designed to find entries in your filesystem. The most basic search you can perform is to
run *fd* with a single argument: the search pattern. For example, assume that you want to find an
old script of yours (the name included `netflix`):
``` bash
> fd netfl
Software/python/imdb-ratings/netflix-details.py
```
If called with just a single argument like this, *fd* searches the current directory recursively
for any entries that *contain* the pattern `netfl`.
### Regular expression search
The search pattern is treated as a regular expression. Here, we search for entries that start
with `x` and end with `rc`:
``` bash
> cd /etc
> fd '^x.*rc$'
X11/xinit/xinitrc
X11/xinit/xserverrc
```
The regular expression syntax used by `fd` is [documented here](https://docs.rs/regex/1.0.0/regex/#syntax).
### Specifying the root directory
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
/etc/pam.d/passwd
/etc/passwd
```
### List all files, recursively
*fd* can be called with no arguments. This is very useful to get a quick overview of all entries
in the current directory, recursively (similar to `ls -R`):
``` bash
> cd fd/tests
> fd
testenv
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
`--extension`) option. Here, we search for all Markdown files in the fd repository:
``` bash
> cd fd
> fd -e md
CONTRIBUTING.md
README.md
```
The `-e` option can be used in combination with a search pattern:
``` bash
> fd -e rs mod
src/fshelper/mod.rs
src/lscolors/mod.rs
tests/testenv/mod.rs
```
### Searching for a particular file name
To find files with exactly the provided search pattern, use the `-g` (or `--glob`) option:
``` bash
> fd -g libc.so /usr
/usr/lib32/libc.so
/usr/lib/libc.so
```
### Hidden and ignored files
By default, *fd* does not search hidden directories and does not show hidden files in the
search results. To disable this behavior, we can use the `-H` (or `--hidden`) option:
``` bash
> fd pre-commit
> fd -H pre-commit
.git/hooks/pre-commit.sample
```
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 `--no-ignore`) option:
``` bash
> fd num_cpu
> fd -I num_cpu
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`).
### Matching the full path
By default, *fd* only matches the filename of each file. However, using the `--full-path` or `-p` option,
you can match against the full path.
```bash
> fd -p -g '**/.git/config'
> fd -p '.*/lesson-\d+/[a-z]+.(jpg|png)'
```
### Command execution
Instead of just showing the search results, you often want to *do something* with them. `fd`
provides two ways to execute external commands for each of your search results:
* The `-x`/`--exec` option runs an external command *for each of the search results* (in parallel).
* The `-X`/`--exec-batch` option launches the external command once, with *all search results as arguments*.
#### Examples
Recursively find all zip archives and unpack them:
``` bash
fd -e zip -x unzip
```
If there are two such files, `file1.zip` and `backup/file2.zip`, this would execute
`unzip file1.zip` and `unzip backup/file2.zip`. The two `unzip` processes run in parallel
(if the files are found fast enough).
Find all `*.h` and `*.cpp` files and auto-format them inplace with `clang-format -i`:
``` bash
fd -e h -e cpp -x clang-format -i
```
Note how the `-i` option to `clang-format` can be passed as a separate argument. This is why
we put the `-x` option last.
Find all `test_*.py` files and open them in your favorite editor:
``` bash
fd -g 'test_*.py' -X vim
```
Note that we use capital `-X` here to open a single `vim` instance. If there are two such files,
`test_basic.py` and `lib/test_advanced.py`, this will run `vim test_basic.py lib/test_advanced.py`.
To see details like file permissions, owners, file sizes etc., you can tell `fd` to show them
by running `ls` for each result:
``` bash
fd … -X ls -lhd --color=always
```
This pattern is so useful that `fd` provides a shortcut. You can use the `-l`/`--list-details`
option to execute `ls` in this way: `fd … -l`.
The `-X` option is also useful when combining `fd` with [ripgrep](https://github.com/BurntSushi/ripgrep/) (`rg`) in order to search within a certain class of files, like all C++ source files:
```bash
fd -e cpp -e cxx -e h -e hpp -X rg 'std::cout'
```
Convert all `*.jpg` files to `*.png` files:
``` bash
fd -e jpg -x convert {} {.}.png
```
Here, `{}` is a placeholder for the search result. `{.}` is the same, without the file extension.
See below for more details on the placeholder syntax.
#### Placeholder syntax
The `-x` and `-X` options take a *command template* as a series of arguments (instead of a single string).
If you want to add additional options to `fd` after the command template, you can terminate it with a `\;`.
The syntax for generating commands is similar to that of [GNU Parallel](https://www.gnu.org/software/parallel/):
- `{}`: A placeholder token that will be replaced with the path of the search result
(`documents/images/party.jpg`).
- `{.}`: Like `{}`, but without the file extension (`documents/images/party`).
- `{/}`: A placeholder that will be replaced by the basename of the search result (`party.jpg`).
- `{//}`: The parent of the discovered path (`documents/images`).
- `{/.}`: The basename, with the extension removed (`party`).
If you do not include a placeholder, *fd* automatically adds a `{}` at the end.
#### Parallel vs. serial execution
For `-x`/`--exec`, you can control the number of parallel jobs by using the `-j`/`--threads` option.
Use `--threads=1` for serial execution.
### 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`.
If you want `fd` to ignore these patterns globally, you can put them in `fd`'s global ignore file.
This is usually located in `~/.config/fd/ignore` in macOS or Linux, and `%APPDATA%\fd\ignore` in
Windows.
### 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.
### Command-line options
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.
```
USAGE:
fd [FLAGS/OPTIONS] [<pattern>] [<path>...]
FLAGS:
-H, --hidden Search hidden files and directories
-I, --no-ignore Do not respect .(git|fd)ignore 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)
-a, --absolute-path Show absolute instead of relative paths
-l, --list-details Use a long listing format with file metadata
-L, --follow Follow symbolic links
-p, --full-path Search full abs. path (default: filename only)
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-d, --max-depth <depth> Set maximum search depth (default: none)
-t, --type <filetype>... Filter by type: file (f), directory (d), symlink (l),
executable (x), empty (e), socket (s), pipe (p)
-e, --extension <ext>... Filter by file extension
-x, --exec <cmd> Execute a command for each search result
-X, --exec-batch <cmd> Execute a command with all search results at once
-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)
-o, --owner <user:group> Filter by owning user and/or group
ARGS:
<pattern> the search pattern (a regular expression, unless '--glob' is used; optional)
<path>... the root directory for the filesystem search (optional)
```
## Benchmark
Let's search my home folder for files that end in `[0-9].jpg`. It contains ~190.000
@@ -41,7 +335,7 @@ Let's start with `find`:
Benchmark #1: find ~ -iregex '.*[0-9]\.jpg$'
Time (mean ± σ): 7.236 s ± 0.090 s
Range (min … max): 7.133 s … 7.385 s
```
@@ -50,7 +344,7 @@ Benchmark #1: find ~ -iregex '.*[0-9]\.jpg$'
Benchmark #2: find ~ -iname '*[0-9].jpg'
Time (mean ± σ): 3.914 s ± 0.027 s
Range (min … max): 3.876 s … 3.964 s
```
@@ -61,7 +355,7 @@ otherwise `fd` does not have to traverse hidden folders and ignored paths (see b
Benchmark #3: fd -HI '.*[0-9]\.jpg$' ~
Time (mean ± σ): 811.6 ms ± 26.9 ms
Range (min … max): 786.0 ms … 870.7 ms
```
For this particular example, `fd` is approximately nine times faster than `find -iregex`
@@ -75,74 +369,186 @@ folders, it is almost an order of magnitude faster:
Benchmark #4: fd '[0-9]\.jpg$' ~
Time (mean ± σ): 123.7 ms ± 6.0 ms
Range (min … max): 118.8 ms … 140.0 ms
```
**Note**: This is *one particular* benchmark on *one particular* machine. While I have
performed quite a lot of different tests (and found consistent results), things might
be different for you! I encourage everyone to try it out on their own. See
be different for you! I encourage everyone to try it out on their own. See
[this repository](https://github.com/sharkdp/fd-benchmarks) for all necessary scripts.
Concerning *fd*'s speed, the main credit goes to the `regex` and `ignore` crates that are also used
in [ripgrep](https://github.com/BurntSushi/ripgrep) (check it out!).
## Colorized output
## Troubleshooting
### Colorized output
`fd` can colorize files by extension, just like `ls`. In order for this to work, the environment
variable [`LS_COLORS`](https://linux.die.net/man/5/dir_colors) has to be set. Typically, the value
of this variable is set by the `dircolors` command which provides a convenient configuration format
to define colors for different file formats.
On most distributions, `LS_COLORS` should be set already. If you are looking for alternative, more
complete (and more colorful) variants, see
On most distributions, `LS_COLORS` should be set already. If you are on Windows or if you are looking
for alternative, more complete (or more colorful) variants, see [here](https://github.com/sharkdp/vivid),
[here](https://github.com/seebi/dircolors-solarized) or
[here](https://github.com/trapd00r/LS_COLORS).
## Parallel command execution
If the `-x`/`--exec` option is specified alongside a command template, a job pool will be created
for executing commands in parallel for each discovered path as the input. The syntax for generating
commands is similar to that of GNU Parallel:
`fd` also honors the [`NO_COLOR`](https://no-color.org/) environment variable.
- `{}`: A placeholder token that will be replaced with the path of the search result
(`documents/images/party.jpg`).
- `{.}`: Like `{}`, but without the file extension (`documents/images/party`).
- `{/}`: A placeholder that will be replaced by the basename of the search result (`party.jpg`).
- `{//}`: Uses the parent of the discovered path (`documents/images`).
- `{/.}`: Uses the basename, with the extension removed (`party`).
### `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
# Convert all jpg files to png files:
fd -e jpg -x convert {} {.}.png
# Unpack all zip files (if no placeholder is given, the path is appended):
fd -e zip -x unzip
# Convert all flac files into opus files:
fd -e flac -x ffmpeg -i {} -c:a libopus {.}.opus
# Count the number of lines in Rust files (the command template can be terminated with ';'):
fd -x wc -l \; -e rs
> 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'
```
### "Command not found" for `alias`es or shell functions
Shell `alias`es and shell functions can not be used for command execution via `fd -x` or
`fd -X`. In `zsh`, you can make the alias global via `alias -g myalias="…"`. In `bash`,
you can use `export -f my_function` to make available to child processes. You would still
need to call `fd -x bash -c 'my_function "$1"' bash`. For other use cases or shells, use
a (temporary) shell script.
## Integration with other programs
### Using fd with `fzf`
You can use *fd* to generate input for the command-line fuzzy finder [fzf](https://github.com/junegunn/fzf):
``` bash
export FZF_DEFAULT_COMMAND='fd --type file'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
```
Then, you can type `vim <Ctrl-T>` on your terminal to open fzf and search through the fd-results.
Alternatively, you might like to follow symbolic links and include hidden files (but exclude `.git` folders):
``` bash
export FZF_DEFAULT_COMMAND='fd --type file --follow --hidden --exclude .git'
```
You can even use fd's colored output inside fzf by setting:
``` bash
export FZF_DEFAULT_COMMAND="fd --type file --color=always"
export FZF_DEFAULT_OPTS="--ansi"
```
For more details, see the [Tips section](https://github.com/junegunn/fzf#tips) of the fzf README.
### Using fd with `rofi`
[*rofi*](https://github.com/davatorium/rofi) is a graphical launch menu application that is able to create menus by reading from *stdin*. Piping `fd` output into `rofi`s `-dmenu` mode creates fuzzy-searchable lists of files and directories.
#### Example
Create a case-insensitive searchable multi-select list of *PDF* files under your `$HOME` directory and open the selection with your configured PDF viewer. To list all file types, drop the `-e pdf` argument.
``` bash
fd --type f -e pdf . $HOME | rofi -keep-right -dmenu -i -p FILES -multi-select | xargs -I {} xdg-open {}
```
To modify the list that is presented by rofi, add arguments to the `fd` command. To modify the search behaviour of rofi, add arguments to the `rofi` command.
### 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.
### Printing the output as a tree
To format the output of `fd` similar to the `tree` command, install [`as-tree`] and pipe the output
of `fd` to `as-tree`:
```bash
fd | as-tree
```
This can be more useful than running `tree` by itself because `tree` does not ignore any files by
default, nor does it support as rich a set of options as `fd` does to control what to print:
```bash
fd --extension rs | as-tree
.
├── build.rs
└── src
├── app.rs
└── error.rs
```
For more information about `as-tree`, see [the `as-tree` README][`as-tree`].
[`as-tree`]: https://github.com/jez/as-tree
### Using fd with `xargs` or `parallel`
Note that `fd` has a builtin feature for [command execution](#command-execution) with
its `-x`/`--exec` and `-X`/`--exec-batch` options. If you prefer, you can still use
it in combination with `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.
## Installation
[![Packaging status](https://repology.org/badge/vertical-allrepos/fd-find.svg)](https://repology.org/project/fd-find/versions)
### On Ubuntu
*... and other Debian-based Linux distributions.*
Download the latest `.deb` package from the [release page](https://github.com/sharkdp/fd/releases) and install it via:
If you run Ubuntu 19.04 (Disco Dingo) or newer, you can install the
[officially maintained package](https://packages.ubuntu.com/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 after installation, you add a link to `fd` by executing command
`ln -s $(which fdfind) ~/.local/bin/fd`, in order to use `fd` in the same way as in this documentation.
Make sure that `$HOME/.local/bin` is in your `$PATH`.
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_7.2.0_amd64.deb # adapt version number and architecture
sudo dpkg -i fd_8.3.2_amd64.deb # adapt version number and architecture
```
### On Debian
If you run Debian sid (and soon Debian Buster), fd is
[officially maintained by Debian](https://tracker.debian.org/pkg/rust-fd-find).
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
```
The binary is called `fdfind` as the binary name `fd` is already used by another package.
It is recommended that you add an `alias fd=fdfind` to your shells initialization file,
in order to use `fd` in the same way as in this documentation.
Note that the binary is called `fdfind` as the binary name `fd` is already used by another package.
It is recommended that after installation, you add a link to `fd` by executing command
`ln -s $(which fdfind) ~/.local/bin/fd`, in order to use `fd` in the same way as in this documentation.
Make sure that `$HOME/.local/bin` is in your `$PATH`.
### On Fedora
@@ -157,6 +563,14 @@ dnf copr enable keefle/fd
dnf install fd
```
### On Alpine Linux
You can install [the fd package](https://pkgs.alpinelinux.org/packages?name=fd)
from the official sources, provided you have the appropriate repository enabled:
```
apk add fd
```
### On Arch Linux
You can install [the fd package](https://www.archlinux.org/packages/community/x86_64/fd/) from the official repos:
@@ -186,7 +600,7 @@ xbps-install -S fd
### On macOS
You can install `fd` with [Homebrew](http://braumeister.org/formula/fd):
You can install `fd` with [Homebrew](https://formulae.brew.sh/formula/fd):
```
brew install fd
```
@@ -219,9 +633,17 @@ nix-env -i fd
### On FreeBSD
You can install `sysutils/fd` via portmaster:
You can install [the fd-find package](https://www.freshports.org/sysutils/fd) from the official repo:
```
portmaster sysutils/fd
pkg install fd-find
```
### From npm
On linux and macOS, you can install the [fd-find](https://npm.im/fd-find) package:
```
npm install -g fd-find
```
### From source
@@ -230,11 +652,13 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can
```
cargo install fd-find
```
Note that rust version *1.29.0* or later is required.
Note that rust version *1.54.0* or later is required.
`make` is also needed for the build.
### From binaries
The [release page](https://github.com/sharkdp/fd/releases) includes precompiled binaries for Linux, macOS and Windows.
The [release page](https://github.com/sharkdp/fd/releases) includes precompiled binaries for Linux, macOS and Windows. Statically-linked binaries are also available: look for archives with `musl` in the file name.
## Development
```bash
@@ -248,206 +672,19 @@ cargo build
cargo test
# Install
cargo install
cargo install --path .
```
## Command-line options
```
USAGE:
fd [FLAGS/OPTIONS] [<pattern>] [<path>...]
## Maintainers
FLAGS:
-H, --hidden Search hidden files and directories
-I, --no-ignore Do not respect .(git|fd)ignore files
--no-ignore-vcs Do not respect .gitignore files
-s, --case-sensitive Case-sensitive search (default: smart case)
-i, --ignore-case Case-insensitive search (default: smart case)
-F, --fixed-strings Treat the pattern as a literal string
-a, --absolute-path Show absolute instead of relative paths
-L, --follow Follow symbolic links
-p, --full-path Search full path (default: file-/dirname only)
-0, --print0 Separate results by the null character
-h, --help Prints help information
-V, --version Prints version information
- [sharkdp](https://github.com/sharkdp)
- [tmccombs](https://github.com/tmccombs)
- [tavianator](https://github.com/tavianator)
OPTIONS:
-d, --max-depth <depth> Set maximum search depth (default: none)
-t, --type <filetype>... Filter by type: file (f), directory (d), symlink (l),
executable (x), empty (e)
-e, --extension <ext>... Filter by file extension
-x, --exec <cmd> Execute a command for each search result
-E, --exclude <pattern>... Exclude entries that match the given glob pattern
-c, --color <when> When to use colors: never, *auto*, always
-S, --size <size>... Limit results based on the size of files.
--changed-within <date|dur> Filter by file modification time (newer than)
--changed-before <date|dur> Filter by file modification time (older than)
## License
ARGS:
<pattern> the search pattern, a regular expression (optional)
<path>... the root directory for the filesystem search (optional)
```
Copyright (c) 2017-2021 The fd developers
## Tutorial
`fd` is distributed under the terms of both the MIT License and the Apache License 2.0.
First, to get an overview of all available command line options, you can either run
`fd -h` for a concise help message (see above) or `fd --help` for a more detailed
version.
### Simple search
*fd* is designed to find entries in your filesystem. The most basic search you can perform is to
run *fd* with a single argument: the search pattern. For example, assume that you want to find an
old script of yours (the name included `netflix`):
``` bash
> fd netfl
Software/python/imdb-ratings/netflix-details.py
```
If called with just a single argument like this, *fd* searches the current directory recursively
for any entries that *contain* the pattern `netfl`.
### Regular expression search
The search pattern is treated as a regular expression. Here, we search for entries that start
with `x` and end with `rc`:
``` bash
> cd /etc
> fd '^x.*rc$'
X11/xinit/xinitrc
X11/xinit/xserverrc
```
### Specifying the root directory
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
/etc/pam.d/passwd
/etc/passwd
```
### Running *fd* without any arguments
*fd* can be called with no arguments. This is very useful to get a quick overview of all entries
in the current directory, recursively (similar to `ls -R`):
``` bash
> cd fd/tests
> fd
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
`--extension`) option. Here, we search for all Markdown files in the fd repository:
``` bash
> cd fd
> fd -e md
CONTRIBUTING.md
README.md
```
The `-e` option can be used in combination with a search pattern:
``` bash
> fd -e rs mod
src/fshelper/mod.rs
src/lscolors/mod.rs
tests/testenv/mod.rs
```
### Hidden and ignored files
By default, *fd* does not search hidden directories and does not show hidden files in the
search results. To disable this behavior, we can use the `-H` (or `--hidden`) option:
``` bash
> fd pre-commit
> fd -H pre-commit
.git/hooks/pre-commit.sample
```
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 `--no-ignore`) option:
``` bash
> fd num_cpu
> fd -I num_cpu
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.
### Integration with other programs
#### Using fd with `fzf`
You can use *fd* to generate input for the command-line fuzzy finder [fzf](https://github.com/junegunn/fzf):
``` bash
export FZF_DEFAULT_COMMAND='fd --type file'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
```
Then, you can type `vim <Ctrl-T>` on your terminal to open fzf and search through the fd-results.
Alternatively, you might like to follow symbolic links and include hidden files (but exclude `.git` folders):
``` bash
export FZF_DEFAULT_COMMAND='fd --type file --follow --hidden --exclude .git'
```
You can even use fd's colored output inside fzf by setting:
``` bash
export FZF_DEFAULT_COMMAND="fd --type file --color=always"
export FZF_DEFAULT_OPTS="--ansi"
```
For more details, see the [Tips section](https://github.com/junegunn/fzf#tips) of the fzf README.
#### Using fd with `emacs`
The emacs package [find-file-in-project](https://github.com/technomancy/find-file-in-project) can
use *fd* to find files.
After installing `find-file-in-project`, add the line `(setq ffip-use-rust-fd t)` to your
`~/.emacs` or `~/.emacs.d/init.el` file.
In emacs, run `M-x find-file-in-project-by-selected` to find matching files. Alternatively, run
`M-x find-file-in-project` to list all available files in the project.
See the [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) files for license details.
-94
View File
@@ -1,94 +0,0 @@
# Source: https://github.com/starkat99/appveyor-rust/
environment:
global:
PROJECT_NAME: fd
matrix:
# Stable channel
- TARGET: i686-pc-windows-gnu
CHANNEL: stable
- TARGET: i686-pc-windows-msvc
CHANNEL: stable
- TARGET: x86_64-pc-windows-gnu
CHANNEL: stable
- TARGET: x86_64-pc-windows-msvc
CHANNEL: stable
# Beta channel (disabled for speed reasons)
#- TARGET: i686-pc-windows-gnu
# CHANNEL: beta
#- TARGET: i686-pc-windows-msvc
# CHANNEL: beta
#- TARGET: x86_64-pc-windows-gnu
# CHANNEL: beta
#- TARGET: x86_64-pc-windows-msvc
# CHANNEL: beta
# Nightly channel (disabled for speed reasons)
#- TARGET: i686-pc-windows-gnu
# CHANNEL: nightly
#- TARGET: i686-pc-windows-msvc
# CHANNEL: nightly
#- TARGET: x86_64-pc-windows-gnu
# CHANNEL: nightly
#- TARGET: x86_64-pc-windows-msvc
# CHANNEL: nightly
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
install:
- curl -sSf -o rustup-init.exe https://win.rustup.rs
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -Vv
- cargo -V
# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents
# the "directory does not contain a project or solution file" error.
# source: https://github.com/starkat99/appveyor-rust/blob/master/appveyor.yml#L113
build: false
# Equivalent to Travis' `script` phase
test_script:
- cargo build --verbose
- cargo run
- cargo test
before_deploy:
# Generate artifacts for release
- cargo build --release
- mkdir staging
- copy target\release\fd.exe staging
- ps: copy target\release\build\fd-find*\out\_fd.ps1 staging
- cd staging
# release zipfile will look like 'rust-everywhere-v1.2.3-x86_64-pc-windows-msvc'
- 7z a ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip *
- appveyor PushArtifact ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip
deploy:
description: 'Windows release'
# All the zipped artifacts will be deployed
artifact: /.*\.zip/
# Here's how:
# - Go to 'https://github.com/settings/tokens/new' and generate a Token with only the
# `public_repo` scope enabled
# - Then go to 'https://ci.appveyor.com/tools/encrypt' and enter the newly generated token.
# - Enter the "encrypted value" below
auth_token:
secure: mWJ8ieZdGEgHf232fdMyzb9T1rKbkJivYbp/REMo8ax3X5vkQJDHhFjWeuWA3wIC
provider: GitHub
# deploy when a new tag is pushed and only on the stable channel
on:
# channel to use to produce the release artifacts
CHANNEL: stable
appveyor_repo_tag: true
branches:
only:
- master
# IMPORTANT Regex to match tags. Required, or appveyor may not trigger deploys when a new tag
# is pushed. This regex matches semantic versions like v1.2.3-rc4+2016.02.22
- /^v\d+\.\d+\.\d+.*$/
# Disable caching, for now
#cache:
# - '%USERPROFILE%\.cargo'
# - 'target -> Cargo.lock'
+15 -24
View File
@@ -1,33 +1,24 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
#[macro_use]
extern crate clap;
extern crate version_check;
use clap::Shell;
use std::fs;
use std::io::{self, Write};
use std::process::exit;
use clap_complete::{generate_to, Shell};
use Shell::*;
//use clap_complete::shells::Shel{Bash, Fish, PowerShell, Elvish};
include!("src/app.rs");
fn main() {
match version_check::is_min_version("1.29") {
Some((true, _)) => {}
let min_version = "1.54";
match version_check::is_min_version(min_version) {
Some(true) => {}
// rustc version too small or can't figure it out
_ => {
writeln!(&mut io::stderr(), "'fd' requires rustc >= 1.29").unwrap();
exit(1);
eprintln!("'fd' requires rustc >= {}", min_version);
std::process::exit(1);
}
}
let var = std::env::var_os("SHELL_COMPLETIONS_DIR").or(std::env::var_os("OUT_DIR"));
let var = std::env::var_os("SHELL_COMPLETIONS_DIR").or_else(|| std::env::var_os("OUT_DIR"));
let outdir = match var {
None => return,
Some(outdir) => outdir,
@@ -35,8 +26,8 @@ fn main() {
fs::create_dir_all(&outdir).unwrap();
let mut app = build_app();
app.gen_completions("fd", Shell::Bash, &outdir);
app.gen_completions("fd", Shell::Fish, &outdir);
app.gen_completions("fd", Shell::Zsh, &outdir);
app.gen_completions("fd", Shell::PowerShell, &outdir);
// NOTE: zsh completions are hand written in contrib/completion/_fd
for shell in [Bash, PowerShell, Fish, Elvish] {
generate_to(shell, &mut app, "fd", &outdir).unwrap();
}
}
-128
View File
@@ -1,128 +0,0 @@
#!/usr/bin/env bash
# Building and packaging for release
set -ex
build() {
cargo build --target "$TARGET" --release --verbose
}
pack() {
local tempdir
local out_dir
local package_name
local gcc_prefix
tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
out_dir=$(pwd)
package_name="$PROJECT_NAME-$TRAVIS_TAG-$TARGET"
if [[ $TARGET == "arm-unknown-linux-gnueabihf" ]]; then
gcc_prefix="arm-linux-gnueabihf-"
else
gcc_prefix=""
fi
# create a "staging" directory
mkdir "$tempdir/$package_name"
mkdir "$tempdir/$package_name/autocomplete"
# copying the main binary
cp "target/$TARGET/release/$PROJECT_NAME" "$tempdir/$package_name/"
"${gcc_prefix}"strip "$tempdir/$package_name/$PROJECT_NAME"
# manpage, readme and license
cp "doc/$PROJECT_NAME.1" "$tempdir/$package_name"
cp README.md "$tempdir/$package_name"
cp LICENSE-MIT "$tempdir/$package_name"
cp LICENSE-APACHE "$tempdir/$package_name"
# various autocomplete
cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".bash "$tempdir/$package_name/autocomplete/${PROJECT_NAME}.bash-completion"
cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".fish "$tempdir/$package_name/autocomplete"
cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/_"$PROJECT_NAME" "$tempdir/$package_name/autocomplete"
# archiving
pushd "$tempdir"
tar czf "$out_dir/$package_name.tar.gz" "$package_name"/*
popd
rm -r "$tempdir"
}
make_deb() {
local tempdir
local architecture
local version
local dpkgname
local conflictname
case $TARGET in
x86_64*)
architecture=amd64
;;
i686*)
architecture=i386
;;
*)
echo "make_deb: skipping target '${TARGET}'" >&2
return 0
;;
esac
version=${TRAVIS_TAG#v}
if [[ $TARGET = *musl* ]]; then
dpkgname=$PROJECT_NAME-musl
conflictname=$PROJECT_NAME
else
dpkgname=$PROJECT_NAME
conflictname=$PROJECT_NAME-musl
fi
tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
# copy the main binary
install -Dm755 "target/$TARGET/release/$PROJECT_NAME" "$tempdir/usr/bin/$PROJECT_NAME"
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"
# completions
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.bash "$tempdir/usr/share/bash-completion/completions/${PROJECT_NAME}"
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.fish "$tempdir/usr/share/fish/completions/$PROJECT_NAME.fish"
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/_$PROJECT_NAME "$tempdir/usr/share/zsh/vendor-completions/_$PROJECT_NAME"
# Control file
mkdir "$tempdir/DEBIAN"
cat > "$tempdir/DEBIAN/control" <<EOF
Package: $dpkgname
Version: $version
Section: utils
Priority: optional
Maintainer: David Peter <mail@david-peter.de>
Architecture: $architecture
Provides: $PROJECT_NAME
Conflicts: $conflictname
Description: Simple, fast and user-friendly alternative to find
While fd does not seek to mirror all of find's powerful functionality, it
provides sensible (opinionated) defaults for 80% of the use cases.
EOF
fakeroot dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
}
main() {
build
pack
if [[ $TARGET = *linux* ]]; then
make_deb
fi
}
main
-26
View File
@@ -1,26 +0,0 @@
#!/usr/bin/env bash
set -ex
if [ "$TRAVIS_OS_NAME" != linux ]; then
exit 0
fi
sudo apt-get update
# needed to build deb packages
sudo apt-get install -y fakeroot
# needed for i686 linux gnu target
if [[ $TARGET == i686-unknown-linux-gnu ]]; then
sudo apt-get install -y gcc-multilib
fi
# needed for cross-compiling for arm
if [[ $TARGET == arm-unknown-linux-gnueabihf ]]; then
sudo apt-get install -y \
gcc-4.8-arm-linux-gnueabihf \
binutils-arm-linux-gnueabihf \
libc6-armhf-cross \
libc6-dev-armhf-cross
fi
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -ex
# Incorporate TARGET env var to the build and test process
cargo build --target "$TARGET" --verbose
# We cannot run arm executables on linux
if [[ $TARGET != arm-unknown-linux-gnueabihf ]]; then
cargo test --target "$TARGET" --verbose
fi
+1
View File
@@ -0,0 +1 @@
msrv = "1.54.0"
+273
View File
@@ -0,0 +1,273 @@
#compdef fd
##
# zsh completion function for fd
#
# Based on ripgrep completion function.
# Originally based on code from the zsh-users project — see copyright notice
# below.
autoload -U is-at-least
_fd() {
local curcontext="$curcontext" no='!' ret=1
local -a context line state state_descr _arguments_options fd_types fd_args
local -A opt_args
if is-at-least 5.2; then
_arguments_options=( -s -S )
else
_arguments_options=( -s )
fi
fd_types=(
{f,file}'\:"regular files"'
{d,directory}'\:"directories"'
{l,symlink}'\:"symbolic links"'
{e,empty}'\:"empty files or directories"'
{x,executable}'\:"executable (files)"'
{s,socket}'\:"sockets"'
{p,pipe}'\:"named pipes (FIFOs)"'
)
# Do not complete rare options unless either the current prefix
# matches one of those options or the user has the `complete-all`
# style set. Note that this prefix check has to be updated manually to account
# for all of the potential negation options listed below!
if
# (--[bpsu]* => match all options marked with '$no')
[[ $PREFIX$SUFFIX == --[bopsu]* ]] ||
zstyle -t ":complete:$curcontext:*" complete-all
then
no=
fi
# We make heavy use of argument groups here to prevent the option specs from
# growing unwieldy. These aren't supported in zsh <5.4, though, so we'll strip
# them out below if necessary. This makes the exclusions inaccurate on those
# older versions, but oh well — it's not that big a deal
fd_args=(
+ '(hidden)' # hidden files
{-H,--hidden}'[search hidden files/directories]'
+ '(no-ignore-full)' # all ignore files
'(no-ignore-partial)'{-I,--no-ignore}"[don't respect .(git|fd)ignore and global ignore files]"
$no'(no-ignore-partial)*'{-u,--unrestricted}'[alias for --no-ignore, when repeated also alias for --hidden]'
+ no-ignore-partial # some ignore files
"(no-ignore-full --no-ignore-vcs)--no-ignore-vcs[don't respect .gitignore files]"
"!(no-ignore-full --no-global-ignore-file)--no-global-ignore-file[don't respect the global ignore file]"
$no'(no-ignore-full --no-ignore-parent)--no-ignore-parent[]'
+ '(case)' # case-sensitivity
{-s,--case-sensitive}'[perform a case-sensitive search]'
{-i,--ignore-case}'[perform a case-insensitive search]'
+ '(regex-pattern)' # regex-based search pattern
'(no-regex-pattern)--regex[perform a regex-based search (default)]'
+ '(no-regex-pattern)' # non-regex-based search pattern
{-g,--glob}'[perform a glob-based search]'
{-F,--fixed-strings}'[treat pattern as literal string instead of a regex]'
+ '(match-full)' # match against full path
{-p,--full-path}'[match the pattern against the full path instead of the basename]'
+ '(follow)' # follow symlinks
{-L,--follow}'[follow symbolic links to directories]'
+ '(abs-path)' # show absolute paths
'(long-listing)'{-a,--absolute-path}'[show absolute paths instead of relative paths]'
+ '(null-sep)' # use null separator for output
'(long-listing)'{-0,--print0}'[separate search results by the null character]'
+ '(long-listing)' # long-listing output
'(abs-path null-sep max-results exec-cmds)'{-l,--list-details}'[use a long listing format with file metadata]'
+ '(max-results)' # max number of results
'(long-listing exec-cmds)--max-results=[limit number of search results to given count and quit]:count'
'(long-listing exec-cmds)-1[limit to a single search result and quit]'
+ '(fs-errors)' # file-system errors
$no'--show-errors[enable the display of filesystem errors]'
+ '(fs-traversal)' # file-system traversal
$no"--one-file-system[don't descend into directories on other file systems]"
'!--mount'
'!--xdev'
+ dir-depth # directory depth
'(--exact-depth -d --max-depth)'{-d+,--max-depth=}'[set max directory depth to descend when searching]:depth'
'!(--exact-depth -d --max-depth)--maxdepth:depth'
'(--exact-depth --min-depth)--min-depth=[set directory depth to descend before start searching]:depth'
'(--exact-depth -d --max-depth --maxdepth --min-depth)--exact-depth=[only search at the exact given directory depth]:depth'
+ prune # pruning
"--prune[don't traverse into matching directories]"
+ filter-misc # filter search
'*'{-t+,--type=}"[filter search by type]:type:(($fd_types))"
'*'{-e+,--extension=}'[filter search by file extension]:extension'
'*'{-E+,--exclude=}'[exclude files/directories that match the given glob pattern]:glob pattern'
'*'{-S+,--size=}'[limit search by file size]:size limit:->size'
'(-o --owner)'{-o+,--owner=}'[filter by owning user and/or group]:owner and/or group:->owner'
+ ignore-file # extra ignore files
'*--ignore-file=[add a custom, low-precedence ignore-file with .gitignore format]: :_files'
+ '(filter-mtime-newer)' # filter by files modified after than
'--changed-within=[limit search to files/directories modified within the given date/duration]:date or duration'
'!--change-newer-than=:date/duration'
'!--newer=:date/duration'
+ '(filter-mtime-older)' # filter by files modified before than
'--changed-before=[limit search to files/directories modified before the given date/duration]:date or duration'
'!--change-older-than=:date/duration'
'!--older=:date/duration'
+ '(color)' # colorize output
{-c+,--color=}'[declare when to colorize search results]:when to colorize:((
auto\:"show colors if the output goes to an interactive console (default)"
never\:"do not use colorized output"
always\:"always use colorized output"
))'
+ '(threads)'
{-j+,--threads=}'[set the number of threads for searching and executing]:number of threads'
+ '(exec-cmds)' # execute command
'(long-listing max-results)'{-x+,--exec=}'[execute command for each search result]:command: _command_names -e:*\;::program arguments: _normal'
'(long-listing max-results)'{-X+,--exec-batch=}'[execute command for all search results at once]:command: _command_names -e:*\;::program arguments: _normal'
'(long-listing max-results)--batch-size=[max number of args for each -X call]:size'
+ other
'!(--max-buffer-time)--max-buffer-time=[set amount of time to buffer before showing output]:time (ms)'
+ '(about)' # about flags
'(: * -)'{-h,--help}'[display help message]'
'(: * -)'{-v,--version}'[display version information]'
+ path-sep # set path separator for output
$no'(--path-separator)--path-separator=[set the path separator to use when printing file paths]:path separator'
+ search-path
$no'(--base-directory)--base-directory=[change the current working directory to the given path]:directory:_files -/'
$no'(*)*--search-path=[set search path (instead of positional <path> arguments)]:directory:_files -/'
+ strip-cwd-prefix
$no'(strip-cwd-prefix exec-cmds)--strip-cwd-prefix[Strip ./ prefix when output is redirected]'
+ args # positional arguments
'1: :_guard "^-*" pattern'
'(--search-path)*:directory:_files -/'
)
# Strip out argument groups where unsupported (see above)
is-at-least 5.4 ||
fd_args=( ${(@)args:#(#i)(+|[a-z0-9][a-z0-9_-]#|\([a-z0-9][a-z0-9_-]#\))} )
_arguments $_arguments_options : $fd_args && ret=0
case ${state} in
owner)
compset -P '(\\|)\!'
if compset -P '*:'; then
_groups && ret=0
else
if
compset -S ':*' ||
# Do not add the colon suffix when completing "!user<TAB>
# (with a starting double-quote) otherwise pressing tab again
# after the inserted colon "!user:<TAB> will complete history modifiers
[[ $IPREFIX == (\\|\!)* && ($QIPREFIX == \"* && -z $QISUFFIX) ]]
then
_users && ret=0
else
local q
# Since quotes are needed when using the negation prefix !,
# automatically remove the colon suffix also when closing the quote
if [[ $QIPREFIX == [\'\"]* ]]; then
q=${QIPREFIX:0:1}
fi
_users -r ": \t\n\-$q" -S : && ret=0
fi
fi
;;
size)
if compset -P '[-+][0-9]##'; then
local -a suff=(
'B:bytes'
'K:kilobytes (10^3 = 1000 bytes)'
'M:megabytes (10^6 = 1000^2 bytes)'
'G:gigabytes (10^9 = 1000^3 bytes)'
'T:terabytes (10^12 = 1000^4 bytes)'
'Ki:kibibytes ( 2^10 = 1024 bytes)'
'Mi:mebibytes ( 2^20 = 1024^2 bytes)'
'Gi:gigibytes ( 2^30 = 1024^3 bytes)'
'Ti:tebibytes ( 2^40 = 1024^4 bytes)'
)
_describe -t units 'size limit units' suff -V 'units'
elif compset -P '[-+]'; then
_message -e 'size limit number (full format: <+-><number><unit>)'
else
_values 'size limit prefix (full format: <prefix><number><unit>)' \
'\+[file size must be greater or equal to]'\
'-[file size must be less than or equal to]' && ret=0
fi
;;
esac
return ret
}
_fd "$@"
# ------------------------------------------------------------------------------
# Copyright (c) 2011 GitHub zsh-users - http://github.com/zsh-users
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the zsh-users nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL ZSH-USERS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# Description
# -----------
#
# Completion script for fd
#
# ------------------------------------------------------------------------------
# Authors
# -------
#
# * smancill (https://github.com/smancill)
#
# ------------------------------------------------------------------------------
# Local Variables:
# mode: shell-script
# coding: utf-8-unix
# indent-tabs-mode: nil
# sh-indentation: 2
# sh-basic-offset: 2
# End:
# vim: ft=zsh sw=2 ts=2 et
+1
View File
@@ -0,0 +1 @@
* linguist-vendored
Vendored
+250 -22
View File
@@ -28,20 +28,47 @@ is a simple, fast and user-friendly alternative to
.TP
.B \-H, \-\-hidden
Include hidden files and directories in the search results
(default: hidden files and directories are skipped).
(default: hidden files and directories are skipped). The flag can be overridden with '--no-hidden'.
.TP
.B \-I, \-\-no\-ignore
Show search results from files and directories that would otherwise be ignored by
.IR .gitignore ,
.RS
.IP \[bu] 2
.I .gitignore
.IP \[bu]
.I .git/info/exclude
.IP \[bu]
The global gitignore configuration (by default
.IR $HOME/.config/git/ignore )
.IP \[bu]
.I .ignore
or
.IP \[bu]
.I .fdignore
files.
.IP \[bu]
The global fd ignore file (usually
.I $HOME/.config/fd/ignore
)
.RE
.IP
The flag can be overridden with '--ignore'.
.TP
.B \-u, \-\-unrestricted
Alias for '--no-ignore'. Can be repeated; '-uu' is an alias for '--no-ignore --hidden'.
.TP
.B \-\-no\-ignore\-vcs
Show search results from files and directories that would otherwise be ignored by
.I .gitignore
files.
Show search results from files and directories that would otherwise be ignored by gitignore files
including
.IR .gitignore ,
.IR .git/info/exclude ,
and the global gitignore configuration
.RI ( core.excludesFile
git setting, which defaults to
.IR $HOME/.config/git/ignore ).
The flag can be overridden with '--ignore-vcs'.
.TP
.B \-\-no\-ignore\-parent
Show search results from files and directories that would otherwise be ignored by gitignore files in
parent directories.
.TP
.B \-s, \-\-case\-sensitive
Perform a case-sensitive search. By default, fd uses case-insensitive searches, unless the
@@ -51,15 +78,30 @@ pattern contains an uppercase character (smart case).
Perform a case-insensitive search. By default, fd uses case-insensitive searches, unless the
pattern contains an uppercase character (smart case).
.TP
.B \-g, \-\-glob
Perform a glob-based search instead of a regular expression search.
If combined with the '\-\-full-path' option, '**' can be used to match multiple path components.
.TP
.B \-\-regex
Perform a regular-expression based search (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.
Treat the pattern as a literal string instead of a regular expression. Note that this also
performs substring comparison. If you want to match on an exact filename, consider using '\-\-glob'.
.TP
.B \-a, \-\-absolute\-path
Shows the full path starting from the root as opposed to relative paths.
The flag can be overridden with '--relative-path'.
.TP
.B \-l, \-\-list\-details
Use a detailed listing format like 'ls -l'. This is basically an alias
for '--exec-batch ls -l' with some additional 'ls' options. This can be used
to see more metadata, to show symlink targets and to achieve a deterministic
sort order.
.TP
.B \-L, \-\-follow
By default, fd does not descend into symlinked directories. Using this flag, symbolic links are
also traversed.
also traversed. The flag can be overridden with '--no-follow'.
.TP
.B \-p, \-\-full\-path
By default, the search pattern is only matched against the filename (or directory name). Using
@@ -71,10 +113,30 @@ is matched against the full path.
Separate search results by the null character (instead of newlines). Useful for piping results to
.IR xargs .
.TP
.B \-\-max\-results count
Limit the number of search results to 'count' and quit immediately.
.TP
.B \-1
Limit the search to a single result and quit immediately. This is an alias for '--max-results=1'.
.TP
.B \-q, \-\-quiet
When the flag is present, the program does not print anything and will instead exit with a code of 0 if there is at least one search result.
Otherwise, the exit code will be 1.
This is mainly for usage in scripts and can be faster than checking for output because the search can be stopped early after the first match.
.B \-\-has\-results
can be used as an alias.
.TP
.B \-\-show-errors
Enable the display of filesystem errors for situations such as insufficient
permissions or dead symlinks.
.TP
.B \-\-strip-cwd-prefix
By default, relative paths are prefixed with './' when the output goes to a non interactive terminal
(TTY). Use this flag to disable this behaviour.
.TP
.B \-\-one\-file\-system, \-\-mount, \-\-xdev
By default, fd will traverse the file system tree as far as other options dictate. With this flag, fd ensures that it does not descend into a different file system than the one it started in. Comparable to the -mount or -xdev filters of find(1).
.TP
.B \-h, \-\-help
Print help information.
.TP
@@ -86,15 +148,28 @@ Limit directory traversal to at most
.I d
levels of depth. By default, there is no limit on the search depth.
.TP
.BI "\-\-min\-depth " d
Only show search results starting at the given depth. See also: '--max-depth' and '--exact-depth'.
.TP
.BI "\-\-exact\-depth " d
Only show search results at the exact given depth. This is an alias for '--min-depth <depth> --max-depth <depth>'.
.TP
.B \-\-prune
Do not traverse into matching directories.
.TP
.BI "\-t, \-\-type " filetype
Filter search by type:
.RS
.IP "f, file"
regular files
.IP "d, directories"
.IP "d, directory"
directories
.IP "l, symlink"
symbolic links
.IP "s, socket"
sockets
.IP "p, pipe"
named pipes (FIFOs)
.IP "x, executable"
executable (files)
.IP "e, empty"
@@ -102,18 +177,46 @@ empty files or directories
.RE
.RS
This option can be used repeatedly to allow for multiple file types.
This option can be specified more than once to include multiple file types.
Searching for '--type file --type symlink' will show both regular files as well as
symlinks. Note that the 'executable' and 'empty' filters work differently: '--type
executable' implies '--type file' by default. And '--type empty' searches for
empty files and directories, unless either '--type file' or '--type directory' is
specified in addition.
Examples:
- Only search for files:
fd --type file …
fd -tf …
- Find both files and symlinks
fd --type file --type symlink …
fd -tf -tl …
- Find executable files:
fd --type executable
fd -tx
- Find empty files:
fd --type empty --type file
fd -te -tf
- Find empty directories:
fd --type empty --type directory
fd -te -td
.RE
.TP
.BI "\-e, \-\-extension " ext
Filter search results by file extension
.IR ext .
This option can be used repeatedly to allow for multiple possible file extensions.
If you want to search for files without extension, you can use the regex '^[^.]+$'
as a normal search pattern.
.TP
.BI "\-E, \-\-exclude " pattern
Exclude files/directories that match the given glob pattern.
This overrides any other ignore logic.
Multiple exclude patterns can be specified.
Examples:
\-\-exclude '*.pyc'
\-\-exclude node_modules
.TP
.BI "\-\-ignore-file " path
Add a custom ignore-file in '.gitignore' format.
@@ -133,7 +236,7 @@ Always colorize output.
.RE
.TP
.BI "\-j, \-\-threads " num
Number of threads to use for searching (default: number of available CPUs).
Set number of threads to use for searching & executing (default: number of available CPU cores).
.TP
.BI "\-S, \-\-size " size
Limit results based on the size of files using the format
@@ -143,6 +246,8 @@ Limit results based on the size of files using the format
file size must be greater than or equal to this
.IP '-'
file size must be less than or equal to this
.P
If neither '+' nor '-' is specified, file size must be exactly equal to this.
.IP 'NUM'
The numeric size (e.g. 500)
.IP 'UNIT'
@@ -152,7 +257,7 @@ Allowed unit values:
.IP 'b'
bytes
.IP 'k'
kilobytes
kilobytes (base ten, 10^3 = 1000 bytes)
.IP 'm'
megabytes
.IP 'g'
@@ -160,7 +265,7 @@ gigabytes
.IP 't'
terabytes
.IP 'ki'
kibibytes
kibibytes (base two, 2^10 = 1024 bytes)
.IP 'mi'
mebibytes
.IP 'gi'
@@ -171,8 +276,11 @@ tebibytes
.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).
Filter results based on the file modification time.
Files with modification times greater than the argument will be returned.
The argument can be provided as a duration (\fI10h, 1d, 35min\fR) or as a specific point
in time in either full RFC3339 format with time zone, or as a date or datetime in the
local time zone (\fIYYYY-MM-DD\fR or \fIYYYY-MM-DD HH:MM:SS\fR).
.B --change-newer-than
can be used as an alias.
@@ -181,8 +289,11 @@ Examples:
\-\-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).
Filter results based on the file modification time.
Files with modification times less than the argument will be returned.
The argument can be provided as a duration (\fI10h, 1d, 35min\fR) or as a specific point
in time in either full RFC3339 format with time zone, or as a date or datetime in the
local time zone (\fIYYYY-MM-DD\fR or \fIYYYY-MM-DD HH:MM:SS\fR).
.B --change-older-than
can be used as an alias.
@@ -190,13 +301,46 @@ Examples:
\-\-changed-before "2018-10-27 10:00:00"
\-\-change-older-than 2weeks
.TP
.BI "\-x, \-\-exec " command "\fR [args...] ;"
.BI "-o, \-\-owner " [user][:group]
Filter files by their user and/or group. Format: [(user|uid)][:(group|gid)]. Either side
is optional. Precede either side with a '!' to exclude files instead.
Examples:
\-\-owner john
\-\-owner :students
\-\-owner "!john:students"
.TP
.BI "\-\-base\-directory " path
Change the current working directory of fd to the provided path. This means that search results will
be shown with respect to the given base path. Note that relative paths which are passed to fd via the
positional \fIpath\fR argument or the \fB\-\-search\-path\fR option will also be resolved relative to
this directory.
.TP
.BI "\-\-path\-separator " separator
Set the path separator to use when printing file paths. The default is the OS-specific separator
('/' on Unix, '\\' on Windows).
.TP
.BI "\-\-search\-path " search\-path
Provide paths to search as an alternative to the positional \fIpath\fR argument. Changes the usage to
\'fd [FLAGS/OPTIONS] \-\-search\-path PATH \-\-search\-path PATH2 [PATTERN]\'
.TP
.BI "\-x, \-\-exec " command
.RS
Execute
.I command
for each search result. The following placeholders are substituted by a path derived from the current search result:
for each search result in parallel (use --threads=1 for sequential command execution).
Note that all subsequent positional arguments are considered to be arguments to the
.I command
- not to fd.
It is therefore recommended to place the \-x/\-\-exec option last. Alternatively, you can supply
a ';' argument to end the argument list and continue with more fd options.
Most shells require ';' to be escaped: '\\;'.
The following placeholders are substituted before the command is executed:
.RS
.IP {}
path
path (of the current search result)
.IP {/}
basename
.IP {//}
@@ -206,11 +350,86 @@ path without file extension
.IP {/.}
basename without file extension
.RE
If no placeholder is present, an implicit "{}" at the end is assumed.
Examples:
- find all *.zip files and unzip them:
fd -e zip -x unzip
- find *.h and *.cpp files and run "clang-format -i .." for each of them:
fd -e h -e cpp -x clang-format -i
- Convert all *.jpg files to *.png files:
fd -e jpg -x convert {} {.}.png
.RE
.TP
.BI "\-X, \-\-exec-batch " command
.RS
Execute
.I command
once, with all search results as arguments.
One of the following placeholders is substituted before the command is executed:
.RS
.IP {}
path (of all search results)
.IP {/}
basename
.IP {//}
parent directory
.IP {.}
path without file extension
.IP {/.}
basename without file extension
.RE
If no placeholder is present, an implicit "{}" at the end is assumed.
Examples:
- Find all test_*.py files and open them in your favorite editor:
fd -g 'test_*.py' -X vim
Note that this executes a single "vim" process with all search results as arguments.
- Find all *.rs files and count the lines with "wc -l ...":
fd -e rs -X wc -l
.RE
.TP
.BI "\-\-batch-size " size
Maximum number of arguments to pass to the command given with -X. If the number of results is
greater than the given size, the command given with -X is run again with remaining arguments. A
batch size of zero means there is no limit.
.SH PATTERN SYNTAX
The regular expression syntax used by fd is documented here:
https://docs.rs/regex/1.0.0/regex/#syntax
The glob syntax is documented here:
https://docs.rs/globset/#syntax
.SH ENVIRONMENT
.TP
.B LS_COLORS
Determines how to colorize search results, see
.BR dircolors (1) .
.TP
.B NO_COLOR
Disables colorized output.
.TP
.B XDG_CONFIG_HOME, HOME
Used to locate the global ignore file. If
.B XDG_CONFIG_HOME
is set, use
.IR $XDG_CONFIG_HOME/fd/ignore .
Otherwise, use
.IR $HOME/.config/fd/ignore .
.SH EXAMPLES
.TP
.RI "Find files and directories that match the pattern '" needle "':"
@@ -219,7 +438,16 @@ $ fd needle
.RI "Start a search in a given directory (" /var/log "):"
$ fd nginx /var/log
.TP
.RI "Find all Python files (all files with the extention " .py ") in the current directory:"
.RI "Find all Python files (all files with the extension " .py ") in the current directory:"
$ fd -e py
.TP
.RI "Open all search results with vim:"
$ fd pattern -X vim
.TP
.BI "\-\-batch\-size " size
Pass at most
.I size
arguments to each call to the command given with -X.
.TP
.SH SEE ALSO
.BR find (1)
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

+161
View File
@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="66mm"
height="66mm"
viewBox="0 0 66 66.000001"
version="1.1"
id="svg5"
inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
sodipodi:docname="logo.svg"
inkscape:export-filename="/home/shark/Informatik/rust/fd/doc/logo.png"
inkscape:export-xdpi="192.42"
inkscape:export-ydpi="192.42"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="1"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="2.1795515"
inkscape:cx="114.47309"
inkscape:cy="176.18304"
inkscape:window-width="1920"
inkscape:window-height="1175"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showguides="false"
inkscape:guide-bbox="true"
inkscape:snap-global="false"
fit-margin-top="2"
fit-margin-left="2"
fit-margin-right="2"
fit-margin-bottom="2"
lock-margins="true">
<sodipodi:guide
position="26.228232,26.126763"
orientation="0,-1"
id="guide47826" />
<sodipodi:guide
position="25.799494,2.3628924"
orientation="0,-1"
id="guide47828" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-21.358009,-148.28012)">
<g
id="g66267"
transform="matrix(0.84959471,0,0,0.84959471,7.9920783,43.351816)">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.7126px;line-height:0;font-family:'Fira Sans Condensed';-inkscape-font-specification:'Fira Sans Condensed, ';white-space:pre;inline-size:37.3715;fill:#e5e5e5;fill-opacity:1;stroke-width:0.742816"
x="50.647034"
y="173.19841"
id="text50653"
transform="matrix(1.0604862,0,0,1.0604862,-3.3101428,-10.150043)"><tspan
x="50.647034"
y="173.19841"
id="tspan66635"><tspan
style="font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro'"
id="tspan66633">fd</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.7126px;line-height:0;font-family:'Fira Sans Condensed';-inkscape-font-specification:'Fira Sans Condensed, ';white-space:pre;inline-size:37.3715;fill:#00ccff;fill-opacity:0.996078;stroke-width:0.742816"
x="50.647034"
y="173.19841"
id="text1244"
transform="matrix(1.0604862,0,0,1.0604862,-2.8008599,-9.6407599)"><tspan
x="50.647034"
y="173.19841"
id="tspan66639"><tspan
style="font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro'"
id="tspan66637">fd</tspan></tspan></text>
<g
id="g47824"
transform="translate(0.1724878,-0.35338542)">
<g
id="g42041">
<path
style="fill:none;stroke:#939dac;stroke-width:1.065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 28.209616,155.51329 v 15.68758 H 40.83001"
id="path39763"
sodipodi:nodetypes="ccc" />
<path
style="fill:#b7bec8;stroke:#939dac;stroke-width:1.065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 27.869464,161.83767 H 40.261291"
id="path39765" />
</g>
<g
id="g41945"
transform="translate(-1.0583333)">
<path
style="fill:#0088aa;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
id="path40006"
sodipodi:nodetypes="ccccc" />
<rect
style="fill:#01ccff;fill-opacity:1;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
id="rect39949"
width="9.4925022"
height="6.2080379"
x="37.492516"
y="158.82776"
ry="0.90871465" />
</g>
<g
id="g41951"
transform="translate(-1.0583334,9.3665773)">
<path
style="fill:#373e48;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
id="path41947"
sodipodi:nodetypes="ccccc" />
<rect
style="fill:#535d6c;fill-opacity:0.993797;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
id="rect41949"
width="9.4925022"
height="6.2080379"
x="37.492516"
y="158.82776"
ry="0.90871465" />
</g>
<g
id="g41957"
transform="translate(-14.306994,-6.8962642)">
<path
style="fill:#373e48;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
id="path41953"
sodipodi:nodetypes="ccccc" />
<rect
style="fill:#535d6c;fill-opacity:0.993797;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
id="rect41955"
width="9.4925022"
height="6.2080379"
x="37.492516"
y="158.82776"
ry="0.90871465" />
</g>
</g>
<g
id="g65006"
transform="matrix(0.55302761,0,0,0.55302761,66.463548,117.45819)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

+1 -1
View File
@@ -20,7 +20,7 @@ enter() {
}
prompt() {
printf '%b ' $PROMPT | pv -q
printf '%b ' "$PROMPT" | pv -q
}
type() {
+667 -249
View File
File diff suppressed because it is too large Load Diff
+50 -9
View File
@@ -1,14 +1,16 @@
use exec::CommandTemplate;
use internal::{
filter::{SizeFilter, TimeFilter},
FileTypes,
};
use std::{path::PathBuf, sync::Arc, time::Duration};
use lscolors::LsColors;
use regex::RegexSet;
use std::{path::PathBuf, time::Duration};
use regex::bytes::RegexSet;
use crate::exec::CommandTemplate;
use crate::filetypes::FileTypes;
#[cfg(unix)]
use crate::filter::OwnerFilter;
use crate::filter::{SizeFilter, TimeFilter};
/// Configuration options for *fd*.
pub struct FdOptions {
pub struct Config {
/// Whether the search is case-sensitive or case-insensitive.
pub case_sensitive: bool,
@@ -22,12 +24,21 @@ pub struct FdOptions {
/// Whether to respect `.fdignore` files or not.
pub read_fdignore: bool,
/// Whether to respect ignore files in parent directories or not.
pub read_parent_ignore: bool,
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
pub read_vcsignore: bool,
/// Whether to respect the global ignore file or not.
pub read_global_ignore: bool,
/// Whether to follow symlinks or not.
pub follow_links: bool,
/// Whether to limit the search to starting file system or not.
pub one_file_system: bool,
/// Whether elements of output should be separated by a null character
pub null_separator: bool,
@@ -37,9 +48,19 @@ pub struct FdOptions {
/// all files under subdirectories of the current directory, etc.
pub max_depth: Option<usize>,
/// The minimum depth for reported entries, or `None`.
pub min_depth: Option<usize>,
/// Whether to stop traversing into matching directories.
pub prune: bool,
/// The number of threads to use.
pub threads: usize,
/// If true, the program doesn't print anything and will instead return an exit code of 0
/// if there's at least one match. Otherwise, the exit code will be 1.
pub quiet: bool,
/// 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`.
@@ -49,6 +70,9 @@ pub struct FdOptions {
/// how to style different filetypes.
pub ls_colors: Option<LsColors>,
/// Whether or not we are writing to an interactive terminal
pub interactive_terminal: bool,
/// The type of file to search for. If set to `None`, all file types are displayed. If
/// set to `Some(..)`, only the types that are specified are shown.
pub file_types: Option<FileTypes>,
@@ -59,7 +83,11 @@ pub struct FdOptions {
pub extensions: Option<RegexSet>,
/// If a value is supplied, each item found will be used to generate and execute commands.
pub command: Option<CommandTemplate>,
pub command: Option<Arc<CommandTemplate>>,
/// Maximum number of search results to pass to each `command`. If zero, the number is
/// unlimited.
pub batch_size: usize,
/// A list of glob patterns that should be excluded from the search.
pub exclude_patterns: Vec<String>,
@@ -73,6 +101,19 @@ pub struct FdOptions {
/// Constraints on last modification time of files
pub time_constraints: Vec<TimeFilter>,
#[cfg(unix)]
/// User/group ownership constraint
pub owner_constraint: Option<OwnerFilter>,
/// Whether or not to display filesystem errors
pub show_filesystem_errors: bool,
/// The separator used to print file paths.
pub path_separator: Option<String>,
/// The maximum number of search results
pub max_results: Option<usize>,
/// Whether or not to strip the './' prefix for search results
pub strip_cwd_prefix: bool,
}
+3
View File
@@ -0,0 +1,3 @@
pub fn print_error(msg: impl Into<String>) {
eprintln!("[fd error]: {}", msg.into());
}
+26 -13
View File
@@ -1,20 +1,25 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::io;
use std::io::Write;
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::sync::Mutex;
use crate::error::print_error;
use crate::exit_codes::ExitCode;
/// Executes a command.
pub fn execute_command(mut cmd: Command, out_perm: Arc<Mutex<()>>) {
pub fn execute_command(
mut cmd: Command,
out_perm: &Mutex<()>,
enable_output_buffering: bool,
) -> ExitCode {
// Spawn the supplied command.
let output = cmd.output();
let output = if enable_output_buffering {
cmd.output()
} else {
// If running on only one thread, don't buffer output
// Allows for viewing and interacting with intermediate command output
cmd.spawn().and_then(|c| c.wait_with_output())
};
// Then wait for the command to exit, if it was spawned.
match output {
@@ -28,12 +33,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);
print_error(format!("Command not found: {:?}", cmd));
ExitCode::GeneralError
}
Err(why) => {
print_error!("Problem while executing command: {}", why);
print_error(format!("Problem while executing command: {}", why));
ExitCode::GeneralError
}
}
}
+59 -137
View File
@@ -1,165 +1,87 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::path::MAIN_SEPARATOR;
use crate::filesystem::strip_current_dir;
/// Removes the parent component of the path
pub fn basename(path: &str) -> &str {
let mut index = 0;
for (id, character) in path.char_indices() {
if character == MAIN_SEPARATOR {
index = id;
}
}
// FIXME: On Windows, should return what for C:file.txt D:file.txt and \\server\share ?
if index != 0 {
return &path[index + 1..];
}
path
pub fn basename(path: &Path) -> &OsStr {
path.file_name().unwrap_or_else(|| path.as_os_str())
}
/// Removes the extension from the path
pub fn remove_extension(path: &str) -> &str {
let mut has_dir = false;
let mut dir_index = 0;
let mut ext_index = 0;
pub fn remove_extension(path: &Path) -> OsString {
let dirname = dirname(path);
let stem = path.file_stem().unwrap_or_else(|| path.as_os_str());
for (id, character) in path.char_indices() {
if character == MAIN_SEPARATOR {
has_dir = true;
dir_index = id;
}
if character == '.' {
ext_index = id;
}
}
let path = PathBuf::from(dirname).join(stem);
// Account for hidden files and directories
if ext_index != 0 && (!has_dir || dir_index + 2 <= ext_index) {
return &path[0..ext_index];
}
path
strip_current_dir(&path).to_owned().into_os_string()
}
/// Removes the basename from the path.
pub fn dirname(path: &str) -> &str {
let mut has_dir = false;
let mut index = 0;
for (id, character) in path.char_indices() {
if character == MAIN_SEPARATOR {
has_dir = true;
index = id;
}
}
// FIXME: On Windows, return what for C:file.txt D:file.txt and \\server\share ?
if !has_dir {
"."
} else if index == 0 {
&path[..1]
} else {
&path[0..index]
}
pub fn dirname(path: &Path) -> OsString {
path.parent()
.map(|p| {
if p == OsStr::new("") {
OsString::from(".")
} else {
p.as_os_str().to_owned()
}
})
.unwrap_or_else(|| path.as_os_str().to_owned())
}
#[cfg(test)]
mod tests {
mod path_tests {
use super::*;
use std::path::MAIN_SEPARATOR;
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() {
let input_path = PathBuf::from(&correct($input));
let output_string = OsString::from(correct($output));
assert_eq!($func(&input_path), output_string);
}
)+
}
}
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"
}
#[test]
fn path_remove_ext_dir() {
assert_eq!(
remove_extension(&correct("dir/foo.txt")),
correct("dir/foo")
);
#[cfg(windows)]
fn dirname_root() {
assert_eq!(dirname(&PathBuf::from("C:")), OsString::from("C:"));
assert_eq!(dirname(&PathBuf::from("\\")), OsString::from("\\"));
}
#[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() {
#[cfg(windows)]
assert_eq!(dirname("C:\\"), "C:");
#[cfg(windows)]
assert_eq!(dirname("\\"), "\\");
#[cfg(not(windows))]
assert_eq!(dirname("/"), "/");
#[cfg(not(windows))]
fn dirname_root() {
assert_eq!(dirname(&PathBuf::from("/")), OsString::from("/"));
}
}
+47 -16
View File
@@ -1,16 +1,12 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use super::CommandTemplate;
use std::path::PathBuf;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex};
use walk::WorkerResult;
use crate::error::print_error;
use crate::exit_codes::{merge_exitcodes, ExitCode};
use crate::walk::WorkerResult;
use super::CommandTemplate;
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
/// generate a command with the supplied command template. The generated command will then
@@ -20,7 +16,9 @@ pub fn job(
cmd: Arc<CommandTemplate>,
out_perm: Arc<Mutex<()>>,
show_filesystem_errors: bool,
) {
buffer_output: bool,
) -> ExitCode {
let mut results: Vec<ExitCode> = Vec::new();
loop {
// Create a lock on the shared receiver for this thread.
let lock = rx.lock().unwrap();
@@ -28,19 +26,52 @@ pub fn job(
// Obtain the next result from the receiver, else if the channel
// has closed, exit from the loop
let value: PathBuf = match lock.recv() {
Ok(WorkerResult::Entry(val)) => val,
Ok(WorkerResult::Entry(path)) => path,
Ok(WorkerResult::Error(err)) => {
if show_filesystem_errors {
print_error!("{}", err);
print_error(err.to_string());
}
continue;
}
Err(_) => break,
};
// Drop the lock so that other threads can read from the the receiver.
// Drop the lock so that other threads can read from the receiver.
drop(lock);
// Generate a command and execute it.
cmd.generate_and_execute(&value, Arc::clone(&out_perm));
// Generate a command, execute it and store its exit code.
results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm), buffer_output))
}
// Returns error in case of any error.
merge_exitcodes(results)
}
pub fn batch(
rx: Receiver<WorkerResult>,
cmd: &CommandTemplate,
show_filesystem_errors: bool,
buffer_output: bool,
limit: usize,
) -> ExitCode {
let paths = rx.iter().filter_map(|value| match value {
WorkerResult::Entry(path) => Some(path),
WorkerResult::Error(err) => {
if show_filesystem_errors {
print_error(err.to_string());
}
None
}
});
if limit == 0 {
// no limit
return cmd.generate_and_execute_batch(paths, buffer_output);
}
let mut exit_codes = Vec::new();
let mut peekable = paths.peekable();
while peekable.peek().is_some() {
let limited = peekable.by_ref().take(limit);
let exit_code = cmd.generate_and_execute_batch(limited, buffer_output);
exit_codes.push(exit_code);
}
merge_exitcodes(exit_codes)
}
+280 -42
View File
@@ -1,29 +1,34 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
// TODO: Possible optimization could avoid pushing characters on a buffer.
mod command;
mod input;
mod job;
mod token;
use std::borrow::Cow;
use std::path::Path;
use std::process::Command;
use std::ffi::{OsStr, OsString};
use std::path::{Component, Path, PathBuf, Prefix};
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use anyhow::{anyhow, Result};
use once_cell::sync::Lazy;
use regex::Regex;
use crate::exit_codes::ExitCode;
use self::command::execute_command;
use self::input::{basename, dirname, remove_extension};
pub use self::job::job;
pub use self::job::{batch, job};
use self::token::Token;
/// 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.
///
/// The template is meant to be coupled with an input in order to generate a command. The
@@ -31,17 +36,43 @@ use self::token::Token;
#[derive(Debug, Clone, PartialEq)]
pub struct CommandTemplate {
args: Vec<ArgumentTemplate>,
mode: ExecutionMode,
path_separator: Option<String>,
}
impl CommandTemplate {
pub fn new<I, S>(input: I) -> CommandTemplate
pub fn new<I, S>(input: I, path_separator: Option<String>) -> CommandTemplate
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
lazy_static! {
static ref PLACEHOLDER_PATTERN: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap();
Self::build(input, ExecutionMode::OneByOne, path_separator)
}
pub fn new_batch<I, S>(input: I, path_separator: Option<String>) -> Result<CommandTemplate>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let cmd = Self::build(input, ExecutionMode::Batch, path_separator);
if cmd.number_of_tokens() > 1 {
return Err(anyhow!("Only one placeholder allowed for batch commands"));
}
if cmd.args[0].has_tokens() {
return Err(anyhow!(
"First argument of exec-batch is expected to be a fixed executable"
));
}
Ok(cmd)
}
fn build<I, S>(input: I, mode: ExecutionMode, path_separator: Option<String>) -> CommandTemplate
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
static PLACEHOLDER_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new(r"\{(/?\.?|//)\}").unwrap());
let mut args = Vec::new();
let mut has_placeholder = false;
@@ -66,7 +97,7 @@ impl CommandTemplate {
"{/}" => tokens.push(Token::Basename),
"{//}" => tokens.push(Token::Parent),
"{/.}" => tokens.push(Token::BasenameNoExt),
_ => panic!("Unhandled placeholder"),
_ => unreachable!("Unhandled placeholder"),
}
has_placeholder = true;
@@ -91,26 +122,71 @@ impl CommandTemplate {
args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder]));
}
CommandTemplate { args: args }
CommandTemplate {
args,
mode,
path_separator,
}
}
fn number_of_tokens(&self) -> usize {
self.args.iter().filter(|arg| arg.has_tokens()).count()
}
/// Generates and executes a command.
///
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
/// build. Once all arguments have been processed, the command is executed.
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) {
let input = input
.strip_prefix(".")
.unwrap_or(input)
.to_string_lossy()
.into_owned();
let mut cmd = Command::new(self.args[0].generate(&input).as_ref());
pub fn generate_and_execute(
&self,
input: &Path,
out_perm: Arc<Mutex<()>>,
buffer_output: bool,
) -> ExitCode {
let mut cmd = Command::new(self.args[0].generate(&input, self.path_separator.as_deref()));
for arg in &self.args[1..] {
cmd.arg(arg.generate(&input).as_ref());
cmd.arg(arg.generate(&input, self.path_separator.as_deref()));
}
execute_command(cmd, out_perm)
execute_command(cmd, &out_perm, buffer_output)
}
pub fn in_batch_mode(&self) -> bool {
self.mode == ExecutionMode::Batch
}
pub fn generate_and_execute_batch<I>(&self, paths: I, buffer_output: bool) -> ExitCode
where
I: Iterator<Item = PathBuf>,
{
let mut cmd = Command::new(self.args[0].generate("", None));
cmd.stdin(Stdio::inherit());
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
let mut paths: Vec<_> = paths.collect();
let mut has_path = false;
for arg in &self.args[1..] {
if arg.has_tokens() {
paths.sort();
// A single `Tokens` is expected
// So we can directly consume the iterator once and for all
for path in &mut paths {
cmd.arg(arg.generate(path, self.path_separator.as_deref()));
has_path = true;
}
} else {
cmd.arg(arg.generate("", None));
}
}
if has_path {
execute_command(cmd, &Mutex::new(()), buffer_output)
} else {
ExitCode::Success
}
}
}
@@ -125,27 +201,98 @@ enum ArgumentTemplate {
}
impl ArgumentTemplate {
pub fn generate<'a>(&'a self, path: &str) -> Cow<'a, str> {
pub fn has_tokens(&self) -> bool {
matches!(self, ArgumentTemplate::Tokens(_))
}
/// Generate an argument from this template. If path_separator is Some, then it will replace
/// the path separator in all placeholder tokens. Text arguments and tokens are not affected by
/// path separator substitution.
pub fn generate(&self, path: impl AsRef<Path>, path_separator: Option<&str>) -> OsString {
use self::Token::*;
let path = path.as_ref();
match *self {
ArgumentTemplate::Tokens(ref tokens) => {
let mut s = String::new();
let mut s = OsString::new();
for token in tokens {
match *token {
Basename => s += basename(path),
BasenameNoExt => s += remove_extension(basename(path)),
NoExt => s += remove_extension(path),
Parent => s += dirname(path),
Placeholder => s += path,
Text(ref string) => s += string,
Basename => s.push(Self::replace_separator(basename(path), path_separator)),
BasenameNoExt => s.push(Self::replace_separator(
&remove_extension(basename(path).as_ref()),
path_separator,
)),
NoExt => s.push(Self::replace_separator(
&remove_extension(path),
path_separator,
)),
Parent => s.push(Self::replace_separator(&dirname(path), path_separator)),
Placeholder => {
s.push(Self::replace_separator(path.as_ref(), path_separator))
}
Text(ref string) => s.push(string),
}
}
Cow::Owned(s)
s
}
ArgumentTemplate::Text(ref text) => Cow::Borrowed(text),
ArgumentTemplate::Text(ref text) => OsString::from(text),
}
}
/// Replace the path separator in the input with the custom separator string. If path_separator
/// is None, simply return a borrowed Cow<OsStr> of the input. Otherwise, the input is
/// interpreted as a Path and its components are iterated through and re-joined into a new
/// OsString.
fn replace_separator<'a>(path: &'a OsStr, path_separator: Option<&str>) -> Cow<'a, OsStr> {
// fast-path - no replacement necessary
if path_separator.is_none() {
return Cow::Borrowed(path);
}
let path_separator = path_separator.unwrap();
let mut out = OsString::with_capacity(path.len());
let mut components = Path::new(path).components().peekable();
while let Some(comp) = components.next() {
match comp {
// Absolute paths on Windows are tricky. A Prefix component is usually a drive
// letter or UNC path, and is usually followed by RootDir. There are also
// "verbatim" prefixes beginning with "\\?\" that skip normalization. We choose to
// ignore verbatim path prefixes here because they're very rare, might be
// impossible to reach here, and there's no good way to deal with them. If users
// are doing something advanced involving verbatim windows paths, they can do their
// own output filtering with a tool like sed.
Component::Prefix(prefix) => {
if let Prefix::UNC(server, share) = prefix.kind() {
// Prefix::UNC is a parsed version of '\\server\share'
out.push(path_separator);
out.push(path_separator);
out.push(server);
out.push(path_separator);
out.push(share);
} else {
// All other Windows prefix types are rendered as-is. This results in e.g. "C:" for
// drive letters. DeviceNS and Verbatim* prefixes won't have backslashes converted,
// but they're not returned by directories fd can search anyway so we don't worry
// about them.
out.push(comp.as_os_str());
}
}
// Root directory is always replaced with the custom separator.
Component::RootDir => out.push(path_separator),
// Everything else is joined normally, with a trailing separator if we're not last
_ => {
out.push(comp.as_os_str());
if components.peek().is_some() {
out.push(path_separator);
}
}
}
}
Cow::Owned(out)
}
}
#[cfg(test)]
@@ -155,13 +302,15 @@ mod tests {
#[test]
fn tokens_with_placeholder() {
assert_eq!(
CommandTemplate::new(&[&"echo", &"${SHELL}:"]),
CommandTemplate::new(&[&"echo", &"${SHELL}:"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Text("${SHELL}:".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
@@ -169,12 +318,14 @@ mod tests {
#[test]
fn tokens_with_no_extension() {
assert_eq!(
CommandTemplate::new(&["echo", "{.}"]),
CommandTemplate::new(&["echo", "{.}"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::NoExt]),
],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
@@ -182,12 +333,14 @@ mod tests {
#[test]
fn tokens_with_basename() {
assert_eq!(
CommandTemplate::new(&["echo", "{/}"]),
CommandTemplate::new(&["echo", "{/}"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Basename]),
],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
@@ -195,12 +348,14 @@ mod tests {
#[test]
fn tokens_with_parent() {
assert_eq!(
CommandTemplate::new(&["echo", "{//}"]),
CommandTemplate::new(&["echo", "{//}"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Parent]),
],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
@@ -208,13 +363,96 @@ mod tests {
#[test]
fn tokens_with_basename_no_extension() {
assert_eq!(
CommandTemplate::new(&["echo", "{/.}"]),
CommandTemplate::new(&["echo", "{/.}"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
#[test]
fn tokens_multiple() {
assert_eq!(
CommandTemplate::new(&["cp", "{}", "{/.}.ext"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("cp".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
ArgumentTemplate::Tokens(vec![
Token::BasenameNoExt,
Token::Text(".ext".into())
]),
],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
#[test]
fn tokens_single_batch() {
assert_eq!(
CommandTemplate::new_batch(&["echo", "{.}"], None).unwrap(),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::NoExt]),
],
mode: ExecutionMode::Batch,
path_separator: None,
}
);
}
#[test]
fn tokens_multiple_batch() {
assert!(CommandTemplate::new_batch(&["echo", "{.}", "{}"], None).is_err());
}
#[test]
fn generate_custom_path_separator() {
let arg = ArgumentTemplate::Tokens(vec![Token::Placeholder]);
macro_rules! check {
($input:expr, $expected:expr) => {
assert_eq!(arg.generate($input, Some("#")), OsString::from($expected));
};
}
check!("foo", "foo");
check!("foo/bar", "foo#bar");
check!("/foo/bar/baz", "#foo#bar#baz");
}
#[cfg(windows)]
#[test]
fn generate_custom_path_separator_windows() {
let arg = ArgumentTemplate::Tokens(vec![Token::Placeholder]);
macro_rules! check {
($input:expr, $expected:expr) => {
assert_eq!(arg.generate($input, Some("#")), OsString::from($expected));
};
}
// path starting with a drive letter
check!(r"C:\foo\bar", "C:#foo#bar");
// UNC path
check!(r"\\server\share\path", "##server#share#path");
// Drive Relative path - no separator after the colon omits the RootDir path component.
// This is uncommon, but valid
check!(r"C:foo\bar", "C:foo#bar");
// forward slashes should get normalized and interpreted as separators
check!("C:/foo/bar", "C:#foo#bar");
check!("C:foo/bar", "C:foo#bar");
// Rust does not interpret "//server/share" as a UNC path, but rather as a normal
// absolute path that begins with RootDir, and the two slashes get combined together as
// a single path separator during normalization.
//check!("//server/share/path", "##server#share#path");
}
}
-8
View File
@@ -1,11 +1,3 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::fmt::{self, Display, Formatter};
/// Designates what should be written to a buffer
+84 -3
View File
@@ -1,13 +1,94 @@
use std::process;
#[cfg(unix)]
use nix::sys::signal::{raise, signal, SigHandler, Signal};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ExitCode {
Success,
HasResults(bool),
GeneralError,
KilledBySigint,
}
impl Into<i32> for ExitCode {
fn into(self) -> i32 {
match self {
impl From<ExitCode> for i32 {
fn from(code: ExitCode) -> Self {
match code {
ExitCode::Success => 0,
ExitCode::HasResults(has_results) => !has_results as i32,
ExitCode::GeneralError => 1,
ExitCode::KilledBySigint => 130,
}
}
}
impl ExitCode {
fn is_error(self) -> bool {
i32::from(self) != 0
}
/// Exit the process with the appropriate code.
pub fn exit(self) -> ! {
#[cfg(unix)]
if self == ExitCode::KilledBySigint {
// Get rid of the SIGINT handler, if present, and raise SIGINT
unsafe {
if signal(Signal::SIGINT, SigHandler::SigDfl).is_ok() {
let _ = raise(Signal::SIGINT);
}
}
}
process::exit(self.into())
}
}
pub fn merge_exitcodes(results: impl IntoIterator<Item = ExitCode>) -> ExitCode {
if results.into_iter().any(ExitCode::is_error) {
return ExitCode::GeneralError;
}
ExitCode::Success
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn success_when_no_results() {
assert_eq!(merge_exitcodes([]), ExitCode::Success);
}
#[test]
fn general_error_if_at_least_one_error() {
assert_eq!(
merge_exitcodes([ExitCode::GeneralError]),
ExitCode::GeneralError
);
assert_eq!(
merge_exitcodes([ExitCode::KilledBySigint]),
ExitCode::GeneralError
);
assert_eq!(
merge_exitcodes([ExitCode::KilledBySigint, ExitCode::Success]),
ExitCode::GeneralError
);
assert_eq!(
merge_exitcodes([ExitCode::Success, ExitCode::GeneralError]),
ExitCode::GeneralError
);
assert_eq!(
merge_exitcodes([ExitCode::GeneralError, ExitCode::KilledBySigint]),
ExitCode::GeneralError
);
}
#[test]
fn success_if_no_error() {
assert_eq!(merge_exitcodes([ExitCode::Success]), ExitCode::Success);
assert_eq!(
merge_exitcodes([ExitCode::Success, ExitCode::Success]),
ExitCode::Success
);
}
}
+148
View File
@@ -0,0 +1,148 @@
use std::borrow::Cow;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::io;
#[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::{FileTypeExt, PermissionsExt};
use std::path::{Path, PathBuf};
use normpath::PathExt;
use crate::walk;
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
if path.is_absolute() {
return Ok(path.to_path_buf());
}
let path = path.strip_prefix(".").unwrap_or(path);
env::current_dir().map(|path_buf| path_buf.join(path))
}
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
let path_buf = path_absolute_form(path)?;
#[cfg(windows)]
let path_buf = Path::new(
path_buf
.as_path()
.to_string_lossy()
.trim_start_matches(r"\\?\"),
)
.to_path_buf();
Ok(path_buf)
}
pub fn is_existing_directory(path: &Path) -> bool {
// Note: we do not use `.exists()` here, as `.` always exists, even if
// the CWD has been deleted.
path.is_dir() && (path.file_name().is_some() || path.normalize().is_ok())
}
#[cfg(any(unix, target_os = "redox"))]
pub fn is_executable(md: &fs::Metadata) -> bool {
md.permissions().mode() & 0o111 != 0
}
#[cfg(windows)]
pub fn is_executable(_: &fs::Metadata) -> bool {
false
}
pub fn is_empty(entry: &walk::DirEntry) -> bool {
if let Some(file_type) = entry.file_type() {
if file_type.is_dir() {
if let Ok(mut entries) = fs::read_dir(entry.path()) {
entries.next().is_none()
} else {
false
}
} else if file_type.is_file() {
entry.metadata().map(|m| m.len() == 0).unwrap_or(false)
} else {
false
}
} else {
false
}
}
#[cfg(any(unix, target_os = "redox"))]
pub fn is_socket(ft: fs::FileType) -> bool {
ft.is_socket()
}
#[cfg(windows)]
pub fn is_socket(_: fs::FileType) -> bool {
false
}
#[cfg(any(unix, target_os = "redox"))]
pub fn is_pipe(ft: fs::FileType) -> bool {
ft.is_fifo()
}
#[cfg(windows)]
pub fn is_pipe(_: fs::FileType) -> bool {
false
}
#[cfg(any(unix, target_os = "redox"))]
pub fn osstr_to_bytes(input: &OsStr) -> Cow<[u8]> {
use std::os::unix::ffi::OsStrExt;
Cow::Borrowed(input.as_bytes())
}
#[cfg(windows)]
pub fn osstr_to_bytes(input: &OsStr) -> Cow<[u8]> {
let string = input.to_string_lossy();
match string {
Cow::Owned(string) => Cow::Owned(string.into_bytes()),
Cow::Borrowed(string) => Cow::Borrowed(string.as_bytes()),
}
}
/// Remove the `./` prefix from a path.
pub fn strip_current_dir(path: &Path) -> &Path {
path.strip_prefix(".").unwrap_or(path)
}
/// Default value for the path_separator, mainly for MSYS/MSYS2, which set the MSYSTEM
/// environment variable, and we set fd's path separator to '/' rather than Rust's default of '\'.
///
/// Returns Some to use a nonstandard path separator, or None to use rust's default on the target
/// platform.
pub fn default_path_separator() -> Option<String> {
if cfg!(windows) {
let msystem = env::var("MSYSTEM").ok()?;
match msystem.as_str() {
"MINGW64" | "MINGW32" | "MSYS" => Some("/".to_owned()),
_ => None,
}
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::strip_current_dir;
use std::path::Path;
#[test]
fn strip_current_dir_basic() {
assert_eq!(strip_current_dir(Path::new("./foo")), Path::new("foo"));
assert_eq!(strip_current_dir(Path::new("foo")), Path::new("foo"));
assert_eq!(
strip_current_dir(Path::new("./foo/bar/baz")),
Path::new("foo/bar/baz")
);
assert_eq!(
strip_current_dir(Path::new("foo/bar/baz")),
Path::new("foo/bar/baz")
);
}
}
+39
View File
@@ -0,0 +1,39 @@
use crate::filesystem;
use crate::walk;
/// Whether or not to show
#[derive(Default)]
pub struct FileTypes {
pub files: bool,
pub directories: bool,
pub symlinks: bool,
pub sockets: bool,
pub pipes: bool,
pub executables_only: bool,
pub empty_only: bool,
}
impl FileTypes {
pub fn should_ignore(&self, entry: &walk::DirEntry) -> bool {
if let Some(ref entry_type) = entry.file_type() {
(!self.files && entry_type.is_file())
|| (!self.directories && entry_type.is_dir())
|| (!self.symlinks && entry_type.is_symlink())
|| (!self.sockets && filesystem::is_socket(*entry_type))
|| (!self.pipes && filesystem::is_pipe(*entry_type))
|| (self.executables_only
&& !entry
.metadata()
.map(filesystem::is_executable)
.unwrap_or(false))
|| (self.empty_only && !filesystem::is_empty(entry))
|| !(entry_type.is_file()
|| entry_type.is_dir()
|| entry_type.is_symlink()
|| filesystem::is_socket(*entry_type)
|| filesystem::is_pipe(*entry_type))
} else {
true
}
}
}
@@ -1,5 +1,11 @@
pub use self::size::SizeFilter;
pub use self::time::TimeFilter;
#[cfg(unix)]
pub use self::owner::OwnerFilter;
mod size;
mod time;
#[cfg(unix)]
mod owner;
+123
View File
@@ -0,0 +1,123 @@
use anyhow::{anyhow, Result};
use std::fs;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OwnerFilter {
uid: Check<u32>,
gid: Check<u32>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Check<T> {
Equal(T),
NotEq(T),
Ignore,
}
impl OwnerFilter {
/// Parses an owner constraint
/// Returns an error if the string is invalid
/// Returns Ok(None) when string is acceptable but a noop (such as "" or ":")
pub fn from_string(input: &str) -> Result<Option<Self>> {
let mut it = input.split(':');
let (fst, snd) = (it.next(), it.next());
if it.next().is_some() {
return Err(anyhow!(
"more than one ':' present in owner string '{}'. See 'fd --help'.",
input
));
}
let uid = Check::parse(fst, |s| {
s.parse()
.ok()
.or_else(|| users::get_user_by_name(s).map(|user| user.uid()))
.ok_or_else(|| anyhow!("'{}' is not a recognized user name", s))
})?;
let gid = Check::parse(snd, |s| {
s.parse()
.ok()
.or_else(|| users::get_group_by_name(s).map(|group| group.gid()))
.ok_or_else(|| anyhow!("'{}' is not a recognized group name", s))
})?;
if let (Check::Ignore, Check::Ignore) = (uid, gid) {
Ok(None)
} else {
Ok(Some(OwnerFilter { uid, gid }))
}
}
pub fn matches(&self, md: &fs::Metadata) -> bool {
use std::os::unix::fs::MetadataExt;
self.uid.check(md.uid()) && self.gid.check(md.gid())
}
}
impl<T: PartialEq> Check<T> {
fn check(&self, v: T) -> bool {
match self {
Check::Equal(x) => v == *x,
Check::NotEq(x) => v != *x,
Check::Ignore => true,
}
}
fn parse<F>(s: Option<&str>, f: F) -> Result<Self>
where
F: Fn(&str) -> Result<T>,
{
let (s, equality) = match s {
Some("") | None => return Ok(Check::Ignore),
Some(s) if s.starts_with('!') => (&s[1..], false),
Some(s) => (s, true),
};
f(s).map(|x| {
if equality {
Check::Equal(x)
} else {
Check::NotEq(x)
}
})
}
}
#[cfg(test)]
mod owner_parsing {
use super::OwnerFilter;
macro_rules! owner_tests {
($($name:ident: $value:expr => $result:pat,)*) => {
$(
#[test]
fn $name() {
let o = OwnerFilter::from_string($value);
match o {
$result => {},
_ => panic!("{:?} does not match {}", o, stringify!($result)),
}
}
)*
};
}
use super::Check::*;
owner_tests! {
empty: "" => Ok(None),
uid_only: "5" => Ok(Some(OwnerFilter { uid: Equal(5), gid: Ignore })),
uid_gid: "9:3" => Ok(Some(OwnerFilter { uid: Equal(9), gid: Equal(3) })),
gid_only: ":8" => Ok(Some(OwnerFilter { uid: Ignore, gid: Equal(8) })),
colon_only: ":" => Ok(None),
trailing: "5:" => Ok(Some(OwnerFilter { uid: Equal(5), gid: Ignore })),
uid_negate: "!5" => Ok(Some(OwnerFilter { uid: NotEq(5), gid: Ignore })),
both_negate:"!4:!3" => Ok(Some(OwnerFilter { uid: NotEq(4), gid: NotEq(3) })),
uid_not_gid:"6:!8" => Ok(Some(OwnerFilter { uid: Equal(6), gid: NotEq(8) })),
more_colons:"3:5:" => Err(_),
only_colons:"::" => Err(_),
}
}
+211
View File
@@ -0,0 +1,211 @@
use once_cell::sync::Lazy;
use regex::Regex;
static SIZE_CAPTURES: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i)^([+-]?)(\d+)(b|[kmgt]i?b?)$").unwrap());
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SizeFilter {
Max(u64),
Min(u64),
Equals(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;
match limit_kind {
"+" => Some(SizeFilter::Min(size)),
"-" => Some(SizeFilter::Max(size)),
"" => Some(SizeFilter::Equals(size)),
_ => None,
}
}
pub fn is_within(&self, size: u64) -> bool {
match *self {
SizeFilter::Max(limit) => size <= limit,
SizeFilter::Min(limit) => size >= limit,
SizeFilter::Equals(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(100_000)),
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(10_240)),
kibi_plus_suffix: ("+1kib", SizeFilter::Min(1024)),
kibi_minus: ("-1ki", SizeFilter::Max(1024)),
kibi_minus_multiplier: ("-100ki", SizeFilter::Max(102_400)),
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(1_000_000)),
mega_plus_suffix: ("+1mb", SizeFilter::Min(1_000_000)),
mega_minus: ("-1m", SizeFilter::Max(1_000_000)),
mega_minus_suffix: ("-1mb", SizeFilter::Max(1_000_000)),
mega_plus_upper: ("+1M", SizeFilter::Min(1_000_000)),
mega_plus_suffix_upper: ("+1MB", SizeFilter::Min(1_000_000)),
mega_minus_upper: ("-1M", SizeFilter::Max(1_000_000)),
mega_minus_suffix_upper: ("-1Mb", SizeFilter::Max(1_000_000)),
mebi_plus: ("+1mi", SizeFilter::Min(1_048_576)),
mebi_plus_suffix: ("+1mib", SizeFilter::Min(1_048_576)),
mebi_minus: ("-1mi", SizeFilter::Max(1_048_576)),
mebi_minus_suffix: ("-1mib", SizeFilter::Max(1_048_576)),
mebi_plus_upper: ("+1MI", SizeFilter::Min(1_048_576)),
mebi_plus_suffix_upper: ("+1MiB", SizeFilter::Min(1_048_576)),
mebi_minus_upper: ("-1Mi", SizeFilter::Max(1_048_576)),
mebi_minus_suffix_upper: ("-1MIB", SizeFilter::Max(1_048_576)),
giga_plus: ("+1g", SizeFilter::Min(1_000_000_000)),
giga_plus_suffix: ("+1gb", SizeFilter::Min(1_000_000_000)),
giga_minus: ("-1g", SizeFilter::Max(1_000_000_000)),
giga_minus_suffix: ("-1gb", SizeFilter::Max(1_000_000_000)),
giga_plus_upper: ("+1G", SizeFilter::Min(1_000_000_000)),
giga_plus_suffix_upper: ("+1GB", SizeFilter::Min(1_000_000_000)),
giga_minus_upper: ("-1G", SizeFilter::Max(1_000_000_000)),
giga_minus_suffix_upper: ("-1Gb", SizeFilter::Max(1_000_000_000)),
gibi_plus: ("+1gi", SizeFilter::Min(1_073_741_824)),
gibi_plus_suffix: ("+1gib", SizeFilter::Min(1_073_741_824)),
gibi_minus: ("-1gi", SizeFilter::Max(1_073_741_824)),
gibi_minus_suffix: ("-1gib", SizeFilter::Max(1_073_741_824)),
gibi_plus_upper: ("+1GI", SizeFilter::Min(1_073_741_824)),
gibi_plus_suffix_upper: ("+1GiB", SizeFilter::Min(1_073_741_824)),
gibi_minus_upper: ("-1Gi", SizeFilter::Max(1_073_741_824)),
gibi_minus_suffix_upper: ("-1GIB", SizeFilter::Max(1_073_741_824)),
tera_plus: ("+1t", SizeFilter::Min(1_000_000_000_000)),
tera_plus_suffix: ("+1tb", SizeFilter::Min(1_000_000_000_000)),
tera_minus: ("-1t", SizeFilter::Max(1_000_000_000_000)),
tera_minus_suffix: ("-1tb", SizeFilter::Max(1_000_000_000_000)),
tera_plus_upper: ("+1T", SizeFilter::Min(1_000_000_000_000)),
tera_plus_suffix_upper: ("+1TB", SizeFilter::Min(1_000_000_000_000)),
tera_minus_upper: ("-1T", SizeFilter::Max(1_000_000_000_000)),
tera_minus_suffix_upper: ("-1Tb", SizeFilter::Max(1_000_000_000_000)),
tebi_plus: ("+1ti", SizeFilter::Min(1_099_511_627_776)),
tebi_plus_suffix: ("+1tib", SizeFilter::Min(1_099_511_627_776)),
tebi_minus: ("-1ti", SizeFilter::Max(1_099_511_627_776)),
tebi_minus_suffix: ("-1tib", SizeFilter::Max(1_099_511_627_776)),
tebi_plus_upper: ("+1TI", SizeFilter::Min(1_099_511_627_776)),
tebi_plus_suffix_upper: ("+1TiB", SizeFilter::Min(1_099_511_627_776)),
tebi_minus_upper: ("-1Ti", SizeFilter::Max(1_099_511_627_776)),
tebi_minus_suffix_upper: ("-1TIB", SizeFilter::Max(1_099_511_627_776)),
}
/// 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_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));
}
}
@@ -1,3 +1,5 @@
use chrono::{offset::TimeZone, DateTime, Local, NaiveDate};
use std::time::SystemTime;
/// Filter based on time ranges.
@@ -9,11 +11,22 @@ pub enum TimeFilter {
impl TimeFilter {
fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
use humantime;
humantime::parse_duration(s)
.map(|duration| *ref_time - duration)
.or_else(|_| humantime::parse_rfc3339_weak(s))
.ok()
.or_else(|| {
DateTime::parse_from_rfc3339(s)
.map(|dt| dt.into())
.ok()
.or_else(|| {
NaiveDate::parse_from_str(s, "%F")
.map(|nd| nd.and_hms(0, 0, 0))
.ok()
.and_then(|ndt| Local.from_local_datetime(&ndt).single())
})
.or_else(|| Local.datetime_from_str(s, "%F %T").ok())
.map(|dt| dt.into())
})
}
pub fn before(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
@@ -26,8 +39,8 @@ impl TimeFilter {
pub fn applies_to(&self, t: &SystemTime) -> bool {
match self {
TimeFilter::Before(limit) => t <= limit,
TimeFilter::After(limit) => t >= limit,
TimeFilter::Before(limit) => t < limit,
TimeFilter::After(limit) => t > limit,
}
}
}
@@ -39,9 +52,11 @@ mod tests {
#[test]
fn is_time_filter_applicable() {
use humantime;
let ref_time = Local
.datetime_from_str("2010-10-10 10:10:10", "%F %T")
.unwrap()
.into();
let ref_time = humantime::parse_rfc3339("2010-10-10T10:10:10Z").unwrap();
assert!(TimeFilter::after(&ref_time, "1min")
.unwrap()
.applies_to(&ref_time));
@@ -78,5 +93,39 @@ mod tests {
assert!(!TimeFilter::after(&ref_time, t10s_before)
.unwrap()
.applies_to(&t1m_ago));
let same_day = "2010-10-10";
assert!(!TimeFilter::before(&ref_time, same_day)
.unwrap()
.applies_to(&ref_time));
assert!(!TimeFilter::before(&ref_time, same_day)
.unwrap()
.applies_to(&t1m_ago));
assert!(TimeFilter::after(&ref_time, same_day)
.unwrap()
.applies_to(&ref_time));
assert!(TimeFilter::after(&ref_time, same_day)
.unwrap()
.applies_to(&t1m_ago));
let ref_time = DateTime::parse_from_rfc3339("2010-10-10T10:10:10+00:00")
.unwrap()
.into();
let t1m_ago = ref_time - Duration::from_secs(60);
let t10s_before = "2010-10-10T10:10:00+00: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));
}
}
-74
View File
@@ -1,74 +0,0 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::env::current_dir;
use std::fs;
use std::io;
#[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use ignore::DirEntry;
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
if path.is_absolute() {
return Ok(path.to_path_buf());
}
let path = path.strip_prefix(".").unwrap_or(path);
current_dir().map(|path_buf| path_buf.join(path))
}
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
let path_buf = path_absolute_form(path)?;
#[cfg(windows)]
let path_buf = Path::new(
path_buf
.as_path()
.to_string_lossy()
.trim_left_matches(r"\\?\"),
)
.to_path_buf();
Ok(path_buf)
}
// Path::is_dir() is not guaranteed to be intuitively correct for "." and ".."
// See: https://github.com/rust-lang/rust/issues/45302
pub fn is_dir(path: &Path) -> bool {
path.is_dir() && (path.file_name().is_some() || path.canonicalize().is_ok())
}
#[cfg(any(unix, target_os = "redox"))]
pub fn is_executable(md: &fs::Metadata) -> bool {
md.permissions().mode() & 0o111 != 0
}
#[cfg(windows)]
pub fn is_executable(_: &fs::Metadata) -> bool {
false
}
pub fn is_empty(entry: &DirEntry) -> bool {
if let Some(file_type) = entry.file_type() {
if file_type.is_dir() {
if let Ok(mut entries) = fs::read_dir(entry.path()) {
entries.next().is_none()
} else {
false
}
} else if file_type.is_file() {
entry.metadata().map(|m| m.len() == 0).unwrap_or(false)
} else {
false
}
} else {
false
}
}
-20
View File
@@ -1,20 +0,0 @@
/// 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,
}
}
}
-208
View File
@@ -1,208 +0,0 @@
use regex::Regex;
lazy_static! {
static ref SIZE_CAPTURES: Regex = { Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap() };
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SizeFilter {
Max(u64),
Min(u64),
}
// SI prefixes (powers of 10)
const KILO: u64 = 1000;
const MEGA: u64 = KILO * 1000;
const GIGA: u64 = MEGA * 1000;
const TERA: u64 = GIGA * 1000;
// Binary prefixes (powers of 2)
const KIBI: u64 = 1024;
const MEBI: u64 = KIBI * 1024;
const GIBI: u64 = MEBI * 1024;
const TEBI: u64 = GIBI * 1024;
impl SizeFilter {
pub fn from_string<'a>(s: &str) -> Option<Self> {
if !SIZE_CAPTURES.is_match(s) {
return None;
}
let captures = SIZE_CAPTURES.captures(s)?;
let limit_kind = captures.get(1).map_or("+", |m| m.as_str());
let quantity = captures
.get(2)
.and_then(|v| v.as_str().parse::<u64>().ok())?;
let multiplier = match &captures.get(3).map_or("b", |m| m.as_str()).to_lowercase()[..] {
v if v.starts_with("ki") => KIBI,
v if v.starts_with("k") => KILO,
v if v.starts_with("mi") => MEBI,
v if v.starts_with("m") => MEGA,
v if v.starts_with("gi") => GIBI,
v if v.starts_with("g") => GIGA,
v if v.starts_with("ti") => TEBI,
v if v.starts_with("t") => TERA,
"b" => 1,
_ => return None,
};
let size = quantity * multiplier;
Some(match limit_kind {
"+" => SizeFilter::Min(size),
_ => SizeFilter::Max(size),
})
}
pub fn is_within(&self, size: u64) -> bool {
match self {
&SizeFilter::Max(limit) => size <= limit,
&SizeFilter::Min(limit) => size >= limit,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! gen_size_filter_parse_test {
($($name: ident: $val: expr,)*) => {
$(
#[test]
fn $name() {
let (txt, expected) = $val;
let actual = SizeFilter::from_string(txt).unwrap();
assert_eq!(actual, expected);
}
)*
};
}
/// Parsing and size conversion tests data. Ensure that each type gets properly interpreted.
/// Call with higher base values to ensure expected multiplication (only need a couple)
gen_size_filter_parse_test! {
byte_plus: ("+1b", SizeFilter::Min(1)),
byte_plus_multiplier: ("+10b", SizeFilter::Min(10)),
byte_minus: ("-1b", SizeFilter::Max(1)),
kilo_plus: ("+1k", SizeFilter::Min(1000)),
kilo_plus_suffix: ("+1kb", SizeFilter::Min(1000)),
kilo_minus: ("-1k", SizeFilter::Max(1000)),
kilo_minus_multiplier: ("-100k", SizeFilter::Max(100000)),
kilo_minus_suffix: ("-1kb", SizeFilter::Max(1000)),
kilo_plus_upper: ("+1K", SizeFilter::Min(1000)),
kilo_plus_suffix_upper: ("+1KB", SizeFilter::Min(1000)),
kilo_minus_upper: ("-1K", SizeFilter::Max(1000)),
kilo_minus_suffix_upper: ("-1Kb", SizeFilter::Max(1000)),
kibi_plus: ("+1ki", SizeFilter::Min(1024)),
kibi_plus_multiplier: ("+10ki", SizeFilter::Min(10240)),
kibi_plus_suffix: ("+1kib", SizeFilter::Min(1024)),
kibi_minus: ("-1ki", SizeFilter::Max(1024)),
kibi_minus_multiplier: ("-100ki", SizeFilter::Max(102400)),
kibi_minus_suffix: ("-1kib", SizeFilter::Max(1024)),
kibi_plus_upper: ("+1KI", SizeFilter::Min(1024)),
kibi_plus_suffix_upper: ("+1KiB", SizeFilter::Min(1024)),
kibi_minus_upper: ("-1Ki", SizeFilter::Max(1024)),
kibi_minus_suffix_upper: ("-1KIB", SizeFilter::Max(1024)),
mega_plus: ("+1m", SizeFilter::Min(1000000)),
mega_plus_suffix: ("+1mb", SizeFilter::Min(1000000)),
mega_minus: ("-1m", SizeFilter::Max(1000000)),
mega_minus_suffix: ("-1mb", SizeFilter::Max(1000000)),
mega_plus_upper: ("+1M", SizeFilter::Min(1000000)),
mega_plus_suffix_upper: ("+1MB", SizeFilter::Min(1000000)),
mega_minus_upper: ("-1M", SizeFilter::Max(1000000)),
mega_minus_suffix_upper: ("-1Mb", SizeFilter::Max(1000000)),
mebi_plus: ("+1mi", SizeFilter::Min(1048576)),
mebi_plus_suffix: ("+1mib", SizeFilter::Min(1048576)),
mebi_minus: ("-1mi", SizeFilter::Max(1048576)),
mebi_minus_suffix: ("-1mib", SizeFilter::Max(1048576)),
mebi_plus_upper: ("+1MI", SizeFilter::Min(1048576)),
mebi_plus_suffix_upper: ("+1MiB", SizeFilter::Min(1048576)),
mebi_minus_upper: ("-1Mi", SizeFilter::Max(1048576)),
mebi_minus_suffix_upper: ("-1MIB", SizeFilter::Max(1048576)),
giga_plus: ("+1g", SizeFilter::Min(1000000000)),
giga_plus_suffix: ("+1gb", SizeFilter::Min(1000000000)),
giga_minus: ("-1g", SizeFilter::Max(1000000000)),
giga_minus_suffix: ("-1gb", SizeFilter::Max(1000000000)),
giga_plus_upper: ("+1G", SizeFilter::Min(1000000000)),
giga_plus_suffix_upper: ("+1GB", SizeFilter::Min(1000000000)),
giga_minus_upper: ("-1G", SizeFilter::Max(1000000000)),
giga_minus_suffix_upper: ("-1Gb", SizeFilter::Max(1000000000)),
gibi_plus: ("+1gi", SizeFilter::Min(1073741824)),
gibi_plus_suffix: ("+1gib", SizeFilter::Min(1073741824)),
gibi_minus: ("-1gi", SizeFilter::Max(1073741824)),
gibi_minus_suffix: ("-1gib", SizeFilter::Max(1073741824)),
gibi_plus_upper: ("+1GI", SizeFilter::Min(1073741824)),
gibi_plus_suffix_upper: ("+1GiB", SizeFilter::Min(1073741824)),
gibi_minus_upper: ("-1Gi", SizeFilter::Max(1073741824)),
gibi_minus_suffix_upper: ("-1GIB", SizeFilter::Max(1073741824)),
tera_plus: ("+1t", SizeFilter::Min(1000000000000)),
tera_plus_suffix: ("+1tb", SizeFilter::Min(1000000000000)),
tera_minus: ("-1t", SizeFilter::Max(1000000000000)),
tera_minus_suffix: ("-1tb", SizeFilter::Max(1000000000000)),
tera_plus_upper: ("+1T", SizeFilter::Min(1000000000000)),
tera_plus_suffix_upper: ("+1TB", SizeFilter::Min(1000000000000)),
tera_minus_upper: ("-1T", SizeFilter::Max(1000000000000)),
tera_minus_suffix_upper: ("-1Tb", SizeFilter::Max(1000000000000)),
tebi_plus: ("+1ti", SizeFilter::Min(1099511627776)),
tebi_plus_suffix: ("+1tib", SizeFilter::Min(1099511627776)),
tebi_minus: ("-1ti", SizeFilter::Max(1099511627776)),
tebi_minus_suffix: ("-1tib", SizeFilter::Max(1099511627776)),
tebi_plus_upper: ("+1TI", SizeFilter::Min(1099511627776)),
tebi_plus_suffix_upper: ("+1TiB", SizeFilter::Min(1099511627776)),
tebi_minus_upper: ("-1Ti", SizeFilter::Max(1099511627776)),
tebi_minus_suffix_upper: ("-1TIB", SizeFilter::Max(1099511627776)),
}
/// Invalid parse testing
macro_rules! gen_size_filter_failure {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
let i = SizeFilter::from_string($value);
assert!(i.is_none());
}
)*
};
}
/// Invalid parse data
gen_size_filter_failure! {
ensure_missing_symbol_returns_none: "10M",
ensure_missing_number_returns_none: "+g",
ensure_missing_unit_returns_none: "+18",
ensure_bad_format_returns_none_1: "$10M",
ensure_bad_format_returns_none_2: "badval",
ensure_bad_format_returns_none_3: "9999",
ensure_invalid_unit_returns_none_1: "+50a",
ensure_invalid_unit_returns_none_2: "-10v",
ensure_invalid_unit_returns_none_3: "+1Mv",
ensure_bib_format_returns_none: "+1bib",
ensure_bb_format_returns_none: "+1bb",
}
#[test]
fn is_within_less_than() {
let f = SizeFilter::from_string("-1k").unwrap();
assert!(f.is_within(999));
}
#[test]
fn is_within_less_than_equal() {
let f = SizeFilter::from_string("-1k").unwrap();
assert!(f.is_within(1000));
}
#[test]
fn is_within_greater_than() {
let f = SizeFilter::from_string("+1k").unwrap();
assert!(f.is_within(1001));
}
#[test]
fn is_within_greater_than_equal() {
let f = SizeFilter::from_string("+1K").unwrap();
assert!(f.is_within(1000));
}
}
-173
View File
@@ -1,173 +0,0 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use regex_syntax::hir::Hir;
use regex_syntax::Parser;
use std::ffi::OsString;
pub use self::file_types::FileTypes;
mod file_types;
pub mod filter;
pub mod opts;
macro_rules! print_error {
($($arg:tt)*) => (eprintln!("[fd error]: {}", format!($($arg)*)))
}
macro_rules! print_error_and_exit {
($($arg:tt)*) => {
print_error!($($arg)*);
::std::process::exit(1);
};
}
/// Determine if a regex pattern contains a literal uppercase character.
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
Parser::new()
.parse(pattern)
.map(|hir| hir_has_uppercase_char(&hir))
.unwrap_or(false)
}
/// Determine if a regex expression contains a literal uppercase character.
fn hir_has_uppercase_char(hir: &Hir) -> bool {
use regex_syntax::hir::*;
match *hir.kind() {
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
HirKind::Class(Class::Unicode(ref ranges)) => ranges
.iter()
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
hir_has_uppercase_char(hir)
}
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
hirs.iter().any(hir_has_uppercase_char)
}
_ => false,
}
}
/// Maximum size of the output buffer before flushing results to the console
pub const MAX_BUFFER_LENGTH: usize = 1000;
/// Traverse args_os, looking for -exec and replacing it with --exec.
///
/// # Returns
///
/// * The args, with substitution if required
pub fn transform_args_with_exec<I>(original: I) -> Vec<OsString>
where
I: Iterator<Item = OsString>,
{
let mut in_exec_opt = false;
let target = OsString::from("-exec");
let long_start = OsString::from("--exec");
let short_start = OsString::from("-x");
let exec_end = OsString::from(";");
original.fold(vec![], |mut args, curr| {
if in_exec_opt {
if curr == exec_end {
in_exec_opt = false;
}
args.push(curr);
return args;
}
if curr == target || curr == long_start || curr == short_start {
args.push(if curr == target {
OsString::from("--exec")
} else {
curr
});
in_exec_opt = true;
} else {
args.push(curr);
}
args
})
}
#[cfg(test)]
mod tests {
use super::*;
fn oss_vec(strs: &[&str]) -> Vec<OsString> {
strs.into_iter().map(OsString::from).collect()
}
/// Ensure that -exec gets transformed into --exec
#[test]
fn normal_exec_substitution() {
let original = oss_vec(&["fd", "foo", "-exec", "cmd"]);
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
/// Ensure that --exec is not touched
#[test]
fn passthru_of_original_exec() {
let original = oss_vec(&["fd", "foo", "--exec", "cmd"]);
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
#[test]
fn temp_check_that_exec_context_observed() {
let original = oss_vec(&[
"fd",
"foo",
"-exec",
"cmd",
"-exec",
"ls",
";",
"-exec",
"rm",
";",
"--exec",
"find",
"-exec",
"rm",
";",
"-x",
"foo",
"-exec",
"something",
";",
"-exec",
]);
let expected = oss_vec(&[
"fd",
"foo",
"--exec",
"cmd",
"-exec",
"ls",
";",
"--exec",
"rm",
";",
"--exec",
"find",
"-exec",
"rm",
";",
"-x",
"foo",
"-exec",
"something",
";",
"--exec",
]);
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
}
-236
View File
@@ -1,236 +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 ansi_term::{Colour, Style};
/// A parser for the `LS_COLORS` environment variable.
use std::collections::HashMap;
/// Maps file extensions to ANSI colors / styles.
pub type ExtensionStyles = HashMap<String, Style>;
/// 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
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_simple() {
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31"));
}
#[test]
fn test_parse_decoration() {
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("00;31"));
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("03;34"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("01;36"));
}
#[test]
fn test_parse_decoration_backwards() {
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("34;03"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("36;01"));
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31;00"));
}
#[test]
fn test_parse_256() {
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("38;5;115")
);
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("00;38;5;115")
);
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("01;38;5;119")
);
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("38;5;119;01")
);
}
#[test]
fn test_from_string() {
assert_eq!(LsColors::default(), LsColors::from_string(&String::new()));
let result = LsColors::from_string(&String::from(
"rs=0:di=03;34:ln=01;36:*.foo=01;35:*README=33",
));
assert_eq!(Colour::Blue.italic(), result.directory);
assert_eq!(Colour::Cyan.bold(), result.symlink);
assert_eq!(Some(&Colour::Purple.bold()), result.extensions.get("foo"));
assert_eq!(
Some(&Colour::Yellow.normal()),
result.filenames.get("README")
);
}
}
+457 -162
View File
@@ -1,180 +1,258 @@
// 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 ansi_term;
extern crate atty;
#[macro_use]
extern crate clap;
extern crate ignore;
#[macro_use]
extern crate lazy_static;
extern crate humantime;
#[cfg(all(unix, not(target_os = "redox")))]
extern crate libc;
extern crate num_cpus;
extern crate regex;
extern crate regex_syntax;
#[macro_use]
mod internal;
mod app;
mod config;
mod error;
mod exec;
mod exit_codes;
pub mod fshelper;
pub mod lscolors;
mod filesystem;
mod filetypes;
mod filter;
mod output;
mod regex_helper;
mod walk;
use std::env;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time;
use anyhow::{anyhow, Context, Result};
use atty::Stream;
use regex::{RegexBuilder, RegexSetBuilder};
use exec::CommandTemplate;
use internal::{
filter::{SizeFilter, TimeFilter},
opts::FdOptions,
pattern_has_uppercase_char, transform_args_with_exec, FileTypes,
};
use globset::GlobBuilder;
use lscolors::LsColors;
use normpath::PathExt;
use regex::bytes::{RegexBuilder, RegexSetBuilder};
use crate::config::Config;
use crate::error::print_error;
use crate::exec::CommandTemplate;
use crate::exit_codes::ExitCode;
use crate::filetypes::FileTypes;
#[cfg(unix)]
use crate::filter::OwnerFilter;
use crate::filter::{SizeFilter, TimeFilter};
use crate::regex_helper::{pattern_has_uppercase_char, pattern_matches_strings_with_leading_dot};
// We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481
// FIXME: re-enable jemalloc on macOS, see comment in Cargo.toml file for more infos
#[cfg(all(
not(windows),
not(target_os = "android"),
not(target_os = "macos"),
not(target_os = "freebsd"),
not(target_env = "musl"),
not(target_arch = "riscv64"),
feature = "use-jemalloc"
))]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
// vivid --color-mode 8-bit generate molokai
const DEFAULT_LS_COLORS: &str = "
ow=0:or=0;38;5;16;48;5;203:no=0:ex=1;38;5;203:cd=0;38;5;203;48;5;236:mi=0;38;5;16;48;5;203:*~=0;38;5;243:st=0:pi=0;38;5;16;48;5;81:fi=0:di=0;38;5;81:so=0;38;5;16;48;5;203:bd=0;38;5;81;48;5;236:tw=0:ln=0;38;5;203:*.m=0;38;5;48:*.o=0;38;5;243:*.z=4;38;5;203:*.a=1;38;5;203:*.r=0;38;5;48:*.c=0;38;5;48:*.d=0;38;5;48:*.t=0;38;5;48:*.h=0;38;5;48:*.p=0;38;5;48:*.cc=0;38;5;48:*.ll=0;38;5;48:*.jl=0;38;5;48:*css=0;38;5;48:*.md=0;38;5;185:*.gz=4;38;5;203:*.nb=0;38;5;48:*.mn=0;38;5;48:*.go=0;38;5;48:*.xz=4;38;5;203:*.so=1;38;5;203:*.rb=0;38;5;48:*.pm=0;38;5;48:*.bc=0;38;5;243:*.py=0;38;5;48:*.as=0;38;5;48:*.pl=0;38;5;48:*.rs=0;38;5;48:*.sh=0;38;5;48:*.7z=4;38;5;203:*.ps=0;38;5;186:*.cs=0;38;5;48:*.el=0;38;5;48:*.rm=0;38;5;208:*.hs=0;38;5;48:*.td=0;38;5;48:*.ui=0;38;5;149:*.ex=0;38;5;48:*.js=0;38;5;48:*.cp=0;38;5;48:*.cr=0;38;5;48:*.la=0;38;5;243:*.kt=0;38;5;48:*.ml=0;38;5;48:*.vb=0;38;5;48:*.gv=0;38;5;48:*.lo=0;38;5;243:*.hi=0;38;5;243:*.ts=0;38;5;48:*.ko=1;38;5;203:*.hh=0;38;5;48:*.pp=0;38;5;48:*.di=0;38;5;48:*.bz=4;38;5;203:*.fs=0;38;5;48:*.png=0;38;5;208:*.zsh=0;38;5;48:*.mpg=0;38;5;208:*.pid=0;38;5;243:*.xmp=0;38;5;149:*.iso=4;38;5;203:*.m4v=0;38;5;208:*.dot=0;38;5;48:*.ods=0;38;5;186:*.inc=0;38;5;48:*.sxw=0;38;5;186:*.aif=0;38;5;208:*.git=0;38;5;243:*.gvy=0;38;5;48:*.tbz=4;38;5;203:*.log=0;38;5;243:*.txt=0;38;5;185:*.ico=0;38;5;208:*.csx=0;38;5;48:*.vob=0;38;5;208:*.pgm=0;38;5;208:*.pps=0;38;5;186:*.ics=0;38;5;186:*.img=4;38;5;203:*.fon=0;38;5;208:*.hpp=0;38;5;48:*.bsh=0;38;5;48:*.sql=0;38;5;48:*TODO=1:*.php=0;38;5;48:*.pkg=4;38;5;203:*.ps1=0;38;5;48:*.csv=0;38;5;185:*.ilg=0;38;5;243:*.ini=0;38;5;149:*.pyc=0;38;5;243:*.psd=0;38;5;208:*.htc=0;38;5;48:*.swp=0;38;5;243:*.mli=0;38;5;48:*hgrc=0;38;5;149:*.bst=0;38;5;149:*.ipp=0;38;5;48:*.fsi=0;38;5;48:*.tcl=0;38;5;48:*.exs=0;38;5;48:*.out=0;38;5;243:*.jar=4;38;5;203:*.xls=0;38;5;186:*.ppm=0;38;5;208:*.apk=4;38;5;203:*.aux=0;38;5;243:*.rpm=4;38;5;203:*.dll=1;38;5;203:*.eps=0;38;5;208:*.exe=1;38;5;203:*.doc=0;38;5;186:*.wma=0;38;5;208:*.deb=4;38;5;203:*.pod=0;38;5;48:*.ind=0;38;5;243:*.nix=0;38;5;149:*.lua=0;38;5;48:*.epp=0;38;5;48:*.dpr=0;38;5;48:*.htm=0;38;5;185:*.ogg=0;38;5;208:*.bin=4;38;5;203:*.otf=0;38;5;208:*.yml=0;38;5;149:*.pro=0;38;5;149:*.cxx=0;38;5;48:*.tex=0;38;5;48:*.fnt=0;38;5;208:*.erl=0;38;5;48:*.sty=0;38;5;243:*.bag=4;38;5;203:*.rst=0;38;5;185:*.pdf=0;38;5;186:*.pbm=0;38;5;208:*.xcf=0;38;5;208:*.clj=0;38;5;48:*.gif=0;38;5;208:*.rar=4;38;5;203:*.elm=0;38;5;48:*.bib=0;38;5;149:*.tsx=0;38;5;48:*.dmg=4;38;5;203:*.tmp=0;38;5;243:*.bcf=0;38;5;243:*.mkv=0;38;5;208:*.svg=0;38;5;208:*.cpp=0;38;5;48:*.vim=0;38;5;48:*.bmp=0;38;5;208:*.ltx=0;38;5;48:*.fls=0;38;5;243:*.flv=0;38;5;208:*.wav=0;38;5;208:*.m4a=0;38;5;208:*.mid=0;38;5;208:*.hxx=0;38;5;48:*.pas=0;38;5;48:*.wmv=0;38;5;208:*.tif=0;38;5;208:*.kex=0;38;5;186:*.mp4=0;38;5;208:*.bak=0;38;5;243:*.xlr=0;38;5;186:*.dox=0;38;5;149:*.swf=0;38;5;208:*.tar=4;38;5;203:*.tgz=4;38;5;203:*.cfg=0;38;5;149:*.xml=0;
38;5;185:*.jpg=0;38;5;208:*.mir=0;38;5;48:*.sxi=0;38;5;186:*.bz2=4;38;5;203:*.odt=0;38;5;186:*.mov=0;38;5;208:*.toc=0;38;5;243:*.bat=1;38;5;203:*.asa=0;38;5;48:*.awk=0;38;5;48:*.sbt=0;38;5;48:*.vcd=4;38;5;203:*.kts=0;38;5;48:*.arj=4;38;5;203:*.blg=0;38;5;243:*.c++=0;38;5;48:*.odp=0;38;5;186:*.bbl=0;38;5;243:*.idx=0;38;5;243:*.com=1;38;5;203:*.mp3=0;38;5;208:*.avi=0;38;5;208:*.def=0;38;5;48:*.cgi=0;38;5;48:*.zip=4;38;5;203:*.ttf=0;38;5;208:*.ppt=0;38;5;186:*.tml=0;38;5;149:*.fsx=0;38;5;48:*.h++=0;38;5;48:*.rtf=0;38;5;186:*.inl=0;38;5;48:*.yaml=0;38;5;149:*.html=0;38;5;185:*.mpeg=0;38;5;208:*.java=0;38;5;48:*.hgrc=0;38;5;149:*.orig=0;38;5;243:*.conf=0;38;5;149:*.dart=0;38;5;48:*.psm1=0;38;5;48:*.rlib=0;38;5;243:*.fish=0;38;5;48:*.bash=0;38;5;48:*.make=0;38;5;149:*.docx=0;38;5;186:*.json=0;38;5;149:*.psd1=0;38;5;48:*.lisp=0;38;5;48:*.tbz2=4;38;5;203:*.diff=0;38;5;48:*.epub=0;38;5;186:*.xlsx=0;38;5;186:*.pptx=0;38;5;186:*.toml=0;38;5;149:*.h264=0;38;5;208:*.purs=0;38;5;48:*.flac=0;38;5;208:*.tiff=0;38;5;208:*.jpeg=0;38;5;208:*.lock=0;38;5;243:*.less=0;38;5;48:*.dyn_o=0;38;5;243:*.scala=0;38;5;48:*.mdown=0;38;5;185:*.shtml=0;38;5;185:*.class=0;38;5;243:*.cache=0;38;5;243:*.cmake=0;38;5;149:*passwd=0;38;5;149:*.swift=0;38;5;48:*shadow=0;38;5;149:*.xhtml=0;38;5;185:*.patch=0;38;5;48:*.cabal=0;38;5;48:*README=0;38;5;16;48;5;186:*.toast=4;38;5;203:*.ipynb=0;38;5;48:*COPYING=0;38;5;249:*.gradle=0;38;5;48:*.matlab=0;38;5;48:*.config=0;38;5;149:*LICENSE=0;38;5;249:*.dyn_hi=0;38;5;243:*.flake8=0;38;5;149:*.groovy=0;38;5;48:*INSTALL=0;38;5;16;48;5;186:*TODO.md=1:*.ignore=0;38;5;149:*Doxyfile=0;38;5;149:*TODO.txt=1:*setup.py=0;38;5;149:*Makefile=0;38;5;149:*.gemspec=0;38;5;149:*.desktop=0;38;5;149:*.rgignore=0;38;5;149:*.markdown=0;38;5;185:*COPYRIGHT=0;38;5;249:*configure=0;38;5;149:*.DS_Store=0;38;5;243:*.kdevelop=0;38;5;149:*.fdignore=0;38;5;149:*README.md=0;38;5;16;48;5;186:*.cmake.in=0;38;5;149:*SConscript=0;38;5;149:*CODEOWNERS=0;38;5;149:*.localized=0;38;5;243:*.gitignore=0;38;5;149:*Dockerfile=0;38;5;149:*.gitconfig=0;38;5;149:*INSTALL.md=0;38;5;16;48;5;186:*README.txt=0;38;5;16;48;5;186:*SConstruct=0;38;5;149:*.scons_opt=0;38;5;243:*.travis.yml=0;38;5;186:*.gitmodules=0;38;5;149:*.synctex.gz=0;38;5;243:*LICENSE-MIT=0;38;5;249:*MANIFEST.in=0;38;5;149:*Makefile.in=0;38;5;243:*Makefile.am=0;38;5;149:*INSTALL.txt=0;38;5;16;48;5;186:*configure.ac=0;38;5;149:*.applescript=0;38;5;48:*appveyor.yml=0;38;5;186:*.fdb_latexmk=0;38;5;243:*CONTRIBUTORS=0;38;5;16;48;5;186:*.clang-format=0;38;5;149:*LICENSE-APACHE=0;38;5;249:*CMakeLists.txt=0;38;5;149:*CMakeCache.txt=0;38;5;243:*.gitattributes=0;38;5;149:*CONTRIBUTORS.md=0;38;5;16;48;5;186:*.sconsign.dblite=0;38;5;243:*requirements.txt=0;38;5;149:*CONTRIBUTORS.txt=0;38;5;16;48;5;186:*package-lock.json=0;38;5;243:*.CFUserTextEncoding=0;38;5;243
";
fn main() {
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("");
// Get the current working directory
let current_dir = Path::new(".");
if !fshelper::is_dir(current_dir) {
print_error_and_exit!("Could not get current directory.");
let result = run();
match result {
Ok(exit_code) => {
exit_code.exit();
}
Err(err) => {
eprintln!("[fd error]: {:#}", err);
ExitCode::GeneralError.exit();
}
}
}
// Get one or more root directories to search.
let mut dir_vec: Vec<_> = match matches
.values_of("path")
.or(matches.values_of("search-path"))
{
Some(paths) => paths
.map(|path| {
let path_buffer = PathBuf::from(path);
if !fshelper::is_dir(&path_buffer) {
print_error_and_exit!(
"'{}' is not a directory.",
path_buffer.to_string_lossy()
);
}
path_buffer
})
.collect::<Vec<_>>(),
None => vec![current_dir.to_path_buf()],
};
fn run() -> Result<ExitCode> {
let matches = app::build_app().get_matches_from(env::args_os());
set_working_dir(&matches)?;
let current_directory = Path::new(".");
ensure_current_directory_exists(current_directory)?;
let search_paths = extract_search_paths(&matches, current_directory)?;
let pattern = extract_search_pattern(&matches)?;
ensure_search_pattern_is_not_a_path(&matches, pattern)?;
let pattern_regex = build_pattern_regex(&matches, pattern)?;
let config = construct_config(matches, &pattern_regex)?;
ensure_use_hidden_option_for_leading_dot_pattern(&config, &pattern_regex)?;
let re = build_regex(pattern_regex, &config)?;
walk::scan(&search_paths, Arc::new(re), Arc::new(config))
}
fn set_working_dir(matches: &clap::ArgMatches) -> Result<()> {
if let Some(base_directory) = matches.value_of_os("base-directory") {
let base_directory = Path::new(base_directory);
if !filesystem::is_existing_directory(base_directory) {
return Err(anyhow!(
"The '--base-directory' path '{}' is not a directory.",
base_directory.to_string_lossy()
));
}
env::set_current_dir(base_directory).with_context(|| {
format!(
"Could not set '{}' as the current working directory",
base_directory.to_string_lossy()
)
})?;
}
Ok(())
}
fn ensure_current_directory_exists(current_directory: &Path) -> Result<()> {
if filesystem::is_existing_directory(current_directory) {
Ok(())
} else {
Err(anyhow!(
"Could not retrieve current directory (has it been deleted?)."
))
}
}
fn extract_search_pattern<'a>(matches: &'a clap::ArgMatches) -> Result<&'a str> {
let pattern = matches
.value_of_os("pattern")
.map(|p| {
p.to_str()
.ok_or_else(|| anyhow!("The search pattern includes invalid UTF-8 sequences."))
})
.transpose()?
.unwrap_or("");
Ok(pattern)
}
fn extract_search_paths(
matches: &clap::ArgMatches,
current_directory: &Path,
) -> Result<Vec<PathBuf>> {
let mut search_paths = matches
.values_of_os("path")
.or_else(|| matches.values_of_os("search-path"))
.map_or_else(
|| vec![current_directory.to_path_buf()],
|paths| {
paths
.filter_map(|path| {
let path_buffer = PathBuf::from(path);
if filesystem::is_existing_directory(&path_buffer) {
Some(path_buffer)
} else {
print_error(format!(
"Search path '{}' is not a directory.",
path_buffer.to_string_lossy()
));
None
}
})
.collect()
},
);
if search_paths.is_empty() {
return Err(anyhow!("No valid search paths given."));
}
if matches.is_present("absolute-path") {
dir_vec = dir_vec
.iter()
.map(|path_buffer| {
path_buffer
.canonicalize()
.and_then(|pb| fshelper::absolute_path(pb.as_path()))
.unwrap()
})
.collect();
update_to_absolute_paths(&mut search_paths);
}
Ok(search_paths)
}
// Detect if the user accidentally supplied a path instead of a search pattern
fn update_to_absolute_paths(search_paths: &mut [PathBuf]) {
for buffer in search_paths.iter_mut() {
*buffer = filesystem::absolute_path(buffer.normalize().unwrap().as_path()).unwrap();
}
}
/// Detect if the user accidentally supplied a path instead of a search pattern
fn ensure_search_pattern_is_not_a_path(matches: &clap::ArgMatches, pattern: &str) -> Result<()> {
if !matches.is_present("full-path")
&& pattern.contains(std::path::MAIN_SEPARATOR)
&& fshelper::is_dir(Path::new(pattern))
&& Path::new(pattern).is_dir()
{
print_error_and_exit!(
Err(anyhow!(
"The search pattern '{pattern}' contains a path-separation character ('{sep}') \
and will not lead to any search results.\n\n\
If you want to search for all files inside the '{pattern}' directory, use a match-all pattern:\n\n \
fd . '{pattern}'\n\n\
Instead, if you want to search for the pattern in the full path, use:\n\n \
Instead, if you want your pattern to match the full file path, use:\n\n \
fd --full-path '{pattern}'",
pattern = pattern,
sep = std::path::MAIN_SEPARATOR,
);
))
} else {
Ok(())
}
}
// Treat pattern as literal string if '--fixed-strings' is used
let pattern_regex = if matches.is_present("fixed-strings") {
fn build_pattern_regex(matches: &clap::ArgMatches, pattern: &str) -> Result<String> {
Ok(if matches.is_present("glob") && !pattern.is_empty() {
let glob = GlobBuilder::new(pattern).literal_separator(true).build()?;
glob.regex().to_owned()
} else if matches.is_present("fixed-strings") {
// Treat pattern as literal string if '--fixed-strings' is used
regex::escape(pattern)
} else {
String::from(pattern)
};
})
}
fn check_path_separator_length(path_separator: Option<&str>) -> Result<()> {
match (cfg!(windows), path_separator) {
(true, Some(sep)) if sep.len() > 1 => Err(anyhow!(
"A path separator must be exactly one byte, but \
the given separator is {} bytes: '{}'.\n\
In some shells on Windows, '/' is automatically \
expanded. Try to use '//' instead.",
sep.len(),
sep
)),
_ => Ok(()),
}
}
fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result<Config> {
// 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_regex));
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(pattern_regex));
let path_separator = matches
.value_of("path-separator")
.map_or_else(filesystem::default_path_separator, |s| Some(s.to_owned()));
check_path_separator_length(path_separator.as_deref())?;
let size_limits = extract_size_limits(&matches)?;
let time_constraints = extract_time_constraints(&matches)?;
#[cfg(unix)]
let owner_constraint = matches
.value_of("owner")
.map(OwnerFilter::from_string)
.transpose()?
.flatten();
#[cfg(windows)]
let ansi_colors_support =
ansi_term::enable_ansi_support().is_ok() || std::env::var_os("TERM").is_some();
#[cfg(not(windows))]
let ansi_colors_support = true;
let interactive_terminal = atty::is(Stream::Stdout);
let colored_output = match matches.value_of("color") {
Some("always") => true,
Some("never") => false,
_ => atty::is(Stream::Stdout),
_ => ansi_colors_support && env::var_os("NO_COLOR").is_none() && interactive_terminal,
};
#[cfg(windows)]
let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok();
let 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_else(|| LsColors::from_string(DEFAULT_LS_COLORS)))
} else {
None
};
let command = extract_command(&matches, path_separator.as_deref(), colored_output)?;
let command = matches.values_of("exec").map(CommandTemplate::new);
let size_limits: Vec<SizeFilter> = matches
.values_of("size")
.map(|v| {
v.map(|sf| {
if let Some(f) = SizeFilter::from_string(sf) {
return f;
}
print_error_and_exit!("'{}' is not a valid size constraint. See 'fd --help'.", sf);
})
.collect()
})
.unwrap_or_else(|| vec![]);
let now = time::SystemTime::now();
let mut time_constraints: Vec<TimeFilter> = Vec::new();
if let Some(t) = matches.value_of("changed-within") {
if let Some(f) = TimeFilter::after(&now, t) {
time_constraints.push(f);
} else {
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
}
}
if let Some(t) = matches.value_of("changed-before") {
if let Some(f) = TimeFilter::before(&now, t) {
time_constraints.push(f);
} else {
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
}
}
let config = FdOptions {
Ok(Config {
case_sensitive,
search_full_path: matches.is_present("full-path"),
ignore_hidden: !(matches.is_present("hidden")
@@ -184,24 +262,53 @@ fn main() {
read_vcsignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")
|| matches.is_present("no-ignore-vcs")),
read_parent_ignore: !matches.is_present("no-ignore-parent"),
read_global_ignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")
|| matches.is_present("no-global-ignore-file")),
follow_links: matches.is_present("follow"),
one_file_system: matches.is_present("one-file-system"),
null_separator: matches.is_present("null_separator"),
quiet: matches.is_present("quiet"),
max_depth: matches
.value_of("depth")
.value_of("max-depth")
.or_else(|| matches.value_of("rg-depth"))
.and_then(|n| usize::from_str_radix(n, 10).ok()),
.or_else(|| matches.value_of("exact-depth"))
.map(|n| n.parse::<usize>())
.transpose()
.context("Failed to parse argument to --max-depth/--exact-depth")?,
min_depth: matches
.value_of("min-depth")
.or_else(|| matches.value_of("exact-depth"))
.map(|n| n.parse::<usize>())
.transpose()
.context("Failed to parse argument to --min-depth/--exact-depth")?,
prune: matches.is_present("prune"),
threads: std::cmp::max(
matches
.value_of("threads")
.and_then(|n| usize::from_str_radix(n, 10).ok())
.map(|n| n.parse::<usize>())
.transpose()
.context("Failed to parse number of threads")?
.map(|n| {
if n > 0 {
Ok(n)
} else {
Err(anyhow!("Number of threads must be positive."))
}
})
.transpose()?
.unwrap_or_else(num_cpus::get),
1,
),
max_buffer_time: matches
.value_of("max-buffer-time")
.and_then(|n| u64::from_str_radix(n, 10).ok())
.map(|n| n.parse::<u64>())
.transpose()
.context("Failed to parse max. buffer time argument")?
.map(time::Duration::from_millis),
ls_colors,
interactive_terminal,
file_types: matches.values_of("file-type").map(|values| {
let mut file_types = FileTypes::default();
for value in values {
@@ -213,9 +320,9 @@ fn main() {
file_types.executables_only = true;
file_types.files = true;
}
"e" | "empty" => {
file_types.empty_only = true;
}
"e" | "empty" => file_types.empty_only = true,
"s" | "socket" => file_types.sockets = true,
"p" | "pipe" => file_types.pipes = true,
_ => unreachable!(),
}
}
@@ -228,46 +335,234 @@ fn main() {
file_types
}),
extensions: matches.values_of("extension").map(|exts| {
let patterns = exts
.map(|e| e.trim_left_matches('.'))
.map(|e| format!(r".\.{}$", regex::escape(e)));
match RegexSetBuilder::new(patterns)
.case_insensitive(true)
.build()
{
Ok(re) => re,
Err(err) => {
print_error_and_exit!("{}", err.description());
}
}
}),
command,
extensions: matches
.values_of("extension")
.map(|exts| {
let patterns = exts
.map(|e| e.trim_start_matches('.'))
.map(|e| format!(r".\.{}$", regex::escape(e)));
RegexSetBuilder::new(patterns)
.case_insensitive(true)
.build()
})
.transpose()?,
command: command.map(Arc::new),
batch_size: matches
.value_of("batch-size")
.map(|n| n.parse::<usize>())
.transpose()
.context("Failed to parse --batch-size argument")?
.unwrap_or_default(),
exclude_patterns: matches
.values_of("exclude")
.map(|v| v.map(|p| String::from("!") + p).collect())
.unwrap_or_else(|| vec![]),
.unwrap_or_else(Vec::new),
ignore_files: matches
.values_of("ignore-file")
.map(|vs| vs.map(PathBuf::from).collect())
.unwrap_or_else(|| vec![]),
.unwrap_or_else(Vec::new),
size_constraints: size_limits,
time_constraints,
#[cfg(unix)]
owner_constraint,
show_filesystem_errors: matches.is_present("show-errors"),
};
path_separator,
max_results: matches
.value_of("max-results")
.map(|n| n.parse::<usize>())
.transpose()
.context("Failed to parse --max-results argument")?
.filter(|&n| n > 0)
.or_else(|| {
if matches.is_present("max-one-result") {
Some(1)
} else {
None
}
}),
strip_cwd_prefix: (!matches.is_present("path")
&& !matches.is_present("search-path")
&& (interactive_terminal || matches.is_present("strip-cwd-prefix"))),
})
}
match RegexBuilder::new(&pattern_regex)
fn extract_command(
matches: &clap::ArgMatches,
path_separator: Option<&str>,
colored_output: bool,
) -> Result<Option<CommandTemplate>> {
None.or_else(|| {
matches.values_of("exec").map(|args| {
Ok(CommandTemplate::new(
args,
path_separator.map(str::to_string),
))
})
})
.or_else(|| {
matches
.values_of("exec-batch")
.map(|args| CommandTemplate::new_batch(args, path_separator.map(str::to_string)))
})
.or_else(|| {
if !matches.is_present("list-details") {
return None;
}
let color = matches.value_of("color").unwrap_or("auto");
let color_arg = format!("--color={}", color);
let res = determine_ls_command(&color_arg, colored_output).map(|cmd| {
CommandTemplate::new_batch(cmd, path_separator.map(str::to_string)).unwrap()
});
Some(res)
})
.transpose()
}
fn determine_ls_command(color_arg: &str, colored_output: bool) -> Result<Vec<&str>> {
#[allow(unused)]
let gnu_ls = |command_name| {
// Note: we use short options here (instead of --long-options) to support more
// platforms (like BusyBox).
vec![
command_name,
"-l", // long listing format
"-h", // human readable file sizes
"-d", // list directories themselves, not their contents
color_arg,
]
};
let cmd: Vec<&str> = if cfg!(unix) {
if !cfg!(any(
target_os = "macos",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)) {
// Assume ls is GNU ls
gnu_ls("ls")
} else {
// MacOS, DragonFlyBSD, FreeBSD
use std::process::{Command, Stdio};
// Use GNU ls, if available (support for --color=auto, better LS_COLORS support)
let gnu_ls_exists = Command::new("gls")
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok();
if gnu_ls_exists {
gnu_ls("gls")
} else {
let mut cmd = vec![
"ls", // BSD version of ls
"-l", // long listing format
"-h", // '--human-readable' is not available, '-h' is
"-d", // '--directory' is not available, but '-d' is
];
if !cfg!(any(target_os = "netbsd", target_os = "openbsd")) && colored_output {
// -G is not available in NetBSD's and OpenBSD's ls
cmd.push("-G");
}
cmd
}
}
} else if cfg!(windows) {
use std::process::{Command, Stdio};
// Use GNU ls, if available
let gnu_ls_exists = Command::new("ls")
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok();
if gnu_ls_exists {
gnu_ls("ls")
} else {
return Err(anyhow!(
"'fd --list-details' is not supported on Windows unless GNU 'ls' is installed."
));
}
} else {
return Err(anyhow!(
"'fd --list-details' is not supported on this platform."
));
};
Ok(cmd)
}
fn extract_size_limits(matches: &clap::ArgMatches) -> Result<Vec<SizeFilter>> {
matches.values_of("size").map_or(Ok(Vec::new()), |vs| {
vs.map(|sf| {
SizeFilter::from_string(sf)
.ok_or_else(|| anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", sf))
})
.collect::<Result<Vec<_>>>()
})
}
fn extract_time_constraints(matches: &clap::ArgMatches) -> Result<Vec<TimeFilter>> {
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 {
return Err(anyhow!(
"'{}' is not a valid date or duration. See 'fd --help'.",
t
));
}
}
if let Some(t) = matches.value_of("changed-before") {
if let Some(f) = TimeFilter::before(&now, t) {
time_constraints.push(f);
} else {
return Err(anyhow!(
"'{}' is not a valid date or duration. See 'fd --help'.",
t
));
}
}
Ok(time_constraints)
}
fn ensure_use_hidden_option_for_leading_dot_pattern(
config: &Config,
pattern_regex: &str,
) -> Result<()> {
if cfg!(unix) && config.ignore_hidden && pattern_matches_strings_with_leading_dot(pattern_regex)
{
Err(anyhow!(
"The pattern seems to only match files with a leading dot, but hidden files are \
filtered by default. Consider adding -H/--hidden to search hidden files as well \
or adjust your search pattern."
))
} else {
Ok(())
}
}
fn build_regex(pattern_regex: String, config: &Config) -> Result<regex::bytes::Regex> {
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) => {
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()
);
}
}
.map_err(|e| {
anyhow!(
"{}\n\nNote: You can use the '--fixed-strings' option to search for a \
literal string instead of a regular expression. Alternatively, you can \
also use the '--glob' option to match on a glob pattern.",
e.to_string()
)
})
}
+98 -104
View File
@@ -1,138 +1,132 @@
// 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 exit_codes::ExitCode;
use fshelper::is_executable;
use internal::opts::FdOptions;
use lscolors::LsColors;
use std::borrow::Cow;
use std::io::{self, Write};
use std::ops::Deref;
use std::path::{self, Component, Path, PathBuf};
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::path::Path;
use ansi_term;
use lscolors::{Indicator, LsColors, Style};
/// Remove the `./` prefix from a path.
fn strip_current_dir<'a>(pathbuf: &'a PathBuf) -> &'a Path {
let mut iter = pathbuf.components();
let mut iter_next = iter.clone();
if iter_next.next() == Some(Component::CurDir) {
iter.next();
}
iter.as_path()
use crate::config::Config;
use crate::error::print_error;
use crate::exit_codes::ExitCode;
use crate::filesystem::strip_current_dir;
fn replace_path_separator(path: &str, new_path_separator: &str) -> String {
path.replace(std::path::MAIN_SEPARATOR, new_path_separator)
}
pub fn print_entry(entry: &PathBuf, config: &FdOptions, wants_to_quit: &Arc<AtomicBool>) {
let path = if entry.is_absolute() {
entry.as_path()
} else {
// TODO: this function is performance critical and can probably be optimized
pub fn print_entry<W: Write>(stdout: &mut W, entry: &Path, config: &Config) {
let path = if config.strip_cwd_prefix {
strip_current_dir(entry)
} else {
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)
} 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::GeneralError.into());
if let Err(e) = r {
if e.kind() == ::std::io::ErrorKind::BrokenPipe {
// Exit gracefully in case of a broken pipe (e.g. 'fd ... | head -n 3').
ExitCode::Success.exit();
} else {
print_error(format!("Could not write to output: {}", e));
ExitCode::GeneralError.exit();
}
}
}
fn print_entry_colorized(
// TODO: this function is performance critical and can probably be optimized
fn print_entry_colorized<W: Write>(
stdout: &mut W,
path: &Path,
config: &FdOptions,
config: &Config,
ls_colors: &LsColors,
wants_to_quit: &Arc<AtomicBool>,
) -> io::Result<()> {
let default_style = ansi_term::Style::default();
// Split the path between the parent and the last component
let mut offset = 0;
let path_str = path.to_string_lossy();
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()));
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(),
};
if wants_to_quit.load(Ordering::Relaxed) {
write!(handle, "\n")?;
process::exit(ExitCode::KilledBySigint.into());
if let Some(parent) = path.parent() {
offset = parent.to_string_lossy().len();
for c in path_str[offset..].chars() {
if std::path::is_separator(c) {
offset += c.len_utf8();
} else {
break;
}
}
}
if config.null_separator {
write!(handle, "\0")
} else {
writeln!(handle, "")
if offset > 0 {
let mut parent_str = Cow::from(&path_str[..offset]);
if let Some(ref separator) = config.path_separator {
*parent_str.to_mut() = replace_path_separator(&parent_str, separator);
}
let style = ls_colors
.style_for_indicator(Indicator::Directory)
.map(Style::to_ansi_term_style)
.unwrap_or_default();
write!(stdout, "{}", style.paint(parent_str))?;
}
let style = ls_colors
.style_for_path(path)
.map(Style::to_ansi_term_style)
.unwrap_or_default();
write!(stdout, "{}", style.paint(&path_str[offset..]))?;
if config.null_separator {
write!(stdout, "\0")?;
} else {
writeln!(stdout)?;
}
Ok(())
}
fn print_entry_uncolorized(path: &Path, config: &FdOptions) -> io::Result<()> {
// TODO: this function is performance critical and can probably be optimized
fn print_entry_uncolorized_base<W: Write>(
stdout: &mut W,
path: &Path,
config: &Config,
) -> 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)
let mut path_string = path.to_string_lossy();
if let Some(ref separator) = config.path_separator {
*path_string.to_mut() = replace_path_separator(&path_string, separator);
}
write!(stdout, "{}{}", path_string, separator)
}
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);
}
#[cfg(not(unix))]
fn print_entry_uncolorized<W: Write>(
stdout: &mut W,
path: &Path,
config: &Config,
) -> io::Result<()> {
print_entry_uncolorized_base(stdout, path, config)
}
let metadata = path.metadata();
#[cfg(unix)]
fn print_entry_uncolorized<W: Write>(
stdout: &mut W,
path: &Path,
config: &Config,
) -> io::Result<()> {
use std::os::unix::ffi::OsStrExt;
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)
if config.interactive_terminal || config.path_separator.is_some() {
// Fall back to the base implementation
print_entry_uncolorized_base(stdout, path, config)
} else {
None
// Print path as raw bytes, allowing invalid UTF-8 filenames to be passed to other processes
let separator = if config.null_separator { b"\0" } else { b"\n" };
stdout.write_all(path.as_os_str().as_bytes())?;
stdout.write_all(separator)
}
}
+100
View File
@@ -0,0 +1,100 @@
use regex_syntax::hir::Hir;
use regex_syntax::ParserBuilder;
/// Determine if a regex pattern contains a literal uppercase character.
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
let mut parser = ParserBuilder::new().allow_invalid_utf8(true).build();
parser
.parse(pattern)
.map(|hir| hir_has_uppercase_char(&hir))
.unwrap_or(false)
}
/// Determine if a regex expression contains a literal uppercase character.
fn hir_has_uppercase_char(hir: &Hir) -> bool {
use regex_syntax::hir::*;
match *hir.kind() {
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
HirKind::Literal(Literal::Byte(b)) => char::from(b).is_uppercase(),
HirKind::Class(Class::Unicode(ref ranges)) => ranges
.iter()
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
HirKind::Class(Class::Bytes(ref ranges)) => ranges
.iter()
.any(|r| char::from(r.start()).is_uppercase() || char::from(r.end()).is_uppercase()),
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
hir_has_uppercase_char(hir)
}
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
hirs.iter().any(hir_has_uppercase_char)
}
_ => false,
}
}
/// Determine if a regex pattern only matches strings starting with a literal dot (hidden files)
pub fn pattern_matches_strings_with_leading_dot(pattern: &str) -> bool {
let mut parser = ParserBuilder::new().allow_invalid_utf8(true).build();
parser
.parse(pattern)
.map(|hir| hir_matches_strings_with_leading_dot(&hir))
.unwrap_or(false)
}
/// See above.
fn hir_matches_strings_with_leading_dot(hir: &Hir) -> bool {
use regex_syntax::hir::*;
// Note: this only really detects the simplest case where a regex starts with
// "^\\.", i.e. a start text anchor and a literal dot character. There are a lot
// of other patterns that ONLY match hidden files, e.g. ^(\\.foo|\\.bar) which are
// not (yet) detected by this algorithm.
match *hir.kind() {
HirKind::Concat(ref hirs) => {
let mut hirs = hirs.iter();
if let Some(hir) = hirs.next() {
if *hir.kind() != HirKind::Anchor(Anchor::StartText) {
return false;
}
} else {
return false;
}
if let Some(hir) = hirs.next() {
*hir.kind() == HirKind::Literal(Literal::Unicode('.'))
} else {
false
}
}
_ => false,
}
}
#[test]
fn pattern_has_uppercase_char_simple() {
assert!(pattern_has_uppercase_char("A"));
assert!(pattern_has_uppercase_char("foo.EXE"));
assert!(!pattern_has_uppercase_char("a"));
assert!(!pattern_has_uppercase_char("foo.exe123"));
}
#[test]
fn pattern_has_uppercase_char_advanced() {
assert!(pattern_has_uppercase_char("foo.[a-zA-Z]"));
assert!(!pattern_has_uppercase_char(r"\Acargo"));
assert!(!pattern_has_uppercase_char(r"carg\x6F"));
}
#[test]
fn matches_strings_with_leading_dot_simple() {
assert!(pattern_matches_strings_with_leading_dot("^\\.gitignore"));
assert!(!pattern_matches_strings_with_leading_dot("^.gitignore"));
assert!(!pattern_matches_strings_with_leading_dot("\\.gitignore"));
assert!(!pattern_matches_strings_with_leading_dot("^gitignore"));
}
+493 -214
View File
@@ -1,33 +1,30 @@
// 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 ctrlc;
use exec;
use exit_codes::ExitCode;
use fshelper;
use internal::{opts::FdOptions, MAX_BUFFER_LENGTH};
use output;
use std::error::Error;
use std::path::PathBuf;
use std::process;
use std::ffi::OsStr;
use std::fs::{FileType, Metadata};
use std::io;
use std::mem;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::channel;
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time;
use std::time::{Duration, Instant};
use std::{borrow::Cow, io::Write};
use anyhow::{anyhow, Result};
use ignore::overrides::OverrideBuilder;
use ignore::{self, WalkBuilder};
use regex::Regex;
use once_cell::unsync::OnceCell;
use regex::bytes::Regex;
use crate::config::Config;
use crate::error::print_error;
use crate::exec;
use crate::exit_codes::{merge_exitcodes, ExitCode};
use crate::filesystem;
use crate::output;
/// The receiver thread can either be buffering results or directly streaming to the console.
#[derive(PartialEq)]
enum ReceiverMode {
/// Receiver is still buffering in order to sort the results, if the search finishes fast
/// enough.
@@ -43,64 +40,91 @@ pub enum WorkerResult {
Error(ignore::Error),
}
/// Maximum size of the output buffer before flushing results to the console
pub const MAX_BUFFER_LENGTH: usize = 1000;
/// Default duration until output buffering switches to streaming.
pub const DEFAULT_MAX_BUFFER_TIME: Duration = Duration::from_millis(100);
/// Recursively scan the given search path for files / pathnames matching the pattern.
///
/// If the `--exec` argument was supplied, this will create a thread pool for executing
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
/// path will simply be written to standard output.
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> Result<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 show_filesystem_errors = config.show_filesystem_errors;
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
for pattern in &config.exclude_patterns {
let res = override_builder.add(pattern);
if res.is_err() {
print_error_and_exit!("Malformed exclude pattern '{}'", pattern);
}
override_builder
.add(pattern)
.map_err(|e| anyhow!("Malformed exclude pattern: {}", e))?;
}
let overrides = override_builder.build().unwrap_or_else(|_| {
print_error_and_exit!("Mismatch in exclude patterns");
});
let overrides = override_builder
.build()
.map_err(|_| anyhow!("Mismatch in exclude patterns"))?;
let mut walker = WalkBuilder::new(first_path_buf.as_path());
walker
.hidden(config.ignore_hidden)
.ignore(config.read_fdignore)
.parents(config.read_fdignore || config.read_vcsignore)
.parents(config.read_parent_ignore && (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)
// No need to check for supported platforms, option is unavailable on unsupported ones
.same_file_system(config.one_file_system)
.max_depth(config.max_depth);
if config.read_fdignore {
walker.add_custom_ignore_filename(".fdignore");
}
if config.read_global_ignore {
#[cfg(target_os = "macos")]
let config_dir_op = std::env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.filter(|p| p.is_absolute())
.or_else(|| dirs_next::home_dir().map(|d| d.join(".config")));
#[cfg(not(target_os = "macos"))]
let config_dir_op = dirs_next::config_dir();
if let Some(global_ignore_file) = config_dir_op
.map(|p| p.join("fd").join("ignore"))
.filter(|p| p.is_file())
{
let result = walker.add_ignore(global_ignore_file);
match result {
Some(ignore::Error::Partial(_)) => (),
Some(err) => {
print_error(format!(
"Malformed pattern in global ignore file. {}.",
err.to_string()
));
}
None => (),
}
}
}
for ignore_file in &config.ignore_files {
let result = walker.add_ignore(ignore_file);
if let Some(err) = result {
match err {
ignore::Error::Partial(_) => (),
_ => {
print_error!(
"{}",
format!(
"Malformed pattern in custom ignore file '{}': {}.",
ignore_file.to_string_lossy(),
err.description()
)
);
}
match result {
Some(ignore::Error::Partial(_)) => (),
Some(err) => {
print_error(format!(
"Malformed pattern in custom ignore file. {}.",
err.to_string()
));
}
None => (),
}
}
@@ -108,155 +132,425 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
walker.add(path_entry.as_path());
}
let parallel_walker = walker.threads(threads).build_parallel();
let parallel_walker = walker.threads(config.threads).build_parallel();
// Flag for cleanly shutting down the parallel walk
let quit_flag = Arc::new(AtomicBool::new(false));
// Flag specifically for quitting due to ^C
let interrupt_flag = Arc::new(AtomicBool::new(false));
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() && config.command.is_none() {
let wq = Arc::clone(&receiver_wtq);
let quit_flag = Arc::clone(&quit_flag);
let interrupt_flag = Arc::clone(&interrupt_flag);
ctrlc::set_handler(move || {
wq.store(true, Ordering::Relaxed);
quit_flag.store(true, Ordering::Relaxed);
if interrupt_flag.fetch_or(true, Ordering::Relaxed) {
// Ctrl-C has been pressed twice, exit NOW
ExitCode::KilledBySigint.exit();
}
})
.unwrap();
}
// Spawn the thread that receives all results through the channel.
let rx_config = Arc::clone(&config);
let receiver_thread = 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));
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);
// 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));
// 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();
}
} else {
let start = time::Instant::now();
let mut buffer = vec![];
// Start in buffering mode
let mut mode = ReceiverMode::Buffering;
// Maximum time to wait before we start streaming to the console.
let max_buffer_time = rx_config
.max_buffer_time
.unwrap_or_else(|| time::Duration::from_millis(100));
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(v, &rx_config, &receiver_wtq);
}
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);
}
}
}
}
// If we have finished fast enough (faster than max_buffer_time), we haven't streamed
// anything to the console, yet. In this case, sort the results and print them:
if !buffer.is_empty() {
buffer.sort();
for value in buffer {
output::print_entry(&value, &rx_config, &receiver_wtq);
}
}
}
});
let receiver_thread = spawn_receiver(&config, &quit_flag, &interrupt_flag, rx);
// Spawn the sender threads.
spawn_senders(&config, &quit_flag, pattern, parallel_walker, tx);
// Wait for the receiver thread to print out all results.
let exit_code = receiver_thread.join().unwrap();
if interrupt_flag.load(Ordering::Relaxed) {
Ok(ExitCode::KilledBySigint)
} else {
Ok(exit_code)
}
}
/// Wrapper for the receiver thread's buffering behavior.
struct ReceiverBuffer<W> {
/// The configuration.
config: Arc<Config>,
/// For shutting down the senders.
quit_flag: Arc<AtomicBool>,
/// The ^C notifier.
interrupt_flag: Arc<AtomicBool>,
/// Receiver for worker results.
rx: Receiver<WorkerResult>,
/// Standard output.
stdout: W,
/// The current buffer mode.
mode: ReceiverMode,
/// The deadline to switch to streaming mode.
deadline: Instant,
/// The buffer of quickly received paths.
buffer: Vec<PathBuf>,
/// Result count.
num_results: usize,
}
impl<W: Write> ReceiverBuffer<W> {
/// Create a new receiver buffer.
fn new(
config: Arc<Config>,
quit_flag: Arc<AtomicBool>,
interrupt_flag: Arc<AtomicBool>,
rx: Receiver<WorkerResult>,
stdout: W,
) -> Self {
let max_buffer_time = config.max_buffer_time.unwrap_or(DEFAULT_MAX_BUFFER_TIME);
let deadline = Instant::now() + max_buffer_time;
Self {
config,
quit_flag,
interrupt_flag,
rx,
stdout,
mode: ReceiverMode::Buffering,
deadline,
buffer: Vec::with_capacity(MAX_BUFFER_LENGTH),
num_results: 0,
}
}
/// Process results until finished.
fn process(&mut self) -> ExitCode {
loop {
if let Err(ec) = self.poll() {
self.quit_flag.store(true, Ordering::Relaxed);
return ec;
}
}
}
/// Receive the next worker result.
fn recv(&self) -> Result<WorkerResult, RecvTimeoutError> {
match self.mode {
ReceiverMode::Buffering => {
// Wait at most until we should switch to streaming
let now = Instant::now();
self.deadline
.checked_duration_since(now)
.ok_or(RecvTimeoutError::Timeout)
.and_then(|t| self.rx.recv_timeout(t))
}
ReceiverMode::Streaming => {
// Wait however long it takes for a result
Ok(self.rx.recv()?)
}
}
}
/// Wait for a result or state change.
fn poll(&mut self) -> Result<(), ExitCode> {
match self.recv() {
Ok(WorkerResult::Entry(path)) => {
if self.config.quiet {
return Err(ExitCode::HasResults(true));
}
match self.mode {
ReceiverMode::Buffering => {
self.buffer.push(path);
if self.buffer.len() > MAX_BUFFER_LENGTH {
self.stream()?;
}
}
ReceiverMode::Streaming => {
self.print(&path)?;
self.flush()?;
}
}
self.num_results += 1;
if let Some(max_results) = self.config.max_results {
if self.num_results >= max_results {
return self.stop();
}
}
}
Ok(WorkerResult::Error(err)) => {
if self.config.show_filesystem_errors {
print_error(err.to_string());
}
}
Err(RecvTimeoutError::Timeout) => {
self.stream()?;
}
Err(RecvTimeoutError::Disconnected) => {
return self.stop();
}
}
Ok(())
}
/// Output a path.
fn print(&mut self, path: &Path) -> Result<(), ExitCode> {
output::print_entry(&mut self.stdout, path, &self.config);
if self.interrupt_flag.load(Ordering::Relaxed) {
// Ignore any errors on flush, because we're about to exit anyway
let _ = self.flush();
return Err(ExitCode::KilledBySigint);
}
Ok(())
}
/// Switch ourselves into streaming mode.
fn stream(&mut self) -> Result<(), ExitCode> {
self.mode = ReceiverMode::Streaming;
let buffer = mem::take(&mut self.buffer);
for path in buffer {
self.print(&path)?;
}
self.flush()
}
/// Stop looping.
fn stop(&mut self) -> Result<(), ExitCode> {
if self.mode == ReceiverMode::Buffering {
self.buffer.sort();
self.stream()?;
}
if self.config.quiet {
Err(ExitCode::HasResults(self.num_results > 0))
} else {
Err(ExitCode::Success)
}
}
/// Flush stdout if necessary.
fn flush(&mut self) -> Result<(), ExitCode> {
if self.config.interactive_terminal && self.stdout.flush().is_err() {
// Probably a broken pipe. Exit gracefully.
return Err(ExitCode::GeneralError);
}
Ok(())
}
}
fn spawn_receiver(
config: &Arc<Config>,
quit_flag: &Arc<AtomicBool>,
interrupt_flag: &Arc<AtomicBool>,
rx: Receiver<WorkerResult>,
) -> thread::JoinHandle<ExitCode> {
let config = Arc::clone(config);
let quit_flag = Arc::clone(quit_flag);
let interrupt_flag = Arc::clone(interrupt_flag);
let show_filesystem_errors = config.show_filesystem_errors;
let threads = config.threads;
// This will be used to check if output should be buffered when only running a single thread
let enable_output_buffering: bool = threads > 1;
thread::spawn(move || {
// This will be set to `Some` if the `--exec` argument was supplied.
if let Some(ref cmd) = config.command {
if cmd.in_batch_mode() {
exec::batch(
rx,
cmd,
show_filesystem_errors,
enable_output_buffering,
config.batch_size,
)
} else {
let shared_rx = Arc::new(Mutex::new(rx));
let out_perm = Arc::new(Mutex::new(()));
// 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,
enable_output_buffering,
)
});
// 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.
let exit_codes = handles
.into_iter()
.map(|handle| handle.join().unwrap())
.collect::<Vec<_>>();
merge_exitcodes(exit_codes)
}
} else {
let stdout = io::stdout();
let stdout = stdout.lock();
let stdout = io::BufWriter::new(stdout);
let mut rxbuffer = ReceiverBuffer::new(config, quit_flag, interrupt_flag, rx, stdout);
rxbuffer.process()
}
})
}
enum DirEntryInner {
Normal(ignore::DirEntry),
BrokenSymlink(PathBuf),
}
pub struct DirEntry {
inner: DirEntryInner,
metadata: OnceCell<Option<Metadata>>,
}
impl DirEntry {
fn normal(e: ignore::DirEntry) -> Self {
Self {
inner: DirEntryInner::Normal(e),
metadata: OnceCell::new(),
}
}
fn broken_symlink(path: PathBuf) -> Self {
Self {
inner: DirEntryInner::BrokenSymlink(path),
metadata: OnceCell::new(),
}
}
pub fn path(&self) -> &Path {
match &self.inner {
DirEntryInner::Normal(e) => e.path(),
DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(),
}
}
pub fn file_type(&self) -> Option<FileType> {
match &self.inner {
DirEntryInner::Normal(e) => e.file_type(),
DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()),
}
}
pub fn metadata(&self) -> Option<&Metadata> {
self.metadata
.get_or_init(|| match &self.inner {
DirEntryInner::Normal(e) => e.metadata().ok(),
DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(),
})
.as_ref()
}
pub fn depth(&self) -> Option<usize> {
match &self.inner {
DirEntryInner::Normal(e) => Some(e.depth()),
DirEntryInner::BrokenSymlink(_) => None,
}
}
}
fn spawn_senders(
config: &Arc<Config>,
quit_flag: &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 quit_flag = Arc::clone(quit_flag);
Box::new(move |entry_o| {
if wants_to_quit.load(Ordering::Relaxed) {
if quit_flag.load(Ordering::Relaxed) {
return ignore::WalkState::Quit;
}
let entry = match entry_o {
Ok(e) => e,
Err(err) => {
tx_thread.send(WorkerResult::Error(err)).unwrap();
Ok(ref e) if e.depth() == 0 => {
// Skip the root directory entry.
return ignore::WalkState::Continue;
}
Ok(e) => DirEntry::normal(e),
Err(ignore::Error::WithPath {
path,
err: inner_err,
}) => match inner_err.as_ref() {
ignore::Error::Io(io_error)
if io_error.kind() == io::ErrorKind::NotFound
&& path
.symlink_metadata()
.ok()
.map_or(false, |m| m.file_type().is_symlink()) =>
{
DirEntry::broken_symlink(path)
}
_ => {
return match tx_thread.send(WorkerResult::Error(ignore::Error::WithPath {
path,
err: inner_err,
})) {
Ok(_) => ignore::WalkState::Continue,
Err(_) => ignore::WalkState::Quit,
}
}
},
Err(err) => {
return match tx_thread.send(WorkerResult::Error(err)) {
Ok(_) => ignore::WalkState::Continue,
Err(_) => ignore::WalkState::Quit,
}
}
};
if entry.depth() == 0 {
if let Some(min_depth) = config.min_depth {
if entry.depth().map_or(true, |d| d < min_depth) {
return ignore::WalkState::Continue;
}
}
// Check the name first, since it doesn't require metadata
let entry_path = entry.path();
let search_str: Cow<OsStr> = if config.search_full_path {
let path_abs_buf = filesystem::path_absolute_form(entry_path)
.expect("Retrieving absolute path succeeds");
Cow::Owned(path_abs_buf.as_os_str().to_os_string())
} else {
match entry_path.file_name() {
Some(filename) => Cow::Borrowed(filename),
None => unreachable!(
"Encountered file system entry without a file name. This should only \
happen for paths like 'foo/bar/..' or '/' which are not supposed to \
appear in a file system traversal."
),
}
};
if !pattern.is_match(&filesystem::osstr_to_bytes(search_str.as_ref())) {
return ignore::WalkState::Continue;
}
// Filter out unwanted file types.
if let Some(ref file_types) = config.file_types {
if let Some(ref entry_type) = entry.file_type() {
if (!file_types.files && entry_type.is_file())
|| (!file_types.directories && entry_type.is_dir())
|| (!file_types.symlinks && entry_type.is_symlink())
|| (file_types.executables_only
&& !entry
.metadata()
.map(|m| fshelper::is_executable(&m))
.unwrap_or(false))
|| (file_types.empty_only && !fshelper::is_empty(&entry))
{
return ignore::WalkState::Continue;
} else if !(entry_type.is_file()
|| entry_type.is_dir()
|| entry_type.is_symlink())
{
// This is probably a block device, char device, fifo or socket. Skip it.
// Filter out unwanted extensions.
if let Some(ref exts_regex) = config.extensions {
if let Some(path_str) = entry_path.file_name() {
if !exts_regex.is_match(&filesystem::osstr_to_bytes(path_str)) {
return ignore::WalkState::Continue;
}
} else {
@@ -264,23 +558,30 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
}
}
let entry_path = entry.path();
// Filter out unwanted file types.
if let Some(ref file_types) = config.file_types {
if file_types.should_ignore(&entry) {
return ignore::WalkState::Continue;
}
}
// Filter out unwanted extensions.
if let Some(ref exts_regex) = config.extensions {
if let Some(path_str) = entry_path.file_name().map_or(None, |s| s.to_str()) {
if !exts_regex.is_match(path_str) {
#[cfg(unix)]
{
if let Some(ref owner_constraint) = config.owner_constraint {
if let Some(metadata) = entry.metadata() {
if !owner_constraint.matches(metadata) {
return ignore::WalkState::Continue;
}
} else {
return ignore::WalkState::Continue;
}
} else {
return ignore::WalkState::Continue;
}
}
// Filter out unwanted sizes if it is a file and we have been given size constraints.
if config.size_constraints.len() > 0 {
if !config.size_constraints.is_empty() {
if entry_path.is_file() {
if let Ok(metadata) = entry_path.metadata() {
if let Some(metadata) = entry.metadata() {
let file_size = metadata.len();
if config
.size_constraints
@@ -300,14 +601,12 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
// Filter out unwanted modification times
if !config.time_constraints.is_empty() {
let mut matched = false;
if entry_path.is_file() {
if let Ok(metadata) = entry_path.metadata() {
if let Ok(modified) = metadata.modified() {
matched = config
.time_constraints
.iter()
.all(|tf| tf.applies_to(&modified));
}
if let Some(metadata) = entry.metadata() {
if let Ok(modified) = metadata.modified() {
matched = config
.time_constraints
.iter()
.all(|tf| tf.applies_to(&modified));
}
}
if !matched {
@@ -315,38 +614,18 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
}
}
let search_str_o = if config.search_full_path {
match fshelper::path_absolute_form(entry_path) {
Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),
Err(_) => {
print_error_and_exit!("Unable to retrieve absolute path.");
}
}
} else {
entry_path.file_name().map(|f| f.to_string_lossy())
};
let send_result = tx_thread.send(WorkerResult::Entry(entry_path.to_owned()));
if let Some(search_str) = search_str_o {
if pattern.is_match(&*search_str) {
// TODO: take care of the unwrap call
tx_thread
.send(WorkerResult::Entry(entry_path.to_owned()))
.unwrap()
}
if send_result.is_err() {
return ignore::WalkState::Quit;
}
// Apply pruning.
if config.prune {
return ignore::WalkState::Skip;
}
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::KilledBySigint.into());
}
}
+158 -40
View File
@@ -1,29 +1,14 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std;
use std::env;
use std::fs;
use std::io;
use std::io::Write;
use std::io::{self, Write};
#[cfg(unix)]
use std::os::unix;
#[cfg(windows)]
use std::os::windows;
use std::path::{Path, PathBuf};
use std::process;
#[cfg(unix)]
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 +17,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.
@@ -121,33 +109,77 @@ 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")
.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_unstable();
return words.join(" ");
}
line
})
.collect::<Vec<_>>();
lines.sort_by_key(|s| s.clone());
lines.sort();
lines.join("\n")
}
/// Trim whitespace from the beginning of each line.
fn trim_lines(s: &str) -> String {
s.lines()
.map(|line| line.trim_start())
.fold(String::new(), |mut str, line| {
str.push_str(line);
str.push('\n');
str
})
}
impl TestEnv {
pub fn new(directories: &[&'static str], files: &[&'static str]) -> TestEnv {
let temp_dir = create_working_directory(directories, files).expect("working directory");
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,
}
}
/// Create a broken symlink at the given path in the temp_dir.
pub fn create_broken_symlink<P: AsRef<Path>>(
&mut self,
link_path: P,
) -> Result<PathBuf, io::Error> {
let root = self.test_root();
let broken_symlink_link = root.join(link_path);
{
let temp_target_dir = TempDir::new("fd-tests-broken-symlink")?;
let broken_symlink_target = temp_target_dir.path().join("broken_symlink_target");
fs::File::create(&broken_symlink_target)?;
#[cfg(unix)]
unix::fs::symlink(&broken_symlink_target, &broken_symlink_link)?;
#[cfg(windows)]
windows::fs::symlink_file(&broken_symlink_target, &broken_symlink_link)?;
}
Ok(broken_symlink_link)
}
/// Get the root directory for the tests.
pub fn test_root(&self) -> PathBuf {
self.temp_dir.path().to_path_buf()
@@ -159,11 +191,55 @@ impl TestEnv {
PathBuf::from(components.next().expect("root directory").as_os_str())
}
/// Assert that calling *fd* in the specified path under the root working directory,
/// and with the specified arguments produces the expected output.
pub fn assert_success_and_get_output<P: AsRef<Path>>(
&self,
path: P,
args: &[&str],
) -> process::Output {
// Setup *fd* command.
let mut cmd = process::Command::new(&self.fd_exe);
cmd.current_dir(self.temp_dir.path().join(path));
cmd.arg("--no-global-ignore-file").args(args);
// Run *fd*.
let output = cmd.output().expect("fd output");
// Check for exit status.
if !output.status.success() {
panic!("{}", format_exit_error(args, &output));
}
output
}
pub fn assert_success_and_get_normalized_output<P: AsRef<Path>>(
&self,
path: P,
args: &[&str],
) -> String {
let output = self.assert_success_and_get_output(path, args);
normalize_output(
&String::from_utf8_lossy(&output.stdout),
false,
self.normalize_line,
)
}
/// Assert that calling *fd* with the specified arguments produces the expected output.
pub fn assert_output(&self, args: &[&str], expected: &str) {
self.assert_output_subdirectory(".", args, expected)
}
/// Similar to assert_output, but able to handle non-utf8 output
#[cfg(all(unix, not(target_os = "macos")))]
pub fn assert_output_raw(&self, args: &[&str], expected: &[u8]) {
let output = self.assert_success_and_get_output(".", args);
assert_eq!(expected, &output.stdout[..]);
}
/// Assert that calling *fd* in the specified path under the root working directory,
/// and with the specified arguments produces the expected output.
pub fn assert_output_subdirectory<P: AsRef<Path>>(
@@ -172,26 +248,68 @@ impl TestEnv {
args: &[&str],
expected: &str,
) {
// Normalize both expected and actual output.
let expected = normalize_output(expected, true, self.normalize_line);
let actual = self.assert_success_and_get_normalized_output(path, args);
// 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,
/// and does not succeed.
pub fn assert_failure_with_error(&self, args: &[&str], expected: &str) {
let status = self.assert_error_subdirectory(".", args, Some(expected));
if status.success() {
panic!("error '{}' did not occur.", expected);
}
}
/// Assert that calling *fd* with the specified arguments does not succeed.
pub fn assert_failure(&self, args: &[&str]) {
let status = self.assert_error_subdirectory(".", args, None);
if status.success() {
panic!("Failure did not occur as expected.");
}
}
/// Assert that calling *fd* with the specified arguments produces the expected error.
pub fn assert_error(&self, args: &[&str], expected: &str) -> process::ExitStatus {
self.assert_error_subdirectory(".", args, Some(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: Option<&str>,
) -> process::ExitStatus {
// Setup *fd* command.
let mut cmd = process::Command::new(&self.fd_exe);
cmd.current_dir(self.temp_dir.path().join(path));
cmd.args(args);
cmd.arg("--no-global-ignore-file").args(args);
// Run *fd*.
let output = cmd.output().expect("fd output");
// Check for exit status.
if !output.status.success() {
panic!(format_exit_error(args, &output));
if let Some(expected) = expected {
// Normalize both expected and actual output.
let expected_error = trim_lines(expected);
let actual_err = trim_lines(&String::from_utf8_lossy(&output.stderr));
// Compare actual output to expected output.
if !actual_err.trim_start().starts_with(&expected_error) {
panic!(
"{}",
format_output_error(args, &expected_error, &actual_err)
);
}
}
// Normalize both expected and actual output.
let expected = normalize_output(expected, true);
let actual = normalize_output(&String::from_utf8_lossy(&output.stdout), false);
// Compare actual output to expected output.
if expected != actual {
panic!(format_output_error(args, &expected, &actual));
}
output.status
}
}
+1157 -317
View File
File diff suppressed because it is too large Load Diff