mirror of
https://github.com/sharkdp/fd.git
synced 2026-09-01 04:28:07 +08:00
Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f5ed8534e | |||
| c48d02e0a9 | |||
| b3399c239b | |||
| 4377954cf9 | |||
| 125cb81a5b | |||
| 2e9be3e3f5 | |||
| 43f276e073 | |||
| ec38e23d58 | |||
| 56060a5b6b | |||
| 03548a847f | |||
| 06cd391845 | |||
| ef4abfc1d2 | |||
| 72895675ef | |||
| ccc6cc54b2 | |||
| aa1c0250b7 | |||
| d5f740c9d3 | |||
| aa6933d9e3 | |||
| c800c79976 | |||
| 075c1af7d8 | |||
| fbebaa299d | |||
| f6bbeb35f5 | |||
| 0dc49b07a0 | |||
| c2e5075316 | |||
| e7f192f1c6 | |||
| e5145ffb98 | |||
| 30add71233 | |||
| dade9736ac | |||
| 639ed34791 | |||
| da7ea79034 | |||
| b7e077320d | |||
| 81669f4c10 | |||
| 5ea7cb7a05 | |||
| 4baefb2f5a | |||
| 97a8825b00 | |||
| 7fe4bfaacb | |||
| 1a6638ba23 | |||
| 8772708aa7 | |||
| a4bb734482 | |||
| 5fbfdfadb8 | |||
| 16ae03c3b4 | |||
| 52f22557a5 | |||
| 72cc9a876c | |||
| 5d06491370 | |||
| 67aaf82d37 | |||
| f219da4b3a | |||
| e990a13405 | |||
| 7b7876e701 | |||
| feac240eb5 | |||
| fba6db5096 | |||
| f2eb1be678 | |||
| b211ded5de | |||
| 92bd7850d0 | |||
| 06c58b99f5 | |||
| 776f8d0f11 | |||
| 47421a49b8 | |||
| f347379fb0 | |||
| 4d7e34ad59 | |||
| 359c0269ba | |||
| 13cdfb32f8 | |||
| 0484486f3f | |||
| f32060b0de | |||
| fe992706ae | |||
| 2b1bf471b1 | |||
| cab31e280b | |||
| 088e42deef | |||
| ecdaf58e7f | |||
| fc2a972082 | |||
| 66e3ccc5e1 | |||
| 21fd013073 | |||
| a539181f1f | |||
| cdc6a37ed6 | |||
| 2570fbd04e | |||
| 690976380d | |||
| 828649a30d | |||
| 20dc7a984d | |||
| 653bc0e55d | |||
| e8fe1508e2 | |||
| fd493eb709 | |||
| f1b39d49c1 | |||
| 1236b1dbcf | |||
| 17dd2a6dfe | |||
| bf9e6fd36e | |||
| 46db1c4ef3 | |||
| b9cb5d54a4 | |||
| a41021b0d2 | |||
| 82f04755bf | |||
| c42bf4b904 | |||
| fbc836b553 | |||
| 91860bf682 | |||
| a4a4709320 | |||
| b6f8bc8ff8 | |||
| 782fc278aa | |||
| 12650a0913 | |||
| 2e115df9e7 | |||
| 953f586f26 | |||
| 08d913f167 | |||
| 6b92c5db90 | |||
| 7b5b3ec47b | |||
| 02e9850112 | |||
| b8c575cc8f | |||
| 3de948ae0d | |||
| 45a86459b2 | |||
| a64a607fd8 | |||
| 00eb6461cb | |||
| feb969881b | |||
| c06efe1317 | |||
| 04fac46c5c | |||
| e0d03d170e | |||
| fd1c3d376e | |||
| c3f786db43 | |||
| a5f17db53a | |||
| f1a5aa0887 | |||
| 3c619afe30 | |||
| 476d404938 | |||
| 43f5c8adc9 | |||
| c749c95136 | |||
| 3ebd78cf02 | |||
| 8da936abd8 | |||
| 668af05dca | |||
| 3ba90dd768 | |||
| 87caef9513 | |||
| 78e21395b8 | |||
| 1b52948e42 | |||
| 334488cab7 | |||
| 1c72f80ff5 | |||
| 5b4869a940 | |||
| 9bc8fabba0 | |||
| 8984e57dd6 | |||
| 049232439a | |||
| e4730603d0 | |||
| 154cd6e329 | |||
| 394e967b5f | |||
| 0f0fa76c81 | |||
| 1795e0e9dd | |||
| 500f404c4f | |||
| 6a18b36037 | |||
| 115ae93df9 | |||
| c06c9952b6 | |||
| 42dce35dd7 | |||
| c2e4be3d12 | |||
| 37852aa388 | |||
| 25d7c4801f | |||
| d34e18a58c | |||
| ba60a163fe | |||
| 4e04d13215 | |||
| f8ae334ca9 |
@@ -0,0 +1 @@
|
||||
github: [sharkdp, tavianator]
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug.
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug you encountered:**
|
||||
|
||||
<!--
|
||||
Please check out the troubleshooting section first:
|
||||
https://github.com/sharkdp/fd#troubleshooting
|
||||
-->
|
||||
|
||||
|
||||
**Describe what you expected to happen:**
|
||||
|
||||
|
||||
**What version of `fd` are you using?**
|
||||
<!-- paste the output of `fd --version` here -->
|
||||
|
||||
**Which operating system / distribution are you on?**
|
||||
<!--
|
||||
Unix: paste the output of `uname -srm` and `lsb_release -a` here.
|
||||
Windows: please tell us your Windows version
|
||||
-->
|
||||
@@ -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,2 +1 @@
|
||||
blank_issues_enabled: true
|
||||
|
||||
|
||||
@@ -6,5 +6,3 @@ labels: feature-request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
+46
-15
@@ -1,7 +1,7 @@
|
||||
name: CICD
|
||||
|
||||
env:
|
||||
MIN_SUPPORTED_RUST_VERSION: "1.42.0"
|
||||
MIN_SUPPORTED_RUST_VERSION: "1.54.0"
|
||||
CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
||||
|
||||
on:
|
||||
@@ -14,9 +14,35 @@ on:
|
||||
- '*'
|
||||
|
||||
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-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
@@ -26,17 +52,18 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }}
|
||||
default: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
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: --all-targets --all-features
|
||||
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 }})
|
||||
@@ -45,13 +72,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { os: ubuntu-18.04, target: arm-unknown-linux-gnueabihf , use-cross: true }
|
||||
- { os: ubuntu-18.04, target: arm-unknown-linux-musleabihf, use-cross: true }
|
||||
- { os: ubuntu-18.04, target: aarch64-unknown-linux-gnu , use-cross: true }
|
||||
- { os: ubuntu-18.04, target: i686-unknown-linux-gnu , use-cross: true }
|
||||
- { os: ubuntu-18.04, target: i686-unknown-linux-musl , use-cross: true }
|
||||
- { os: ubuntu-18.04, target: x86_64-unknown-linux-gnu }
|
||||
- { os: ubuntu-18.04, target: x86_64-unknown-linux-musl , use-cross: true }
|
||||
- { 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 }
|
||||
@@ -100,7 +127,7 @@ jobs:
|
||||
with:
|
||||
use-cross: ${{ matrix.job.use-cross }}
|
||||
command: build
|
||||
args: --release --target=${{ matrix.job.target }}
|
||||
args: --locked --release --target=${{ matrix.job.target }}
|
||||
|
||||
- name: Strip debug information from executable
|
||||
id: strip
|
||||
@@ -142,6 +169,7 @@ jobs:
|
||||
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}
|
||||
@@ -151,7 +179,7 @@ jobs:
|
||||
with:
|
||||
use-cross: ${{ matrix.job.use-cross }}
|
||||
command: test
|
||||
args: --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
|
||||
args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
|
||||
|
||||
- name: Create tarball
|
||||
id: package
|
||||
@@ -292,8 +320,11 @@ jobs:
|
||||
Architecture: ${DPKG_ARCH}
|
||||
Provides: ${{ env.PROJECT_NAME }}
|
||||
Conflicts: ${DPKG_CONFLICTS}
|
||||
Description: cat(1) clone with wings.
|
||||
A cat(1) clone with syntax highlighting and Git integration.
|
||||
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}"
|
||||
|
||||
+78
-13
@@ -1,13 +1,77 @@
|
||||
# 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
|
||||
|
||||
@@ -17,6 +81,7 @@
|
||||
|
||||
- Many documentation updates
|
||||
|
||||
|
||||
# v8.2.1
|
||||
|
||||
No functional changes with respect to v8.2.0. Bugfix in the release process.
|
||||
@@ -455,7 +520,7 @@ I'd also like to take this chance to say a special Thank You to a few people tha
|
||||
* 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
|
||||
* Proper Windows support, see #70
|
||||
|
||||
## Changes
|
||||
|
||||
@@ -483,9 +548,9 @@ I'd also like to take this chance to say a special Thank You to a few people tha
|
||||
|
||||
* 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
|
||||
* 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:
|
||||
@@ -493,8 +558,8 @@ Misc:
|
||||
|
||||
# v1.1.0
|
||||
|
||||
- Windows compatibility (@sebasv), see #29 #35
|
||||
- Safely exit on broken output pipes (e.g.: usage with `head`, `tail`, ..), see #24
|
||||
- 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
|
||||
@@ -506,17 +571,17 @@ Misc:
|
||||
|
||||
# v0.3.0
|
||||
|
||||
- Parse dircolors files, closes #20
|
||||
- Colorize each path component, closes #19
|
||||
- Add short command line option for --hidden, see #18
|
||||
- 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
|
||||
- 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
|
||||
- Option to search hidden directories, closes #12
|
||||
- Configurable search depth, closes #13
|
||||
- Detect interactive terminal, closes #11
|
||||
|
||||
# v0.1.0
|
||||
|
||||
|
||||
Generated
+230
-111
@@ -11,15 +11,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
@@ -31,9 +22,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.42"
|
||||
version = "1.0.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
|
||||
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
@@ -54,30 +45,24 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.14"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.66"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -86,37 +71,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.3"
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||
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 = [
|
||||
"ansi_term 0.11.0",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"term_size",
|
||||
"termcolor",
|
||||
"terminal_size",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[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.1"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
|
||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.1.9"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "232295399409a8b7ae41276757b5a1cc21032848d42bff2352261f958b3ca29a"
|
||||
checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"winapi",
|
||||
@@ -134,15 +141,15 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
@@ -151,12 +158,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fd-find"
|
||||
version = "8.2.1"
|
||||
version = "8.3.2"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
"atty",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"ctrlc",
|
||||
"diff",
|
||||
"dirs-next",
|
||||
@@ -165,27 +174,29 @@ dependencies = [
|
||||
"humantime",
|
||||
"ignore",
|
||||
"jemallocator",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"lscolors",
|
||||
"nix",
|
||||
"normpath",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"tempdir",
|
||||
"test-case",
|
||||
"users",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.14"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
|
||||
checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.2.10",
|
||||
"redox_syscall",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -209,11 +220,11 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.15"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
@@ -232,25 +243,31 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.17"
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.0.1"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.17"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b287fb45c60bb826a0dc68ff08742b9d88a2fea13d6e0c286b3172065aaf878c"
|
||||
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"globset",
|
||||
@@ -264,6 +281,16 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jemalloc-sys"
|
||||
version = "0.3.2"
|
||||
@@ -293,65 +320,127 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.98"
|
||||
version = "0.2.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.11"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lscolors"
|
||||
version = "0.7.1"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e"
|
||||
checksum = "9dd58d8727f3035fa6d5272f16b519741fd4875936b99d8a7cde21291b7d9174"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"ansi_term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.20.0"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a"
|
||||
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "normpath"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27e6e8f70e9fbbe3752d330d769e3424f24b9458ce266df93a3b456902fd696a"
|
||||
checksum = "04aaf5e9cb0fbf883cc0423159eacdf96a9878022084b35c462c428cab73bcaf"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
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.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
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.6"
|
||||
@@ -389,12 +478,6 @@ dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.10"
|
||||
@@ -406,12 +489,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.3.5"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
|
||||
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall 0.1.57",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -451,9 +534,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
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"
|
||||
@@ -466,39 +560,70 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term_size"
|
||||
version = "0.3.2"
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
name = "test-case"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
checksum = "c7cad0a06f9a61e94355aa3b3dc92d85ab9c83406722b1ca5e918d4297c12c23"
|
||||
dependencies = [
|
||||
"term_size",
|
||||
"unicode-width",
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||
dependencies = [
|
||||
"terminal_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.0.1"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
name = "time"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "users"
|
||||
@@ -510,23 +635,17 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.1"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
|
||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi",
|
||||
@@ -535,9 +654,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
|
||||
+21
-13
@@ -16,7 +16,7 @@ license = "MIT/Apache-2.0"
|
||||
name = "fd-find"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/sharkdp/fd"
|
||||
version = "8.2.1"
|
||||
version = "8.3.2"
|
||||
edition= "2018"
|
||||
|
||||
[badges.appveyor]
|
||||
@@ -30,31 +30,34 @@ name = "fd"
|
||||
path = "src/main.rs"
|
||||
|
||||
[build-dependencies]
|
||||
clap = "2.31.2"
|
||||
clap = "3.0"
|
||||
clap_complete = "3.0"
|
||||
version_check = "0.9"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12"
|
||||
atty = "0.2"
|
||||
ignore = "0.4.3"
|
||||
lazy_static = "1.1.0"
|
||||
num_cpus = "1.8"
|
||||
num_cpus = "1.13"
|
||||
regex = "1.5.4"
|
||||
regex-syntax = "0.6"
|
||||
ctrlc = "3.1"
|
||||
humantime = "2.0"
|
||||
lscolors = "0.7"
|
||||
ctrlc = "3.2"
|
||||
humantime = "2.1"
|
||||
lscolors = "0.8"
|
||||
globset = "0.4"
|
||||
anyhow = "1.0"
|
||||
dirs-next = "2.0"
|
||||
normpath = "0.3"
|
||||
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"
|
||||
@@ -62,14 +65,19 @@ 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_env = "musl")))'.dependencies]
|
||||
jemallocator = "0.3.0"
|
||||
[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.14"
|
||||
filetime = "0.2"
|
||||
test-case = "1.2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
||||
[features]
|
||||
use-jemalloc = ["jemallocator"]
|
||||
default = ["use-jemalloc"]
|
||||
|
||||
@@ -115,6 +115,15 @@ 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:
|
||||
@@ -136,6 +145,15 @@ 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`
|
||||
@@ -176,6 +194,11 @@ 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
|
||||
@@ -277,8 +300,7 @@ FLAGS:
|
||||
-a, --absolute-path Show absolute instead of relative paths
|
||||
-l, --list-details Use a long listing format with file metadata
|
||||
-L, --follow Follow symbolic links
|
||||
-p, --full-path Search full path (default: file-/dirname only)
|
||||
-0, --print0 Separate results by the null character
|
||||
-p, --full-path Search full abs. path (default: filename only)
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
@@ -291,12 +313,13 @@ OPTIONS:
|
||||
-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.
|
||||
-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)
|
||||
<pattern> the search pattern (a regular expression, unless '--glob' is used; optional)
|
||||
<path>... the root directory for the filesystem search (optional)
|
||||
```
|
||||
|
||||
@@ -400,6 +423,14 @@ use a character class with a single hyphen character:
|
||||
> 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`
|
||||
@@ -504,7 +535,7 @@ 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_8.2.1_amd64.deb # adapt version number and architecture
|
||||
sudo dpkg -i fd_8.3.2_amd64.deb # adapt version number and architecture
|
||||
```
|
||||
|
||||
### On Debian
|
||||
@@ -607,7 +638,7 @@ You can install [the fd-find package](https://www.freshports.org/sysutils/fd) fr
|
||||
pkg install fd-find
|
||||
```
|
||||
|
||||
### From NPM
|
||||
### From npm
|
||||
|
||||
On linux and macOS, you can install the [fd-find](https://npm.im/fd-find) package:
|
||||
|
||||
@@ -621,7 +652,9 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can
|
||||
```
|
||||
cargo install fd-find
|
||||
```
|
||||
Note that rust version *1.42.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
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::fs;
|
||||
|
||||
use clap::Shell;
|
||||
use clap_complete::{generate_to, Shell};
|
||||
use Shell::*;
|
||||
//use clap_complete::shells::Shel{Bash, Fish, PowerShell, Elvish};
|
||||
|
||||
include!("src/app.rs");
|
||||
|
||||
fn main() {
|
||||
let min_version = "1.42";
|
||||
let min_version = "1.54";
|
||||
|
||||
match version_check::is_min_version(min_version) {
|
||||
Some(true) => {}
|
||||
@@ -24,7 +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::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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
msrv = "1.54.0"
|
||||
@@ -57,6 +57,7 @@ _fd() {
|
||||
+ 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]'
|
||||
@@ -138,6 +139,7 @@ _fd() {
|
||||
+ '(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)'
|
||||
@@ -153,6 +155,9 @@ _fd() {
|
||||
$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 -/'
|
||||
@@ -220,7 +225,7 @@ _fd() {
|
||||
_fd "$@"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright (c) 2011 Github zsh-users - http://github.com/zsh-users
|
||||
# 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
|
||||
|
||||
@@ -28,22 +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 ,
|
||||
.IR .ignore ,
|
||||
.IR .fdignore ,
|
||||
or the global ignore file.
|
||||
.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
|
||||
.IP \[bu]
|
||||
.I .fdignore
|
||||
.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
|
||||
@@ -55,6 +80,7 @@ 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.
|
||||
@@ -65,6 +91,7 @@ performs substring comparison. If you want to match on an exact filename, consid
|
||||
.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
|
||||
@@ -74,7 +101,7 @@ 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
|
||||
@@ -92,10 +119,21 @@ Limit the number of search results to 'count' and quit immediately.
|
||||
.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
|
||||
@@ -128,18 +166,40 @@ regular files
|
||||
directories
|
||||
.IP "l, symlink"
|
||||
symbolic links
|
||||
.IP "x, executable"
|
||||
executable (files)
|
||||
.IP "e, empty"
|
||||
empty files or directories
|
||||
.IP "s, socket"
|
||||
sockets
|
||||
.IP "p, pipe"
|
||||
named pipes (FIFOs)
|
||||
.IP "x, executable"
|
||||
executable (files)
|
||||
.IP "e, empty"
|
||||
empty files or directories
|
||||
.RE
|
||||
|
||||
.RS
|
||||
This option can be used repeatedly to allow for multiple file types.
|
||||
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
|
||||
@@ -216,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.
|
||||
|
||||
@@ -226,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.
|
||||
|
||||
@@ -335,7 +401,11 @@ Examples:
|
||||
|
||||
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:
|
||||
|
||||
@@ -373,5 +443,11 @@ $ 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)
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
Vendored
+161
@@ -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 |
Vendored
+1
-1
@@ -20,7 +20,7 @@ enter() {
|
||||
}
|
||||
|
||||
prompt() {
|
||||
printf '%b ' $PROMPT | pv -q
|
||||
printf '%b ' "$PROMPT" | pv -q
|
||||
}
|
||||
|
||||
type() {
|
||||
|
||||
+270
-115
@@ -1,75 +1,119 @@
|
||||
use clap::{crate_version, App, AppSettings, Arg};
|
||||
use clap::{crate_version, App, AppSettings, Arg, ColorChoice};
|
||||
|
||||
pub fn build_app() -> App<'static, 'static> {
|
||||
let clap_color_setting = if std::env::var_os("NO_COLOR").is_none() {
|
||||
AppSettings::ColoredHelp
|
||||
pub fn build_app() -> App<'static> {
|
||||
let clap_color_choice = if std::env::var_os("NO_COLOR").is_none() {
|
||||
ColorChoice::Auto
|
||||
} else {
|
||||
AppSettings::ColorNever
|
||||
ColorChoice::Never
|
||||
};
|
||||
|
||||
let mut app = App::new("fd")
|
||||
.version(crate_version!())
|
||||
.usage("fd [FLAGS/OPTIONS] [<pattern>] [<path>...]")
|
||||
.setting(clap_color_setting)
|
||||
.color(clap_color_choice)
|
||||
.setting(AppSettings::DeriveDisplayOrder)
|
||||
.setting(AppSettings::DontCollapseArgsInUsage)
|
||||
.after_help(
|
||||
"Note: `fd -h` prints a short and concise overview while `fd --help` gives all \
|
||||
details.",
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("hidden")
|
||||
Arg::new("hidden")
|
||||
.long("hidden")
|
||||
.short("H")
|
||||
.short('H')
|
||||
.overrides_with("hidden")
|
||||
.help("Search hidden files and directories")
|
||||
.long_help(
|
||||
"Include hidden directories and files in the search results (default: \
|
||||
hidden files and directories are skipped). Files and directories are \
|
||||
considered to be hidden if their name starts with a `.` sign (dot).",
|
||||
considered to be hidden if their name starts with a `.` sign (dot). \
|
||||
The flag can be overridden with --no-hidden.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-ignore")
|
||||
Arg::new("no-hidden")
|
||||
.long("no-hidden")
|
||||
.overrides_with("hidden")
|
||||
.hide(true)
|
||||
.long_help(
|
||||
"Overrides --hidden.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no-ignore")
|
||||
.long("no-ignore")
|
||||
.short("I")
|
||||
.short('I')
|
||||
.overrides_with("no-ignore")
|
||||
.help("Do not respect .(git|fd)ignore files")
|
||||
.long_help(
|
||||
"Show search results from files and directories that would otherwise be \
|
||||
ignored by '.gitignore', '.ignore', '.fdignore', or the global ignore file.",
|
||||
ignored by '.gitignore', '.ignore', '.fdignore', or the global ignore file. \
|
||||
The flag can be overridden with --ignore.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-ignore-vcs")
|
||||
Arg::new("ignore")
|
||||
.long("ignore")
|
||||
.overrides_with("no-ignore")
|
||||
.hide(true)
|
||||
.long_help(
|
||||
"Overrides --no-ignore.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no-ignore-vcs")
|
||||
.long("no-ignore-vcs")
|
||||
.overrides_with("no-ignore-vcs")
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.help("Do not respect .gitignore files")
|
||||
.long_help(
|
||||
"Show search results from files and directories that would otherwise be \
|
||||
ignored by '.gitignore' files.",
|
||||
ignored by '.gitignore' files. The flag can be overridden with --ignore-vcs.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-global-ignore-file")
|
||||
Arg::new("ignore-vcs")
|
||||
.long("ignore-vcs")
|
||||
.overrides_with("no-ignore-vcs")
|
||||
.hide(true)
|
||||
.long_help(
|
||||
"Overrides --no-ignore-vcs.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no-ignore-parent")
|
||||
.long("no-ignore-parent")
|
||||
.overrides_with("no-ignore-parent")
|
||||
.hide_short_help(true)
|
||||
.help("Do not respect .(git|fd)ignore files in parent directories")
|
||||
.long_help(
|
||||
"Show search results from files and directories that would otherwise be \
|
||||
ignored by '.gitignore', '.ignore', or '.fdignore' files in parent directories.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no-global-ignore-file")
|
||||
.long("no-global-ignore-file")
|
||||
.hidden(true)
|
||||
.hide(true)
|
||||
.help("Do not respect the global ignore file")
|
||||
.long_help("Do not respect the global ignore file."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rg-alias-hidden-ignore")
|
||||
.short("u")
|
||||
Arg::new("rg-alias-hidden-ignore")
|
||||
.short('u')
|
||||
.long("unrestricted")
|
||||
.multiple(true)
|
||||
.hidden_short_help(true)
|
||||
.overrides_with_all(&["ignore", "no-hidden"])
|
||||
.multiple_occurrences(true)
|
||||
.hide_short_help(true)
|
||||
.help("Alias for '--no-ignore', and '--hidden' when given twice")
|
||||
.long_help(
|
||||
"Alias for '--no-ignore'. Can be repeated. '-uu' is an alias for \
|
||||
'--no-ignore --hidden'.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("case-sensitive")
|
||||
Arg::new("case-sensitive")
|
||||
.long("case-sensitive")
|
||||
.short("s")
|
||||
.short('s')
|
||||
.overrides_with_all(&["ignore-case", "case-sensitive"])
|
||||
.help("Case-sensitive search (default: smart case)")
|
||||
.long_help(
|
||||
@@ -79,9 +123,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ignore-case")
|
||||
Arg::new("ignore-case")
|
||||
.long("ignore-case")
|
||||
.short("i")
|
||||
.short('i')
|
||||
.overrides_with_all(&["case-sensitive", "ignore-case"])
|
||||
.help("Case-insensitive search (default: smart case)")
|
||||
.long_help(
|
||||
@@ -91,31 +135,33 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("glob")
|
||||
Arg::new("glob")
|
||||
.long("glob")
|
||||
.short("g")
|
||||
.short('g')
|
||||
.conflicts_with("fixed-strings")
|
||||
.overrides_with("glob")
|
||||
.help("Glob-based search (default: regular expression)")
|
||||
.long_help("Perform a glob-based search instead of a regular expression search."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("regex")
|
||||
Arg::new("regex")
|
||||
.long("regex")
|
||||
.overrides_with_all(&["glob", "regex"])
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.help("Regular-expression based search (default)")
|
||||
.long_help(
|
||||
"Perform a regular-expression based search (default). This can be used to \
|
||||
override --glob.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("fixed-strings")
|
||||
Arg::new("fixed-strings")
|
||||
.long("fixed-strings")
|
||||
.short("F")
|
||||
.short('F')
|
||||
.alias("literal")
|
||||
.overrides_with("fixed-strings")
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.help("Treat pattern as literal string instead of regex")
|
||||
.long_help(
|
||||
"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 \
|
||||
@@ -123,19 +169,29 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("absolute-path")
|
||||
Arg::new("absolute-path")
|
||||
.long("absolute-path")
|
||||
.short("a")
|
||||
.short('a')
|
||||
.overrides_with("absolute-path")
|
||||
.help("Show absolute instead of relative paths")
|
||||
.long_help(
|
||||
"Shows the full path starting from the root as opposed to relative paths.",
|
||||
"Shows the full path starting from the root as opposed to relative paths. \
|
||||
The flag can be overridden with --relative-path.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("list-details")
|
||||
Arg::new("relative-path")
|
||||
.long("relative-path")
|
||||
.overrides_with("absolute-path")
|
||||
.hide(true)
|
||||
.long_help(
|
||||
"Overrides --absolute-path.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("list-details")
|
||||
.long("list-details")
|
||||
.short("l")
|
||||
.short('l')
|
||||
.conflicts_with("absolute-path")
|
||||
.help("Use a long listing format with file metadata")
|
||||
.long_help(
|
||||
@@ -146,36 +202,47 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("follow")
|
||||
Arg::new("follow")
|
||||
.long("follow")
|
||||
.short("L")
|
||||
.short('L')
|
||||
.alias("dereference")
|
||||
.overrides_with("follow")
|
||||
.help("Follow symbolic links")
|
||||
.long_help(
|
||||
"By default, fd does not descend into symlinked directories. Using this \
|
||||
flag, symbolic links are also traversed.",
|
||||
flag, symbolic links are also traversed. \
|
||||
Flag can be overriden with --no-follow.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("full-path")
|
||||
Arg::new("no-follow")
|
||||
.long("no-follow")
|
||||
.overrides_with("follow")
|
||||
.hide(true)
|
||||
.long_help(
|
||||
"Overrides --follow.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("full-path")
|
||||
.long("full-path")
|
||||
.short("p")
|
||||
.short('p')
|
||||
.overrides_with("full-path")
|
||||
.help("Search full path (default: file-/dirname only)")
|
||||
.help("Search full abs. path (default: filename only)")
|
||||
.long_help(
|
||||
"By default, the search pattern is only matched against the filename (or \
|
||||
directory name). Using this flag, the pattern is matched against the \
|
||||
full path.",
|
||||
directory name). Using this flag, the pattern is matched against the full \
|
||||
(absolute) path. Example:\n \
|
||||
fd --glob -p '**/.git/config'",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("null_separator")
|
||||
Arg::new("null_separator")
|
||||
.long("print0")
|
||||
.short("0")
|
||||
.short('0')
|
||||
.overrides_with("print0")
|
||||
.conflicts_with("list-details")
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.help("Separate results by the null character")
|
||||
.long_help(
|
||||
"Separate search results by the null character (instead of newlines). \
|
||||
@@ -183,9 +250,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("max-depth")
|
||||
Arg::new("max-depth")
|
||||
.long("max-depth")
|
||||
.short("d")
|
||||
.short('d')
|
||||
.takes_value(true)
|
||||
.value_name("depth")
|
||||
.help("Set maximum search depth (default: none)")
|
||||
@@ -196,46 +263,51 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
)
|
||||
// support --maxdepth as well, for compatibility with rg
|
||||
.arg(
|
||||
Arg::with_name("rg-depth")
|
||||
Arg::new("rg-depth")
|
||||
.long("maxdepth")
|
||||
.hidden(true)
|
||||
.hide(true)
|
||||
.takes_value(true)
|
||||
.help("Set maximum search depth (default: none)")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("min-depth")
|
||||
Arg::new("min-depth")
|
||||
.long("min-depth")
|
||||
.takes_value(true)
|
||||
.value_name("depth")
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.help("Only show results starting at given depth")
|
||||
.long_help(
|
||||
"Only show search results starting at the given depth. \
|
||||
See also: '--max-depth' and '--exact-depth'",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("exact-depth")
|
||||
Arg::new("exact-depth")
|
||||
.long("exact-depth")
|
||||
.takes_value(true)
|
||||
.value_name("depth")
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.conflicts_with_all(&["max-depth", "min-depth"])
|
||||
.help("Only show results at exact given depth")
|
||||
.long_help(
|
||||
"Only show search results at the exact given depth. This is an alias for \
|
||||
'--min-depth <depth> --max-depth <depth>'.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("prune")
|
||||
Arg::new("prune")
|
||||
.long("prune")
|
||||
.conflicts_with_all(&["size", "exact-depth"])
|
||||
.hidden_short_help(true)
|
||||
.long_help("Do not traverse into matching directories.")
|
||||
.hide_short_help(true)
|
||||
.help("Do not traverse into matching directories")
|
||||
.long_help("Do not traverse into directories that match the search criteria. If \
|
||||
you want to exclude specific directories, use the '--exclude=…' option.")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("file-type")
|
||||
Arg::new("file-type")
|
||||
.long("type")
|
||||
.short("t")
|
||||
.multiple(true)
|
||||
.short('t')
|
||||
.multiple_occurrences(true)
|
||||
.number_of_values(1)
|
||||
.takes_value(true)
|
||||
.value_name("filetype")
|
||||
@@ -261,21 +333,43 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
empty (e), socket (s), pipe (p)",
|
||||
)
|
||||
.long_help(
|
||||
"Filter the search by type (multiple allowable filetypes can be specified):\n \
|
||||
"Filter the search by type:\n \
|
||||
'f' or 'file': regular files\n \
|
||||
'd' or 'directory': directories\n \
|
||||
'l' or 'symlink': symbolic links\n \
|
||||
'x' or 'executable': executables\n \
|
||||
'e' or 'empty': empty files or directories\n \
|
||||
's' or 'socket': socket\n \
|
||||
'p' or 'pipe': named pipe (FIFO)",
|
||||
'p' or 'pipe': named pipe (FIFO)\n\n \
|
||||
'x' or 'executable': executables\n \
|
||||
'e' or 'empty': empty files or directories\n\n\
|
||||
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.\n\n\
|
||||
Examples:\n \
|
||||
- Only search for files:\n \
|
||||
fd --type file …\n \
|
||||
fd -tf …\n \
|
||||
- Find both files and symlinks\n \
|
||||
fd --type file --type symlink …\n \
|
||||
fd -tf -tl …\n \
|
||||
- Find executable files:\n \
|
||||
fd --type executable\n \
|
||||
fd -tx\n \
|
||||
- Find empty files:\n \
|
||||
fd --type empty --type file\n \
|
||||
fd -te -tf\n \
|
||||
- Find empty directories:\n \
|
||||
fd --type empty --type directory\n \
|
||||
fd -te -td"
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("extension")
|
||||
Arg::new("extension")
|
||||
.long("extension")
|
||||
.short("e")
|
||||
.multiple(true)
|
||||
.short('e')
|
||||
.multiple_occurrences(true)
|
||||
.number_of_values(1)
|
||||
.takes_value(true)
|
||||
.value_name("ext")
|
||||
@@ -288,9 +382,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("exec")
|
||||
Arg::new("exec")
|
||||
.long("exec")
|
||||
.short("x")
|
||||
.short('x')
|
||||
.min_values(1)
|
||||
.allow_hyphen_values(true)
|
||||
.value_terminator(";")
|
||||
@@ -319,9 +413,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("exec-batch")
|
||||
Arg::new("exec-batch")
|
||||
.long("exec-batch")
|
||||
.short("X")
|
||||
.short('X')
|
||||
.min_values(1)
|
||||
.allow_hyphen_values(true)
|
||||
.value_terminator(";")
|
||||
@@ -346,13 +440,28 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("exclude")
|
||||
Arg::new("batch-size")
|
||||
.long("batch-size")
|
||||
.takes_value(true)
|
||||
.value_name("size")
|
||||
.hide_short_help(true)
|
||||
.requires("exec-batch")
|
||||
.help("Max number of arguments to run as a batch with -X")
|
||||
.long_help(
|
||||
"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.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("exclude")
|
||||
.long("exclude")
|
||||
.short("E")
|
||||
.short('E')
|
||||
.takes_value(true)
|
||||
.value_name("pattern")
|
||||
.number_of_values(1)
|
||||
.multiple(true)
|
||||
.multiple_occurrences(true)
|
||||
.help("Exclude entries that match the given glob pattern")
|
||||
.long_help(
|
||||
"Exclude files/directories that match the given glob pattern. This \
|
||||
@@ -364,22 +473,23 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ignore-file")
|
||||
Arg::new("ignore-file")
|
||||
.long("ignore-file")
|
||||
.takes_value(true)
|
||||
.value_name("path")
|
||||
.number_of_values(1)
|
||||
.multiple(true)
|
||||
.hidden_short_help(true)
|
||||
.multiple_occurrences(true)
|
||||
.hide_short_help(true)
|
||||
.help("Add custom ignore-file in '.gitignore' format")
|
||||
.long_help(
|
||||
"Add a custom ignore-file in '.gitignore' format. These files have a low \
|
||||
precedence.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("color")
|
||||
Arg::new("color")
|
||||
.long("color")
|
||||
.short("c")
|
||||
.short('c')
|
||||
.takes_value(true)
|
||||
.value_name("when")
|
||||
.possible_values(&["never", "auto", "always"])
|
||||
@@ -393,26 +503,27 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("threads")
|
||||
Arg::new("threads")
|
||||
.long("threads")
|
||||
.short("j")
|
||||
.short('j')
|
||||
.takes_value(true)
|
||||
.value_name("num")
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.help("Set number of threads")
|
||||
.long_help(
|
||||
"Set number of threads to use for searching & executing (default: number \
|
||||
of available CPU cores)",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("size")
|
||||
Arg::new("size")
|
||||
.long("size")
|
||||
.short("S")
|
||||
.short('S')
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.allow_hyphen_values(true)
|
||||
.multiple(true)
|
||||
.help("Limit results based on the size of files.")
|
||||
.multiple_occurrences(true)
|
||||
.help("Limit results based on the size of files")
|
||||
.long_help(
|
||||
"Limit results based on the size of files using the format <+-><NUM><UNIT>.\n \
|
||||
'+': file size must be greater than or equal to this\n \
|
||||
@@ -433,17 +544,18 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("max-buffer-time")
|
||||
Arg::new("max-buffer-time")
|
||||
.long("max-buffer-time")
|
||||
.takes_value(true)
|
||||
.hidden(true)
|
||||
.hide(true)
|
||||
.help("Milliseconds to buffer before streaming search results to console")
|
||||
.long_help(
|
||||
"Amount of time in milliseconds to buffer, before streaming the search \
|
||||
results to the console.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("changed-within")
|
||||
Arg::new("changed-within")
|
||||
.long("changed-within")
|
||||
.alias("change-newer-than")
|
||||
.alias("newer")
|
||||
@@ -463,7 +575,7 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("changed-before")
|
||||
Arg::new("changed-before")
|
||||
.long("changed-before")
|
||||
.alias("change-older-than")
|
||||
.alias("older")
|
||||
@@ -482,7 +594,7 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("max-results")
|
||||
Arg::new("max-results")
|
||||
.long("max-results")
|
||||
.takes_value(true)
|
||||
.value_name("count")
|
||||
@@ -492,35 +604,55 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
// same search with `--exec rm` attached and get a reliable removal of
|
||||
// the files they saw in the previous search.
|
||||
.conflicts_with_all(&["exec", "exec-batch", "list-details"])
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.help("Limit number of search results")
|
||||
.long_help("Limit the number of search results to 'count' and quit immediately."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("max-one-result")
|
||||
.short("1")
|
||||
.hidden_short_help(true)
|
||||
Arg::new("max-one-result")
|
||||
.short('1')
|
||||
.hide_short_help(true)
|
||||
.overrides_with("max-results")
|
||||
.conflicts_with_all(&["exec", "exec-batch", "list-details"])
|
||||
.help("Limit search to a single result")
|
||||
.long_help("Limit the search to a single result and quit immediately. \
|
||||
This is an alias for '--max-results=1'.")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("show-errors")
|
||||
Arg::new("quiet")
|
||||
.long("quiet")
|
||||
.short('q')
|
||||
.alias("has-results")
|
||||
.hide_short_help(true)
|
||||
.conflicts_with_all(&["exec", "exec-batch", "list-details", "max-results"])
|
||||
.help("Print nothing, exit code 0 if match found, 1 otherwise")
|
||||
.long_help(
|
||||
"When the flag is present, the program does not print anything and will \
|
||||
return with an exit code of 0 if there is at least one match. Otherwise, the \
|
||||
exit code will be 1. \
|
||||
'--has-results' can be used as an alias."
|
||||
)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("show-errors")
|
||||
.long("show-errors")
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.overrides_with("show-errors")
|
||||
.help("Show filesystem errors")
|
||||
.long_help(
|
||||
"Enable the display of filesystem errors for situations such as \
|
||||
insufficient permissions or dead symlinks.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("base-directory")
|
||||
Arg::new("base-directory")
|
||||
.long("base-directory")
|
||||
.takes_value(true)
|
||||
.value_name("path")
|
||||
.number_of_values(1)
|
||||
.hidden_short_help(true)
|
||||
.allow_invalid_utf8(true)
|
||||
.hide_short_help(true)
|
||||
.help("Change current working directory")
|
||||
.long_help(
|
||||
"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 \
|
||||
@@ -530,7 +662,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("pattern").help(
|
||||
Arg::new("pattern")
|
||||
.allow_invalid_utf8(true)
|
||||
.help(
|
||||
"the search pattern (a regular expression, unless '--glob' is used; optional)",
|
||||
).long_help(
|
||||
"the search pattern which is either a regular expression (default) or a glob \
|
||||
@@ -539,19 +673,21 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
pass '--' first, or it will be considered as a flag (fd -- '-foo').")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("path-separator")
|
||||
Arg::new("path-separator")
|
||||
.takes_value(true)
|
||||
.value_name("separator")
|
||||
.long("path-separator")
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.help("Set path separator when printing file paths")
|
||||
.long_help(
|
||||
"Set the path separator to use when printing file paths. The default is \
|
||||
the OS-specific separator ('/' on Unix, '\\' on Windows).",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("path")
|
||||
.multiple(true)
|
||||
Arg::new("path")
|
||||
.multiple_occurrences(true)
|
||||
.allow_invalid_utf8(true)
|
||||
.help("the root directory for the filesystem search (optional)")
|
||||
.long_help(
|
||||
"The directory where the filesystem search is rooted (optional). If \
|
||||
@@ -559,25 +695,38 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("search-path")
|
||||
Arg::new("search-path")
|
||||
.long("search-path")
|
||||
.takes_value(true)
|
||||
.conflicts_with("path")
|
||||
.multiple(true)
|
||||
.hidden_short_help(true)
|
||||
.multiple_occurrences(true)
|
||||
.hide_short_help(true)
|
||||
.number_of_values(1)
|
||||
.allow_invalid_utf8(true)
|
||||
.help("Provide paths to search as an alternative to the positional <path>")
|
||||
.long_help(
|
||||
"Provide paths to search as an alternative to the positional <path> \
|
||||
argument. Changes the usage to `fd [FLAGS/OPTIONS] --search-path <path> \
|
||||
argument. Changes the usage to `fd [OPTIONS] --search-path <path> \
|
||||
--search-path <path2> [<pattern>]`",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("strip-cwd-prefix")
|
||||
.long("strip-cwd-prefix")
|
||||
.conflicts_with_all(&["path", "search-path"])
|
||||
.hide_short_help(true)
|
||||
.help("strip './' prefix from non-tty outputs")
|
||||
.long_help(
|
||||
"By default, relative paths are prefixed with './' when the output goes to a non \
|
||||
interactive terminal (TTY). Use this flag to disable this behaviour."
|
||||
)
|
||||
);
|
||||
|
||||
if cfg!(unix) {
|
||||
app = app.arg(
|
||||
Arg::with_name("owner")
|
||||
Arg::new("owner")
|
||||
.long("owner")
|
||||
.short("o")
|
||||
.short('o')
|
||||
.takes_value(true)
|
||||
.value_name("user:group")
|
||||
.help("Filter by owning user and/or group")
|
||||
@@ -598,10 +747,11 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
// Provide aliases `mount` and `xdev` for people coming from `find`.
|
||||
if cfg!(any(unix, windows)) {
|
||||
app = app.arg(
|
||||
Arg::with_name("one-file-system")
|
||||
Arg::new("one-file-system")
|
||||
.long("one-file-system")
|
||||
.aliases(&["mount", "xdev"])
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.help("Do not descend into a different file system")
|
||||
.long_help(
|
||||
"By default, fd will traverse the file system tree as far as other options \
|
||||
dictate. With this flag, fd ensures that it does not descend into a \
|
||||
@@ -613,3 +763,8 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
|
||||
app
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_app() {
|
||||
build_app().debug_assert()
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::filter::OwnerFilter;
|
||||
use crate::filter::{SizeFilter, TimeFilter};
|
||||
|
||||
/// Configuration options for *fd*.
|
||||
pub struct Options {
|
||||
pub struct Config {
|
||||
/// Whether the search is case-sensitive or case-insensitive.
|
||||
pub case_sensitive: bool,
|
||||
|
||||
@@ -24,6 +24,9 @@ pub struct Options {
|
||||
/// 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,
|
||||
|
||||
@@ -54,6 +57,10 @@ pub struct Options {
|
||||
/// 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`.
|
||||
@@ -78,6 +85,10 @@ pub struct Options {
|
||||
/// If a value is supplied, each item found will be used to generate and execute commands.
|
||||
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>,
|
||||
|
||||
@@ -102,4 +113,7 @@ pub struct Options {
|
||||
|
||||
/// 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,
|
||||
}
|
||||
+17
-4
@@ -26,7 +26,7 @@ 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.to_string());
|
||||
@@ -42,7 +42,7 @@ pub fn job(
|
||||
results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm), buffer_output))
|
||||
}
|
||||
// Returns error in case of any error.
|
||||
merge_exitcodes(&results)
|
||||
merge_exitcodes(results)
|
||||
}
|
||||
|
||||
pub fn batch(
|
||||
@@ -50,9 +50,10 @@ pub fn batch(
|
||||
cmd: &CommandTemplate,
|
||||
show_filesystem_errors: bool,
|
||||
buffer_output: bool,
|
||||
limit: usize,
|
||||
) -> ExitCode {
|
||||
let paths = rx.iter().filter_map(|value| match value {
|
||||
WorkerResult::Entry(val) => Some(val),
|
||||
WorkerResult::Entry(path) => Some(path),
|
||||
WorkerResult::Error(err) => {
|
||||
if show_filesystem_errors {
|
||||
print_error(err.to_string());
|
||||
@@ -60,5 +61,17 @@ pub fn batch(
|
||||
None
|
||||
}
|
||||
});
|
||||
cmd.generate_and_execute_batch(paths, buffer_output)
|
||||
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)
|
||||
}
|
||||
|
||||
+6
-10
@@ -10,11 +10,10 @@ use std::process::{Command, Stdio};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::filesystem::strip_current_dir;
|
||||
|
||||
use self::command::execute_command;
|
||||
use self::input::{basename, dirname, remove_extension};
|
||||
@@ -72,9 +71,8 @@ impl CommandTemplate {
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
lazy_static! {
|
||||
static ref PLACEHOLDER_PATTERN: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap();
|
||||
}
|
||||
static PLACEHOLDER_PATTERN: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"\{(/?\.?|//)\}").unwrap());
|
||||
|
||||
let mut args = Vec::new();
|
||||
let mut has_placeholder = false;
|
||||
@@ -145,8 +143,6 @@ impl CommandTemplate {
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
buffer_output: bool,
|
||||
) -> ExitCode {
|
||||
let input = strip_current_dir(input);
|
||||
|
||||
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, self.path_separator.as_deref()));
|
||||
@@ -178,7 +174,7 @@ impl CommandTemplate {
|
||||
// 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(strip_current_dir(path), self.path_separator.as_deref()));
|
||||
cmd.arg(arg.generate(path, self.path_separator.as_deref()));
|
||||
has_path = true;
|
||||
}
|
||||
} else {
|
||||
@@ -450,11 +446,11 @@ mod tests {
|
||||
// This is uncommon, but valid
|
||||
check!(r"C:foo\bar", "C:foo#bar");
|
||||
|
||||
// forward slashses should get normalized and interpreted as separators
|
||||
// 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 intrepret "//server/share" as a UNC path, but rather as a normal
|
||||
// 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");
|
||||
|
||||
+33
-11
@@ -1,6 +1,12 @@
|
||||
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,
|
||||
}
|
||||
@@ -9,6 +15,7 @@ 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,
|
||||
}
|
||||
@@ -17,12 +24,27 @@ impl From<ExitCode> for i32 {
|
||||
|
||||
impl ExitCode {
|
||||
fn is_error(self) -> bool {
|
||||
self != ExitCode::Success
|
||||
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: &[ExitCode]) -> ExitCode {
|
||||
if results.iter().any(|&c| ExitCode::is_error(c)) {
|
||||
pub fn merge_exitcodes(results: impl IntoIterator<Item = ExitCode>) -> ExitCode {
|
||||
if results.into_iter().any(ExitCode::is_error) {
|
||||
return ExitCode::GeneralError;
|
||||
}
|
||||
ExitCode::Success
|
||||
@@ -34,38 +56,38 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn success_when_no_results() {
|
||||
assert_eq!(merge_exitcodes(&[]), ExitCode::Success);
|
||||
assert_eq!(merge_exitcodes([]), ExitCode::Success);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn general_error_if_at_least_one_error() {
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::GeneralError]),
|
||||
merge_exitcodes([ExitCode::GeneralError]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::KilledBySigint]),
|
||||
merge_exitcodes([ExitCode::KilledBySigint]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::KilledBySigint, ExitCode::Success]),
|
||||
merge_exitcodes([ExitCode::KilledBySigint, ExitCode::Success]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::Success, ExitCode::GeneralError]),
|
||||
merge_exitcodes([ExitCode::Success, ExitCode::GeneralError]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::GeneralError, ExitCode::KilledBySigint]),
|
||||
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);
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::Success, ExitCode::Success]),
|
||||
merge_exitcodes([ExitCode::Success, ExitCode::Success]),
|
||||
ExitCode::Success
|
||||
);
|
||||
}
|
||||
|
||||
+25
-10
@@ -1,4 +1,8 @@
|
||||
use crate::filesystem;
|
||||
use crate::walk;
|
||||
|
||||
/// Whether or not to show
|
||||
#[derive(Default)]
|
||||
pub struct FileTypes {
|
||||
pub files: bool,
|
||||
pub directories: bool,
|
||||
@@ -9,16 +13,27 @@ pub struct FileTypes {
|
||||
pub empty_only: bool,
|
||||
}
|
||||
|
||||
impl Default for FileTypes {
|
||||
fn default() -> FileTypes {
|
||||
FileTypes {
|
||||
files: false,
|
||||
directories: false,
|
||||
symlinks: false,
|
||||
sockets: false,
|
||||
pipes: false,
|
||||
executables_only: false,
|
||||
empty_only: false,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -1,9 +1,8 @@
|
||||
use lazy_static::lazy_static;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static! {
|
||||
static ref SIZE_CAPTURES: Regex = Regex::new(r"(?i)^([+-]?)(\d+)(b|[kmgt]i?b?)$").unwrap();
|
||||
}
|
||||
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 {
|
||||
|
||||
+56
-5
@@ -1,3 +1,5 @@
|
||||
use chrono::{offset::TimeZone, DateTime, Local, NaiveDate};
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
/// Filter based on time ranges.
|
||||
@@ -11,9 +13,20 @@ impl TimeFilter {
|
||||
fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
|
||||
humantime::parse_duration(s)
|
||||
.map(|duration| *ref_time - duration)
|
||||
.or_else(|_| humantime::parse_rfc3339_weak(s))
|
||||
.or_else(|_| humantime::parse_rfc3339_weak(&(s.to_owned() + " 00:00:00")))
|
||||
.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,7 +52,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn is_time_filter_applicable() {
|
||||
let ref_time = humantime::parse_rfc3339("2010-10-10T10:10:10Z").unwrap();
|
||||
let ref_time = Local
|
||||
.datetime_from_str("2010-10-10 10:10:10", "%F %T")
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
assert!(TimeFilter::after(&ref_time, "1min")
|
||||
.unwrap()
|
||||
.applies_to(&ref_time));
|
||||
@@ -76,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));
|
||||
}
|
||||
}
|
||||
|
||||
+301
-224
@@ -1,18 +1,17 @@
|
||||
mod app;
|
||||
mod config;
|
||||
mod error;
|
||||
mod exec;
|
||||
mod exit_codes;
|
||||
mod filesystem;
|
||||
mod filetypes;
|
||||
mod filter;
|
||||
mod options;
|
||||
mod output;
|
||||
mod regex_helper;
|
||||
mod walk;
|
||||
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
|
||||
@@ -20,9 +19,10 @@ use anyhow::{anyhow, Context, Result};
|
||||
use atty::Stream;
|
||||
use globset::GlobBuilder;
|
||||
use lscolors::LsColors;
|
||||
use regex::bytes::{RegexBuilder, RegexSetBuilder};
|
||||
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;
|
||||
@@ -30,7 +30,6 @@ use crate::filetypes::FileTypes;
|
||||
#[cfg(unix)]
|
||||
use crate::filter::OwnerFilter;
|
||||
use crate::filter::{SizeFilter, TimeFilter};
|
||||
use crate::options::Options;
|
||||
use crate::regex_helper::{pattern_has_uppercase_char, pattern_matches_strings_with_leading_dot};
|
||||
|
||||
// We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481
|
||||
@@ -39,7 +38,10 @@ use crate::regex_helper::{pattern_has_uppercase_char, pattern_matches_strings_wi
|
||||
not(windows),
|
||||
not(target_os = "android"),
|
||||
not(target_os = "macos"),
|
||||
not(target_env = "musl")
|
||||
not(target_os = "freebsd"),
|
||||
not(target_env = "musl"),
|
||||
not(target_arch = "riscv64"),
|
||||
feature = "use-jemalloc"
|
||||
))]
|
||||
#[global_allocator]
|
||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||
@@ -50,10 +52,38 @@ 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;1
|
||||
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 result = run();
|
||||
match result {
|
||||
Ok(exit_code) => {
|
||||
exit_code.exit();
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("[fd error]: {:#}", err);
|
||||
ExitCode::GeneralError.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<ExitCode> {
|
||||
let matches = app::build_app().get_matches_from(env::args_os());
|
||||
|
||||
// Set the current working directory of the process
|
||||
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) {
|
||||
@@ -69,15 +99,20 @@ fn run() -> Result<ExitCode> {
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let current_directory = Path::new(".");
|
||||
if !filesystem::is_existing_directory(current_directory) {
|
||||
return Err(anyhow!(
|
||||
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?)."
|
||||
));
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Get the search pattern
|
||||
fn extract_search_pattern<'a>(matches: &'a clap::ArgMatches) -> Result<&'a str> {
|
||||
let pattern = matches
|
||||
.value_of_os("pattern")
|
||||
.map(|p| {
|
||||
@@ -86,54 +121,57 @@ fn run() -> Result<ExitCode> {
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or("");
|
||||
Ok(pattern)
|
||||
}
|
||||
|
||||
// Get one or more root directories to search.
|
||||
let passed_arguments = matches
|
||||
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"));
|
||||
|
||||
let mut search_paths = if let Some(paths) = passed_arguments {
|
||||
let mut directories = vec![];
|
||||
for path in paths {
|
||||
let path_buffer = PathBuf::from(path);
|
||||
if filesystem::is_existing_directory(&path_buffer) {
|
||||
directories.push(path_buffer);
|
||||
} else {
|
||||
print_error(format!(
|
||||
"Search path '{}' is not a directory.",
|
||||
path_buffer.to_string_lossy()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
directories
|
||||
} else {
|
||||
vec![current_directory.to_path_buf()]
|
||||
};
|
||||
|
||||
// Check if we have no valid search paths.
|
||||
.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") {
|
||||
search_paths = search_paths
|
||||
.iter()
|
||||
.map(|path_buffer| {
|
||||
path_buffer
|
||||
.normalize()
|
||||
.and_then(|pb| filesystem::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)
|
||||
&& Path::new(pattern).is_dir()
|
||||
{
|
||||
return Err(anyhow!(
|
||||
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 \
|
||||
@@ -142,10 +180,14 @@ fn run() -> Result<ExitCode> {
|
||||
fd --full-path '{pattern}'",
|
||||
pattern = pattern,
|
||||
sep = std::path::MAIN_SEPARATOR,
|
||||
));
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let pattern_regex = if matches.is_present("glob") && !pattern.is_empty() {
|
||||
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") {
|
||||
@@ -153,17 +195,46 @@ fn run() -> Result<ExitCode> {
|
||||
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;
|
||||
|
||||
@@ -174,161 +245,14 @@ fn run() -> Result<ExitCode> {
|
||||
_ => ansi_colors_support && env::var_os("NO_COLOR").is_none() && interactive_terminal,
|
||||
};
|
||||
|
||||
let path_separator = matches
|
||||
.value_of("path-separator")
|
||||
.map_or_else(filesystem::default_path_separator, |s| Some(s.to_owned()));
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(ref sep) = path_separator {
|
||||
if sep.len() > 1 {
|
||||
return 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
|
||||
));
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
let ls_colors = if colored_output {
|
||||
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 = if let Some(args) = matches.values_of("exec") {
|
||||
Some(CommandTemplate::new(args, path_separator.clone()))
|
||||
} else if let Some(args) = matches.values_of("exec-batch") {
|
||||
Some(CommandTemplate::new_batch(args, path_separator.clone())?)
|
||||
} else if matches.is_present("list-details") {
|
||||
let color = matches.value_of("color").unwrap_or("auto");
|
||||
let color_arg = ["--color=", color].concat();
|
||||
|
||||
#[allow(unused)]
|
||||
let gnu_ls = |command_name| {
|
||||
vec![
|
||||
command_name,
|
||||
"-l", // long listing format
|
||||
"--human-readable", // human readable file sizes
|
||||
"--directory", // list directories themselves, not their contents
|
||||
&color_arg,
|
||||
]
|
||||
};
|
||||
|
||||
let cmd: Vec<&str> = if cfg!(unix) {
|
||||
if !cfg!(any(
|
||||
target_os = "macos",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
)) {
|
||||
// Assume ls is GNU ls
|
||||
gnu_ls("ls")
|
||||
} else {
|
||||
// MacOS, DragonFlyBSD, FreeBSD
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// Use GNU ls, if available (support for --color=auto, better LS_COLORS support)
|
||||
let gnu_ls_exists = Command::new("gls")
|
||||
.arg("--version")
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.is_ok();
|
||||
|
||||
if gnu_ls_exists {
|
||||
gnu_ls("gls")
|
||||
} else {
|
||||
let mut cmd = vec![
|
||||
"ls", // BSD version of ls
|
||||
"-l", // long listing format
|
||||
"-h", // '--human-readable' is not available, '-h' is
|
||||
"-d", // '--directory' is not available, but '-d' is
|
||||
];
|
||||
|
||||
if !cfg!(any(target_os = "netbsd", target_os = "openbsd")) && colored_output {
|
||||
// -G is not available in NetBSD's and OpenBSD's ls
|
||||
cmd.push("-G");
|
||||
}
|
||||
|
||||
cmd
|
||||
}
|
||||
}
|
||||
} else if cfg!(windows) {
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// Use GNU ls, if available
|
||||
let gnu_ls_exists = Command::new("ls")
|
||||
.arg("--version")
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.is_ok();
|
||||
|
||||
if gnu_ls_exists {
|
||||
gnu_ls("ls")
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"'fd --list-details' is not supported on Windows unless GNU 'ls' is installed."
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"'fd --list-details' is not supported on this platform."
|
||||
));
|
||||
};
|
||||
|
||||
Some(CommandTemplate::new_batch(&cmd, path_separator.clone()).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let size_limits = if let Some(vs) = matches.values_of("size") {
|
||||
vs.map(|sf| {
|
||||
SizeFilter::from_string(sf)
|
||||
.ok_or_else(|| anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", sf))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
} 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 {
|
||||
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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
let owner_constraint = if let Some(s) = matches.value_of("owner") {
|
||||
OwnerFilter::from_string(s)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let config = Options {
|
||||
Ok(Config {
|
||||
case_sensitive,
|
||||
search_full_path: matches.is_present("full-path"),
|
||||
ignore_hidden: !(matches.is_present("hidden")
|
||||
@@ -338,12 +262,14 @@ fn run() -> Result<ExitCode> {
|
||||
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("max-depth")
|
||||
.or_else(|| matches.value_of("rg-depth"))
|
||||
@@ -421,6 +347,12 @@ fn run() -> Result<ExitCode> {
|
||||
})
|
||||
.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())
|
||||
@@ -448,20 +380,180 @@ fn run() -> Result<ExitCode> {
|
||||
None
|
||||
}
|
||||
}),
|
||||
};
|
||||
strip_cwd_prefix: (!matches.is_present("path")
|
||||
&& !matches.is_present("search-path")
|
||||
&& (interactive_terminal || matches.is_present("strip-cwd-prefix"))),
|
||||
})
|
||||
}
|
||||
|
||||
if cfg!(unix)
|
||||
&& config.ignore_hidden
|
||||
&& pattern_matches_strings_with_leading_dot(&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(())
|
||||
}
|
||||
}
|
||||
|
||||
let re = RegexBuilder::new(&pattern_regex)
|
||||
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()
|
||||
@@ -472,20 +564,5 @@ fn run() -> Result<ExitCode> {
|
||||
also use the '--glob' option to match on a glob pattern.",
|
||||
e.to_string()
|
||||
)
|
||||
})?;
|
||||
|
||||
walk::scan(&search_paths, Arc::new(re), Arc::new(config))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = run();
|
||||
match result {
|
||||
Ok(exit_code) => {
|
||||
process::exit(exit_code.into());
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("[fd error]: {:#}", err);
|
||||
process::exit(ExitCode::GeneralError.into());
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+57
-50
@@ -1,35 +1,28 @@
|
||||
use std::io::{self, StdoutLock, Write};
|
||||
use std::borrow::Cow;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use lscolors::{LsColors, Style};
|
||||
use lscolors::{Indicator, LsColors, Style};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::print_error;
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::filesystem::strip_current_dir;
|
||||
use crate::options::Options;
|
||||
|
||||
fn replace_path_separator(path: &str, new_path_separator: &str) -> String {
|
||||
path.replace(std::path::MAIN_SEPARATOR, new_path_separator)
|
||||
}
|
||||
|
||||
// TODO: this function is performance critical and can probably be optimized
|
||||
pub fn print_entry(
|
||||
stdout: &mut StdoutLock,
|
||||
entry: &Path,
|
||||
config: &Options,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
) {
|
||||
let path = if entry.is_absolute() {
|
||||
entry
|
||||
} else {
|
||||
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(stdout, path, config, ls_colors, wants_to_quit)
|
||||
print_entry_colorized(stdout, path, config, ls_colors)
|
||||
} else {
|
||||
print_entry_uncolorized(stdout, path, config)
|
||||
};
|
||||
@@ -37,55 +30,69 @@ pub fn print_entry(
|
||||
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').
|
||||
process::exit(0);
|
||||
ExitCode::Success.exit();
|
||||
} else {
|
||||
print_error(format!("Could not write to output: {}", e));
|
||||
process::exit(ExitCode::GeneralError.into());
|
||||
ExitCode::GeneralError.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this function is performance critical and can probably be optimized
|
||||
fn print_entry_colorized(
|
||||
stdout: &mut StdoutLock,
|
||||
fn print_entry_colorized<W: Write>(
|
||||
stdout: &mut W,
|
||||
path: &Path,
|
||||
config: &Options,
|
||||
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();
|
||||
|
||||
// Traverse the path and colorize each component
|
||||
for (component, style) in ls_colors.style_for_path_components(path) {
|
||||
let style = style
|
||||
.map(Style::to_ansi_term_style)
|
||||
.unwrap_or(default_style);
|
||||
|
||||
let mut path_string = component.to_string_lossy();
|
||||
if let Some(ref separator) = config.path_separator {
|
||||
*path_string.to_mut() = replace_path_separator(&path_string, separator);
|
||||
}
|
||||
write!(stdout, "{}", style.paint(path_string))?;
|
||||
|
||||
// TODO: can we move this out of the if-statement? Why do we call it that often?
|
||||
if wants_to_quit.load(Ordering::Relaxed) {
|
||||
writeln!(stdout)?;
|
||||
process::exit(ExitCode::KilledBySigint.into());
|
||||
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 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")
|
||||
write!(stdout, "\0")?;
|
||||
} else {
|
||||
writeln!(stdout)
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: this function is performance critical and can probably be optimized
|
||||
fn print_entry_uncolorized_base(
|
||||
stdout: &mut StdoutLock,
|
||||
fn print_entry_uncolorized_base<W: Write>(
|
||||
stdout: &mut W,
|
||||
path: &Path,
|
||||
config: &Options,
|
||||
config: &Config,
|
||||
) -> io::Result<()> {
|
||||
let separator = if config.null_separator { "\0" } else { "\n" };
|
||||
|
||||
@@ -97,19 +104,19 @@ fn print_entry_uncolorized_base(
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn print_entry_uncolorized(
|
||||
stdout: &mut StdoutLock,
|
||||
fn print_entry_uncolorized<W: Write>(
|
||||
stdout: &mut W,
|
||||
path: &Path,
|
||||
config: &Options,
|
||||
config: &Config,
|
||||
) -> io::Result<()> {
|
||||
print_entry_uncolorized_base(stdout, path, config)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn print_entry_uncolorized(
|
||||
stdout: &mut StdoutLock,
|
||||
fn print_entry_uncolorized<W: Write>(
|
||||
stdout: &mut W,
|
||||
path: &Path,
|
||||
config: &Options,
|
||||
config: &Config,
|
||||
) -> io::Result<()> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
|
||||
+269
-159
@@ -1,28 +1,30 @@
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{FileType, Metadata};
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
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 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::options::Options;
|
||||
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.
|
||||
@@ -40,13 +42,15 @@ pub enum WorkerResult {
|
||||
|
||||
/// 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<Options>) -> Result<ExitCode> {
|
||||
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()
|
||||
@@ -68,7 +72,7 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Options>) ->
|
||||
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)
|
||||
@@ -130,43 +134,219 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Options>) ->
|
||||
|
||||
let parallel_walker = walker.threads(config.threads).build_parallel();
|
||||
|
||||
let wants_to_quit = Arc::new(AtomicBool::new(false));
|
||||
// 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));
|
||||
|
||||
if config.ls_colors.is_some() && config.command.is_none() {
|
||||
let wq = Arc::clone(&wants_to_quit);
|
||||
let quit_flag = Arc::clone(&quit_flag);
|
||||
let interrupt_flag = Arc::clone(&interrupt_flag);
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
if wq.load(Ordering::Relaxed) {
|
||||
quit_flag.store(true, Ordering::Relaxed);
|
||||
|
||||
if interrupt_flag.fetch_or(true, Ordering::Relaxed) {
|
||||
// Ctrl-C has been pressed twice, exit NOW
|
||||
process::exit(ExitCode::KilledBySigint.into());
|
||||
} else {
|
||||
wq.store(true, Ordering::Relaxed);
|
||||
ExitCode::KilledBySigint.exit();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Spawn the thread that receives all results through the channel.
|
||||
let receiver_thread = spawn_receiver(&config, &wants_to_quit, rx);
|
||||
let receiver_thread = spawn_receiver(&config, &quit_flag, &interrupt_flag, rx);
|
||||
|
||||
// Spawn the sender threads.
|
||||
spawn_senders(&config, &wants_to_quit, pattern, parallel_walker, tx);
|
||||
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 wants_to_quit.load(Ordering::Relaxed) {
|
||||
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<Options>,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
config: &Arc<Config>,
|
||||
quit_flag: &Arc<AtomicBool>,
|
||||
interrupt_flag: &Arc<AtomicBool>,
|
||||
rx: Receiver<WorkerResult>,
|
||||
) -> thread::JoinHandle<ExitCode> {
|
||||
let config = Arc::clone(config);
|
||||
let wants_to_quit = Arc::clone(wants_to_quit);
|
||||
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;
|
||||
@@ -176,7 +356,13 @@ fn spawn_receiver(
|
||||
// This will be set to `Some` if the `--exec` argument was supplied.
|
||||
if let Some(ref cmd) = config.command {
|
||||
if cmd.in_batch_mode() {
|
||||
exec::batch(rx, cmd, show_filesystem_errors, enable_output_buffering)
|
||||
exec::batch(
|
||||
rx,
|
||||
cmd,
|
||||
show_filesystem_errors,
|
||||
enable_output_buffering,
|
||||
config.batch_size,
|
||||
)
|
||||
} else {
|
||||
let shared_rx = Arc::new(Mutex::new(rx));
|
||||
|
||||
@@ -205,132 +391,82 @@ fn spawn_receiver(
|
||||
}
|
||||
|
||||
// Wait for all threads to exit before exiting the program.
|
||||
let mut results: Vec<ExitCode> = Vec::new();
|
||||
for h in handles {
|
||||
results.push(h.join().unwrap());
|
||||
}
|
||||
|
||||
merge_exitcodes(&results)
|
||||
let exit_codes = handles
|
||||
.into_iter()
|
||||
.map(|handle| handle.join().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
merge_exitcodes(exit_codes)
|
||||
}
|
||||
} 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 = config
|
||||
.max_buffer_time
|
||||
.unwrap_or_else(|| time::Duration::from_millis(100));
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let stdout = stdout.lock();
|
||||
let stdout = io::BufWriter::new(stdout);
|
||||
|
||||
let mut num_results = 0;
|
||||
|
||||
for worker_result in rx {
|
||||
match worker_result {
|
||||
WorkerResult::Entry(value) => {
|
||||
match mode {
|
||||
ReceiverMode::Buffering => {
|
||||
buffer.push(value);
|
||||
|
||||
// Have we reached the maximum buffer size or maximum buffering time?
|
||||
if buffer.len() > MAX_BUFFER_LENGTH
|
||||
|| time::Instant::now() - start > max_buffer_time
|
||||
{
|
||||
// Flush the buffer
|
||||
for v in &buffer {
|
||||
output::print_entry(
|
||||
&mut stdout,
|
||||
v,
|
||||
&config,
|
||||
&wants_to_quit,
|
||||
);
|
||||
}
|
||||
buffer.clear();
|
||||
|
||||
// Start streaming
|
||||
mode = ReceiverMode::Streaming;
|
||||
}
|
||||
}
|
||||
ReceiverMode::Streaming => {
|
||||
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
|
||||
}
|
||||
}
|
||||
|
||||
num_results += 1;
|
||||
}
|
||||
WorkerResult::Error(err) => {
|
||||
if show_filesystem_errors {
|
||||
print_error(err.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(max_results) = config.max_results {
|
||||
if num_results >= max_results {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have finished fast enough (faster than max_buffer_time), we haven't streamed
|
||||
// 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(&mut stdout, &value, &config, &wants_to_quit);
|
||||
}
|
||||
}
|
||||
|
||||
ExitCode::Success
|
||||
let mut rxbuffer = ReceiverBuffer::new(config, quit_flag, interrupt_flag, rx, stdout);
|
||||
rxbuffer.process()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub enum DirEntry {
|
||||
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 {
|
||||
DirEntry::Normal(e) => e.path(),
|
||||
DirEntry::BrokenSymlink(pathbuf) => pathbuf.as_path(),
|
||||
match &self.inner {
|
||||
DirEntryInner::Normal(e) => e.path(),
|
||||
DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_type(&self) -> Option<FileType> {
|
||||
match self {
|
||||
DirEntry::Normal(e) => e.file_type(),
|
||||
DirEntry::BrokenSymlink(pathbuf) => {
|
||||
pathbuf.symlink_metadata().map(|m| m.file_type()).ok()
|
||||
}
|
||||
match &self.inner {
|
||||
DirEntryInner::Normal(e) => e.file_type(),
|
||||
DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> Option<Metadata> {
|
||||
match self {
|
||||
DirEntry::Normal(e) => e.metadata().ok(),
|
||||
DirEntry::BrokenSymlink(_) => None,
|
||||
}
|
||||
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 {
|
||||
DirEntry::Normal(e) => Some(e.depth()),
|
||||
DirEntry::BrokenSymlink(_) => None,
|
||||
match &self.inner {
|
||||
DirEntryInner::Normal(e) => Some(e.depth()),
|
||||
DirEntryInner::BrokenSymlink(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_senders(
|
||||
config: &Arc<Options>,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
config: &Arc<Config>,
|
||||
quit_flag: &Arc<AtomicBool>,
|
||||
pattern: Arc<Regex>,
|
||||
parallel_walker: ignore::WalkParallel,
|
||||
tx: Sender<WorkerResult>,
|
||||
@@ -339,10 +475,10 @@ fn spawn_senders(
|
||||
let config = Arc::clone(config);
|
||||
let pattern = Arc::clone(&pattern);
|
||||
let tx_thread = tx.clone();
|
||||
let wants_to_quit = Arc::clone(wants_to_quit);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -351,7 +487,7 @@ fn spawn_senders(
|
||||
// Skip the root directory entry.
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
Ok(e) => DirEntry::Normal(e),
|
||||
Ok(e) => DirEntry::normal(e),
|
||||
Err(ignore::Error::WithPath {
|
||||
path,
|
||||
err: inner_err,
|
||||
@@ -363,30 +499,24 @@ fn spawn_senders(
|
||||
.ok()
|
||||
.map_or(false, |m| m.file_type().is_symlink()) =>
|
||||
{
|
||||
DirEntry::BrokenSymlink(path)
|
||||
DirEntry::broken_symlink(path)
|
||||
}
|
||||
_ => {
|
||||
match tx_thread.send(WorkerResult::Error(ignore::Error::WithPath {
|
||||
return match tx_thread.send(WorkerResult::Error(ignore::Error::WithPath {
|
||||
path,
|
||||
err: inner_err,
|
||||
})) {
|
||||
Ok(_) => {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
Err(_) => {
|
||||
return ignore::WalkState::Quit;
|
||||
}
|
||||
Ok(_) => ignore::WalkState::Continue,
|
||||
Err(_) => ignore::WalkState::Quit,
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => match tx_thread.send(WorkerResult::Error(err)) {
|
||||
Ok(_) => {
|
||||
return ignore::WalkState::Continue;
|
||||
Err(err) => {
|
||||
return match tx_thread.send(WorkerResult::Error(err)) {
|
||||
Ok(_) => ignore::WalkState::Continue,
|
||||
Err(_) => ignore::WalkState::Quit,
|
||||
}
|
||||
Err(_) => {
|
||||
return ignore::WalkState::Quit;
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(min_depth) = config.min_depth {
|
||||
@@ -430,27 +560,7 @@ fn spawn_senders(
|
||||
|
||||
// Filter out unwanted file types.
|
||||
if let Some(ref file_types) = config.file_types {
|
||||
if let Some(ref entry_type) = entry.file_type() {
|
||||
if (!file_types.files && entry_type.is_file())
|
||||
|| (!file_types.directories && entry_type.is_dir())
|
||||
|| (!file_types.symlinks && entry_type.is_symlink())
|
||||
|| (!file_types.sockets && filesystem::is_socket(*entry_type))
|
||||
|| (!file_types.pipes && filesystem::is_pipe(*entry_type))
|
||||
|| (file_types.executables_only
|
||||
&& !entry
|
||||
.metadata()
|
||||
.map(|m| filesystem::is_executable(&m))
|
||||
.unwrap_or(false))
|
||||
|| (file_types.empty_only && !filesystem::is_empty(&entry))
|
||||
|| !(entry_type.is_file()
|
||||
|| entry_type.is_dir()
|
||||
|| entry_type.is_symlink()
|
||||
|| filesystem::is_socket(*entry_type)
|
||||
|| filesystem::is_pipe(*entry_type))
|
||||
{
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
if file_types.should_ignore(&entry) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
}
|
||||
@@ -458,7 +568,7 @@ fn spawn_senders(
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if let Some(ref owner_constraint) = config.owner_constraint {
|
||||
if let Ok(ref metadata) = entry_path.metadata() {
|
||||
if let Some(metadata) = entry.metadata() {
|
||||
if !owner_constraint.matches(metadata) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
@@ -471,7 +581,7 @@ fn spawn_senders(
|
||||
// Filter out unwanted sizes if it is a file and we have been given size constraints.
|
||||
if !config.size_constraints.is_empty() {
|
||||
if entry_path.is_file() {
|
||||
if let Ok(metadata) = entry_path.metadata() {
|
||||
if let Some(metadata) = entry.metadata() {
|
||||
let file_size = metadata.len();
|
||||
if config
|
||||
.size_constraints
|
||||
@@ -491,7 +601,7 @@ fn spawn_senders(
|
||||
// Filter out unwanted modification times
|
||||
if !config.time_constraints.is_empty() {
|
||||
let mut matched = false;
|
||||
if let Ok(metadata) = entry_path.metadata() {
|
||||
if let Some(metadata) = entry.metadata() {
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
matched = config
|
||||
.time_constraints
|
||||
|
||||
+27
-13
@@ -130,6 +130,17 @@ fn normalize_output(s: &str, trim_start: bool, normalize_line: bool) -> String {
|
||||
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");
|
||||
@@ -203,6 +214,19 @@ impl TestEnv {
|
||||
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)
|
||||
@@ -224,15 +248,9 @@ impl TestEnv {
|
||||
args: &[&str],
|
||||
expected: &str,
|
||||
) {
|
||||
let output = self.assert_success_and_get_output(path, args);
|
||||
|
||||
// Normalize both expected and actual output.
|
||||
let expected = normalize_output(expected, true, self.normalize_line);
|
||||
let actual = normalize_output(
|
||||
&String::from_utf8_lossy(&output.stdout),
|
||||
false,
|
||||
self.normalize_line,
|
||||
);
|
||||
let actual = self.assert_success_and_get_normalized_output(path, args);
|
||||
|
||||
// Compare actual output to expected output.
|
||||
if expected != actual {
|
||||
@@ -280,12 +298,8 @@ impl TestEnv {
|
||||
|
||||
if let Some(expected) = expected {
|
||||
// Normalize both expected and actual output.
|
||||
let expected_error = normalize_output(expected, true, self.normalize_line);
|
||||
let actual_err = normalize_output(
|
||||
&String::from_utf8_lossy(&output.stderr),
|
||||
false,
|
||||
self.normalize_line,
|
||||
);
|
||||
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) {
|
||||
|
||||
+574
-360
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user