mirror of
https://github.com/sharkdp/fd.git
synced 2024-09-27 20:41:30 +02:00
Merge branch 'master' into master
This commit is contained in:
commit
50c0fa812f
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
github: [sharkdp, tavianator]
|
16
.github/workflows/CICD.yml
vendored
16
.github/workflows/CICD.yml
vendored
@ -1,7 +1,7 @@
|
||||
name: CICD
|
||||
|
||||
env:
|
||||
MIN_SUPPORTED_RUST_VERSION: "1.53.0"
|
||||
MIN_SUPPORTED_RUST_VERSION: "1.56.0"
|
||||
CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
||||
|
||||
on:
|
||||
@ -46,11 +46,18 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }}
|
||||
default: true
|
||||
components: clippy
|
||||
- name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --locked --all-targets --all-features
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
- name: Run tests
|
||||
uses: actions-rs/cargo@v1
|
||||
@ -313,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}"
|
||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@ -2,16 +2,42 @@
|
||||
|
||||
## Performance improvements
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
## Bugfixes
|
||||
|
||||
|
||||
## Changes
|
||||
|
||||
- Directories are now printed with an additional path separator at the end: `foo/bar/`
|
||||
|
||||
## 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
|
||||
|
||||
|
191
Cargo.lock
generated
191
Cargo.lock
generated
@ -22,9 +22,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.51"
|
||||
version = "1.0.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
|
||||
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
@ -39,9 +39,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
@ -60,9 +60,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.72"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@ -85,35 +85,35 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.34.0"
|
||||
version = "3.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"term_size",
|
||||
"termcolor",
|
||||
"terminal_size",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.1"
|
||||
name = "clap_complete"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
||||
checksum = "df6f3613c0a3cddfd78b41b10203eb322cb29b600cbdf808a7d3db95691b8e25"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.5"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
||||
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
@ -158,14 +158,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fd-find"
|
||||
version = "8.3.0"
|
||||
version = "8.3.2"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
"atty",
|
||||
"chrono",
|
||||
"clap",
|
||||
"crossbeam-channel",
|
||||
"clap_complete",
|
||||
"ctrlc",
|
||||
"diff",
|
||||
"dirs-next",
|
||||
@ -220,9 +220,9 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@ -242,6 +242,12 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@ -275,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"
|
||||
@ -304,9 +320,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.108"
|
||||
version = "0.2.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@ -319,9 +335,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lscolors"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd58d8727f3035fa6d5272f16b519741fd4875936b99d8a7cde21291b7d9174"
|
||||
checksum = "4e9323b3525d4efad2dead1837a105e313253bfdbad1d470994038eededa4d62"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
]
|
||||
@ -334,18 +350,18 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.4"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.0"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188"
|
||||
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
@ -356,9 +372,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "normpath"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "640c20e9df4a2d4a5adad5b47e17d76dac3e824346b181931c3ec9f7a85687b1"
|
||||
checksum = "04aaf5e9cb0fbf883cc0423159eacdf96a9878022084b35c462c428cab73bcaf"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
@ -384,9 +400,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
@ -394,24 +410,57 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
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-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.32"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -455,9 +504,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||
checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
@ -509,15 +558,15 @@ 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.82"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -535,10 +584,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term_size"
|
||||
version = "0.3.2"
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
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",
|
||||
@ -546,32 +604,31 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "test-case"
|
||||
version = "1.2.1"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7cad0a06f9a61e94355aa3b3dc92d85ab9c83406722b1ca5e918d4297c12c23"
|
||||
checksum = "4f7d58e237f65d5fe5eaf1105188c94c9a441e1fbc298ed5df45ec9c9af236d3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
dependencies = [
|
||||
"term_size",
|
||||
"unicode-width",
|
||||
"terminal_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
|
||||
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
@ -586,12 +643,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
@ -608,17 +659,11 @@ 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"
|
||||
|
32
Cargo.toml
32
Cargo.toml
@ -16,8 +16,8 @@ license = "MIT/Apache-2.0"
|
||||
name = "fd-find"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/sharkdp/fd"
|
||||
version = "8.3.0"
|
||||
edition= "2018"
|
||||
version = "8.3.2"
|
||||
edition= "2021"
|
||||
|
||||
[badges.appveyor]
|
||||
repository = "sharkdp/fd"
|
||||
@ -30,34 +30,34 @@ name = "fd"
|
||||
path = "src/main.rs"
|
||||
|
||||
[build-dependencies]
|
||||
clap = "2.34.0"
|
||||
clap = { version = "3.1", features = ["cargo"] }
|
||||
clap_complete = "3.1"
|
||||
version_check = "0.9"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12"
|
||||
atty = "0.2"
|
||||
ignore = "0.4.3"
|
||||
num_cpus = "1.8"
|
||||
num_cpus = "1.13"
|
||||
regex = "1.5.4"
|
||||
regex-syntax = "0.6"
|
||||
ctrlc = "3.2"
|
||||
humantime = "2.1"
|
||||
lscolors = "0.8"
|
||||
lscolors = "0.9"
|
||||
globset = "0.4"
|
||||
anyhow = "1.0"
|
||||
dirs-next = "2.0"
|
||||
normpath = "0.3"
|
||||
normpath = "0.3.2"
|
||||
chrono = "0.4"
|
||||
once_cell = "1.8.0"
|
||||
crossbeam-channel = "0.5.1"
|
||||
once_cell = "1.9.0"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "2.34.0"
|
||||
features = ["suggestions", "color", "wrap_help"]
|
||||
version = "3.1"
|
||||
features = ["suggestions", "color", "wrap_help", "cargo", "unstable-grouped"]
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
users = "0.11.0"
|
||||
nix = "0.23.0"
|
||||
nix = "0.23.1"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
|
||||
libc = "0.2"
|
||||
@ -65,15 +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_os = "freebsd"), 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"
|
||||
test-case = "1.2"
|
||||
test-case = "2.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
||||
[features]
|
||||
use-jemalloc = ["jemallocator"]
|
||||
default = ["use-jemalloc"]
|
||||
|
12
README.md
12
README.md
@ -20,7 +20,7 @@ Quick links:
|
||||
* Intuitive syntax: `fd PATTERN` instead of `find -iname '*PATTERN*'`.
|
||||
* Regular expression (default) and glob-based patterns.
|
||||
* [Very fast](#benchmark) due to parallelized directory traversal.
|
||||
* Uses colors to highlight different file types (same as *ls*).
|
||||
* Uses colors to highlight different file types (same as `ls`).
|
||||
* Supports [parallel command execution](#command-execution)
|
||||
* Smart case: the search is case-insensitive by default. It switches to
|
||||
case-sensitive if the pattern contains an uppercase
|
||||
@ -535,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.3.0_amd64.deb # adapt version number and architecture
|
||||
sudo dpkg -i fd_8.3.2_amd64.deb # adapt version number and architecture
|
||||
```
|
||||
|
||||
### On Debian
|
||||
@ -557,12 +557,6 @@ Starting with Fedora 28, you can install `fd` from the official package sources:
|
||||
dnf install fd-find
|
||||
```
|
||||
|
||||
For older versions, you can use this [Fedora copr](https://copr.fedorainfracloud.org/coprs/keefle/fd/) to install `fd`:
|
||||
``` bash
|
||||
dnf copr enable keefle/fd
|
||||
dnf install fd
|
||||
```
|
||||
|
||||
### On Alpine Linux
|
||||
|
||||
You can install [the fd package](https://pkgs.alpinelinux.org/packages?name=fd)
|
||||
@ -652,7 +646,7 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can
|
||||
```
|
||||
cargo install fd-find
|
||||
```
|
||||
Note that rust version *1.53.0* or later is required.
|
||||
Note that rust version *1.56.0* or later is required.
|
||||
|
||||
`make` is also needed for the build.
|
||||
|
||||
|
13
build.rs
13
build.rs
@ -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.53";
|
||||
let min_version = "1.56";
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
msrv = "1.53.0"
|
||||
msrv = "1.56.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]'
|
||||
@ -154,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 -/'
|
||||
|
25
doc/fd.1
vendored
25
doc/fd.1
vendored
@ -24,6 +24,11 @@ fd \- find entries in the filesystem
|
||||
.B fd
|
||||
is a simple, fast and user-friendly alternative to
|
||||
.BR find (1).
|
||||
.P
|
||||
By default
|
||||
.B fd
|
||||
uses regular expressions for the pattern. However, this can be changed to use simple glob patterns
|
||||
with the '\-\-glob' option.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \-H, \-\-hidden
|
||||
@ -66,6 +71,10 @@ 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
|
||||
pattern contains an uppercase character (smart case).
|
||||
@ -126,6 +135,10 @@ can be used as an alias.
|
||||
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
|
||||
@ -328,6 +341,9 @@ Note that all subsequent positional arguments are considered to be arguments to
|
||||
It is therefore recommended to place the \-x/\-\-exec option last. Alternatively, you can supply
|
||||
a ';' argument to end the argument list and continue with more fd options.
|
||||
Most shells require ';' to be escaped: '\\;'.
|
||||
This option can be specified multiple times, in which case all commands are run for each
|
||||
file found, in the order they are provided. In that case, you must supply a ';' argument for
|
||||
all but the last commands.
|
||||
|
||||
The following placeholders are substituted before the command is executed:
|
||||
.RS
|
||||
@ -381,6 +397,9 @@ basename without file extension
|
||||
|
||||
If no placeholder is present, an implicit "{}" at the end is assumed.
|
||||
|
||||
Like \-\-exec, this can be used multiple times, in which case each command will be run in
|
||||
the order given.
|
||||
|
||||
Examples:
|
||||
|
||||
- Find all test_*.py files and open them in your favorite editor:
|
||||
@ -393,7 +412,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:
|
||||
|
||||
|
BIN
doc/logo.png
vendored
Normal file
BIN
doc/logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.9 KiB |
161
doc/logo.svg
vendored
Normal file
161
doc/logo.svg
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="66mm"
|
||||
height="66mm"
|
||||
viewBox="0 0 66 66.000001"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:export-filename="/home/shark/Informatik/rust/fd/doc/logo.png"
|
||||
inkscape:export-xdpi="192.42"
|
||||
inkscape:export-ydpi="192.42"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#999999"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.1795515"
|
||||
inkscape:cx="114.47309"
|
||||
inkscape:cy="176.18304"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1175"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
showguides="false"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:snap-global="false"
|
||||
fit-margin-top="2"
|
||||
fit-margin-left="2"
|
||||
fit-margin-right="2"
|
||||
fit-margin-bottom="2"
|
||||
lock-margins="true">
|
||||
<sodipodi:guide
|
||||
position="26.228232,26.126763"
|
||||
orientation="0,-1"
|
||||
id="guide47826" />
|
||||
<sodipodi:guide
|
||||
position="25.799494,2.3628924"
|
||||
orientation="0,-1"
|
||||
id="guide47828" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-21.358009,-148.28012)">
|
||||
<g
|
||||
id="g66267"
|
||||
transform="matrix(0.84959471,0,0,0.84959471,7.9920783,43.351816)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.7126px;line-height:0;font-family:'Fira Sans Condensed';-inkscape-font-specification:'Fira Sans Condensed, ';white-space:pre;inline-size:37.3715;fill:#e5e5e5;fill-opacity:1;stroke-width:0.742816"
|
||||
x="50.647034"
|
||||
y="173.19841"
|
||||
id="text50653"
|
||||
transform="matrix(1.0604862,0,0,1.0604862,-3.3101428,-10.150043)"><tspan
|
||||
x="50.647034"
|
||||
y="173.19841"
|
||||
id="tspan66635"><tspan
|
||||
style="font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro'"
|
||||
id="tspan66633">fd</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.7126px;line-height:0;font-family:'Fira Sans Condensed';-inkscape-font-specification:'Fira Sans Condensed, ';white-space:pre;inline-size:37.3715;fill:#00ccff;fill-opacity:0.996078;stroke-width:0.742816"
|
||||
x="50.647034"
|
||||
y="173.19841"
|
||||
id="text1244"
|
||||
transform="matrix(1.0604862,0,0,1.0604862,-2.8008599,-9.6407599)"><tspan
|
||||
x="50.647034"
|
||||
y="173.19841"
|
||||
id="tspan66639"><tspan
|
||||
style="font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro'"
|
||||
id="tspan66637">fd</tspan></tspan></text>
|
||||
<g
|
||||
id="g47824"
|
||||
transform="translate(0.1724878,-0.35338542)">
|
||||
<g
|
||||
id="g42041">
|
||||
<path
|
||||
style="fill:none;stroke:#939dac;stroke-width:1.065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 28.209616,155.51329 v 15.68758 H 40.83001"
|
||||
id="path39763"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="fill:#b7bec8;stroke:#939dac;stroke-width:1.065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 27.869464,161.83767 H 40.261291"
|
||||
id="path39765" />
|
||||
</g>
|
||||
<g
|
||||
id="g41945"
|
||||
transform="translate(-1.0583333)">
|
||||
<path
|
||||
style="fill:#0088aa;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
|
||||
id="path40006"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<rect
|
||||
style="fill:#01ccff;fill-opacity:1;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
|
||||
id="rect39949"
|
||||
width="9.4925022"
|
||||
height="6.2080379"
|
||||
x="37.492516"
|
||||
y="158.82776"
|
||||
ry="0.90871465" />
|
||||
</g>
|
||||
<g
|
||||
id="g41951"
|
||||
transform="translate(-1.0583334,9.3665773)">
|
||||
<path
|
||||
style="fill:#373e48;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
|
||||
id="path41947"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<rect
|
||||
style="fill:#535d6c;fill-opacity:0.993797;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
|
||||
id="rect41949"
|
||||
width="9.4925022"
|
||||
height="6.2080379"
|
||||
x="37.492516"
|
||||
y="158.82776"
|
||||
ry="0.90871465" />
|
||||
</g>
|
||||
<g
|
||||
id="g41957"
|
||||
transform="translate(-14.306994,-6.8962642)">
|
||||
<path
|
||||
style="fill:#373e48;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
|
||||
id="path41953"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<rect
|
||||
style="fill:#535d6c;fill-opacity:0.993797;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
|
||||
id="rect41955"
|
||||
width="9.4925022"
|
||||
height="6.2080379"
|
||||
x="37.492516"
|
||||
y="158.82776"
|
||||
ry="0.90871465" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g65006"
|
||||
transform="matrix(0.55302761,0,0,0.55302761,66.463548,117.45819)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.8 KiB |
254
src/app.rs
254
src/app.rs
@ -1,25 +1,25 @@
|
||||
use clap::{crate_version, App, AppSettings, Arg};
|
||||
use clap::{crate_version, AppSettings, Arg, ColorChoice, Command};
|
||||
|
||||
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() -> Command<'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")
|
||||
let mut app = Command::new("fd")
|
||||
.version(crate_version!())
|
||||
.usage("fd [FLAGS/OPTIONS] [<pattern>] [<path>...]")
|
||||
.setting(clap_color_setting)
|
||||
.color(clap_color_choice)
|
||||
.setting(AppSettings::DeriveDisplayOrder)
|
||||
.dont_collapse_args_in_usage(true)
|
||||
.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(
|
||||
@ -30,18 +30,18 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-hidden")
|
||||
Arg::new("no-hidden")
|
||||
.long("no-hidden")
|
||||
.overrides_with("hidden")
|
||||
.hidden(true)
|
||||
.hide(true)
|
||||
.long_help(
|
||||
"Overrides --hidden.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-ignore")
|
||||
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(
|
||||
@ -51,19 +51,19 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ignore")
|
||||
Arg::new("ignore")
|
||||
.long("ignore")
|
||||
.overrides_with("no-ignore")
|
||||
.hidden(true)
|
||||
.hide(true)
|
||||
.long_help(
|
||||
"Overrides --no-ignore.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-ignore-vcs")
|
||||
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 \
|
||||
@ -71,19 +71,19 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ignore-vcs")
|
||||
Arg::new("ignore-vcs")
|
||||
.long("ignore-vcs")
|
||||
.overrides_with("no-ignore-vcs")
|
||||
.hidden(true)
|
||||
.hide(true)
|
||||
.long_help(
|
||||
"Overrides --no-ignore-vcs.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-ignore-parent")
|
||||
Arg::new("no-ignore-parent")
|
||||
.long("no-ignore-parent")
|
||||
.overrides_with("no-ignore-parent")
|
||||
.hidden_short_help(true)
|
||||
.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 \
|
||||
@ -91,19 +91,19 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-global-ignore-file")
|
||||
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")
|
||||
.overrides_with_all(&["ignore", "no-hidden"])
|
||||
.multiple(true)
|
||||
.hidden_short_help(true)
|
||||
.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 \
|
||||
@ -111,9 +111,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.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(
|
||||
@ -123,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(
|
||||
@ -135,19 +135,19 @@ 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 \
|
||||
@ -155,12 +155,12 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.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 \
|
||||
@ -169,9 +169,9 @@ 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(
|
||||
@ -180,18 +180,18 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("relative-path")
|
||||
Arg::new("relative-path")
|
||||
.long("relative-path")
|
||||
.overrides_with("absolute-path")
|
||||
.hidden(true)
|
||||
.hide(true)
|
||||
.long_help(
|
||||
"Overrides --absolute-path.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("list-details")
|
||||
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(
|
||||
@ -202,9 +202,9 @@ 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")
|
||||
@ -215,18 +215,18 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-follow")
|
||||
Arg::new("no-follow")
|
||||
.long("no-follow")
|
||||
.overrides_with("follow")
|
||||
.hidden(true)
|
||||
.hide(true)
|
||||
.long_help(
|
||||
"Overrides --follow.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("full-path")
|
||||
Arg::new("full-path")
|
||||
.long("full-path")
|
||||
.short("p")
|
||||
.short('p')
|
||||
.overrides_with("full-path")
|
||||
.help("Search full abs. path (default: filename only)")
|
||||
.long_help(
|
||||
@ -237,12 +237,12 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.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). \
|
||||
@ -250,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)")
|
||||
@ -263,18 +263,18 @@ 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. \
|
||||
@ -282,11 +282,11 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.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(
|
||||
@ -295,19 +295,19 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("prune")
|
||||
Arg::new("prune")
|
||||
.long("prune")
|
||||
.conflicts_with_all(&["size", "exact-depth"])
|
||||
.hidden_short_help(true)
|
||||
.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")
|
||||
@ -366,10 +366,10 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.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")
|
||||
@ -382,10 +382,11 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("exec")
|
||||
Arg::new("exec")
|
||||
.long("exec")
|
||||
.short("x")
|
||||
.short('x')
|
||||
.min_values(1)
|
||||
.multiple_occurrences(true)
|
||||
.allow_hyphen_values(true)
|
||||
.value_terminator(";")
|
||||
.value_name("cmd")
|
||||
@ -413,10 +414,11 @@ 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)
|
||||
.multiple_occurrences(true)
|
||||
.allow_hyphen_values(true)
|
||||
.value_terminator(";")
|
||||
.value_name("cmd")
|
||||
@ -440,11 +442,11 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("batch-size")
|
||||
Arg::new("batch-size")
|
||||
.long("batch-size")
|
||||
.takes_value(true)
|
||||
.value_name("size")
|
||||
.hidden_short_help(true)
|
||||
.hide_short_help(true)
|
||||
.requires("exec-batch")
|
||||
.help("Max number of arguments to run as a batch with -X")
|
||||
.long_help(
|
||||
@ -455,13 +457,13 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("exclude")
|
||||
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 \
|
||||
@ -473,13 +475,13 @@ 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 \
|
||||
@ -487,9 +489,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.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"])
|
||||
@ -503,12 +505,12 @@ 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 \
|
||||
@ -516,13 +518,13 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.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)
|
||||
.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 \
|
||||
@ -544,10 +546,10 @@ 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 \
|
||||
@ -555,7 +557,7 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("changed-within")
|
||||
Arg::new("changed-within")
|
||||
.long("changed-within")
|
||||
.alias("change-newer-than")
|
||||
.alias("newer")
|
||||
@ -575,7 +577,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")
|
||||
@ -594,7 +596,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")
|
||||
@ -604,14 +606,14 @@ 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")
|
||||
@ -619,11 +621,11 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
This is an alias for '--max-results=1'.")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("quiet")
|
||||
Arg::new("quiet")
|
||||
.long("quiet")
|
||||
.short("q")
|
||||
.short('q')
|
||||
.alias("has-results")
|
||||
.hidden_short_help(true)
|
||||
.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(
|
||||
@ -634,9 +636,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("show-errors")
|
||||
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(
|
||||
@ -645,12 +647,13 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.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 \
|
||||
@ -661,7 +664,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 \
|
||||
@ -670,11 +675,11 @@ 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 \
|
||||
@ -682,8 +687,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
),
|
||||
)
|
||||
.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 \
|
||||
@ -691,25 +697,26 @@ 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::with_name("strip-cwd-prefix")
|
||||
Arg::new("strip-cwd-prefix")
|
||||
.long("strip-cwd-prefix")
|
||||
.conflicts_with_all(&["path", "search-path"])
|
||||
.hidden_short_help(true)
|
||||
.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 \
|
||||
@ -719,9 +726,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
|
||||
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")
|
||||
@ -742,10 +749,10 @@ 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 \
|
||||
@ -758,3 +765,8 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
|
||||
app
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_app() {
|
||||
build_app().debug_assert()
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
use lscolors::LsColors;
|
||||
use regex::bytes::RegexSet;
|
||||
|
||||
use crate::exec::CommandTemplate;
|
||||
use crate::exec::CommandSet;
|
||||
use crate::filetypes::FileTypes;
|
||||
#[cfg(unix)]
|
||||
use crate::filter::OwnerFilter;
|
||||
@ -83,7 +83,7 @@ pub struct Config {
|
||||
pub extensions: Option<RegexSet>,
|
||||
|
||||
/// If a value is supplied, each item found will be used to generate and execute commands.
|
||||
pub command: Option<Arc<CommandTemplate>>,
|
||||
pub command: Option<Arc<CommandSet>>,
|
||||
|
||||
/// Maximum number of search results to pass to each `command`. If zero, the number is
|
||||
/// unlimited.
|
||||
|
@ -6,12 +6,57 @@ use std::sync::Mutex;
|
||||
use crate::error::print_error;
|
||||
use crate::exit_codes::ExitCode;
|
||||
|
||||
struct Outputs {
|
||||
stdout: Vec<u8>,
|
||||
stderr: Vec<u8>,
|
||||
}
|
||||
struct OutputBuffer<'a> {
|
||||
output_permission: &'a Mutex<()>,
|
||||
outputs: Vec<Outputs>,
|
||||
}
|
||||
|
||||
impl<'a> OutputBuffer<'a> {
|
||||
fn new(output_permission: &'a Mutex<()>) -> Self {
|
||||
Self {
|
||||
output_permission,
|
||||
outputs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, stdout: Vec<u8>, stderr: Vec<u8>) {
|
||||
self.outputs.push(Outputs { stdout, stderr });
|
||||
}
|
||||
|
||||
fn write(self) {
|
||||
// avoid taking the lock if there is nothing to do
|
||||
if self.outputs.is_empty() {
|
||||
return;
|
||||
}
|
||||
// While this lock is active, this thread will be the only thread allowed
|
||||
// to write its outputs.
|
||||
let _lock = self.output_permission.lock().unwrap();
|
||||
|
||||
let stdout = io::stdout();
|
||||
let stderr = io::stderr();
|
||||
|
||||
let mut stdout = stdout.lock();
|
||||
let mut stderr = stderr.lock();
|
||||
|
||||
for output in self.outputs.iter() {
|
||||
let _ = stdout.write_all(&output.stdout);
|
||||
let _ = stderr.write_all(&output.stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a command.
|
||||
pub fn execute_command(
|
||||
mut cmd: Command,
|
||||
pub fn execute_commands<I: Iterator<Item = Command>>(
|
||||
cmds: I,
|
||||
out_perm: &Mutex<()>,
|
||||
enable_output_buffering: bool,
|
||||
) -> ExitCode {
|
||||
let mut output_buffer = OutputBuffer::new(out_perm);
|
||||
for mut cmd in cmds {
|
||||
// Spawn the supplied command.
|
||||
let output = if enable_output_buffering {
|
||||
cmd.output()
|
||||
@ -24,29 +69,30 @@ pub fn execute_command(
|
||||
// Then wait for the command to exit, if it was spawned.
|
||||
match output {
|
||||
Ok(output) => {
|
||||
// While this lock is active, this thread will be the only thread allowed
|
||||
// to write its outputs.
|
||||
let _lock = out_perm.lock().unwrap();
|
||||
|
||||
let stdout = io::stdout();
|
||||
let stderr = io::stderr();
|
||||
|
||||
let _ = stdout.lock().write_all(&output.stdout);
|
||||
let _ = stderr.lock().write_all(&output.stderr);
|
||||
|
||||
if output.status.code() == Some(0) {
|
||||
ExitCode::Success
|
||||
} else {
|
||||
ExitCode::GeneralError
|
||||
if enable_output_buffering {
|
||||
output_buffer.push(output.stdout, output.stderr);
|
||||
}
|
||||
if output.status.code() != Some(0) {
|
||||
output_buffer.write();
|
||||
return ExitCode::GeneralError;
|
||||
}
|
||||
Err(ref why) if why.kind() == io::ErrorKind::NotFound => {
|
||||
print_error(format!("Command not found: {:?}", cmd));
|
||||
ExitCode::GeneralError
|
||||
}
|
||||
Err(why) => {
|
||||
print_error(format!("Problem while executing command: {}", why));
|
||||
output_buffer.write();
|
||||
return handle_cmd_error(&cmd, why);
|
||||
}
|
||||
}
|
||||
}
|
||||
output_buffer.write();
|
||||
ExitCode::Success
|
||||
}
|
||||
|
||||
pub fn handle_cmd_error(cmd: &Command, err: io::Error) -> ExitCode {
|
||||
if err.kind() == io::ErrorKind::NotFound {
|
||||
print_error(format!("Command not found: {:?}", cmd));
|
||||
ExitCode::GeneralError
|
||||
} else {
|
||||
print_error(format!("Problem while executing command: {}", err));
|
||||
ExitCode::GeneralError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,13 @@ use crate::filesystem::strip_current_dir;
|
||||
|
||||
/// Removes the parent component of the path
|
||||
pub fn basename(path: &Path) -> &OsStr {
|
||||
path.file_name().unwrap_or_else(|| path.as_os_str())
|
||||
path.file_name().unwrap_or(path.as_os_str())
|
||||
}
|
||||
|
||||
/// Removes the extension from the path
|
||||
pub fn remove_extension(path: &Path) -> OsString {
|
||||
let dirname = dirname(path);
|
||||
let stem = path.file_stem().unwrap_or_else(|| path.as_os_str());
|
||||
let stem = path.file_stem().unwrap_or(path.as_os_str());
|
||||
|
||||
let path = PathBuf::from(dirname).join(stem);
|
||||
|
||||
|
@ -1,20 +1,21 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
|
||||
use crate::dir_entry::DirEntry;
|
||||
use crate::error::print_error;
|
||||
use crate::exit_codes::{merge_exitcodes, ExitCode};
|
||||
use crate::walk::WorkerResult;
|
||||
|
||||
use super::CommandTemplate;
|
||||
use super::CommandSet;
|
||||
|
||||
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
|
||||
/// generate a command with the supplied command template. The generated command will then
|
||||
/// be executed, and this process will continue until the receiver's sender has closed.
|
||||
pub fn job(
|
||||
rx: Arc<Mutex<Receiver<WorkerResult>>>,
|
||||
cmd: Arc<CommandTemplate>,
|
||||
cmd: Arc<CommandSet>,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
show_filesystem_errors: bool,
|
||||
buffer_output: bool,
|
||||
@ -40,11 +41,7 @@ pub fn job(
|
||||
// Drop the lock so that other threads can read from the receiver.
|
||||
drop(lock);
|
||||
// Generate a command, execute it and store its exit code.
|
||||
results.push(cmd.generate_and_execute(
|
||||
dir_entry.path(),
|
||||
Arc::clone(&out_perm),
|
||||
buffer_output,
|
||||
))
|
||||
results.push(cmd.execute(&dir_entry, Arc::clone(&out_perm), buffer_output))
|
||||
}
|
||||
// Returns error in case of any error.
|
||||
merge_exitcodes(results)
|
||||
@ -52,9 +49,8 @@ pub fn job(
|
||||
|
||||
pub fn batch(
|
||||
rx: Receiver<WorkerResult>,
|
||||
cmd: &CommandTemplate,
|
||||
cmd: &CommandSet,
|
||||
show_filesystem_errors: bool,
|
||||
buffer_output: bool,
|
||||
limit: usize,
|
||||
) -> ExitCode {
|
||||
let paths = rx
|
||||
@ -70,14 +66,14 @@ pub fn batch(
|
||||
});
|
||||
if limit == 0 {
|
||||
// no limit
|
||||
return cmd.generate_and_execute_batch(paths, buffer_output);
|
||||
return cmd.execute_batch(paths);
|
||||
}
|
||||
|
||||
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);
|
||||
let exit_code = cmd.execute_batch(limited);
|
||||
exit_codes.push(exit_code);
|
||||
}
|
||||
merge_exitcodes(exit_codes)
|
||||
|
228
src/exec/mod.rs
228
src/exec/mod.rs
@ -9,13 +9,13 @@ use std::path::{Component, Path, PathBuf, Prefix};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{bail, Result};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::exit_codes::ExitCode;
|
||||
|
||||
use self::command::execute_command;
|
||||
use self::command::{execute_commands, handle_cmd_error};
|
||||
use self::input::{basename, dirname, remove_extension};
|
||||
pub use self::job::{batch, job};
|
||||
use self::token::Token;
|
||||
@ -29,44 +29,94 @@ pub enum ExecutionMode {
|
||||
Batch,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CommandSet {
|
||||
mode: ExecutionMode,
|
||||
path_separator: Option<String>,
|
||||
commands: Vec<CommandTemplate>,
|
||||
}
|
||||
|
||||
impl CommandSet {
|
||||
pub fn new<I, S>(input: I, path_separator: Option<String>) -> Result<CommandSet>
|
||||
where
|
||||
I: IntoIterator<Item = Vec<S>>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
Ok(CommandSet {
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator,
|
||||
commands: input
|
||||
.into_iter()
|
||||
.map(CommandTemplate::new)
|
||||
.collect::<Result<_>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_batch<I, S>(input: I, path_separator: Option<String>) -> Result<CommandSet>
|
||||
where
|
||||
I: IntoIterator<Item = Vec<S>>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
Ok(CommandSet {
|
||||
mode: ExecutionMode::Batch,
|
||||
path_separator,
|
||||
commands: input
|
||||
.into_iter()
|
||||
.map(|args| {
|
||||
let cmd = CommandTemplate::new(args)?;
|
||||
if cmd.number_of_tokens() > 1 {
|
||||
bail!("Only one placeholder allowed for batch commands");
|
||||
}
|
||||
if cmd.args[0].has_tokens() {
|
||||
bail!("First argument of exec-batch is expected to be a fixed executable");
|
||||
}
|
||||
Ok(cmd)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn in_batch_mode(&self) -> bool {
|
||||
self.mode == ExecutionMode::Batch
|
||||
}
|
||||
|
||||
pub fn execute(&self, input: &Path, out_perm: Arc<Mutex<()>>, buffer_output: bool) -> ExitCode {
|
||||
let path_separator = self.path_separator.as_deref();
|
||||
let commands = self
|
||||
.commands
|
||||
.iter()
|
||||
.map(|c| c.generate(input, path_separator));
|
||||
execute_commands(commands, &out_perm, buffer_output)
|
||||
}
|
||||
|
||||
pub fn execute_batch<I>(&self, paths: I) -> ExitCode
|
||||
where
|
||||
I: Iterator<Item = PathBuf>,
|
||||
{
|
||||
let path_separator = self.path_separator.as_deref();
|
||||
let mut paths = paths.collect::<Vec<_>>();
|
||||
paths.sort();
|
||||
for cmd in &self.commands {
|
||||
let exit = cmd.generate_and_execute_batch(&paths, path_separator);
|
||||
if exit != ExitCode::Success {
|
||||
return exit;
|
||||
}
|
||||
}
|
||||
ExitCode::Success
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a template that is utilized to generate command strings.
|
||||
///
|
||||
/// The template is meant to be coupled with an input in order to generate a command. The
|
||||
/// `generate_and_execute()` method will be used to generate a command and execute it.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CommandTemplate {
|
||||
struct CommandTemplate {
|
||||
args: Vec<ArgumentTemplate>,
|
||||
mode: ExecutionMode,
|
||||
path_separator: Option<String>,
|
||||
}
|
||||
|
||||
impl CommandTemplate {
|
||||
pub fn new<I, S>(input: I, path_separator: Option<String>) -> CommandTemplate
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
Self::build(input, ExecutionMode::OneByOne, path_separator)
|
||||
}
|
||||
|
||||
pub fn new_batch<I, S>(input: I, path_separator: Option<String>) -> Result<CommandTemplate>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let cmd = Self::build(input, ExecutionMode::Batch, path_separator);
|
||||
if cmd.number_of_tokens() > 1 {
|
||||
return Err(anyhow!("Only one placeholder allowed for batch commands"));
|
||||
}
|
||||
if cmd.args[0].has_tokens() {
|
||||
return Err(anyhow!(
|
||||
"First argument of exec-batch is expected to be a fixed executable"
|
||||
));
|
||||
}
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
fn build<I, S>(input: I, mode: ExecutionMode, path_separator: Option<String>) -> CommandTemplate
|
||||
fn new<I, S>(input: I) -> Result<CommandTemplate>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
@ -117,16 +167,21 @@ impl CommandTemplate {
|
||||
args.push(ArgumentTemplate::Tokens(tokens));
|
||||
}
|
||||
|
||||
// We need to check that we have at least one argument, because if not
|
||||
// it will try to execute each file and directory it finds.
|
||||
//
|
||||
// Sadly, clap can't currently handle this for us, see
|
||||
// https://github.com/clap-rs/clap/issues/3542
|
||||
if args.is_empty() {
|
||||
bail!("No executable provided for --exec or --exec-batch");
|
||||
}
|
||||
|
||||
// If a placeholder token was not supplied, append one at the end of the command.
|
||||
if !has_placeholder {
|
||||
args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder]));
|
||||
}
|
||||
|
||||
CommandTemplate {
|
||||
args,
|
||||
mode,
|
||||
path_separator,
|
||||
}
|
||||
Ok(CommandTemplate { args })
|
||||
}
|
||||
|
||||
fn number_of_tokens(&self) -> usize {
|
||||
@ -136,45 +191,31 @@ impl CommandTemplate {
|
||||
/// Generates and executes a command.
|
||||
///
|
||||
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
|
||||
/// build. Once all arguments have been processed, the command is executed.
|
||||
pub fn generate_and_execute(
|
||||
&self,
|
||||
input: &Path,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
buffer_output: bool,
|
||||
) -> ExitCode {
|
||||
let mut cmd = Command::new(self.args[0].generate(&input, self.path_separator.as_deref()));
|
||||
/// build.
|
||||
fn generate(&self, input: &Path, path_separator: Option<&str>) -> Command {
|
||||
let mut cmd = Command::new(self.args[0].generate(&input, path_separator));
|
||||
for arg in &self.args[1..] {
|
||||
cmd.arg(arg.generate(&input, self.path_separator.as_deref()));
|
||||
cmd.arg(arg.generate(&input, path_separator));
|
||||
}
|
||||
cmd
|
||||
}
|
||||
|
||||
execute_command(cmd, &out_perm, buffer_output)
|
||||
}
|
||||
|
||||
pub fn in_batch_mode(&self) -> bool {
|
||||
self.mode == ExecutionMode::Batch
|
||||
}
|
||||
|
||||
pub fn generate_and_execute_batch<I>(&self, paths: I, buffer_output: bool) -> ExitCode
|
||||
where
|
||||
I: Iterator<Item = PathBuf>,
|
||||
{
|
||||
fn generate_and_execute_batch(
|
||||
&self,
|
||||
paths: &[PathBuf],
|
||||
path_separator: Option<&str>,
|
||||
) -> ExitCode {
|
||||
let mut cmd = Command::new(self.args[0].generate("", None));
|
||||
cmd.stdin(Stdio::inherit());
|
||||
cmd.stdout(Stdio::inherit());
|
||||
cmd.stderr(Stdio::inherit());
|
||||
|
||||
let mut paths: Vec<_> = paths.collect();
|
||||
let mut has_path = false;
|
||||
|
||||
for arg in &self.args[1..] {
|
||||
if arg.has_tokens() {
|
||||
paths.sort();
|
||||
|
||||
// A single `Tokens` is expected
|
||||
// So we can directly consume the iterator once and for all
|
||||
for path in &mut paths {
|
||||
cmd.arg(arg.generate(path, self.path_separator.as_deref()));
|
||||
for path in paths {
|
||||
cmd.arg(arg.generate(path, path_separator));
|
||||
has_path = true;
|
||||
}
|
||||
} else {
|
||||
@ -183,7 +224,10 @@ impl CommandTemplate {
|
||||
}
|
||||
|
||||
if has_path {
|
||||
execute_command(cmd, &Mutex::new(()), buffer_output)
|
||||
match cmd.spawn().and_then(|mut c| c.wait()) {
|
||||
Ok(_) => ExitCode::Success,
|
||||
Err(e) => handle_cmd_error(&cmd, e),
|
||||
}
|
||||
} else {
|
||||
ExitCode::Success
|
||||
}
|
||||
@ -302,13 +346,15 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_with_placeholder() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&[&"echo", &"${SHELL}:"], None),
|
||||
CommandTemplate {
|
||||
CommandSet::new(vec![vec![&"echo", &"${SHELL}:"]], None).unwrap(),
|
||||
CommandSet {
|
||||
commands: vec![CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Text("${SHELL}:".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
|
||||
],
|
||||
]
|
||||
}],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
@ -318,12 +364,14 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_with_no_extension() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{.}"], None),
|
||||
CommandTemplate {
|
||||
CommandSet::new(vec![vec!["echo", "{.}"]], None).unwrap(),
|
||||
CommandSet {
|
||||
commands: vec![CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::NoExt]),
|
||||
],
|
||||
}],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
@ -333,12 +381,14 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_with_basename() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{/}"], None),
|
||||
CommandTemplate {
|
||||
CommandSet::new(vec![vec!["echo", "{/}"]], None).unwrap(),
|
||||
CommandSet {
|
||||
commands: vec![CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::Basename]),
|
||||
],
|
||||
}],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
@ -348,12 +398,14 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_with_parent() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{//}"], None),
|
||||
CommandTemplate {
|
||||
CommandSet::new(vec![vec!["echo", "{//}"]], None).unwrap(),
|
||||
CommandSet {
|
||||
commands: vec![CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::Parent]),
|
||||
],
|
||||
}],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
@ -363,12 +415,14 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_with_basename_no_extension() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{/.}"], None),
|
||||
CommandTemplate {
|
||||
CommandSet::new(vec![vec!["echo", "{/.}"]], None).unwrap(),
|
||||
CommandSet {
|
||||
commands: vec![CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
|
||||
],
|
||||
}],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
@ -378,8 +432,9 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_multiple() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["cp", "{}", "{/.}.ext"], None),
|
||||
CommandTemplate {
|
||||
CommandSet::new(vec![vec!["cp", "{}", "{/.}.ext"]], None).unwrap(),
|
||||
CommandSet {
|
||||
commands: vec![CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("cp".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
|
||||
@ -388,6 +443,7 @@ mod tests {
|
||||
Token::Text(".ext".into())
|
||||
]),
|
||||
],
|
||||
}],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
@ -397,12 +453,14 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_single_batch() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new_batch(&["echo", "{.}"], None).unwrap(),
|
||||
CommandTemplate {
|
||||
CommandSet::new_batch(vec![vec!["echo", "{.}"]], None).unwrap(),
|
||||
CommandSet {
|
||||
commands: vec![CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::NoExt]),
|
||||
],
|
||||
}],
|
||||
mode: ExecutionMode::Batch,
|
||||
path_separator: None,
|
||||
}
|
||||
@ -411,7 +469,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tokens_multiple_batch() {
|
||||
assert!(CommandTemplate::new_batch(&["echo", "{.}", "{}"], None).is_err());
|
||||
assert!(CommandSet::new_batch(vec![vec!["echo", "{.}", "{}"]], None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_no_args() {
|
||||
assert!(CommandTemplate::new::<Vec<_>, &'static str>(vec![]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_set_no_args() {
|
||||
assert!(CommandSet::new(vec![vec!["echo"], vec![]], None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
33
src/main.rs
33
src/main.rs
@ -25,7 +25,7 @@ use regex::bytes::{RegexBuilder, RegexSetBuilder};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::print_error;
|
||||
use crate::exec::CommandTemplate;
|
||||
use crate::exec::CommandSet;
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::filetypes::FileTypes;
|
||||
#[cfg(unix)]
|
||||
@ -40,7 +40,9 @@ use crate::regex_helper::{pattern_has_uppercase_char, pattern_matches_strings_wi
|
||||
not(target_os = "android"),
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "freebsd"),
|
||||
not(target_env = "musl")
|
||||
not(target_env = "musl"),
|
||||
not(target_arch = "riscv64"),
|
||||
feature = "use-jemalloc"
|
||||
))]
|
||||
#[global_allocator]
|
||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||
@ -111,7 +113,7 @@ fn ensure_current_directory_exists(current_directory: &Path) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_search_pattern<'a>(matches: &'a clap::ArgMatches) -> Result<&'a str> {
|
||||
fn extract_search_pattern(matches: &clap::ArgMatches) -> Result<&'_ str> {
|
||||
let pattern = matches
|
||||
.value_of_os("pattern")
|
||||
.map(|p| {
|
||||
@ -261,10 +263,7 @@ fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result<Co
|
||||
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")
|
||||
|| matches.is_present("rg-alias-hidden-ignore")
|
||||
|| matches.is_present("no-ignore-vcs")
|
||||
|| matches.is_present("no-ignore-parent")),
|
||||
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")),
|
||||
@ -392,19 +391,16 @@ fn extract_command(
|
||||
matches: &clap::ArgMatches,
|
||||
path_separator: Option<&str>,
|
||||
colored_output: bool,
|
||||
) -> Result<Option<CommandTemplate>> {
|
||||
) -> Result<Option<CommandSet>> {
|
||||
None.or_else(|| {
|
||||
matches.values_of("exec").map(|args| {
|
||||
Ok(CommandTemplate::new(
|
||||
args,
|
||||
path_separator.map(str::to_string),
|
||||
))
|
||||
})
|
||||
matches
|
||||
.grouped_values_of("exec")
|
||||
.map(|args| CommandSet::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)))
|
||||
.grouped_values_of("exec-batch")
|
||||
.map(|args| CommandSet::new_batch(args, path_separator.map(str::to_string)))
|
||||
})
|
||||
.or_else(|| {
|
||||
if !matches.is_present("list-details") {
|
||||
@ -414,9 +410,8 @@ fn extract_command(
|
||||
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()
|
||||
});
|
||||
let res = determine_ls_command(&color_arg, colored_output)
|
||||
.map(|cmd| CommandSet::new_batch([cmd], path_separator.map(str::to_string)).unwrap());
|
||||
|
||||
Some(res)
|
||||
})
|
||||
|
30
src/walk.rs
30
src/walk.rs
@ -3,13 +3,13 @@ use std::io;
|
||||
use std::mem;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{borrow::Cow, io::Write};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use crossbeam_channel::{unbounded, Receiver, RecvTimeoutError, Sender};
|
||||
use ignore::overrides::OverrideBuilder;
|
||||
use ignore::{self, WalkBuilder};
|
||||
use regex::bytes::Regex;
|
||||
@ -54,7 +54,7 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> R
|
||||
let first_path_buf = path_iter
|
||||
.next()
|
||||
.expect("Error: Path vector can not be empty");
|
||||
let (tx, rx) = unbounded();
|
||||
let (tx, rx) = channel();
|
||||
|
||||
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
|
||||
|
||||
@ -71,7 +71,7 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> R
|
||||
walker
|
||||
.hidden(config.ignore_hidden)
|
||||
.ignore(config.read_fdignore)
|
||||
.parents(config.read_parent_ignore)
|
||||
.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)
|
||||
@ -103,10 +103,7 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> R
|
||||
match result {
|
||||
Some(ignore::Error::Partial(_)) => (),
|
||||
Some(err) => {
|
||||
print_error(format!(
|
||||
"Malformed pattern in global ignore file. {}.",
|
||||
err.to_string()
|
||||
));
|
||||
print_error(format!("Malformed pattern in global ignore file. {}.", err));
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
@ -118,10 +115,7 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> R
|
||||
match result {
|
||||
Some(ignore::Error::Partial(_)) => (),
|
||||
Some(err) => {
|
||||
print_error(format!(
|
||||
"Malformed pattern in custom ignore file. {}.",
|
||||
err.to_string()
|
||||
));
|
||||
print_error(format!("Malformed pattern in custom ignore file. {}.", err));
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
@ -231,7 +225,11 @@ impl<W: Write> ReceiverBuffer<W> {
|
||||
match self.mode {
|
||||
ReceiverMode::Buffering => {
|
||||
// Wait at most until we should switch to streaming
|
||||
self.rx.recv_deadline(self.deadline)
|
||||
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
|
||||
@ -351,13 +349,7 @@ fn spawn_receiver(
|
||||
// This will be set to `Some` if the `--exec` argument was supplied.
|
||||
if let Some(ref cmd) = config.command {
|
||||
if cmd.in_batch_mode() {
|
||||
exec::batch(
|
||||
rx,
|
||||
cmd,
|
||||
show_filesystem_errors,
|
||||
enable_output_buffering,
|
||||
config.batch_size,
|
||||
)
|
||||
exec::batch(rx, cmd, show_filesystem_errors, config.batch_size)
|
||||
} else {
|
||||
let shared_rx = Arc::new(Mutex::new(rx));
|
||||
|
||||
|
@ -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) {
|
||||
|
100
tests/tests.rs
100
tests/tests.rs
@ -614,6 +614,22 @@ fn test_no_ignore_vcs() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that --no-ignore-vcs still respects .fdignored in parent directory
|
||||
#[test]
|
||||
fn test_no_ignore_vcs_child_dir() {
|
||||
let te = TestEnv::new(
|
||||
&["inner"],
|
||||
&["inner/fdignored.foo", "inner/foo", "inner/gitignored.foo"],
|
||||
);
|
||||
|
||||
te.assert_output_subdirectory(
|
||||
"inner",
|
||||
&["--no-ignore-vcs", "foo"],
|
||||
"./foo
|
||||
./gitignored.foo",
|
||||
);
|
||||
}
|
||||
|
||||
/// Custom ignore files (--ignore-file)
|
||||
#[test]
|
||||
fn test_custom_ignore_files() {
|
||||
@ -1367,6 +1383,67 @@ fn test_exec() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec_multi() {
|
||||
// TODO test for windows
|
||||
if cfg!(windows) {
|
||||
return;
|
||||
}
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&[
|
||||
"--absolute-path",
|
||||
"foo",
|
||||
"--exec",
|
||||
"echo",
|
||||
";",
|
||||
"--exec",
|
||||
"echo",
|
||||
"test",
|
||||
"{/}",
|
||||
],
|
||||
&format!(
|
||||
"{abs_path}/a.foo
|
||||
{abs_path}/one/b.foo
|
||||
{abs_path}/one/two/C.Foo2
|
||||
{abs_path}/one/two/c.foo
|
||||
{abs_path}/one/two/three/d.foo
|
||||
{abs_path}/one/two/three/directory_foo
|
||||
test a.foo
|
||||
test b.foo
|
||||
test C.Foo2
|
||||
test c.foo
|
||||
test d.foo
|
||||
test directory_foo",
|
||||
abs_path = &abs_path
|
||||
),
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&[
|
||||
"e1", "--exec", "echo", "{.}", ";", "--exec", "echo", "{/}", ";", "--exec", "echo",
|
||||
"{//}", ";", "--exec", "echo", "{/.}",
|
||||
],
|
||||
"e1 e2
|
||||
e1 e2
|
||||
.
|
||||
e1 e2",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&[
|
||||
"foo", "--exec", "echo", "-n", "{/}: ", ";", "--exec", "echo", "{//}",
|
||||
],
|
||||
"a.foo: .
|
||||
b.foo: ./one
|
||||
C.Foo2: ./one/two
|
||||
c.foo: ./one/two
|
||||
d.foo: ./one/two/three
|
||||
directory_foo: ./one/two/three",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec_batch() {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
@ -1404,12 +1481,12 @@ fn test_exec_batch() {
|
||||
|
||||
te.assert_failure_with_error(
|
||||
&["foo", "--exec-batch", "echo", "{/}", ";", "-x", "echo"],
|
||||
"error: The argument '--exec <cmd>' cannot be used with '--exec-batch <cmd>'",
|
||||
"error: The argument '--exec-batch <cmd>...' cannot be used with '--exec <cmd>...'",
|
||||
);
|
||||
|
||||
te.assert_failure_with_error(
|
||||
&["foo", "--exec-batch"],
|
||||
"error: The argument '--exec-batch <cmd>' requires a value but none was supplied",
|
||||
"error: The argument '--exec-batch <cmd>...' requires a value but none was supplied",
|
||||
);
|
||||
|
||||
te.assert_failure_with_error(
|
||||
@ -1419,6 +1496,21 @@ fn test_exec_batch() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec_batch_multi() {
|
||||
// TODO test for windows
|
||||
if cfg!(windows) {
|
||||
return;
|
||||
}
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", "--exec-batch", "echo", "{}", ";", "--exec-batch", "echo", "{/}"],
|
||||
"./a.foo ./one/b.foo ./one/two/C.Foo2 ./one/two/c.foo ./one/two/three/d.foo ./one/two/three/directory_foo
|
||||
a.foo b.foo C.Foo2 c.foo d.foo directory_foo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec_batch_with_limit() {
|
||||
// TODO Test for windows
|
||||
@ -1954,8 +2046,8 @@ fn test_opposing(flag: &str, opposing_flags: &[&str]) {
|
||||
|
||||
let mut flags = vec![flag];
|
||||
flags.extend_from_slice(opposing_flags);
|
||||
let out_no_flags = te.assert_success_and_get_output(".", &[]);
|
||||
let out_opposing_flags = te.assert_success_and_get_output(".", &flags);
|
||||
let out_no_flags = te.assert_success_and_get_normalized_output(".", &[]);
|
||||
let out_opposing_flags = te.assert_success_and_get_normalized_output(".", &flags);
|
||||
|
||||
assert_eq!(
|
||||
out_no_flags,
|
||||
|
Loading…
Reference in New Issue
Block a user