mirror of
https://github.com/sharkdp/fd.git
synced 2026-09-01 04:28:07 +08:00
Compare commits
216 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b027b2c322 | |||
| 9350c8544d | |||
| f55033818d | |||
| 1ab4e87dda | |||
| 4e20803e7d | |||
| 5a154866e3 | |||
| ac3e0e18e4 | |||
| 9cc0c8ed57 | |||
| a991ed4488 | |||
| fe79b8ce0a | |||
| 25a728529d | |||
| 1056220421 | |||
| 08fcd7ce59 | |||
| 50fca2dca2 | |||
| 588888112a | |||
| eac20a8132 | |||
| d5da615c17 | |||
| 641594c2c6 | |||
| 2545aaabd2 | |||
| 3599a30ee2 | |||
| f14b854bc1 | |||
| 8f86a06c1a | |||
| 761c445263 | |||
| 588e344d5b | |||
| e3af9b0307 | |||
| 29bf9d731d | |||
| 3857fa8f62 | |||
| 24e108e1ad | |||
| 1d16cd855e | |||
| a0505bd4df | |||
| 630749f706 | |||
| 1daa454615 | |||
| 040c57e007 | |||
| 1ce2add943 | |||
| 418233193a | |||
| 0b04bd19df | |||
| ac2dae0427 | |||
| 53afcd8fc3 | |||
| d266fa3010 | |||
| e7d0a91986 | |||
| 801816ee6c | |||
| 5cbd8405ec | |||
| 35945c4a62 | |||
| 97908beb97 | |||
| f15540e501 | |||
| 074cfad3d4 | |||
| f3ec3d9cf0 | |||
| 46e865f1a2 | |||
| 7f58e8f706 | |||
| f1ab443dd0 | |||
| acd4910f2c | |||
| c83bcfbdfc | |||
| c2b46f247f | |||
| fe53af064b | |||
| 6aa87f3423 | |||
| 74e593c43c | |||
| df4227c614 | |||
| 5ade72a5e1 | |||
| d8bd5f9d51 | |||
| 85f9db0565 | |||
| 2672d84160 | |||
| 8cfdcf43f6 | |||
| 8c197d2866 | |||
| 051ff5987a | |||
| 630f173724 | |||
| 64e6ea9fe9 | |||
| 33feb511f0 | |||
| 985fab16bb | |||
| a17a3fe5ef | |||
| 04debe5c4e | |||
| ec99e5d34c | |||
| 047f1be792 | |||
| ecd5ec6387 | |||
| 3c87952c47 | |||
| 4b5efa3438 | |||
| afcdaf2e3d | |||
| 1abdf47731 | |||
| 92b5c9f71e | |||
| f09f038bd2 | |||
| 6b40a075cd | |||
| 45d1b15cff | |||
| 32ec7afc3a | |||
| 34b8c0df73 | |||
| 831f517f5a | |||
| f064ee5509 | |||
| 26c2371835 | |||
| 8832e471bc | |||
| daf5191aac | |||
| c0c38872c4 | |||
| 81a27fa9bd | |||
| 6ff58abb6c | |||
| 974802e817 | |||
| deea31cbbc | |||
| caad43fe6c | |||
| 095bad550f | |||
| f7a96bd980 | |||
| cdf737bf5c | |||
| e2bb932f87 | |||
| 437bd1188e | |||
| dae09ecf6d | |||
| d81cf34771 | |||
| b98f0783ff | |||
| 8543ca645d | |||
| 984f04f142 | |||
| abe8aa55c0 | |||
| 54c117d72f | |||
| 8691ab4bed | |||
| cb1cfa108b | |||
| 8bdd8f8e8f | |||
| f9c4e8d399 | |||
| adc467b2b2 | |||
| 56b6389dac | |||
| 046b0574dc | |||
| 6407dc4eb4 | |||
| 2ebef2d46f | |||
| 27caa33729 | |||
| 53226f82eb | |||
| 8fb25f15c9 | |||
| d375b5e1f5 | |||
| 2465cd1399 | |||
| 1c7e647f61 | |||
| 67e76b8b5b | |||
| aa70c5a446 | |||
| 50a2bab5cd | |||
| 641976cf7a | |||
| 6fb5004b85 | |||
| e4f61bc795 | |||
| c428f52209 | |||
| 59c9901cde | |||
| 4dee8b7940 | |||
| 9a45947234 | |||
| c1ef68662c | |||
| 9628d7fdcb | |||
| cb47be6497 | |||
| a3200204dc | |||
| e2b2701965 | |||
| 409cf0c7a1 | |||
| f01685c63f | |||
| 060d14b5dc | |||
| fe922688b3 | |||
| 4172ed03f0 | |||
| 0bbc7f5219 | |||
| ace3f512d3 | |||
| 2f3b472dfd | |||
| 0207c1371e | |||
| fea85dbfb3 | |||
| b5e5b132c8 | |||
| 6a9f16e159 | |||
| f02b3cba6a | |||
| 666f90db87 | |||
| 2a1ee0b17d | |||
| 388b90e624 | |||
| 011e09840a | |||
| 37483036e0 | |||
| 8c1b037279 | |||
| f9a32583a5 | |||
| 9d840dd485 | |||
| 4f6c43a6dc | |||
| 2cf8e7b8a5 | |||
| 67f6fdf6a7 | |||
| 8ce127e443 | |||
| b1e48efc4a | |||
| ab3ded8d27 | |||
| 5397787824 | |||
| ffd49c60bd | |||
| 89a8f5573d | |||
| d49b0aa977 | |||
| ddddc3f1db | |||
| 87d0ccd4c0 | |||
| c654159bf2 | |||
| 588b0dc7c9 | |||
| 88fb9d73d1 | |||
| 3386b854b0 | |||
| 47d95284aa | |||
| 631931b431 | |||
| 40d811c7be | |||
| e5ee5eb7b3 | |||
| 82eeeda504 | |||
| f16552f29c | |||
| e30bc16780 | |||
| b4be1f161c | |||
| 8805bd32e2 | |||
| 7a3994decb | |||
| 7e8c2248d0 | |||
| ad8256c217 | |||
| 1bf193af60 | |||
| 1c1be76f35 | |||
| 267598fba5 | |||
| 11b3e96d2d | |||
| a6086abe7f | |||
| 7f65e7a0d6 | |||
| 814d37030e | |||
| 089c183d30 | |||
| 370d9f081f | |||
| 86fe9977e8 | |||
| b1bb60ebb9 | |||
| b4d39270d6 | |||
| 6d83eea32b | |||
| 26beaf97a6 | |||
| 33d37d7623 | |||
| dce004afef | |||
| c1941cc7ed | |||
| 0a1c65b9ce | |||
| 4fb9d84ffc | |||
| 2a84eb5cea | |||
| 6dd59b33b1 | |||
| 4fea5f3a3c | |||
| b269f4902e | |||
| 33791d8400 | |||
| 4e737b4b44 | |||
| c985ba2e6f | |||
| 658a97a94a | |||
| bc2b7a33ae | |||
| 1045930dd9 | |||
| cc36543da2 | |||
| 050fa9c3b1 |
@@ -0,0 +1,29 @@
|
||||
name: fd
|
||||
version: git
|
||||
summary: "A simple, fast and user-friendly alternative to 'find'"
|
||||
description: |
|
||||
fd is a simple, fast and user-friendly alternative to find.
|
||||
- While it does not seek to mirror all of find's powerful functionality, it provides sensible (opinionated) defaults for 80% of the use cases.
|
||||
Features
|
||||
Convenient syntax: fd PATTERN instead of find -iname '*PATTERN*'.
|
||||
Colorized terminal output (similar to ls).
|
||||
It's fast (see benchmarks below).
|
||||
Smart case: the search is case-insensitive by default. It switches to case-sensitive if the pattern contains an uppercase character*.
|
||||
Ignores hidden directories and files, by default.
|
||||
Ignores patterns from your .gitignore, by default.
|
||||
Regular expressions.
|
||||
Unicode-awareness.
|
||||
The command name is 50% shorter* than find :-).
|
||||
Parallel command execution with a syntax similar to GNU Parallel.
|
||||
|
||||
grade: stable
|
||||
confinement: classic
|
||||
|
||||
parts:
|
||||
fd:
|
||||
plugin: rust
|
||||
source: ./
|
||||
|
||||
apps:
|
||||
fd:
|
||||
command: bin/fd
|
||||
+23
-15
@@ -20,6 +20,17 @@ matrix:
|
||||
- os: osx
|
||||
rust: stable
|
||||
env: TARGET=x86_64-apple-darwin
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- TARGET=arm-unknown-linux-gnueabihf
|
||||
- CC_arm_unknown_linux_gnueabihf=/usr/bin/arm-linux-gnueabihf-gcc-4.8
|
||||
- CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- TARGET=arm-unknown-linux-musleabihf
|
||||
- CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
|
||||
|
||||
# Beta channel.
|
||||
- os: linux
|
||||
@@ -59,19 +70,19 @@ matrix:
|
||||
|
||||
# Minimum Rust supported channel.
|
||||
- os: linux
|
||||
rust: 1.20.0
|
||||
rust: 1.31.0
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
- os: linux
|
||||
rust: 1.20.0
|
||||
rust: 1.31.0
|
||||
env: TARGET=x86_64-unknown-linux-musl
|
||||
- os: linux
|
||||
rust: 1.20.0
|
||||
rust: 1.31.0
|
||||
env: TARGET=i686-unknown-linux-gnu
|
||||
- os: linux
|
||||
rust: 1.20.0
|
||||
rust: 1.31.0
|
||||
env: TARGET=i686-unknown-linux-musl
|
||||
- os: osx
|
||||
rust: 1.20.0
|
||||
rust: 1.31.0
|
||||
env: TARGET=x86_64-apple-darwin
|
||||
|
||||
# Code formatting check
|
||||
@@ -79,14 +90,13 @@ matrix:
|
||||
rust: nightly
|
||||
# skip the global install step
|
||||
install:
|
||||
- cargo install --force rustfmt-nightly
|
||||
script: cargo fmt -- --write-mode=diff
|
||||
- cargo install --debug --force rustfmt-nightly
|
||||
script: cargo fmt -- --check
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
# needed for i686-unknown-linux-gnu target
|
||||
- gcc-multilib
|
||||
sudo: required
|
||||
|
||||
before_install:
|
||||
- ci/before_install.bash
|
||||
|
||||
env:
|
||||
global:
|
||||
@@ -101,9 +111,7 @@ install:
|
||||
- if [[ $TRAVIS_OS_NAME = linux && $HOST != $TARGET ]]; then rustup target add $TARGET; fi
|
||||
|
||||
script:
|
||||
# Incorporate TARGET env var to the build and test process
|
||||
- cargo build --target $TARGET --verbose
|
||||
- cargo test --target $TARGET --verbose
|
||||
- ci/script.bash
|
||||
|
||||
before_deploy:
|
||||
- bash ci/before_deploy.bash
|
||||
|
||||
+1
-1
@@ -16,5 +16,5 @@ give us the chance to discuss any potential changes first.
|
||||
* [Open pull requests](https://github.com/sharkdp/fd/pulls)
|
||||
* [Development section in the README](https://github.com/sharkdp/fd#development)
|
||||
* [fd on crates.io](https://crates.io/crates/fd-find)
|
||||
* [LICENSE](https://github.com/sharkdp/fd/blob/master/LICENSE)
|
||||
* [LICENSE-APACHE](https://github.com/sharkdp/fd/blob/master/LICENSE-APACHE) and [LICENSE-MIT](https://github.com/sharkdp/fd/blob/master/LICENSE-MIT)
|
||||
|
||||
|
||||
Generated
+276
-208
@@ -1,74 +1,100 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.6.4"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.9.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.10.2"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.6"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.7.0"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.1"
|
||||
name = "bstr"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.2"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.29.0"
|
||||
version = "2.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.2.10"
|
||||
name = "crossbeam-channel"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.0.3"
|
||||
version = "3.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -78,23 +104,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "fd-find"
|
||||
version = "6.2.0"
|
||||
version = "7.4.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ctrlc 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ignore 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"filetime 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ignore 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jemallocator 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lscolors 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -103,44 +143,69 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.2"
|
||||
name = "fs_extra"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon-sys"
|
||||
version = "0.3.2"
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.2.1"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.2.2"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jemalloc-sys"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jemallocator"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"jemalloc-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -154,129 +219,142 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.0.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.34"
|
||||
version = "0.2.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.3.9"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.1"
|
||||
name = "lscolors"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "1.0.2"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.8.1"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.8.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.19"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fuchsia-zircon 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.1"
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
name = "rand_core"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "0.1.3"
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.6.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "tempdir"
|
||||
version = "0.3.5"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -285,64 +363,40 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.9.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "1.0.0"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.1.3"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -352,12 +406,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "1.0.7"
|
||||
version = "2.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -367,11 +421,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.2"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -381,62 +435,76 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.3.2"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.3.2"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
|
||||
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
|
||||
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
|
||||
"checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859"
|
||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
||||
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
|
||||
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
|
||||
"checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f"
|
||||
"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97"
|
||||
"checksum ctrlc 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "df391ea008fca636e41e40863a0b39a850e2ab26b0cdeed0c3657fd05a66d44c"
|
||||
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
|
||||
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
|
||||
"checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245"
|
||||
"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be"
|
||||
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
|
||||
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
|
||||
"checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa"
|
||||
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
|
||||
"checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f"
|
||||
"checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
|
||||
"checksum filetime 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd7380b54ced79dda72ecc35cc4fbbd1da6bba54afaa37e96fd1c2a308cd469"
|
||||
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||
"checksum fuchsia-zircon 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd510087c325af53ba24f3be8f1c081b0982319adcb8b03cad764512923ccc19"
|
||||
"checksum fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "08b3a6f13ad6b96572b53ce7af74543132f1a7055ccceb6d073dd36c54481859"
|
||||
"checksum globset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "464627f948c3190ae3d04b1bc6d7dca2f785bda0ac01278e6db129ad383dbeb6"
|
||||
"checksum ignore 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3fcaf2365eb14b28ec7603c98c06cc531f19de9eb283d89a3dff8417c8c99f5"
|
||||
"checksum fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674"
|
||||
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
"checksum globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2"
|
||||
"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||
"checksum ignore 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0ec16832258409d571aaef8273f3c3cc5b060d784e159d1a0f3b0017308f84a7"
|
||||
"checksum jemalloc-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45"
|
||||
"checksum jemallocator 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
|
||||
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
|
||||
"checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0"
|
||||
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
|
||||
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
|
||||
"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
|
||||
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
||||
"checksum nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47e49f6982987135c5e9620ab317623e723bd06738fd85377e8d55f57c8b6487"
|
||||
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
|
||||
"checksum rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "9e7944d95d25ace8f377da3ac7068ce517e4c646754c43a1b1849177bbf72e59"
|
||||
"checksum redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "07b8f011e3254d5a9b318fde596d409a0001c9ae4c6e7907520c2eaa4d988c99"
|
||||
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||
"checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa"
|
||||
"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e"
|
||||
"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
|
||||
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
|
||||
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
"checksum lscolors 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea3b3414b2d015c4fd689815f2551797f3c2296bb241dd709c7da233ec7cba4b"
|
||||
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
|
||||
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
|
||||
"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273"
|
||||
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
||||
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
|
||||
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
|
||||
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
||||
"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
|
||||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||
"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327"
|
||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
||||
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
|
||||
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
|
||||
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
|
||||
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
||||
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
|
||||
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
|
||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
||||
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
|
||||
"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
"checksum winapi 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "890b38836c01d72fdb636d15c9cfc52ec7fd783b330abc93cd1686f4308dfccc"
|
||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
+22
-14
@@ -16,7 +16,8 @@ license = "MIT/Apache-2.0"
|
||||
name = "fd-find"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/sharkdp/fd"
|
||||
version = "6.2.0"
|
||||
version = "7.4.0"
|
||||
edition= "2018"
|
||||
|
||||
[[bin]]
|
||||
name = "fd"
|
||||
@@ -28,30 +29,37 @@ repository = "sharkdp/fd"
|
||||
repository = "sharkdp/fd"
|
||||
|
||||
[build-dependencies]
|
||||
clap = "2.26.0"
|
||||
version_check = "0.1.3"
|
||||
clap = "2.31.2"
|
||||
version_check = "0.9"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.9"
|
||||
ansi_term = "0.12"
|
||||
atty = "0.2"
|
||||
ignore = "0.2"
|
||||
lazy_static = "1"
|
||||
num_cpus = "1.6.2"
|
||||
regex = "0.2"
|
||||
regex-syntax = "0.4"
|
||||
ctrlc = "3.0"
|
||||
ignore = "0.4.3"
|
||||
lazy_static = "1.1.0"
|
||||
num_cpus = "1.8"
|
||||
regex = "1.0.0"
|
||||
regex-syntax = "0.6"
|
||||
ctrlc = "3.1"
|
||||
humantime = "1.1.1"
|
||||
lscolors = "0.6"
|
||||
globset = "0.4"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "2.26.0"
|
||||
version = "2.31.2"
|
||||
features = ["suggestions", "color", "wrap_help"]
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
kernel32-sys = "0.2"
|
||||
winapi = "0.2"
|
||||
[target.'cfg(all(not(windows), not(target_env = "musl")))'.dependencies]
|
||||
jemallocator = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
diff = "0.1"
|
||||
tempdir = "0.3"
|
||||
filetime = "0.2.1"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
[](https://travis-ci.org/sharkdp/fd)
|
||||
[](https://ci.appveyor.com/project/sharkdp/fd)
|
||||
[](https://crates.io/crates/fd-find)
|
||||
[中文](https://github.com/chinanf-boy/fd-zh)
|
||||
|
||||
*fd* is a simple, fast and user-friendly alternative to
|
||||
[*find*](https://www.gnu.org/software/findutils/).
|
||||
@@ -26,63 +27,62 @@ While it does not seek to mirror all of *find*'s powerful functionality, it prov
|
||||
|
||||
## Demo
|
||||
|
||||

|
||||

|
||||
|
||||
## Benchmark
|
||||
Let's search my home folder for files that end in `[0-9].jpg`. It contains ~150.000
|
||||
|
||||
Let's search my home folder for files that end in `[0-9].jpg`. It contains ~190.000
|
||||
subdirectories and about a million files. For averaging and statistical analysis, I'm using
|
||||
[bench](https://github.com/Gabriel439/bench). All benchmarks are performed for a "warm
|
||||
cache". Results for a cold cache are similar.
|
||||
[hyperfine](https://github.com/sharkdp/hyperfine). The following benchmarks are performed
|
||||
with a "warm"/pre-filled disk-cache (results for a "cold" disk-cache show the same trends).
|
||||
|
||||
Let's start with `find`:
|
||||
```
|
||||
find ~ -iregex '.*[0-9]\.jpg$'
|
||||
Benchmark #1: find ~ -iregex '.*[0-9]\.jpg$'
|
||||
|
||||
time 6.265 s (6.127 s .. NaN s)
|
||||
1.000 R² (1.000 R² .. 1.000 R²)
|
||||
mean 6.162 s (6.140 s .. 6.181 s)
|
||||
std dev 31.73 ms (0.0 s .. 33.48 ms)
|
||||
Time (mean ± σ): 7.236 s ± 0.090 s
|
||||
|
||||
Range (min … max): 7.133 s … 7.385 s
|
||||
```
|
||||
|
||||
`find` is much faster if it does not need to perform a regular-expression search:
|
||||
```
|
||||
find ~ -iname '*[0-9].jpg'
|
||||
Benchmark #2: find ~ -iname '*[0-9].jpg'
|
||||
|
||||
time 2.866 s (2.754 s .. 2.964 s)
|
||||
1.000 R² (0.999 R² .. 1.000 R²)
|
||||
mean 2.860 s (2.834 s .. 2.875 s)
|
||||
std dev 23.11 ms (0.0 s .. 25.09 ms)
|
||||
Time (mean ± σ): 3.914 s ± 0.027 s
|
||||
|
||||
Range (min … max): 3.876 s … 3.964 s
|
||||
```
|
||||
|
||||
Now let's try the same for `fd`. Note that `fd` *always* performs a regular expression
|
||||
search. The options `--hidden` and `--no-ignore` are needed for a fair comparison,
|
||||
otherwise `fd` does not have to traverse hidden folders and ignored paths (see below):
|
||||
```
|
||||
fd --hidden --no-ignore '.*[0-9]\.jpg$' ~
|
||||
Benchmark #3: fd -HI '.*[0-9]\.jpg$' ~
|
||||
|
||||
time 892.6 ms (839.0 ms .. 915.4 ms)
|
||||
0.999 R² (0.997 R² .. 1.000 R²)
|
||||
mean 871.2 ms (857.9 ms .. 881.3 ms)
|
||||
std dev 15.50 ms (0.0 s .. 17.49 ms)
|
||||
Time (mean ± σ): 811.6 ms ± 26.9 ms
|
||||
|
||||
Range (min … max): 786.0 ms … 870.7 ms
|
||||
```
|
||||
For this particular example, `fd` is approximately seven times faster than `find -iregex`
|
||||
and about three times faster than `find -iname`. By the way, both tools found the exact
|
||||
same 14030 files :smile:.
|
||||
For this particular example, `fd` is approximately nine times faster than `find -iregex`
|
||||
and about five times faster than `find -iname`. By the way, both tools found the exact
|
||||
same 20880 files :smile:.
|
||||
|
||||
Finally, let's run `fd` without `--hidden` and `--no-ignore` (this can lead to different
|
||||
search results, of course):
|
||||
search results, of course). If *fd* does not have to traverse the hidden and git-ignored
|
||||
folders, it is almost an order of magnitude faster:
|
||||
```
|
||||
fd '[0-9]\.jpg$' ~
|
||||
Benchmark #4: fd '[0-9]\.jpg$' ~
|
||||
|
||||
time 159.5 ms (155.8 ms .. 165.3 ms)
|
||||
0.999 R² (0.996 R² .. 1.000 R²)
|
||||
mean 158.7 ms (156.5 ms .. 161.6 ms)
|
||||
std dev 3.263 ms (2.401 ms .. 4.298 ms)
|
||||
Time (mean ± σ): 123.7 ms ± 6.0 ms
|
||||
|
||||
Range (min … max): 118.8 ms … 140.0 ms
|
||||
```
|
||||
|
||||
**Note**: This is *one particular* benchmark on *one particular* machine. While I have
|
||||
performed quite a lot of different tests (and found consistent results), things might
|
||||
be different for you! I encourage everyone to try it out on their own.
|
||||
be different for you! I encourage everyone to try it out on their own. See
|
||||
[this repository](https://github.com/sharkdp/fd-benchmarks) for all necessary scripts.
|
||||
|
||||
Concerning *fd*'s speed, the main credit goes to the `regex` and `ignore` crates that are also used
|
||||
in [ripgrep](https://github.com/BurntSushi/ripgrep) (check it out!).
|
||||
@@ -123,21 +123,78 @@ fd -e flac -x ffmpeg -i {} -c:a libopus {.}.opus
|
||||
fd -x wc -l \; -e rs
|
||||
```
|
||||
|
||||
The number of threads used for command execution can be set with the `--threads`/`-j` option.
|
||||
|
||||
## Installation
|
||||
|
||||
### On Ubuntu
|
||||
*... and other Debian-based Linux distrutions.*
|
||||
*... and other Debian-based Linux distributions.*
|
||||
|
||||
Download the latest `.deb` package from the [release page](https://github.com/sharkdp/fd/releases) and install it via:
|
||||
If you run Ubuntu 19.04 (Disco Dingo) or newer, you can install the
|
||||
[officially maintained package](https://packages.ubuntu.com/disco/fd-find):
|
||||
```
|
||||
sudo apt install fd-find
|
||||
```
|
||||
Note that the binary is called `fdfind` as the binary name `fd` is already used by another package.
|
||||
It is recommended that you add an `alias fd=fdfind` to your shells initialization file, in order to
|
||||
use `fd` in the same way as in this documentation.
|
||||
|
||||
If you use an older version of Ubuntu, you can download the latest `.deb` package from the
|
||||
[release page](https://github.com/sharkdp/fd/releases) and install it via:
|
||||
``` bash
|
||||
sudo dpkg -i fd_6.2.0_amd64.deb # adapt version number and architecture
|
||||
sudo dpkg -i fd_7.4.0_amd64.deb # adapt version number and architecture
|
||||
```
|
||||
|
||||
### On Debian
|
||||
|
||||
If you run Debian Buster or newer, you can install the
|
||||
[officially maintained Debian package](https://tracker.debian.org/pkg/rust-fd-find):
|
||||
```
|
||||
sudo apt-get install fd-find
|
||||
```
|
||||
Note that the binary is called `fdfind` as the binary name `fd` is already used by another package.
|
||||
It is recommended that you add an `alias fd=fdfind` to your shells initialization file, in order to
|
||||
use `fd` in the same way as in this documentation.
|
||||
|
||||
### On Fedora
|
||||
|
||||
Starting with Fedora 28, you can install `fd` from the official package sources:
|
||||
``` bash
|
||||
dnf install fd-find
|
||||
```
|
||||
|
||||
For older versions, you can use this [Fedora copr](https://copr.fedorainfracloud.org/coprs/keefle/fd/) to install `fd`:
|
||||
``` bash
|
||||
dnf copr enable keefle/fd
|
||||
dnf install fd
|
||||
```
|
||||
|
||||
### On Alpine Linux
|
||||
|
||||
You can install [the fd package](https://pkgs.alpinelinux.org/packages?name=fd)
|
||||
from the official sources, provided you have the appropriate repository enabled:
|
||||
```
|
||||
apk add fd
|
||||
```
|
||||
|
||||
### On Arch Linux
|
||||
|
||||
You can install [the fd-rs package](https://www.archlinux.org/packages/community/x86_64/fd-rs/) from the official repos:
|
||||
You can install [the fd package](https://www.archlinux.org/packages/community/x86_64/fd/) from the official repos:
|
||||
```
|
||||
pacman -S fd-rs
|
||||
pacman -S fd
|
||||
```
|
||||
### On Gentoo Linux
|
||||
|
||||
You can use [the fd ebuild](https://packages.gentoo.org/packages/sys-apps/fd) from the official repo:
|
||||
```
|
||||
emerge -av fd
|
||||
```
|
||||
|
||||
### On openSUSE Linux
|
||||
|
||||
You can install [the fd package](https://software.opensuse.org/package/fd) from the official repo:
|
||||
```
|
||||
zypper in fd
|
||||
```
|
||||
|
||||
### On Void Linux
|
||||
@@ -149,15 +206,30 @@ xbps-install -S fd
|
||||
|
||||
### On macOS
|
||||
|
||||
You can install [this Homebrew package](http://braumeister.org/formula/fd):
|
||||
You can install `fd` with [Homebrew](http://braumeister.org/formula/fd):
|
||||
```
|
||||
brew install fd
|
||||
```
|
||||
|
||||
… or with MacPorts:
|
||||
```
|
||||
sudo port install fd
|
||||
```
|
||||
|
||||
### On Windows
|
||||
|
||||
You can download pre-built binaries from the [release page](https://github.com/sharkdp/fd/releases).
|
||||
|
||||
Alternatively, you can install `fd` via [Scoop](http://scoop.sh):
|
||||
```
|
||||
scoop install fd
|
||||
```
|
||||
|
||||
Or via [Chocolatey](https://chocolatey.org):
|
||||
```
|
||||
choco install fd
|
||||
```
|
||||
|
||||
### On NixOS / via Nix
|
||||
|
||||
You can use the [Nix package manager](https://nixos.org/nix/) to install `fd`:
|
||||
@@ -167,9 +239,25 @@ nix-env -i fd
|
||||
|
||||
### On FreeBSD
|
||||
|
||||
You can install `sysutils/fd` via portmaster:
|
||||
You can install [the fd-find package](https://www.freshports.org/sysutils/fd) from the official repo:
|
||||
```
|
||||
portmaster sysutils/fd
|
||||
pkg install fd-find
|
||||
```
|
||||
|
||||
### On OpenBSD
|
||||
You can install the [fd-find](https://gitlab.com/epbsd/ports/tree/master/sysutils/fd) package from the [EPBSD](https://epbsd.org/) repo:
|
||||
```
|
||||
ftp -o /etc/signify/epbsd-pkg.pub https://epbsd.org/epbsd-pkg.pub
|
||||
export PKG_PATH=https://repo.epbsd.org/pub/OpenBSD/$(arch -s):${PKG_PATH}
|
||||
pkg_add fd
|
||||
```
|
||||
|
||||
### From NPM
|
||||
|
||||
On linux and macOS, you can install the [fd-find](https://npm.im/fd-find) package:
|
||||
|
||||
```
|
||||
npm install -g fd-find
|
||||
```
|
||||
|
||||
### From source
|
||||
@@ -178,7 +266,7 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can
|
||||
```
|
||||
cargo install fd-find
|
||||
```
|
||||
Note that rust version *1.20.0* or later is required.
|
||||
Note that rust version *1.31.0* or later is required.
|
||||
|
||||
### From binaries
|
||||
|
||||
@@ -206,10 +294,12 @@ USAGE:
|
||||
|
||||
FLAGS:
|
||||
-H, --hidden Search hidden files and directories
|
||||
-I, --no-ignore Do not respect .(git)ignore files
|
||||
-I, --no-ignore Do not respect .(git|fd)ignore files
|
||||
--no-ignore-vcs Do not respect .gitignore files
|
||||
-s, --case-sensitive Case-sensitive search (default: smart case)
|
||||
-i, --ignore-case Case-insensitive search (default: smart case)
|
||||
-g, --glob Glob-based search (default: regular expression)
|
||||
-F, --fixed-strings Treat the pattern as a literal string
|
||||
-a, --absolute-path Show absolute instead of relative paths
|
||||
-L, --follow Follow symbolic links
|
||||
-p, --full-path Search full path (default: file-/dirname only)
|
||||
@@ -218,19 +308,26 @@ FLAGS:
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-d, --max-depth <depth> Set maximum search depth (default: none)
|
||||
-t, --type <filetype>... Filter by type: f(ile), d(irectory), (sym)l(ink)
|
||||
-e, --extension <ext>... Filter by file extension
|
||||
-x, --exec <cmd>... Execute a command for each search result
|
||||
-E, --exclude <pattern>... Exclude entries that match the given glob pattern
|
||||
-c, --color <when> When to use colors: never, *auto*, always
|
||||
-j, --threads <num> Set number of threads to use for searching & executing
|
||||
-d, --max-depth <depth> Set maximum search depth (default: none)
|
||||
-t, --type <filetype>... Filter by type: file (f), directory (d), symlink (l),
|
||||
executable (x), empty (e)
|
||||
-e, --extension <ext>... Filter by file extension
|
||||
-x, --exec <cmd> Execute a command for each search result
|
||||
-X, --exec-batch <cmd> Execute a command with all search results at once
|
||||
-E, --exclude <pattern>... Exclude entries that match the given glob pattern
|
||||
-c, --color <when> When to use colors: never, *auto*, always
|
||||
-S, --size <size>... Limit results based on the size of files.
|
||||
--changed-within <date|dur> Filter by file modification time (newer than)
|
||||
--changed-before <date|dur> Filter by file modification time (older than)
|
||||
|
||||
ARGS:
|
||||
<pattern> the search pattern, a regular expression (optional)
|
||||
<pattern> the search pattern: a regular expression unless '--glob' is used (optional)
|
||||
<path>... the root directory for the filesystem search (optional)
|
||||
```
|
||||
|
||||
This is the output of `fd -h`. To see the full set of command-line options, use `fd --help` which
|
||||
also includes a much more detailed help text.
|
||||
|
||||
## Tutorial
|
||||
|
||||
First, to get an overview of all available command line options, you can either run
|
||||
@@ -262,7 +359,7 @@ X11/xinit/xserverrc
|
||||
|
||||
### Specifying the root directory
|
||||
|
||||
If we want so search a specific directory, it can be given as a second argument to *fd*:
|
||||
If we want to search a specific directory, it can be given as a second argument to *fd*:
|
||||
``` bash
|
||||
> fd passwd /etc
|
||||
/etc/default/passwd
|
||||
@@ -282,6 +379,15 @@ testenv/mod.rs
|
||||
tests.rs
|
||||
```
|
||||
|
||||
If you want to use this functionality to list all files in a given directory, you have to use
|
||||
a catch-all pattern such as `.` or `^`:
|
||||
``` bash
|
||||
> fd . fd/tests/
|
||||
testenv
|
||||
testenv/mod.rs
|
||||
tests.rs
|
||||
```
|
||||
|
||||
### Searching for a particular file extension
|
||||
|
||||
Often, we are interested in all files of a particular type. This can be done with the `-e` (or
|
||||
@@ -312,7 +418,7 @@ search results. To disable this behavior, we can use the `-H` (or `--hidden`) op
|
||||
|
||||
If we work in a directory that is a Git repository (or includes Git repositories), *fd* does not
|
||||
search folders (and does not show files) that match one of the `.gitignore` patterns. To disable
|
||||
this behavior, we can use the `-I` (or `--ignore`) option:
|
||||
this behavior, we can use the `-I` (or `--no-ignore`) option:
|
||||
``` bash
|
||||
> fd num_cpu
|
||||
> fd -I num_cpu
|
||||
@@ -322,16 +428,98 @@ target/debug/deps/libnum_cpus-f5ce7ef99006aa05.rlib
|
||||
To really search *all* files and directories, simply combine the hidden and ignore features to show
|
||||
everything (`-HI`).
|
||||
|
||||
### Excluding specific files or directories
|
||||
|
||||
Sometimes we want to ignore search results from a specific subdirectory. For example, we might
|
||||
want to search all hidden files and directories (`-H`) but exclude all matches from `.git`
|
||||
directories. We can use the `-E` (or `--exclude`) option for this. It takes an arbitrary glob
|
||||
pattern as an argument:
|
||||
``` bash
|
||||
> fd -H -E .git …
|
||||
```
|
||||
|
||||
We can also use this to skip mounted directories:
|
||||
``` bash
|
||||
> fd -E /mnt/external-drive …
|
||||
```
|
||||
|
||||
.. or to skip certain file types:
|
||||
``` bash
|
||||
> fd -E '*.bak' …
|
||||
```
|
||||
|
||||
To make exclude-patterns like these permanent, you can create a `.fdignore` file. They work like
|
||||
`.gitignore` files, but are specific to `fd`. For example:
|
||||
``` bash
|
||||
> cat ~/.fdignore
|
||||
/mnt/external-drive
|
||||
*.bak
|
||||
```
|
||||
Note: `fd` also supports `.ignore` files that are used by other programs such as `rg` or `ag`.
|
||||
|
||||
### Using fd with `xargs` or `parallel`
|
||||
|
||||
If we want to run a command on all search results, we can pipe the output to `xargs`:
|
||||
``` bash
|
||||
> fd -0 -e rs | xargs -0 wc -l
|
||||
```
|
||||
Here, the `-0` option tells *fd* to separate search results by the NULL character (instead of .
|
||||
newlines) In the same way, the `-0` option of `xargs` tells it to read the input in this way .
|
||||
Here, the `-0` option tells *fd* to separate search results by the NULL character (instead of
|
||||
newlines). In the same way, the `-0` option of `xargs` tells it to read the input in this way.
|
||||
|
||||
### Using fd with `fzf`
|
||||
### Deleting files
|
||||
|
||||
You can use `fd` to remove all files and directories that are matched by your search pattern.
|
||||
If you only want to remove files, you can use the `--exec-batch`/`-X` option to call `rm`. For
|
||||
example, to recursively remove all `.DS_Store` files, run:
|
||||
``` bash
|
||||
> fd -H '^\.DS_Store$' -tf -X rm
|
||||
```
|
||||
If you are unsure, always call `fd` without `-X rm` first. Alternatively, use `rm`s "interactive"
|
||||
option:
|
||||
``` bash
|
||||
> fd -H '^\.DS_Store$' -tf -X rm -i
|
||||
```
|
||||
|
||||
If you also want to remove a certain class of directories, you can use the same technique. You will
|
||||
have to use `rm`s `--recursive`/`-r` flag to remove directories.
|
||||
|
||||
Note: there are scenarios where using `fd … -X rm -r` can cause race conditions: if you have a
|
||||
path like `…/foo/bar/foo/…` and want to remove all directories named `foo`, you can end up in a
|
||||
situation where the outer `foo` directory is removed first, leading to (harmless) *"'foo/bar/foo':
|
||||
No such file or directory"* errors in the `rm` call.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### `fd` does not find my file!
|
||||
|
||||
Remember that `fd` ignores hidden directories and files by default. It also ignores patterns
|
||||
from `.gitignore` files. If you want to make sure to find absolutely every possible file, always
|
||||
use the options `-H` and `-I` to disable these two features:
|
||||
``` bash
|
||||
> fd -HI …
|
||||
```
|
||||
|
||||
#### `fd` doesn't seem to interpret my regex pattern correctly
|
||||
|
||||
A lot of special regex characters (like `[]`, `^`, `$`, ..) are also special characters in your
|
||||
shell. If in doubt, always make sure to put single quotes around the regex pattern:
|
||||
|
||||
``` bash
|
||||
> fd '^[A-Z][0-9]+$'
|
||||
```
|
||||
|
||||
If your pattern starts with a dash, you have to add `--` to signal the end of command line
|
||||
options. Otherwise, the pattern will be interpreted as a command-line option. Alternatively,
|
||||
use a character class with a single hyphen character:
|
||||
|
||||
``` bash
|
||||
> fd -- '-pattern'
|
||||
> fd '[-]pattern'
|
||||
```
|
||||
|
||||
### Integration with other programs
|
||||
|
||||
#### Using fd with `fzf`
|
||||
|
||||
You can use *fd* to generate input for the command-line fuzzy finder [fzf](https://github.com/junegunn/fzf):
|
||||
``` bash
|
||||
@@ -353,3 +541,14 @@ export FZF_DEFAULT_OPTS="--ansi"
|
||||
```
|
||||
|
||||
For more details, see the [Tips section](https://github.com/junegunn/fzf#tips) of the fzf README.
|
||||
|
||||
#### Using fd with `emacs`
|
||||
|
||||
The emacs package [find-file-in-project](https://github.com/technomancy/find-file-in-project) can
|
||||
use *fd* to find files.
|
||||
|
||||
After installing `find-file-in-project`, add the line `(setq ffip-use-rust-fd t)` to your
|
||||
`~/.emacs` or `~/.emacs.d/init.el` file.
|
||||
|
||||
In emacs, run `M-x find-file-in-project-by-selected` to find matching files. Alternatively, run
|
||||
`M-x find-file-in-project` to list all available files in the project.
|
||||
|
||||
@@ -6,24 +6,19 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
extern crate version_check;
|
||||
|
||||
use clap::Shell;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::process::exit;
|
||||
use std::fs;
|
||||
|
||||
include!("src/app.rs");
|
||||
|
||||
fn main() {
|
||||
match version_check::is_min_version("1.20") {
|
||||
// rustc >= 1.20
|
||||
Some((true, _)) => {}
|
||||
// rustc < 1.20 or can't figure it out
|
||||
match version_check::is_min_version("1.31") {
|
||||
Some(true) => {}
|
||||
// rustc version too small or can't figure it out
|
||||
_ => {
|
||||
writeln!(&mut io::stderr(), "This crate requires rustc >= 1.20").unwrap();
|
||||
writeln!(&mut io::stderr(), "'fd' requires rustc >= 1.31").unwrap();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
* linguist-vendored
|
||||
Vendored
+67
-6
@@ -11,18 +11,25 @@ pack() {
|
||||
local tempdir
|
||||
local out_dir
|
||||
local package_name
|
||||
local gcc_prefix
|
||||
|
||||
tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
|
||||
out_dir=$(pwd)
|
||||
package_name="$PROJECT_NAME-$TRAVIS_TAG-$TARGET"
|
||||
|
||||
if [[ $TARGET == arm-unknown-linux-* ]]; then
|
||||
gcc_prefix="arm-linux-gnueabihf-"
|
||||
else
|
||||
gcc_prefix=""
|
||||
fi
|
||||
|
||||
# create a "staging" directory
|
||||
mkdir "$tempdir/$package_name"
|
||||
mkdir "$tempdir/$package_name/autocomplete"
|
||||
|
||||
# copying the main binary
|
||||
cp "target/$TARGET/release/$PROJECT_NAME" "$tempdir/$package_name/"
|
||||
strip "$tempdir/$package_name/$PROJECT_NAME"
|
||||
"${gcc_prefix}"strip "$tempdir/$package_name/$PROJECT_NAME"
|
||||
|
||||
# manpage, readme and license
|
||||
cp "doc/$PROJECT_NAME.1" "$tempdir/$package_name"
|
||||
@@ -48,17 +55,29 @@ make_deb() {
|
||||
local version
|
||||
local dpkgname
|
||||
local conflictname
|
||||
local homepage
|
||||
local maintainer
|
||||
local gcc_prefix
|
||||
|
||||
homepage="https://github.com/sharkdp/fd"
|
||||
maintainer="David Peter <mail@david-peter.de>"
|
||||
|
||||
case $TARGET in
|
||||
x86_64*)
|
||||
architecture=amd64
|
||||
gcc_prefix=""
|
||||
;;
|
||||
i686*)
|
||||
architecture=i386
|
||||
gcc_prefix=""
|
||||
;;
|
||||
arm*hf)
|
||||
architecture=armhf
|
||||
gcc_prefix="arm-linux-gnueabihf-"
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unknown target" >&2
|
||||
return 1
|
||||
echo "make_deb: skipping target '${TARGET}'" >&2
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
version=${TRAVIS_TAG#v}
|
||||
@@ -74,15 +93,54 @@ make_deb() {
|
||||
|
||||
# copy the main binary
|
||||
install -Dm755 "target/$TARGET/release/$PROJECT_NAME" "$tempdir/usr/bin/$PROJECT_NAME"
|
||||
strip "$tempdir/usr/bin/$PROJECT_NAME"
|
||||
"${gcc_prefix}"strip "$tempdir/usr/bin/$PROJECT_NAME"
|
||||
|
||||
# manpage
|
||||
install -Dm644 "doc/$PROJECT_NAME.1" "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
|
||||
gzip --best "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
|
||||
|
||||
# readme and license
|
||||
install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md"
|
||||
install -Dm644 LICENSE-MIT "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-MIT"
|
||||
install -Dm644 LICENSE-APACHE "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-APACHE"
|
||||
cat > "$tempdir/usr/share/doc/$PROJECT_NAME/copyright" <<EOF
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: $PROJECT_NAME
|
||||
Source: $homepage
|
||||
|
||||
Files: *
|
||||
Copyright: $maintainer
|
||||
License: Apache-2.0 or MIT
|
||||
|
||||
License: Apache-2.0
|
||||
On Debian systems, the complete text of the Apache-2.0 can be found in the
|
||||
file /usr/share/common-licenses/Apache-2.0.
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
EOF
|
||||
|
||||
# completions
|
||||
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.bash "$tempdir/usr/share/bash-completion/completions/${PROJECT_NAME}"
|
||||
@@ -96,14 +154,17 @@ Package: $dpkgname
|
||||
Version: $version
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: David Peter
|
||||
Maintainer: $maintainer
|
||||
Architecture: $architecture
|
||||
Provides: $PROJECT_NAME
|
||||
Conflicts: $conflictname
|
||||
Homepage: $homepage
|
||||
Description: Simple, fast and user-friendly alternative to find
|
||||
While fd does not seek to mirror all of find's powerful functionality, it
|
||||
provides sensible (opinionated) defaults for 80% of the use cases.
|
||||
EOF
|
||||
|
||||
dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
|
||||
fakeroot dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
|
||||
}
|
||||
|
||||
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
if [ "$TRAVIS_OS_NAME" != linux ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
sudo apt-get update
|
||||
|
||||
# needed to build deb packages
|
||||
sudo apt-get install -y fakeroot
|
||||
|
||||
# needed for i686 linux gnu target
|
||||
if [[ $TARGET == i686-unknown-linux-gnu ]]; then
|
||||
sudo apt-get install -y gcc-multilib
|
||||
fi
|
||||
|
||||
# needed for cross-compiling for arm
|
||||
if [[ $TARGET == arm-unknown-linux-* ]]; then
|
||||
sudo apt-get install -y \
|
||||
gcc-4.8-arm-linux-gnueabihf \
|
||||
binutils-arm-linux-gnueabihf \
|
||||
libc6-armhf-cross \
|
||||
libc6-dev-armhf-cross
|
||||
fi
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
# Incorporate TARGET env var to the build and test process
|
||||
cargo build --target "$TARGET" --verbose
|
||||
|
||||
# We cannot run arm executables on linux
|
||||
if [[ $TARGET != arm-unknown-linux-* ]]; then
|
||||
cargo test --target "$TARGET" --verbose
|
||||
fi
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
* linguist-vendored
|
||||
@@ -27,39 +27,59 @@ is a simple, fast and user-friendly alternative to
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \-H, \-\-hidden
|
||||
Include hidden files and directories in the search results.
|
||||
Include hidden files and directories in the search results
|
||||
(default: hidden files and directories are skipped).
|
||||
.TP
|
||||
.B \-I, \-\-no\-ignore
|
||||
Do not respect files like
|
||||
.I .gitignore
|
||||
and
|
||||
Show search results from files and directories that would otherwise be ignored by
|
||||
.IR .gitignore ,
|
||||
.I .ignore
|
||||
and include ignored files in the search results.
|
||||
or
|
||||
.I .fdignore
|
||||
files.
|
||||
.TP
|
||||
.B \-\-no\-ignore\-vcs
|
||||
Do not respect version control ignore files like
|
||||
.I ".gitignore"
|
||||
and include the respective entries in the search results.
|
||||
Show search results from files and directories that would otherwise be ignored by
|
||||
.I .gitignore
|
||||
files.
|
||||
.TP
|
||||
.B \-s, \-\-case\-sensitive
|
||||
Perform a case-sensitive search (default: smart case).
|
||||
Perform a case-sensitive search. By default, fd uses case-insensitive searches, unless the
|
||||
pattern contains an uppercase character (smart case).
|
||||
.TP
|
||||
.B \-i, \-\-ignore\-case
|
||||
Perform a case-insensitive search (default: smart case).
|
||||
Perform a case-insensitive search. By default, fd uses case-insensitive searches, unless the
|
||||
pattern contains an uppercase character (smart case).
|
||||
.TP
|
||||
.B \-g, \-\-glob
|
||||
Perform a glob-based search instead of a regular expression search.
|
||||
.TP
|
||||
.B \-\-regex
|
||||
Perform a regular-expression based seach (default). This can be used to override --glob.
|
||||
.TP
|
||||
.B \-F, \-\-fixed\-strings
|
||||
Treat the pattern as a literal string instead of a regular expression.
|
||||
.TP
|
||||
.B \-a, \-\-absolute\-path
|
||||
Show absolute instead of relative paths.
|
||||
Shows the full path starting from the root as opposed to relative paths.
|
||||
.TP
|
||||
.B \-L, \-\-follow
|
||||
Dereference all symbolic links encountered.
|
||||
By default, fd does not descend into symlinked directories. Using this flag, symbolic links are
|
||||
also traversed.
|
||||
.TP
|
||||
.B \-p, \-\-full\-path
|
||||
Match the
|
||||
By default, the search pattern is only matched against the filename (or directory name). Using
|
||||
this flag, the
|
||||
.I pattern
|
||||
against the full path instead of just the file or directory name.
|
||||
is matched against the full path.
|
||||
.TP
|
||||
.B \-0, \-\-print0
|
||||
Separate search results by null characters instead of newlines.
|
||||
Separate search results by the null character (instead of newlines). Useful for piping results to
|
||||
.IR xargs .
|
||||
.TP
|
||||
.B \-\-show-errors
|
||||
Enable the display of filesystem errors for situations such as insufficient
|
||||
permissions or dead symlinks.
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Print help information.
|
||||
@@ -77,10 +97,14 @@ Filter search by type:
|
||||
.RS
|
||||
.IP "f, file"
|
||||
regular files
|
||||
.IP "d, directories"
|
||||
.IP "d, directory"
|
||||
directories
|
||||
.IP "s, symlink"
|
||||
.IP "l, symlink"
|
||||
symbolic links
|
||||
.IP "x, executable"
|
||||
executable (files)
|
||||
.IP "e, empty"
|
||||
empty files or directories
|
||||
.RE
|
||||
|
||||
.RS
|
||||
@@ -97,6 +121,10 @@ Exclude files/directories that match the given glob pattern.
|
||||
This overrides any other ignore logic.
|
||||
Multiple exclude patterns can be specified.
|
||||
.TP
|
||||
.BI "\-\-ignore-file " path
|
||||
Add a custom ignore-file in '.gitignore' format.
|
||||
These files have a low precedence.
|
||||
.TP
|
||||
.BI "\-c, \-\-color " when
|
||||
Declare
|
||||
.I when
|
||||
@@ -111,9 +139,64 @@ Always colorize output.
|
||||
.RE
|
||||
.TP
|
||||
.BI "\-j, \-\-threads " num
|
||||
Number of threads to use for searching (default: number of available CPUs).
|
||||
Set number of threads to use for searching & executing (default: number of available CPU cores).
|
||||
.TP
|
||||
.BI "\-x, \-\-exec " command "\fR [args...] ;"
|
||||
.BI "\-S, \-\-size " size
|
||||
Limit results based on the size of files using the format
|
||||
.I <+-><NUM><UNIT>
|
||||
.RS
|
||||
.IP '+'
|
||||
file size must be greater than or equal to this
|
||||
.IP '-'
|
||||
file size must be less than or equal to this
|
||||
.IP 'NUM'
|
||||
The numeric size (e.g. 500)
|
||||
.IP 'UNIT'
|
||||
The units for NUM. They are not case-sensitive.
|
||||
Allowed unit values:
|
||||
.RS
|
||||
.IP 'b'
|
||||
bytes
|
||||
.IP 'k'
|
||||
kilobytes
|
||||
.IP 'm'
|
||||
megabytes
|
||||
.IP 'g'
|
||||
gigabytes
|
||||
.IP 't'
|
||||
terabytes
|
||||
.IP 'ki'
|
||||
kibibytes
|
||||
.IP 'mi'
|
||||
mebibytes
|
||||
.IP 'gi'
|
||||
gibibytes
|
||||
.IP 'ti'
|
||||
tebibytes
|
||||
.RE
|
||||
.RE
|
||||
.TP
|
||||
.BI "\-\-changed-within " date|duration
|
||||
Filter results based on the file modification time. The argument can be provided as a specific
|
||||
point in time (\fIYYYY-MM-DD HH:MM:SS\fR) or as a duration (\fI10h, 1d, 35min\fR).
|
||||
.B --change-newer-than
|
||||
can be used as an alias.
|
||||
|
||||
Examples:
|
||||
\-\-changed-within 2weeks
|
||||
\-\-change-newer-than "2018-10-27 10:00:00"
|
||||
.TP
|
||||
.BI "\-\-changed-before " date|duration
|
||||
Filter results based on the file modification time. The argument can be provided as a specific
|
||||
point in time (\fIYYYY-MM-DD HH:MM:SS\fR) or as a duration (\fI10h, 1d, 35min\fR).
|
||||
.B --change-older-than
|
||||
can be used as an alias.
|
||||
|
||||
Examples:
|
||||
\-\-changed-before "2018-10-27 10:00:00"
|
||||
\-\-change-older-than 2weeks
|
||||
.TP
|
||||
.BI "\-x, \-\-exec " command
|
||||
Execute
|
||||
.I command
|
||||
for each search result. The following placeholders are substituted by a path derived from the current search result:
|
||||
@@ -129,6 +212,24 @@ path without file extension
|
||||
.IP {/.}
|
||||
basename without file extension
|
||||
.RE
|
||||
.TP
|
||||
.BI "\-X, \-\-exec-batch " command
|
||||
Execute
|
||||
.I command
|
||||
with all search results at once.
|
||||
A single occurence of the following placeholders is authorized and substituted by the paths derived from the search results before the command is executed:
|
||||
.RS
|
||||
.IP {}
|
||||
path
|
||||
.IP {/}
|
||||
basename
|
||||
.IP {//}
|
||||
parent directory
|
||||
.IP {.}
|
||||
path without file extension
|
||||
.IP {/.}
|
||||
basename without file extension
|
||||
.RE
|
||||
.SH ENVIRONMENT
|
||||
.TP
|
||||
.B LS_COLORS
|
||||
@@ -144,5 +245,8 @@ $ fd nginx /var/log
|
||||
.TP
|
||||
.RI "Find all Python files (all files with the extention " .py ") in the current directory:"
|
||||
$ fd -e py
|
||||
.TP
|
||||
.RI "Open all search results with vim:"
|
||||
$ fd pattern -X vim
|
||||
.SH SEE ALSO
|
||||
.BR find (1)
|
||||
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
# Designed to be executed via svg-term from the fd root directory:
|
||||
# svg-term --command="bash doc/screencast.sh" --out doc/screencast.svg --padding=10
|
||||
set -e
|
||||
set -u
|
||||
|
||||
PROMPT="▶"
|
||||
|
||||
enter() {
|
||||
INPUT=$1
|
||||
DELAY=1
|
||||
|
||||
prompt
|
||||
sleep "$DELAY"
|
||||
type "$INPUT"
|
||||
sleep 0.5
|
||||
printf '%b' "\\n"
|
||||
eval "$INPUT"
|
||||
type "\\n"
|
||||
}
|
||||
|
||||
prompt() {
|
||||
printf '%b ' $PROMPT | pv -q
|
||||
}
|
||||
|
||||
type() {
|
||||
printf '%b' "$1" | pv -qL $((10+(-2 + RANDOM%5)))
|
||||
}
|
||||
|
||||
main() {
|
||||
IFS='%'
|
||||
|
||||
enter "fd"
|
||||
|
||||
enter "fd app"
|
||||
|
||||
enter "fd sh"
|
||||
|
||||
enter "fd sh --type f"
|
||||
|
||||
enter "fd -e md"
|
||||
|
||||
enter "fd -e md --exec wc -l"
|
||||
|
||||
enter "fd '^[A-Z]'"
|
||||
|
||||
enter "fd --exclude src"
|
||||
|
||||
enter "fd --hidden sample"
|
||||
|
||||
prompt
|
||||
|
||||
sleep 3
|
||||
|
||||
echo ""
|
||||
|
||||
unset IFS
|
||||
}
|
||||
|
||||
main
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 115 KiB |
+199
-18
@@ -5,10 +5,9 @@
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use clap::{App, AppSettings, Arg};
|
||||
use clap::{crate_version, App, AppSettings, Arg};
|
||||
|
||||
struct Help {
|
||||
short: &'static str,
|
||||
@@ -20,10 +19,13 @@ macro_rules! doc {
|
||||
doc!($map, $name, $short, $short)
|
||||
};
|
||||
($map:expr, $name:expr, $short:expr, $long:expr) => {
|
||||
$map.insert($name, Help {
|
||||
short: $short,
|
||||
long: concat!($long, "\n ")
|
||||
});
|
||||
$map.insert(
|
||||
$name,
|
||||
Help {
|
||||
short: $short,
|
||||
long: concat!($long, "\n "),
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,6 +42,10 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.usage("fd [FLAGS/OPTIONS] [<pattern>] [<path>...]")
|
||||
.setting(AppSettings::ColoredHelp)
|
||||
.setting(AppSettings::DeriveDisplayOrder)
|
||||
.after_help(
|
||||
"Note: `fd -h` prints a short and concise overview while `fd --help` \
|
||||
gives all details.",
|
||||
)
|
||||
.arg(arg("hidden").long("hidden").short("H"))
|
||||
.arg(arg("no-ignore").long("no-ignore").short("I"))
|
||||
.arg(arg("no-ignore-vcs").long("no-ignore-vcs"))
|
||||
@@ -61,11 +67,36 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.short("i")
|
||||
.overrides_with("case-sensitive"),
|
||||
)
|
||||
.arg(
|
||||
arg("glob")
|
||||
.long("glob")
|
||||
.short("g")
|
||||
.conflicts_with("fixed-strings"),
|
||||
)
|
||||
.arg(
|
||||
arg("regex")
|
||||
.long("regex")
|
||||
.overrides_with("glob")
|
||||
.hidden_short_help(true),
|
||||
)
|
||||
.arg(
|
||||
arg("fixed-strings")
|
||||
.long("fixed-strings")
|
||||
.short("F")
|
||||
.alias("literal"),
|
||||
)
|
||||
.arg(arg("absolute-path").long("absolute-path").short("a"))
|
||||
.arg(arg("follow").long("follow").short("L").alias("dereference"))
|
||||
.arg(arg("full-path").long("full-path").short("p"))
|
||||
.arg(arg("null_separator").long("print0").short("0"))
|
||||
.arg(arg("depth").long("max-depth").short("d").takes_value(true))
|
||||
// support --maxdepth as well, for compatibility with rg
|
||||
.arg(
|
||||
arg("rg-depth")
|
||||
.long("maxdepth")
|
||||
.hidden(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
arg("file-type")
|
||||
.long("type")
|
||||
@@ -74,7 +105,18 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.number_of_values(1)
|
||||
.takes_value(true)
|
||||
.value_name("filetype")
|
||||
.possible_values(&["f", "file", "d", "directory", "l", "symlink"])
|
||||
.possible_values(&[
|
||||
"f",
|
||||
"file",
|
||||
"d",
|
||||
"directory",
|
||||
"l",
|
||||
"symlink",
|
||||
"x",
|
||||
"executable",
|
||||
"e",
|
||||
"empty",
|
||||
])
|
||||
.hide_possible_values(true),
|
||||
)
|
||||
.arg(
|
||||
@@ -90,12 +132,21 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
arg("exec")
|
||||
.long("exec")
|
||||
.short("x")
|
||||
.multiple(true)
|
||||
.min_values(1)
|
||||
.allow_hyphen_values(true)
|
||||
.value_terminator(";")
|
||||
.value_name("cmd"),
|
||||
)
|
||||
.arg(
|
||||
arg("exec-batch")
|
||||
.long("exec-batch")
|
||||
.short("X")
|
||||
.min_values(1)
|
||||
.allow_hyphen_values(true)
|
||||
.value_terminator(";")
|
||||
.value_name("cmd")
|
||||
.conflicts_with("exec"),
|
||||
)
|
||||
.arg(
|
||||
arg("exclude")
|
||||
.long("exclude")
|
||||
@@ -105,6 +156,15 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.number_of_values(1)
|
||||
.multiple(true),
|
||||
)
|
||||
.arg(
|
||||
arg("ignore-file")
|
||||
.long("ignore-file")
|
||||
.takes_value(true)
|
||||
.value_name("path")
|
||||
.number_of_values(1)
|
||||
.multiple(true)
|
||||
.hidden_short_help(true),
|
||||
)
|
||||
.arg(
|
||||
arg("color")
|
||||
.long("color")
|
||||
@@ -119,7 +179,17 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.long("threads")
|
||||
.short("j")
|
||||
.takes_value(true)
|
||||
.value_name("num"),
|
||||
.value_name("num")
|
||||
.hidden_short_help(true),
|
||||
)
|
||||
.arg(
|
||||
arg("size")
|
||||
.long("size")
|
||||
.short("S")
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.allow_hyphen_values(true)
|
||||
.multiple(true),
|
||||
)
|
||||
.arg(
|
||||
arg("max-buffer-time")
|
||||
@@ -127,21 +197,59 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.takes_value(true)
|
||||
.hidden(true),
|
||||
)
|
||||
.arg(
|
||||
arg("changed-within")
|
||||
.long("changed-within")
|
||||
.alias("change-newer-than")
|
||||
.takes_value(true)
|
||||
.value_name("date|dur")
|
||||
.number_of_values(1),
|
||||
)
|
||||
.arg(
|
||||
arg("changed-before")
|
||||
.long("changed-before")
|
||||
.alias("change-older-than")
|
||||
.takes_value(true)
|
||||
.value_name("date|dur")
|
||||
.number_of_values(1),
|
||||
)
|
||||
.arg(
|
||||
arg("show-errors")
|
||||
.long("show-errors")
|
||||
.hidden_short_help(true),
|
||||
)
|
||||
.arg(arg("pattern"))
|
||||
.arg(
|
||||
arg("path-separator")
|
||||
.takes_value(true)
|
||||
.value_name("separator")
|
||||
.long("path-separator")
|
||||
.hidden_short_help(true),
|
||||
)
|
||||
.arg(arg("path").multiple(true))
|
||||
.arg(
|
||||
arg("search-path")
|
||||
.long("search-path")
|
||||
.takes_value(true)
|
||||
.conflicts_with("path")
|
||||
.multiple(true)
|
||||
.hidden_short_help(true)
|
||||
.number_of_values(1),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#[rustfmt::skip]
|
||||
fn usage() -> HashMap<&'static str, Help> {
|
||||
let mut h = HashMap::new();
|
||||
doc!(h, "hidden"
|
||||
, "Search hidden files and directories"
|
||||
, "Include hidden directories and files in the search results (default: hidden files \
|
||||
and directories are skipped).");
|
||||
and directories are skipped). Files and directories are considered to be hidden if \
|
||||
their name starts with a `.` sign (dot).");
|
||||
doc!(h, "no-ignore"
|
||||
, "Do not respect .(git)ignore files"
|
||||
, "Do not respect .(git|fd)ignore files"
|
||||
, "Show search results from files and directories that would otherwise be ignored by \
|
||||
'.*ignore' files.");
|
||||
'.gitignore', '.ignore' or '.fdignore' files.");
|
||||
doc!(h, "no-ignore-vcs"
|
||||
, "Do not respect .gitignore files"
|
||||
, "Show search results from files and directories that would otherwise be ignored by \
|
||||
@@ -154,9 +262,22 @@ fn usage() -> HashMap<&'static str, Help> {
|
||||
, "Case-insensitive search (default: smart case)"
|
||||
, "Perform a case-insensitive search. By default, fd uses case-insensitive searches, \
|
||||
unless the pattern contains an uppercase character (smart case).");
|
||||
doc!(h, "glob"
|
||||
, "Glob-based search (default: regular expression)"
|
||||
, "Perform a glob-based search instead of a regular expression search.");
|
||||
doc!(h, "regex"
|
||||
, "Perform a regex-based search"
|
||||
, "Perform a regular-expression based seach (default). This can be used to override --glob.");
|
||||
doc!(h, "fixed-strings"
|
||||
, "Treat the pattern as a literal string"
|
||||
, "Treat the pattern as a literal string instead of a regular expression.");
|
||||
doc!(h, "absolute-path"
|
||||
, "Show absolute instead of relative paths"
|
||||
, "Shows the full path starting from the root as opposed to relative paths.");
|
||||
doc!(h, "path-separator"
|
||||
, "Set the path separator to use when printing file paths."
|
||||
, "Set the path separator to use when printing file paths. The default is the OS-specific \
|
||||
separator ('/' on Unix, '\\' on Windows).");
|
||||
doc!(h, "follow"
|
||||
, "Follow symbolic links"
|
||||
, "By default, fd does not descend into symlinked directories. Using this flag, symbolic \
|
||||
@@ -173,12 +294,17 @@ fn usage() -> HashMap<&'static str, Help> {
|
||||
, "Set maximum search depth (default: none)"
|
||||
, "Limit the directory traversal to a given depth. By default, there is no limit \
|
||||
on the search depth.");
|
||||
doc!(h, "rg-depth"
|
||||
, "See --max-depth"
|
||||
, "See --max-depth");
|
||||
doc!(h, "file-type"
|
||||
, "Filter by type: f(ile), d(irectory), (sym)l(ink)"
|
||||
, "Filter by type: file (f), directory (d), symlink (l),\nexecutable (x), empty (e)"
|
||||
, "Filter the search by type (multiple allowable filetypes can be specified):\n \
|
||||
'f' or 'file': regular files\n \
|
||||
'd' or 'directory': directories\n \
|
||||
'l' or 'symlink': symbolic links");
|
||||
'l' or 'symlink': symbolic links\n \
|
||||
'x' or 'executable': executables\n \
|
||||
'e' or 'empty': empty files or directories");
|
||||
doc!(h, "extension"
|
||||
, "Filter by file extension"
|
||||
, "(Additionally) filter search results by their file extension. Multiple allowable file \
|
||||
@@ -195,10 +321,25 @@ fn usage() -> HashMap<&'static str, Help> {
|
||||
'{//}': parent directory\n \
|
||||
'{.}': path without file extension\n \
|
||||
'{/.}': basename without file extension");
|
||||
doc!(h, "exec-batch"
|
||||
, "Execute a command with all search results at once"
|
||||
, "Execute a command with all search results at once.\n\
|
||||
All arguments following --exec-batch are taken to be arguments to the command until the \
|
||||
argument ';' is encountered.\n\
|
||||
A single occurence of the following placeholders is authorized and substituted by the paths derived from the \
|
||||
search results before the command is executed:\n \
|
||||
'{}': path\n \
|
||||
'{/}': basename\n \
|
||||
'{//}': parent directory\n \
|
||||
'{.}': path without file extension\n \
|
||||
'{/.}': basename without file extension");
|
||||
doc!(h, "exclude"
|
||||
, "Exclude entries that match the given glob pattern"
|
||||
, "Exclude files/directories that match the given glob pattern. This overrides any \
|
||||
other ignore logic. Multiple exclude patterns can be specified.");
|
||||
doc!(h, "ignore-file"
|
||||
, "Add a custom ignore-file in .gitignore format"
|
||||
, "Add a custom ignore-file in '.gitignore' format. These files have a low precedence.");
|
||||
doc!(h, "color"
|
||||
, "When to use colors: never, *auto*, always"
|
||||
, "Declare when to use color for the pattern match output:\n \
|
||||
@@ -211,10 +352,10 @@ fn usage() -> HashMap<&'static str, Help> {
|
||||
CPU cores)");
|
||||
doc!(h, "max-buffer-time"
|
||||
, "the time (in ms) to buffer, before streaming to the console"
|
||||
, "Amount of time in milliseconds to buffer, before streaming the search results to\
|
||||
, "Amount of time in milliseconds to buffer, before streaming the search results to \
|
||||
the console.");
|
||||
doc!(h, "pattern"
|
||||
, "the search pattern, a regular expression (optional)");
|
||||
, "the search pattern: a regular expression unless '--glob' is used (optional)");
|
||||
doc!(h, "path"
|
||||
, "the root directory for the filesystem search (optional)"
|
||||
, "The directory where the filesystem search is rooted (optional). \
|
||||
@@ -222,6 +363,46 @@ fn usage() -> HashMap<&'static str, Help> {
|
||||
doc!(h, "rg-alias-hidden-ignore"
|
||||
, "Alias for no-ignore and/or hidden"
|
||||
, "Alias for no-ignore ('u') and no-ignore and hidden ('uu')");
|
||||
|
||||
doc!(h, "size"
|
||||
, "Limit results based on the size of files."
|
||||
, "Limit results based on the size of files using the format <+-><NUM><UNIT>.\n \
|
||||
'+': file size must be greater than or equal to this\n \
|
||||
'-': file size must be less than or equal to this\n \
|
||||
'NUM': The numeric size (e.g. 500)\n \
|
||||
'UNIT': The units for NUM. They are not case-sensitive.\n\
|
||||
Allowed unit values:\n \
|
||||
'b': bytes\n \
|
||||
'k': kilobytes\n \
|
||||
'm': megabytes\n \
|
||||
'g': gigabytes\n \
|
||||
't': terabytes\n \
|
||||
'ki': kibibytes\n \
|
||||
'mi': mebibytes\n \
|
||||
'gi': gibibytes\n \
|
||||
'ti': tebibytes");
|
||||
doc!(h, "changed-within"
|
||||
, "Filter by file modification time (newer than)"
|
||||
, "Filter results based on the file modification time. The argument can be provided \
|
||||
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
|
||||
'--change-newer-than' can be used as an alias.\n\
|
||||
Examples:\n \
|
||||
--changed-within 2weeks\n \
|
||||
--change-newer-than '2018-10-27 10:00:00'");
|
||||
doc!(h, "changed-before"
|
||||
, "Filter by file modification time (older than)"
|
||||
, "Filter results based on the file modification time. The argument can be provided \
|
||||
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
|
||||
'--change-older-than' can be used as an alias.\n\
|
||||
Examples:\n \
|
||||
--changed-before '2018-10-27 10:00:00'\n \
|
||||
--change-older-than 2weeks");
|
||||
doc!(h, "show-errors"
|
||||
, "Enable display of filesystem errors"
|
||||
, "Enable the display of filesystem errors for situations such as insufficient permissions \
|
||||
or dead symlinks.");
|
||||
doc!(h, "search-path"
|
||||
, "(hidden)"
|
||||
, "Provide paths to search as an alternative to the positional <path> argument. \
|
||||
Changes the usage to `fd [FLAGS/OPTIONS] --search-path <path> --search-path <path2> [<pattern>]`");
|
||||
h
|
||||
}
|
||||
|
||||
+17
-8
@@ -6,13 +6,15 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::process::Command;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::exit_codes::ExitCode;
|
||||
|
||||
/// Executes a command.
|
||||
pub fn execute_command(mut cmd: Command, out_perm: Arc<Mutex<()>>) {
|
||||
pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) -> ExitCode {
|
||||
// Spawn the supplied command.
|
||||
let output = cmd.output();
|
||||
|
||||
@@ -28,13 +30,20 @@ pub fn execute_command(mut cmd: Command, out_perm: Arc<Mutex<()>>) {
|
||||
|
||||
let _ = stdout.lock().write_all(&output.stdout);
|
||||
let _ = stderr.lock().write_all(&output.stderr);
|
||||
|
||||
if output.status.code() == Some(0) {
|
||||
ExitCode::Success
|
||||
} else {
|
||||
ExitCode::GeneralError
|
||||
}
|
||||
}
|
||||
Err(ref why) if why.kind() == io::ErrorKind::NotFound => {
|
||||
print_error!("Command not found: {:?}", cmd);
|
||||
ExitCode::GeneralError
|
||||
}
|
||||
Err(why) => {
|
||||
if why.kind() == io::ErrorKind::NotFound {
|
||||
eprintln!("fd: execution error: command not found");
|
||||
} else {
|
||||
eprintln!("fd: execution error: {}", why);
|
||||
}
|
||||
print_error!("Problem while executing command: {}", why);
|
||||
ExitCode::GeneralError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-76
@@ -71,90 +71,46 @@ pub fn dirname(path: &str) -> &str {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{basename, dirname, remove_extension, MAIN_SEPARATOR};
|
||||
mod path_tests {
|
||||
use super::*;
|
||||
|
||||
fn correct(input: &str) -> String {
|
||||
input.replace('/', &MAIN_SEPARATOR.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_remove_ext_simple() {
|
||||
assert_eq!(remove_extension("foo.txt"), "foo");
|
||||
macro_rules! func_tests {
|
||||
($($name:ident: $func:ident for $input:expr => $output:expr)+) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($func(&correct($input)), correct($output));
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
func_tests! {
|
||||
remove_ext_simple: remove_extension for "foo.txt" => "foo"
|
||||
remove_ext_dir: remove_extension for "dir/foo.txt" => "dir/foo"
|
||||
hidden: remove_extension for ".foo" => ".foo"
|
||||
remove_ext_utf8: remove_extension for "💖.txt" => "💖"
|
||||
remove_ext_empty: remove_extension for "" => ""
|
||||
|
||||
basename_simple: basename for "foo.txt" => "foo.txt"
|
||||
basename_dir: basename for "dir/foo.txt" => "foo.txt"
|
||||
basename_empty: basename for "" => ""
|
||||
basename_utf8_0: basename for "💖/foo.txt" => "foo.txt"
|
||||
basename_utf8_1: basename for "dir/💖.txt" => "💖.txt"
|
||||
|
||||
dirname_simple: dirname for "foo.txt" => "."
|
||||
dirname_dir: dirname for "dir/foo.txt" => "dir"
|
||||
dirname_utf8_0: dirname for "💖/foo.txt" => "💖"
|
||||
dirname_utf8_1: dirname for "dir/💖.txt" => "dir"
|
||||
dirname_empty: dirname for "" => "."
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_remove_ext_dir() {
|
||||
assert_eq!(
|
||||
remove_extension(&correct("dir/foo.txt")),
|
||||
correct("dir/foo")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_hidden() {
|
||||
assert_eq!(remove_extension(".foo"), ".foo")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_remove_ext_utf8() {
|
||||
assert_eq!(remove_extension("💖.txt"), "💖");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_remove_ext_empty() {
|
||||
assert_eq!(remove_extension(""), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_basename_simple() {
|
||||
assert_eq!(basename("foo.txt"), "foo.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_basename_no_ext() {
|
||||
assert_eq!(remove_extension(basename("foo.txt")), "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_basename_dir() {
|
||||
assert_eq!(basename(&correct("dir/foo.txt")), "foo.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_basename_empty() {
|
||||
assert_eq!(basename(""), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_basename_utf8() {
|
||||
assert_eq!(basename(&correct("💖/foo.txt")), "foo.txt");
|
||||
assert_eq!(basename(&correct("dir/💖.txt")), "💖.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_dirname_simple() {
|
||||
assert_eq!(dirname("foo.txt"), ".");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_dirname_dir() {
|
||||
assert_eq!(dirname(&correct("dir/foo.txt")), "dir");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_dirname_utf8() {
|
||||
assert_eq!(dirname(&correct("💖/foo.txt")), "💖");
|
||||
assert_eq!(dirname(&correct("dir/💖.txt")), "dir");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_dirname_empty() {
|
||||
assert_eq!(dirname(""), ".");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_dirname_root() {
|
||||
fn dirname_root() {
|
||||
#[cfg(windows)]
|
||||
assert_eq!(dirname("C:\\"), "C:");
|
||||
#[cfg(windows)]
|
||||
|
||||
+36
-7
@@ -6,24 +6,36 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
use super::CommandTemplate;
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::walk::WorkerResult;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
|
||||
/// generate a command with the supplied command template. The generated command will then
|
||||
/// be executed, and this process will continue until the receiver's sender has closed.
|
||||
pub fn job(rx: Arc<Mutex<Receiver<PathBuf>>>, cmd: Arc<CommandTemplate>, out_perm: Arc<Mutex<()>>) {
|
||||
pub fn job(
|
||||
rx: Arc<Mutex<Receiver<WorkerResult>>>,
|
||||
cmd: Arc<CommandTemplate>,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
show_filesystem_errors: bool,
|
||||
) {
|
||||
loop {
|
||||
// Create a lock on the shared receiver for this thread.
|
||||
let lock = rx.lock().unwrap();
|
||||
|
||||
// Obtain the next path from the receiver, else if the channel
|
||||
// Obtain the next result from the receiver, else if the channel
|
||||
// has closed, exit from the loop
|
||||
let value: PathBuf = match lock.recv() {
|
||||
Ok(value) => value,
|
||||
Ok(WorkerResult::Entry(val)) => val,
|
||||
Ok(WorkerResult::Error(err)) => {
|
||||
if show_filesystem_errors {
|
||||
print_error!("{}", err);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Err(_) => break,
|
||||
};
|
||||
|
||||
@@ -33,3 +45,20 @@ pub fn job(rx: Arc<Mutex<Receiver<PathBuf>>>, cmd: Arc<CommandTemplate>, out_per
|
||||
cmd.generate_and_execute(&value, Arc::clone(&out_perm));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn batch(
|
||||
rx: Receiver<WorkerResult>,
|
||||
cmd: &CommandTemplate,
|
||||
show_filesystem_errors: bool,
|
||||
) -> ExitCode {
|
||||
let paths = rx.iter().filter_map(|value| match value {
|
||||
WorkerResult::Entry(val) => Some(val),
|
||||
WorkerResult::Error(err) => {
|
||||
if show_filesystem_errors {
|
||||
print_error!("{}", err);
|
||||
}
|
||||
None
|
||||
}
|
||||
});
|
||||
cmd.generate_and_execute_batch(paths)
|
||||
}
|
||||
|
||||
+196
-24
@@ -8,21 +8,33 @@
|
||||
|
||||
// TODO: Possible optimization could avoid pushing characters on a buffer.
|
||||
mod command;
|
||||
mod token;
|
||||
mod job;
|
||||
mod input;
|
||||
mod job;
|
||||
mod token;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
use self::input::{basename, dirname, remove_extension};
|
||||
use crate::exit_codes::ExitCode;
|
||||
|
||||
use self::command::execute_command;
|
||||
use self::input::{basename, dirname, remove_extension};
|
||||
pub use self::job::{batch, job};
|
||||
use self::token::Token;
|
||||
pub use self::job::job;
|
||||
|
||||
/// Execution mode of the command
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ExecutionMode {
|
||||
/// Command is executed for each search result
|
||||
OneByOne,
|
||||
/// Command is run for a batch of results at once
|
||||
Batch,
|
||||
}
|
||||
|
||||
/// Represents a template that is utilized to generate command strings.
|
||||
///
|
||||
@@ -31,10 +43,34 @@ pub use self::job::job;
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CommandTemplate {
|
||||
args: Vec<ArgumentTemplate>,
|
||||
mode: ExecutionMode,
|
||||
}
|
||||
|
||||
impl CommandTemplate {
|
||||
pub fn new<I, S>(input: I) -> CommandTemplate
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
Self::build(input, ExecutionMode::OneByOne)
|
||||
}
|
||||
|
||||
pub fn new_batch<I, S>(input: I) -> Result<CommandTemplate, &'static str>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let cmd = Self::build(input, ExecutionMode::Batch);
|
||||
if cmd.number_of_tokens() > 1 {
|
||||
return Err("Only one placeholder allowed for batch commands");
|
||||
}
|
||||
if cmd.args[0].has_tokens() {
|
||||
return Err("First argument of exec-batch is expected to be a fixed executable");
|
||||
}
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
fn build<I, S>(input: I, mode: ExecutionMode) -> CommandTemplate
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
@@ -91,7 +127,19 @@ impl CommandTemplate {
|
||||
args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder]));
|
||||
}
|
||||
|
||||
CommandTemplate { args: args }
|
||||
CommandTemplate { args, mode }
|
||||
}
|
||||
|
||||
fn number_of_tokens(&self) -> usize {
|
||||
self.args.iter().filter(|arg| arg.has_tokens()).count()
|
||||
}
|
||||
|
||||
fn prepare_path(input: &Path) -> String {
|
||||
input
|
||||
.strip_prefix(".")
|
||||
.unwrap_or(input)
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
/// Generates and executes a command.
|
||||
@@ -99,18 +147,50 @@ impl CommandTemplate {
|
||||
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
|
||||
/// build. Once all arguments have been processed, the command is executed.
|
||||
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) {
|
||||
let input = input
|
||||
.strip_prefix(".")
|
||||
.unwrap_or(input)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
let input = Self::prepare_path(input);
|
||||
|
||||
let mut cmd = Command::new(self.args[0].generate(&input).as_ref());
|
||||
for arg in &self.args[1..] {
|
||||
cmd.arg(arg.generate(&input).as_ref());
|
||||
}
|
||||
|
||||
execute_command(cmd, out_perm)
|
||||
execute_command(cmd, &out_perm);
|
||||
}
|
||||
|
||||
pub fn in_batch_mode(&self) -> bool {
|
||||
self.mode == ExecutionMode::Batch
|
||||
}
|
||||
|
||||
pub fn generate_and_execute_batch<I>(&self, paths: I) -> ExitCode
|
||||
where
|
||||
I: Iterator<Item = PathBuf>,
|
||||
{
|
||||
let mut cmd = Command::new(self.args[0].generate("").as_ref());
|
||||
cmd.stdin(Stdio::inherit());
|
||||
cmd.stdout(Stdio::inherit());
|
||||
cmd.stderr(Stdio::inherit());
|
||||
|
||||
let mut paths = paths.map(|p| Self::prepare_path(&p));
|
||||
let mut has_path = false;
|
||||
|
||||
for arg in &self.args[1..] {
|
||||
if arg.has_tokens() {
|
||||
// A single `Tokens` is expected
|
||||
// So we can directy consume the iterator once and for all
|
||||
for path in &mut paths {
|
||||
cmd.arg(arg.generate(&path).as_ref());
|
||||
has_path = true;
|
||||
}
|
||||
} else {
|
||||
cmd.arg(arg.generate("").as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
if has_path {
|
||||
execute_command(cmd, &Mutex::new(()))
|
||||
} else {
|
||||
ExitCode::Success
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +205,13 @@ enum ArgumentTemplate {
|
||||
}
|
||||
|
||||
impl ArgumentTemplate {
|
||||
pub fn has_tokens(&self) -> bool {
|
||||
match self {
|
||||
ArgumentTemplate::Tokens(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate<'a>(&'a self, path: &str) -> Cow<'a, str> {
|
||||
use self::Token::*;
|
||||
|
||||
@@ -150,20 +237,25 @@ impl ArgumentTemplate {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ArgumentTemplate, CommandTemplate, Token};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tokens() {
|
||||
let expected = CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Text("${SHELL}:".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(CommandTemplate::new(&[&"echo", &"${SHELL}:"]), expected);
|
||||
fn tokens_with_placeholder() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&[&"echo", &"${SHELL}:"]),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Text("${SHELL}:".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokens_with_no_extension() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{.}"]),
|
||||
CommandTemplate {
|
||||
@@ -171,7 +263,87 @@ mod tests {
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::NoExt]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokens_with_basename() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{/}"]),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::Basename]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokens_with_parent() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{//}"]),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::Parent]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokens_with_basename_no_extension() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{/.}"]),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokens_multiple() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["cp", "{}", "{/.}.ext"]),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("cp".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
|
||||
ArgumentTemplate::Tokens(vec![
|
||||
Token::BasenameNoExt,
|
||||
Token::Text(".ext".into())
|
||||
]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokens_single_batch() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new_batch(&["echo", "{.}"]).unwrap(),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::NoExt]),
|
||||
],
|
||||
mode: ExecutionMode::Batch,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokens_multiple_batch() {
|
||||
assert!(CommandTemplate::new_batch(&["echo", "{.}", "{}"]).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
+15
-4
@@ -1,4 +1,15 @@
|
||||
/// exit code 1 represents a general error
|
||||
pub const ERROR: i32 = 1;
|
||||
/// exit code 130 represents a process killed by signal SIGINT
|
||||
pub const SIGINT: i32 = 130;
|
||||
pub enum ExitCode {
|
||||
Success,
|
||||
GeneralError,
|
||||
KilledBySigint,
|
||||
}
|
||||
|
||||
impl Into<i32> for ExitCode {
|
||||
fn into(self) -> i32 {
|
||||
match self {
|
||||
ExitCode::Success => 0,
|
||||
ExitCode::GeneralError => 1,
|
||||
ExitCode::KilledBySigint => 130,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+41
-11
@@ -7,16 +7,21 @@
|
||||
// according to those terms.
|
||||
|
||||
use std::env::current_dir;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs;
|
||||
use std::io;
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use ignore::DirEntry;
|
||||
|
||||
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
|
||||
if path.is_absolute() {
|
||||
Ok(path.to_path_buf())
|
||||
} else {
|
||||
let path = path.strip_prefix(".").unwrap_or(path);
|
||||
current_dir().map(|path_buf| path_buf.join(path))
|
||||
return Ok(path.to_path_buf());
|
||||
}
|
||||
|
||||
let path = path.strip_prefix(".").unwrap_or(path);
|
||||
current_dir().map(|path_buf| path_buf.join(path))
|
||||
}
|
||||
|
||||
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
|
||||
@@ -27,18 +32,43 @@ pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
|
||||
path_buf
|
||||
.as_path()
|
||||
.to_string_lossy()
|
||||
.trim_left_matches(r"\\?\"),
|
||||
).to_path_buf();
|
||||
.trim_start_matches(r"\\?\"),
|
||||
)
|
||||
.to_path_buf();
|
||||
|
||||
Ok(path_buf)
|
||||
}
|
||||
|
||||
// Path::is_dir() is not guarandteed to be intuitively correct for "." and ".."
|
||||
// Path::is_dir() is not guaranteed to be intuitively correct for "." and ".."
|
||||
// See: https://github.com/rust-lang/rust/issues/45302
|
||||
pub fn is_dir(path: &Path) -> bool {
|
||||
if path.file_name().is_some() {
|
||||
path.is_dir()
|
||||
path.is_dir() && (path.file_name().is_some() || path.canonicalize().is_ok())
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn is_executable(md: &fs::Metadata) -> bool {
|
||||
md.permissions().mode() & 0o111 != 0
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn is_executable(_: &fs::Metadata) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_empty(entry: &DirEntry) -> bool {
|
||||
if let Some(file_type) = entry.file_type() {
|
||||
if file_type.is_dir() {
|
||||
if let Ok(mut entries) = fs::read_dir(entry.path()) {
|
||||
entries.next().is_none()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else if file_type.is_file() {
|
||||
entry.metadata().map(|m| m.len() == 0).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
path.is_dir() && path.canonicalize().is_ok()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::process;
|
||||
use std::time;
|
||||
use std::io::Write;
|
||||
|
||||
use exec::CommandTemplate;
|
||||
use lscolors::LsColors;
|
||||
use walk::FileType;
|
||||
use regex_syntax::{Expr, ExprBuilder};
|
||||
|
||||
/// Configuration options for *fd*.
|
||||
pub struct FdOptions {
|
||||
/// Whether the search is case-sensitive or case-insensitive.
|
||||
pub case_sensitive: bool,
|
||||
|
||||
/// Whether to search within the full file path or just the base name (filename or directory
|
||||
/// name).
|
||||
pub search_full_path: bool,
|
||||
|
||||
/// Whether to ignore hidden files and directories (or not).
|
||||
pub ignore_hidden: bool,
|
||||
|
||||
/// Whether to respect ignore files (`.gitignore`, `.ignore`, ..) or not.
|
||||
pub read_ignore: bool,
|
||||
|
||||
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
|
||||
pub read_gitignore: bool,
|
||||
|
||||
/// Whether to follow symlinks or not.
|
||||
pub follow_links: bool,
|
||||
|
||||
/// Whether elements of output should be separated by a null character
|
||||
pub null_separator: bool,
|
||||
|
||||
/// The maximum search depth, or `None` if no maximum search depth should be set.
|
||||
///
|
||||
/// A depth of `1` includes all files under the current directory, a depth of `2` also includes
|
||||
/// all files under subdirectories of the current directory, etc.
|
||||
pub max_depth: Option<usize>,
|
||||
|
||||
/// The number of threads to use.
|
||||
pub threads: usize,
|
||||
|
||||
/// Time to buffer results internally before streaming to the console. This is useful to
|
||||
/// provide a sorted output, in case the total execution time is shorter than
|
||||
/// `max_buffer_time`.
|
||||
pub max_buffer_time: Option<time::Duration>,
|
||||
|
||||
/// `None` if the output should not be colorized. Otherwise, a `LsColors` instance that defines
|
||||
/// how to style different filetypes.
|
||||
pub ls_colors: Option<LsColors>,
|
||||
|
||||
/// The type of file to search for. All files other than the specified type will be ignored.
|
||||
pub file_types: HashSet<FileType>,
|
||||
|
||||
/// The extension to search for. Only entries matching the extension will be included.
|
||||
///
|
||||
/// The value (if present) will be a lowercase string without leading dots.
|
||||
pub extensions: Option<HashSet<String>>,
|
||||
|
||||
/// If a value is supplied, each item found will be used to generate and execute commands.
|
||||
pub command: Option<CommandTemplate>,
|
||||
|
||||
/// A list of glob patterns that should be excluded from the search.
|
||||
pub exclude_patterns: Vec<String>,
|
||||
}
|
||||
|
||||
/// Print error message to stderr and exit with status `1`.
|
||||
pub fn error(message: &str) -> ! {
|
||||
writeln!(&mut ::std::io::stderr(), "{}", message).expect("Failed writing to stderr");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
/// Determine if a regex pattern contains a literal uppercase character.
|
||||
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
|
||||
ExprBuilder::new()
|
||||
.parse(pattern)
|
||||
.map(|expr| expr_has_uppercase_char(&expr))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Determine if a regex expression contains a literal uppercase character.
|
||||
fn expr_has_uppercase_char(expr: &Expr) -> bool {
|
||||
match *expr {
|
||||
Expr::Literal { ref chars, .. } => chars.iter().any(|c| c.is_uppercase()),
|
||||
Expr::Class(ref ranges) => ranges
|
||||
.iter()
|
||||
.any(|r| r.start.is_uppercase() || r.end.is_uppercase()),
|
||||
Expr::Group { ref e, .. } | Expr::Repeat { ref e, .. } => expr_has_uppercase_char(e),
|
||||
Expr::Concat(ref es) => es.iter().any(expr_has_uppercase_char),
|
||||
Expr::Alternate(ref es) => es.iter().any(expr_has_uppercase_char),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum size of the output buffer before flushing results to the console
|
||||
pub const MAX_BUFFER_LENGTH: usize = 1000;
|
||||
|
||||
/// Exit code representing a general error
|
||||
pub const EXITCODE_ERROR: i32 = 1;
|
||||
|
||||
/// Exit code representing that the process was killed by SIGINT
|
||||
pub const EXITCODE_SIGINT: i32 = 130;
|
||||
@@ -0,0 +1,20 @@
|
||||
/// Whether or not to show
|
||||
pub struct FileTypes {
|
||||
pub files: bool,
|
||||
pub directories: bool,
|
||||
pub symlinks: bool,
|
||||
pub executables_only: bool,
|
||||
pub empty_only: bool,
|
||||
}
|
||||
|
||||
impl Default for FileTypes {
|
||||
fn default() -> FileTypes {
|
||||
FileTypes {
|
||||
files: false,
|
||||
directories: false,
|
||||
symlinks: false,
|
||||
executables_only: false,
|
||||
empty_only: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub use self::size::SizeFilter;
|
||||
pub use self::time::TimeFilter;
|
||||
|
||||
mod size;
|
||||
mod time;
|
||||
@@ -0,0 +1,209 @@
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static! {
|
||||
static ref SIZE_CAPTURES: Regex = { Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap() };
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum SizeFilter {
|
||||
Max(u64),
|
||||
Min(u64),
|
||||
}
|
||||
|
||||
// SI prefixes (powers of 10)
|
||||
const KILO: u64 = 1000;
|
||||
const MEGA: u64 = KILO * 1000;
|
||||
const GIGA: u64 = MEGA * 1000;
|
||||
const TERA: u64 = GIGA * 1000;
|
||||
|
||||
// Binary prefixes (powers of 2)
|
||||
const KIBI: u64 = 1024;
|
||||
const MEBI: u64 = KIBI * 1024;
|
||||
const GIBI: u64 = MEBI * 1024;
|
||||
const TEBI: u64 = GIBI * 1024;
|
||||
|
||||
impl SizeFilter {
|
||||
pub fn from_string(s: &str) -> Option<Self> {
|
||||
if !SIZE_CAPTURES.is_match(s) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let captures = SIZE_CAPTURES.captures(s)?;
|
||||
let limit_kind = captures.get(1).map_or("+", |m| m.as_str());
|
||||
let quantity = captures
|
||||
.get(2)
|
||||
.and_then(|v| v.as_str().parse::<u64>().ok())?;
|
||||
|
||||
let multiplier = match &captures.get(3).map_or("b", |m| m.as_str()).to_lowercase()[..] {
|
||||
v if v.starts_with("ki") => KIBI,
|
||||
v if v.starts_with("k") => KILO,
|
||||
v if v.starts_with("mi") => MEBI,
|
||||
v if v.starts_with("m") => MEGA,
|
||||
v if v.starts_with("gi") => GIBI,
|
||||
v if v.starts_with("g") => GIGA,
|
||||
v if v.starts_with("ti") => TEBI,
|
||||
v if v.starts_with("t") => TERA,
|
||||
"b" => 1,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let size = quantity * multiplier;
|
||||
Some(match limit_kind {
|
||||
"+" => SizeFilter::Min(size),
|
||||
_ => SizeFilter::Max(size),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_within(&self, size: u64) -> bool {
|
||||
match *self {
|
||||
SizeFilter::Max(limit) => size <= limit,
|
||||
SizeFilter::Min(limit) => size >= limit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! gen_size_filter_parse_test {
|
||||
($($name: ident: $val: expr,)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
let (txt, expected) = $val;
|
||||
let actual = SizeFilter::from_string(txt).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Parsing and size conversion tests data. Ensure that each type gets properly interpreted.
|
||||
// Call with higher base values to ensure expected multiplication (only need a couple)
|
||||
gen_size_filter_parse_test! {
|
||||
byte_plus: ("+1b", SizeFilter::Min(1)),
|
||||
byte_plus_multiplier: ("+10b", SizeFilter::Min(10)),
|
||||
byte_minus: ("-1b", SizeFilter::Max(1)),
|
||||
kilo_plus: ("+1k", SizeFilter::Min(1000)),
|
||||
kilo_plus_suffix: ("+1kb", SizeFilter::Min(1000)),
|
||||
kilo_minus: ("-1k", SizeFilter::Max(1000)),
|
||||
kilo_minus_multiplier: ("-100k", SizeFilter::Max(100000)),
|
||||
kilo_minus_suffix: ("-1kb", SizeFilter::Max(1000)),
|
||||
kilo_plus_upper: ("+1K", SizeFilter::Min(1000)),
|
||||
kilo_plus_suffix_upper: ("+1KB", SizeFilter::Min(1000)),
|
||||
kilo_minus_upper: ("-1K", SizeFilter::Max(1000)),
|
||||
kilo_minus_suffix_upper: ("-1Kb", SizeFilter::Max(1000)),
|
||||
kibi_plus: ("+1ki", SizeFilter::Min(1024)),
|
||||
kibi_plus_multiplier: ("+10ki", SizeFilter::Min(10240)),
|
||||
kibi_plus_suffix: ("+1kib", SizeFilter::Min(1024)),
|
||||
kibi_minus: ("-1ki", SizeFilter::Max(1024)),
|
||||
kibi_minus_multiplier: ("-100ki", SizeFilter::Max(102400)),
|
||||
kibi_minus_suffix: ("-1kib", SizeFilter::Max(1024)),
|
||||
kibi_plus_upper: ("+1KI", SizeFilter::Min(1024)),
|
||||
kibi_plus_suffix_upper: ("+1KiB", SizeFilter::Min(1024)),
|
||||
kibi_minus_upper: ("-1Ki", SizeFilter::Max(1024)),
|
||||
kibi_minus_suffix_upper: ("-1KIB", SizeFilter::Max(1024)),
|
||||
mega_plus: ("+1m", SizeFilter::Min(1000000)),
|
||||
mega_plus_suffix: ("+1mb", SizeFilter::Min(1000000)),
|
||||
mega_minus: ("-1m", SizeFilter::Max(1000000)),
|
||||
mega_minus_suffix: ("-1mb", SizeFilter::Max(1000000)),
|
||||
mega_plus_upper: ("+1M", SizeFilter::Min(1000000)),
|
||||
mega_plus_suffix_upper: ("+1MB", SizeFilter::Min(1000000)),
|
||||
mega_minus_upper: ("-1M", SizeFilter::Max(1000000)),
|
||||
mega_minus_suffix_upper: ("-1Mb", SizeFilter::Max(1000000)),
|
||||
mebi_plus: ("+1mi", SizeFilter::Min(1048576)),
|
||||
mebi_plus_suffix: ("+1mib", SizeFilter::Min(1048576)),
|
||||
mebi_minus: ("-1mi", SizeFilter::Max(1048576)),
|
||||
mebi_minus_suffix: ("-1mib", SizeFilter::Max(1048576)),
|
||||
mebi_plus_upper: ("+1MI", SizeFilter::Min(1048576)),
|
||||
mebi_plus_suffix_upper: ("+1MiB", SizeFilter::Min(1048576)),
|
||||
mebi_minus_upper: ("-1Mi", SizeFilter::Max(1048576)),
|
||||
mebi_minus_suffix_upper: ("-1MIB", SizeFilter::Max(1048576)),
|
||||
giga_plus: ("+1g", SizeFilter::Min(1000000000)),
|
||||
giga_plus_suffix: ("+1gb", SizeFilter::Min(1000000000)),
|
||||
giga_minus: ("-1g", SizeFilter::Max(1000000000)),
|
||||
giga_minus_suffix: ("-1gb", SizeFilter::Max(1000000000)),
|
||||
giga_plus_upper: ("+1G", SizeFilter::Min(1000000000)),
|
||||
giga_plus_suffix_upper: ("+1GB", SizeFilter::Min(1000000000)),
|
||||
giga_minus_upper: ("-1G", SizeFilter::Max(1000000000)),
|
||||
giga_minus_suffix_upper: ("-1Gb", SizeFilter::Max(1000000000)),
|
||||
gibi_plus: ("+1gi", SizeFilter::Min(1073741824)),
|
||||
gibi_plus_suffix: ("+1gib", SizeFilter::Min(1073741824)),
|
||||
gibi_minus: ("-1gi", SizeFilter::Max(1073741824)),
|
||||
gibi_minus_suffix: ("-1gib", SizeFilter::Max(1073741824)),
|
||||
gibi_plus_upper: ("+1GI", SizeFilter::Min(1073741824)),
|
||||
gibi_plus_suffix_upper: ("+1GiB", SizeFilter::Min(1073741824)),
|
||||
gibi_minus_upper: ("-1Gi", SizeFilter::Max(1073741824)),
|
||||
gibi_minus_suffix_upper: ("-1GIB", SizeFilter::Max(1073741824)),
|
||||
tera_plus: ("+1t", SizeFilter::Min(1000000000000)),
|
||||
tera_plus_suffix: ("+1tb", SizeFilter::Min(1000000000000)),
|
||||
tera_minus: ("-1t", SizeFilter::Max(1000000000000)),
|
||||
tera_minus_suffix: ("-1tb", SizeFilter::Max(1000000000000)),
|
||||
tera_plus_upper: ("+1T", SizeFilter::Min(1000000000000)),
|
||||
tera_plus_suffix_upper: ("+1TB", SizeFilter::Min(1000000000000)),
|
||||
tera_minus_upper: ("-1T", SizeFilter::Max(1000000000000)),
|
||||
tera_minus_suffix_upper: ("-1Tb", SizeFilter::Max(1000000000000)),
|
||||
tebi_plus: ("+1ti", SizeFilter::Min(1099511627776)),
|
||||
tebi_plus_suffix: ("+1tib", SizeFilter::Min(1099511627776)),
|
||||
tebi_minus: ("-1ti", SizeFilter::Max(1099511627776)),
|
||||
tebi_minus_suffix: ("-1tib", SizeFilter::Max(1099511627776)),
|
||||
tebi_plus_upper: ("+1TI", SizeFilter::Min(1099511627776)),
|
||||
tebi_plus_suffix_upper: ("+1TiB", SizeFilter::Min(1099511627776)),
|
||||
tebi_minus_upper: ("-1Ti", SizeFilter::Max(1099511627776)),
|
||||
tebi_minus_suffix_upper: ("-1TIB", SizeFilter::Max(1099511627776)),
|
||||
}
|
||||
|
||||
/// Invalid parse testing
|
||||
macro_rules! gen_size_filter_failure {
|
||||
($($name:ident: $value:expr,)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
let i = SizeFilter::from_string($value);
|
||||
assert!(i.is_none());
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Invalid parse data
|
||||
gen_size_filter_failure! {
|
||||
ensure_missing_symbol_returns_none: "10M",
|
||||
ensure_missing_number_returns_none: "+g",
|
||||
ensure_missing_unit_returns_none: "+18",
|
||||
ensure_bad_format_returns_none_1: "$10M",
|
||||
ensure_bad_format_returns_none_2: "badval",
|
||||
ensure_bad_format_returns_none_3: "9999",
|
||||
ensure_invalid_unit_returns_none_1: "+50a",
|
||||
ensure_invalid_unit_returns_none_2: "-10v",
|
||||
ensure_invalid_unit_returns_none_3: "+1Mv",
|
||||
ensure_bib_format_returns_none: "+1bib",
|
||||
ensure_bb_format_returns_none: "+1bb",
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_within_less_than() {
|
||||
let f = SizeFilter::from_string("-1k").unwrap();
|
||||
assert!(f.is_within(999));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_within_less_than_equal() {
|
||||
let f = SizeFilter::from_string("-1k").unwrap();
|
||||
assert!(f.is_within(1000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_within_greater_than() {
|
||||
let f = SizeFilter::from_string("+1k").unwrap();
|
||||
assert!(f.is_within(1001));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_within_greater_than_equal() {
|
||||
let f = SizeFilter::from_string("+1K").unwrap();
|
||||
assert!(f.is_within(1000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
/// Filter based on time ranges.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TimeFilter {
|
||||
Before(SystemTime),
|
||||
After(SystemTime),
|
||||
}
|
||||
|
||||
impl TimeFilter {
|
||||
fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
|
||||
humantime::parse_duration(s)
|
||||
.map(|duration| *ref_time - duration)
|
||||
.or_else(|_| humantime::parse_rfc3339_weak(s))
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn before(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
|
||||
TimeFilter::from_str(ref_time, s).map(TimeFilter::Before)
|
||||
}
|
||||
|
||||
pub fn after(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
|
||||
TimeFilter::from_str(ref_time, s).map(TimeFilter::After)
|
||||
}
|
||||
|
||||
pub fn applies_to(&self, t: &SystemTime) -> bool {
|
||||
match self {
|
||||
TimeFilter::Before(limit) => t <= limit,
|
||||
TimeFilter::After(limit) => t >= limit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn is_time_filter_applicable() {
|
||||
let ref_time = humantime::parse_rfc3339("2010-10-10T10:10:10Z").unwrap();
|
||||
assert!(TimeFilter::after(&ref_time, "1min")
|
||||
.unwrap()
|
||||
.applies_to(&ref_time));
|
||||
assert!(!TimeFilter::before(&ref_time, "1min")
|
||||
.unwrap()
|
||||
.applies_to(&ref_time));
|
||||
|
||||
let t1m_ago = ref_time - Duration::from_secs(60);
|
||||
assert!(!TimeFilter::after(&ref_time, "30sec")
|
||||
.unwrap()
|
||||
.applies_to(&t1m_ago));
|
||||
assert!(TimeFilter::after(&ref_time, "2min")
|
||||
.unwrap()
|
||||
.applies_to(&t1m_ago));
|
||||
|
||||
assert!(TimeFilter::before(&ref_time, "30sec")
|
||||
.unwrap()
|
||||
.applies_to(&t1m_ago));
|
||||
assert!(!TimeFilter::before(&ref_time, "2min")
|
||||
.unwrap()
|
||||
.applies_to(&t1m_ago));
|
||||
|
||||
let t10s_before = "2010-10-10 10:10:00";
|
||||
assert!(!TimeFilter::before(&ref_time, t10s_before)
|
||||
.unwrap()
|
||||
.applies_to(&ref_time));
|
||||
assert!(TimeFilter::before(&ref_time, t10s_before)
|
||||
.unwrap()
|
||||
.applies_to(&t1m_ago));
|
||||
|
||||
assert!(TimeFilter::after(&ref_time, t10s_before)
|
||||
.unwrap()
|
||||
.applies_to(&ref_time));
|
||||
assert!(!TimeFilter::after(&ref_time, t10s_before)
|
||||
.unwrap()
|
||||
.applies_to(&t1m_ago));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
use regex_syntax::hir::Hir;
|
||||
use regex_syntax::ParserBuilder;
|
||||
|
||||
pub use self::file_types::FileTypes;
|
||||
|
||||
mod file_types;
|
||||
pub mod filter;
|
||||
pub mod opts;
|
||||
|
||||
macro_rules! print_error {
|
||||
($($arg:tt)*) => (eprintln!("[fd error]: {}", format!($($arg)*)))
|
||||
}
|
||||
|
||||
macro_rules! print_error_and_exit {
|
||||
($($arg:tt)*) => {
|
||||
print_error!($($arg)*);
|
||||
::std::process::exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn osstr_to_bytes(input: &OsStr) -> Cow<[u8]> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
Cow::Borrowed(input.as_bytes())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn osstr_to_bytes(input: &OsStr) -> Cow<[u8]> {
|
||||
let string = input.to_string_lossy();
|
||||
|
||||
match string {
|
||||
Cow::Owned(string) => Cow::Owned(string.into_bytes()),
|
||||
Cow::Borrowed(string) => Cow::Borrowed(string.as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if a regex pattern contains a literal uppercase character.
|
||||
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
|
||||
let mut parser = ParserBuilder::new().allow_invalid_utf8(true).build();
|
||||
|
||||
parser
|
||||
.parse(pattern)
|
||||
.map(|hir| hir_has_uppercase_char(&hir))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Determine if a regex expression contains a literal uppercase character.
|
||||
fn hir_has_uppercase_char(hir: &Hir) -> bool {
|
||||
use regex_syntax::hir::*;
|
||||
|
||||
match *hir.kind() {
|
||||
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
|
||||
HirKind::Literal(Literal::Byte(b)) => char::from(b).is_uppercase(),
|
||||
HirKind::Class(Class::Unicode(ref ranges)) => ranges
|
||||
.iter()
|
||||
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
|
||||
HirKind::Class(Class::Bytes(ref ranges)) => ranges
|
||||
.iter()
|
||||
.any(|r| char::from(r.start()).is_uppercase() || char::from(r.end()).is_uppercase()),
|
||||
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
|
||||
hir_has_uppercase_char(hir)
|
||||
}
|
||||
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
|
||||
hirs.iter().any(hir_has_uppercase_char)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum size of the output buffer before flushing results to the console
|
||||
pub const MAX_BUFFER_LENGTH: usize = 1000;
|
||||
|
||||
/// Traverse args_os, looking for -exec and replacing it with --exec.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * The args, with substitution if required
|
||||
pub fn transform_args_with_exec<I>(original: I) -> Vec<OsString>
|
||||
where
|
||||
I: Iterator<Item = OsString>,
|
||||
{
|
||||
let mut in_exec_opt = false;
|
||||
let target = OsString::from("-exec");
|
||||
let long_start = OsString::from("--exec");
|
||||
let short_start = OsString::from("-x");
|
||||
let exec_end = OsString::from(";");
|
||||
|
||||
original.fold(vec![], |mut args, curr| {
|
||||
if in_exec_opt {
|
||||
if curr == exec_end {
|
||||
in_exec_opt = false;
|
||||
}
|
||||
args.push(curr);
|
||||
return args;
|
||||
}
|
||||
|
||||
if curr == target || curr == long_start || curr == short_start {
|
||||
args.push(if curr == target {
|
||||
OsString::from("--exec")
|
||||
} else {
|
||||
curr
|
||||
});
|
||||
in_exec_opt = true;
|
||||
} else {
|
||||
args.push(curr);
|
||||
}
|
||||
args
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn oss_vec(strs: &[&str]) -> Vec<OsString> {
|
||||
strs.into_iter().map(OsString::from).collect()
|
||||
}
|
||||
|
||||
/// Ensure that -exec gets transformed into --exec
|
||||
#[test]
|
||||
fn normal_exec_substitution() {
|
||||
let original = oss_vec(&["fd", "foo", "-exec", "cmd"]);
|
||||
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||
let actual = transform_args_with_exec(original.into_iter());
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
/// Ensure that --exec is not touched
|
||||
#[test]
|
||||
fn passthru_of_original_exec() {
|
||||
let original = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||
let actual = transform_args_with_exec(original.into_iter());
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
#[test]
|
||||
fn temp_check_that_exec_context_observed() {
|
||||
let original = oss_vec(&[
|
||||
"fd",
|
||||
"foo",
|
||||
"-exec",
|
||||
"cmd",
|
||||
"-exec",
|
||||
"ls",
|
||||
";",
|
||||
"-exec",
|
||||
"rm",
|
||||
";",
|
||||
"--exec",
|
||||
"find",
|
||||
"-exec",
|
||||
"rm",
|
||||
";",
|
||||
"-x",
|
||||
"foo",
|
||||
"-exec",
|
||||
"something",
|
||||
";",
|
||||
"-exec",
|
||||
]);
|
||||
let expected = oss_vec(&[
|
||||
"fd",
|
||||
"foo",
|
||||
"--exec",
|
||||
"cmd",
|
||||
"-exec",
|
||||
"ls",
|
||||
";",
|
||||
"--exec",
|
||||
"rm",
|
||||
";",
|
||||
"--exec",
|
||||
"find",
|
||||
"-exec",
|
||||
"rm",
|
||||
";",
|
||||
"-x",
|
||||
"foo",
|
||||
"-exec",
|
||||
"something",
|
||||
";",
|
||||
"--exec",
|
||||
]);
|
||||
let actual = transform_args_with_exec(original.into_iter());
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
use crate::exec::CommandTemplate;
|
||||
use crate::internal::{
|
||||
filter::{SizeFilter, TimeFilter},
|
||||
FileTypes,
|
||||
};
|
||||
use lscolors::LsColors;
|
||||
use regex::bytes::RegexSet;
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
/// Configuration options for *fd*.
|
||||
pub struct FdOptions {
|
||||
/// Whether the search is case-sensitive or case-insensitive.
|
||||
pub case_sensitive: bool,
|
||||
|
||||
/// Whether to search within the full file path or just the base name (filename or directory
|
||||
/// name).
|
||||
pub search_full_path: bool,
|
||||
|
||||
/// Whether to ignore hidden files and directories (or not).
|
||||
pub ignore_hidden: bool,
|
||||
|
||||
/// Whether to respect `.fdignore` files or not.
|
||||
pub read_fdignore: bool,
|
||||
|
||||
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
|
||||
pub read_vcsignore: bool,
|
||||
|
||||
/// Whether to follow symlinks or not.
|
||||
pub follow_links: bool,
|
||||
|
||||
/// Whether elements of output should be separated by a null character
|
||||
pub null_separator: bool,
|
||||
|
||||
/// The maximum search depth, or `None` if no maximum search depth should be set.
|
||||
///
|
||||
/// A depth of `1` includes all files under the current directory, a depth of `2` also includes
|
||||
/// all files under subdirectories of the current directory, etc.
|
||||
pub max_depth: Option<usize>,
|
||||
|
||||
/// The number of threads to use.
|
||||
pub threads: usize,
|
||||
|
||||
/// Time to buffer results internally before streaming to the console. This is useful to
|
||||
/// provide a sorted output, in case the total execution time is shorter than
|
||||
/// `max_buffer_time`.
|
||||
pub max_buffer_time: Option<Duration>,
|
||||
|
||||
/// `None` if the output should not be colorized. Otherwise, a `LsColors` instance that defines
|
||||
/// how to style different filetypes.
|
||||
pub ls_colors: Option<LsColors>,
|
||||
|
||||
/// The type of file to search for. If set to `None`, all file types are displayed. If
|
||||
/// set to `Some(..)`, only the types that are specified are shown.
|
||||
pub file_types: Option<FileTypes>,
|
||||
|
||||
/// The extension to search for. Only entries matching the extension will be included.
|
||||
///
|
||||
/// The value (if present) will be a lowercase string without leading dots.
|
||||
pub extensions: Option<RegexSet>,
|
||||
|
||||
/// If a value is supplied, each item found will be used to generate and execute commands.
|
||||
pub command: Option<Arc<CommandTemplate>>,
|
||||
|
||||
/// A list of glob patterns that should be excluded from the search.
|
||||
pub exclude_patterns: Vec<String>,
|
||||
|
||||
/// A list of custom ignore files.
|
||||
pub ignore_files: Vec<PathBuf>,
|
||||
|
||||
/// The given constraints on the size of returned files
|
||||
pub size_constraints: Vec<SizeFilter>,
|
||||
|
||||
/// Constraints on last modification time of files
|
||||
pub time_constraints: Vec<TimeFilter>,
|
||||
|
||||
/// Whether or not to display filesystem errors
|
||||
pub show_filesystem_errors: bool,
|
||||
|
||||
/// The separator used to print file paths.
|
||||
pub path_separator: Option<String>,
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
/// A parser for the `LS_COLORS` environment variable.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use ansi_term::{Colour, Style};
|
||||
|
||||
/// Maps file extensions to ANSI colors / styles.
|
||||
pub type ExtensionStyles = HashMap<String, Style>;
|
||||
|
||||
/// Maps filenames to ANSI colors / styles.
|
||||
pub type FilenameStyles = HashMap<String, Style>;
|
||||
|
||||
const LS_CODES: &[&str] = &[
|
||||
"no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi", "so", "bd", "bd", "cd",
|
||||
"cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec", "su", "su", "sg", "sg", "st", "ow", "ow",
|
||||
"tw", "tw", "ca", "mh", "cl",
|
||||
];
|
||||
|
||||
/// Defines how different file system entries should be colorized / styled.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct LsColors {
|
||||
/// ANSI Style for directories.
|
||||
pub directory: Style,
|
||||
|
||||
/// ANSI style for symbolic links.
|
||||
pub symlink: Style,
|
||||
|
||||
/// ANSI style for executable files.
|
||||
pub executable: Style,
|
||||
|
||||
/// A map that defines ANSI styles for different file extensions.
|
||||
pub extensions: ExtensionStyles,
|
||||
|
||||
/// A map that defines ANSI styles for different specific filenames.
|
||||
pub filenames: FilenameStyles,
|
||||
}
|
||||
|
||||
impl Default for LsColors {
|
||||
/// Get a default LsColors structure.
|
||||
fn default() -> LsColors {
|
||||
LsColors {
|
||||
directory: Colour::Blue.bold(),
|
||||
symlink: Colour::Cyan.normal(),
|
||||
executable: Colour::Red.bold(),
|
||||
extensions: HashMap::new(),
|
||||
filenames: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LsColors {
|
||||
/// Parse a single text-decoration code (normal, bold, italic, ...).
|
||||
fn parse_decoration(code: &str) -> Option<fn(Colour) -> Style> {
|
||||
match code {
|
||||
"0" | "00" => Some(Colour::normal),
|
||||
"1" | "01" => Some(Colour::bold),
|
||||
"3" | "03" => Some(Colour::italic),
|
||||
"4" | "04" => Some(Colour::underline),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse ANSI escape sequences like `38;5;10;1`.
|
||||
fn parse_style(code: &str) -> Option<Style> {
|
||||
let mut split = code.split(';');
|
||||
|
||||
if let Some(first) = split.next() {
|
||||
// Try to match the first part as a text-decoration argument
|
||||
let mut decoration = LsColors::parse_decoration(first);
|
||||
|
||||
let c1 = if decoration.is_none() {
|
||||
Some(first)
|
||||
} else {
|
||||
split.next()
|
||||
};
|
||||
let c2 = split.next();
|
||||
let c3 = split.next();
|
||||
|
||||
let color = if c1 == Some("38") && c2 == Some("5") {
|
||||
let n_white = 7;
|
||||
let n = if let Some(num) = c3 {
|
||||
u8::from_str_radix(num, 10).unwrap_or(n_white)
|
||||
} else {
|
||||
n_white
|
||||
};
|
||||
|
||||
Colour::Fixed(n)
|
||||
} else if let Some(color_s) = c1 {
|
||||
match color_s {
|
||||
"30" => Colour::Black,
|
||||
"31" => Colour::Red,
|
||||
"32" => Colour::Green,
|
||||
"33" => Colour::Yellow,
|
||||
"34" => Colour::Blue,
|
||||
"35" => Colour::Purple,
|
||||
"36" => Colour::Cyan,
|
||||
_ => Colour::White,
|
||||
}
|
||||
} else {
|
||||
Colour::White
|
||||
};
|
||||
|
||||
if decoration.is_none() {
|
||||
// Try to find a decoration somewhere in the sequence
|
||||
decoration = code.split(';').flat_map(LsColors::parse_decoration).next();
|
||||
}
|
||||
|
||||
let ansi_style = decoration.unwrap_or(Colour::normal)(color);
|
||||
|
||||
Some(ansi_style)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new `LS_COLORS` entry.
|
||||
fn add_entry(&mut self, input: &str) {
|
||||
let mut parts = input.trim().split('=');
|
||||
if let Some(pattern) = parts.next() {
|
||||
if let Some(style_code) = parts.next() {
|
||||
// Ensure that the input was split into exactly two parts:
|
||||
if !parts.next().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(style) = LsColors::parse_style(style_code) {
|
||||
// Try to match against one of the known codes
|
||||
let res = LS_CODES.iter().find(|&&c| c == pattern);
|
||||
|
||||
if let Some(code) = res {
|
||||
match code.as_ref() {
|
||||
"di" => self.directory = style,
|
||||
"ln" => self.symlink = style,
|
||||
"ex" => self.executable = style,
|
||||
_ => return,
|
||||
}
|
||||
} else if pattern.starts_with("*.") {
|
||||
let extension = String::from(pattern).split_off(2);
|
||||
self.extensions.insert(extension, style);
|
||||
} else if pattern.starts_with('*') {
|
||||
let filename = String::from(pattern).split_off(1);
|
||||
self.filenames.insert(filename, style);
|
||||
} else {
|
||||
// Unknown/corrupt pattern
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a `LsColors` structure from a string.
|
||||
pub fn from_string(input: &str) -> LsColors {
|
||||
let mut lscolors = LsColors::default();
|
||||
|
||||
for s in input.split(':') {
|
||||
lscolors.add_entry(s);
|
||||
}
|
||||
|
||||
lscolors
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_simple() {
|
||||
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_decoration() {
|
||||
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("00;31"));
|
||||
|
||||
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("03;34"));
|
||||
|
||||
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("01;36"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_decoration_backwards() {
|
||||
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("34;03"));
|
||||
|
||||
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("36;01"));
|
||||
|
||||
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31;00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_256() {
|
||||
assert_eq!(
|
||||
Some(Colour::Fixed(115).normal()),
|
||||
LsColors::parse_style("38;5;115")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some(Colour::Fixed(115).normal()),
|
||||
LsColors::parse_style("00;38;5;115")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some(Colour::Fixed(119).bold()),
|
||||
LsColors::parse_style("01;38;5;119")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some(Colour::Fixed(119).bold()),
|
||||
LsColors::parse_style("38;5;119;01")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_string() {
|
||||
assert_eq!(LsColors::default(), LsColors::from_string(&String::new()));
|
||||
|
||||
let result = LsColors::from_string(&String::from(
|
||||
"rs=0:di=03;34:ln=01;36:*.foo=01;35:*README=33",
|
||||
));
|
||||
|
||||
assert_eq!(Colour::Blue.italic(), result.directory);
|
||||
assert_eq!(Colour::Cyan.bold(), result.symlink);
|
||||
assert_eq!(Some(&Colour::Purple.bold()), result.extensions.get("foo"));
|
||||
assert_eq!(
|
||||
Some(&Colour::Yellow.normal()),
|
||||
result.filenames.get("README")
|
||||
);
|
||||
}
|
||||
+175
-64
@@ -6,46 +6,43 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
extern crate ansi_term;
|
||||
extern crate atty;
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
extern crate ignore;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[cfg(all(unix, not(target_os = "redox")))]
|
||||
extern crate libc;
|
||||
extern crate num_cpus;
|
||||
extern crate regex;
|
||||
extern crate regex_syntax;
|
||||
mod internal;
|
||||
|
||||
pub mod fshelper;
|
||||
pub mod lscolors;
|
||||
mod app;
|
||||
mod exec;
|
||||
mod internal;
|
||||
mod exit_codes;
|
||||
pub mod fshelper;
|
||||
mod output;
|
||||
mod walk;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
|
||||
use atty::Stream;
|
||||
use regex::RegexBuilder;
|
||||
|
||||
use exec::CommandTemplate;
|
||||
use internal::{error, pattern_has_uppercase_char, FdOptions};
|
||||
use globset::Glob;
|
||||
use lscolors::LsColors;
|
||||
use walk::FileType;
|
||||
use regex::bytes::{RegexBuilder, RegexSetBuilder};
|
||||
|
||||
use crate::exec::CommandTemplate;
|
||||
use crate::internal::{
|
||||
filter::{SizeFilter, TimeFilter},
|
||||
opts::FdOptions,
|
||||
pattern_has_uppercase_char, transform_args_with_exec, FileTypes,
|
||||
};
|
||||
|
||||
// We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481
|
||||
#[cfg(all(not(windows), not(target_env = "musl")))]
|
||||
#[global_allocator]
|
||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||
|
||||
fn main() {
|
||||
let matches = app::build_app().get_matches();
|
||||
let checked_args = transform_args_with_exec(env::args_os());
|
||||
let matches = app::build_app().get_matches_from(checked_args);
|
||||
|
||||
// Get the search pattern
|
||||
let pattern = matches.value_of("pattern").unwrap_or("");
|
||||
@@ -53,19 +50,22 @@ fn main() {
|
||||
// Get the current working directory
|
||||
let current_dir = Path::new(".");
|
||||
if !fshelper::is_dir(current_dir) {
|
||||
error("Error: could not get current directory.");
|
||||
print_error_and_exit!("Could not get current directory.");
|
||||
}
|
||||
|
||||
//Get one or more root directories to search.
|
||||
let mut dir_vec: Vec<_> = match matches.values_of("path") {
|
||||
// Get one or more root directories to search.
|
||||
let mut dir_vec: Vec<_> = match matches
|
||||
.values_of("path")
|
||||
.or_else(|| matches.values_of("search-path"))
|
||||
{
|
||||
Some(paths) => paths
|
||||
.map(|path| {
|
||||
let path_buffer = PathBuf::from(path);
|
||||
if !fshelper::is_dir(&path_buffer) {
|
||||
error(&format!(
|
||||
"Error: '{}' is not a directory.",
|
||||
print_error_and_exit!(
|
||||
"'{}' is not a directory.",
|
||||
path_buffer.to_string_lossy()
|
||||
));
|
||||
);
|
||||
}
|
||||
path_buffer
|
||||
})
|
||||
@@ -76,14 +76,51 @@ fn main() {
|
||||
if matches.is_present("absolute-path") {
|
||||
dir_vec = dir_vec
|
||||
.iter()
|
||||
.map(|path_buffer| fshelper::absolute_path(path_buffer).unwrap())
|
||||
.map(|path_buffer| {
|
||||
path_buffer
|
||||
.canonicalize()
|
||||
.and_then(|pb| fshelper::absolute_path(pb.as_path()))
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
// Detect if the user accidentally supplied a path instead of a search pattern
|
||||
if !matches.is_present("full-path")
|
||||
&& pattern.contains(std::path::MAIN_SEPARATOR)
|
||||
&& fshelper::is_dir(Path::new(pattern))
|
||||
{
|
||||
print_error_and_exit!(
|
||||
"The search pattern '{pattern}' contains a path-separation character ('{sep}') \
|
||||
and will not lead to any search results.\n\n\
|
||||
If you want to search for all files inside the '{pattern}' directory, use a match-all pattern:\n\n \
|
||||
fd . '{pattern}'\n\n\
|
||||
Instead, if you want to search for the pattern in the full path, use:\n\n \
|
||||
fd --full-path '{pattern}'",
|
||||
pattern = pattern,
|
||||
sep = std::path::MAIN_SEPARATOR,
|
||||
);
|
||||
}
|
||||
|
||||
let pattern_regex = if matches.is_present("glob") {
|
||||
let glob = match Glob::new(pattern) {
|
||||
Ok(glob) => glob,
|
||||
Err(e) => {
|
||||
print_error_and_exit!("{}", e);
|
||||
}
|
||||
};
|
||||
glob.regex().to_owned()
|
||||
} else if matches.is_present("fixed-strings") {
|
||||
// Treat pattern as literal string if '--fixed-strings' is used
|
||||
regex::escape(pattern)
|
||||
} else {
|
||||
String::from(pattern)
|
||||
};
|
||||
|
||||
// The search will be case-sensitive if the command line flag is set or
|
||||
// if the pattern has an uppercase character (smart case).
|
||||
let case_sensitive = !matches.is_present("ignore-case")
|
||||
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(pattern));
|
||||
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
|
||||
|
||||
let colored_output = match matches.value_of("color") {
|
||||
Some("always") => true,
|
||||
@@ -91,36 +128,73 @@ fn main() {
|
||||
_ => atty::is(Stream::Stdout),
|
||||
};
|
||||
|
||||
let path_separator = matches.value_of("path-separator").map(|str| str.to_owned());
|
||||
|
||||
#[cfg(windows)]
|
||||
let colored_output = colored_output && windows::enable_colored_output();
|
||||
let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok();
|
||||
|
||||
let ls_colors = if colored_output {
|
||||
Some(
|
||||
env::var("LS_COLORS")
|
||||
.ok()
|
||||
.map(|val| LsColors::from_string(&val))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
Some(LsColors::from_env().unwrap_or_default())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let command = matches.values_of("exec").map(CommandTemplate::new);
|
||||
let command = matches
|
||||
.values_of("exec")
|
||||
.map(CommandTemplate::new)
|
||||
.or_else(|| {
|
||||
matches.values_of("exec-batch").map(|m| {
|
||||
CommandTemplate::new_batch(m).unwrap_or_else(|e| {
|
||||
print_error_and_exit!("{}", e);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let size_limits: Vec<SizeFilter> = matches
|
||||
.values_of("size")
|
||||
.map(|v| {
|
||||
v.map(|sf| {
|
||||
if let Some(f) = SizeFilter::from_string(sf) {
|
||||
return f;
|
||||
}
|
||||
print_error_and_exit!("'{}' is not a valid size constraint. See 'fd --help'.", sf);
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| vec![]);
|
||||
|
||||
let now = time::SystemTime::now();
|
||||
let mut time_constraints: Vec<TimeFilter> = Vec::new();
|
||||
if let Some(t) = matches.value_of("changed-within") {
|
||||
if let Some(f) = TimeFilter::after(&now, t) {
|
||||
time_constraints.push(f);
|
||||
} else {
|
||||
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
|
||||
}
|
||||
}
|
||||
if let Some(t) = matches.value_of("changed-before") {
|
||||
if let Some(f) = TimeFilter::before(&now, t) {
|
||||
time_constraints.push(f);
|
||||
} else {
|
||||
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
|
||||
}
|
||||
}
|
||||
|
||||
let config = FdOptions {
|
||||
case_sensitive,
|
||||
search_full_path: matches.is_present("full-path"),
|
||||
ignore_hidden: !(matches.is_present("hidden")
|
||||
|| matches.occurrences_of("rg-alias-hidden-ignore") >= 2),
|
||||
read_ignore: !(matches.is_present("no-ignore")
|
||||
read_fdignore: !(matches.is_present("no-ignore")
|
||||
|| matches.is_present("rg-alias-hidden-ignore")),
|
||||
read_gitignore: !(matches.is_present("no-ignore")
|
||||
read_vcsignore: !(matches.is_present("no-ignore")
|
||||
|| matches.is_present("rg-alias-hidden-ignore")
|
||||
|| matches.is_present("no-ignore-vcs")),
|
||||
follow_links: matches.is_present("follow"),
|
||||
null_separator: matches.is_present("null_separator"),
|
||||
max_depth: matches
|
||||
.value_of("depth")
|
||||
.or_else(|| matches.value_of("rg-depth"))
|
||||
.and_then(|n| usize::from_str_radix(n, 10).ok()),
|
||||
threads: std::cmp::max(
|
||||
matches
|
||||
@@ -134,39 +208,76 @@ fn main() {
|
||||
.and_then(|n| u64::from_str_radix(n, 10).ok())
|
||||
.map(time::Duration::from_millis),
|
||||
ls_colors,
|
||||
file_types: match matches.values_of("file-type") {
|
||||
None => vec![
|
||||
FileType::RegularFile,
|
||||
FileType::Directory,
|
||||
FileType::SymLink,
|
||||
].into_iter()
|
||||
.collect(),
|
||||
Some(values) => values
|
||||
.map(|value| match value {
|
||||
"f" | "file" => FileType::RegularFile,
|
||||
"d" | "directory" => FileType::Directory,
|
||||
"l" | "symlink" => FileType::SymLink,
|
||||
_ => FileType::RegularFile,
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
extensions: matches.values_of("extension").map(|exts| {
|
||||
exts.map(|e| e.trim_left_matches('.').to_lowercase())
|
||||
.collect()
|
||||
file_types: matches.values_of("file-type").map(|values| {
|
||||
let mut file_types = FileTypes::default();
|
||||
for value in values {
|
||||
match value {
|
||||
"f" | "file" => file_types.files = true,
|
||||
"d" | "directory" => file_types.directories = true,
|
||||
"l" | "symlink" => file_types.symlinks = true,
|
||||
"x" | "executable" => {
|
||||
file_types.executables_only = true;
|
||||
file_types.files = true;
|
||||
}
|
||||
"e" | "empty" => {
|
||||
file_types.empty_only = true;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// If only 'empty' was specified, search for both files and directories:
|
||||
if file_types.empty_only && !(file_types.files || file_types.directories) {
|
||||
file_types.files = true;
|
||||
file_types.directories = true;
|
||||
}
|
||||
|
||||
file_types
|
||||
}),
|
||||
command,
|
||||
extensions: matches.values_of("extension").map(|exts| {
|
||||
let patterns = exts
|
||||
.map(|e| e.trim_start_matches('.'))
|
||||
.map(|e| format!(r".\.{}$", regex::escape(e)));
|
||||
match RegexSetBuilder::new(patterns)
|
||||
.case_insensitive(true)
|
||||
.build()
|
||||
{
|
||||
Ok(re) => re,
|
||||
Err(err) => {
|
||||
print_error_and_exit!("{}", err.description());
|
||||
}
|
||||
}
|
||||
}),
|
||||
command: command.map(Arc::new),
|
||||
exclude_patterns: matches
|
||||
.values_of("exclude")
|
||||
.map(|v| v.map(|p| String::from("!") + p).collect())
|
||||
.unwrap_or_else(|| vec![]),
|
||||
ignore_files: matches
|
||||
.values_of("ignore-file")
|
||||
.map(|vs| vs.map(PathBuf::from).collect())
|
||||
.unwrap_or_else(|| vec![]),
|
||||
size_constraints: size_limits,
|
||||
time_constraints,
|
||||
show_filesystem_errors: matches.is_present("show-errors"),
|
||||
path_separator,
|
||||
};
|
||||
|
||||
match RegexBuilder::new(pattern)
|
||||
match RegexBuilder::new(&pattern_regex)
|
||||
.case_insensitive(!config.case_sensitive)
|
||||
.dot_matches_new_line(true)
|
||||
.build()
|
||||
{
|
||||
Ok(re) => walk::scan(&dir_vec, Arc::new(re), Arc::new(config)),
|
||||
Err(err) => error(err.description()),
|
||||
Ok(re) => {
|
||||
let exit_code = walk::scan(&dir_vec, Arc::new(re), Arc::new(config));
|
||||
process::exit(exit_code.into());
|
||||
}
|
||||
Err(err) => {
|
||||
print_error_and_exit!(
|
||||
"{}\nHint: You can use the '--fixed-strings' option to search for a \
|
||||
literal string instead of a regular expression",
|
||||
err.description()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+61
-85
@@ -6,36 +6,64 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use internal::{FdOptions, EXITCODE_ERROR, EXITCODE_SIGINT};
|
||||
use lscolors::LsColors;
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::internal::opts::FdOptions;
|
||||
use lscolors::{LsColors, Style};
|
||||
|
||||
use std::{fs, process};
|
||||
use std::io::{self, Write};
|
||||
use std::ops::Deref;
|
||||
use std::path::{self, Component, Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::borrow::Cow;
|
||||
use std::io::{self, StdoutLock, Write};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ansi_term;
|
||||
|
||||
pub fn print_entry(entry: &PathBuf, config: &FdOptions, wants_to_quit: &Arc<AtomicBool>) {
|
||||
let path = entry.strip_prefix(".").unwrap_or(entry);
|
||||
/// Remove the `./` prefix from a path.
|
||||
fn strip_current_dir(pathbuf: &PathBuf) -> &Path {
|
||||
let mut iter = pathbuf.components();
|
||||
let mut iter_next = iter.clone();
|
||||
if iter_next.next() == Some(Component::CurDir) {
|
||||
iter.next();
|
||||
}
|
||||
iter.as_path()
|
||||
}
|
||||
|
||||
pub fn print_entry(
|
||||
stdout: &mut StdoutLock,
|
||||
entry: &PathBuf,
|
||||
config: &FdOptions,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
) {
|
||||
let path = if entry.is_absolute() {
|
||||
entry.as_path()
|
||||
} else {
|
||||
strip_current_dir(entry)
|
||||
};
|
||||
|
||||
let r = if let Some(ref ls_colors) = config.ls_colors {
|
||||
print_entry_colorized(path, config, ls_colors, &wants_to_quit)
|
||||
print_entry_colorized(stdout, path, config, ls_colors, &wants_to_quit)
|
||||
} else {
|
||||
print_entry_uncolorized(path, config)
|
||||
print_entry_uncolorized(stdout, path, config)
|
||||
};
|
||||
|
||||
if r.is_err() {
|
||||
// Probably a broken pipe. Exit gracefully.
|
||||
process::exit(EXITCODE_ERROR);
|
||||
process::exit(ExitCode::GeneralError.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_path_separator<'a>(config: &FdOptions, path: &mut Cow<'a, str>) {
|
||||
match &config.path_separator {
|
||||
None => {}
|
||||
Some(sep) => {
|
||||
*path.to_mut() = path.replace(std::path::MAIN_SEPARATOR, &sep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_entry_colorized(
|
||||
stdout: &mut StdoutLock,
|
||||
path: &Path,
|
||||
config: &FdOptions,
|
||||
ls_colors: &LsColors,
|
||||
@@ -43,89 +71,37 @@ fn print_entry_colorized(
|
||||
) -> io::Result<()> {
|
||||
let default_style = ansi_term::Style::default();
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut handle = stdout.lock();
|
||||
|
||||
// Separator to use before the current component.
|
||||
let mut separator = String::new();
|
||||
|
||||
// Full path to the current component.
|
||||
let mut component_path = PathBuf::new();
|
||||
|
||||
// Traverse the path and colorize each component
|
||||
for component in path.components() {
|
||||
let comp_str = component.as_os_str().to_string_lossy();
|
||||
component_path.push(Path::new(comp_str.deref()));
|
||||
for (component, style) in ls_colors.style_for_path_components(path) {
|
||||
let style = style
|
||||
.map(Style::to_ansi_term_style)
|
||||
.unwrap_or(default_style);
|
||||
|
||||
let style = get_path_style(&component_path, ls_colors).unwrap_or(&default_style);
|
||||
|
||||
write!(handle, "{}{}", separator, style.paint(comp_str))?;
|
||||
|
||||
// Determine separator to print before next component.
|
||||
separator = match component {
|
||||
// Prefix needs no separator, as it is always followed by RootDir.
|
||||
Component::Prefix(_) => String::new(),
|
||||
// RootDir is already a separator.
|
||||
Component::RootDir => String::new(),
|
||||
// Everything else uses a separator that is painted the same way as the component.
|
||||
_ => style.paint(path::MAIN_SEPARATOR.to_string()).to_string(),
|
||||
};
|
||||
let mut path_string = component.to_string_lossy();
|
||||
replace_path_separator(&config, &mut path_string);
|
||||
write!(stdout, "{}", style.paint(path_string))?;
|
||||
|
||||
if wants_to_quit.load(Ordering::Relaxed) {
|
||||
write!(handle, "\n")?;
|
||||
process::exit(EXITCODE_SIGINT);
|
||||
writeln!(stdout)?;
|
||||
process::exit(ExitCode::KilledBySigint.into());
|
||||
}
|
||||
}
|
||||
|
||||
if config.null_separator {
|
||||
write!(handle, "\0")
|
||||
write!(stdout, "\0")
|
||||
} else {
|
||||
writeln!(handle, "")
|
||||
writeln!(stdout)
|
||||
}
|
||||
}
|
||||
|
||||
fn print_entry_uncolorized(path: &Path, config: &FdOptions) -> io::Result<()> {
|
||||
fn print_entry_uncolorized(
|
||||
stdout: &mut StdoutLock,
|
||||
path: &Path,
|
||||
config: &FdOptions,
|
||||
) -> io::Result<()> {
|
||||
let separator = if config.null_separator { "\0" } else { "\n" };
|
||||
|
||||
let path_str = path.to_string_lossy();
|
||||
write!(&mut io::stdout(), "{}{}", path_str, separator)
|
||||
}
|
||||
|
||||
fn get_path_style<'a>(path: &Path, ls_colors: &'a LsColors) -> Option<&'a ansi_term::Style> {
|
||||
if path.symlink_metadata()
|
||||
.map(|md| md.file_type().is_symlink())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Some(&ls_colors.symlink);
|
||||
}
|
||||
|
||||
let metadata = path.metadata();
|
||||
|
||||
if metadata.as_ref().map(|md| md.is_dir()).unwrap_or(false) {
|
||||
Some(&ls_colors.directory)
|
||||
} else if metadata.map(|md| is_executable(&md)).unwrap_or(false) {
|
||||
Some(&ls_colors.executable)
|
||||
} else if let Some(filename_style) = path.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.and_then(|n| ls_colors.filenames.get(n))
|
||||
{
|
||||
Some(filename_style)
|
||||
} else if let Some(extension_style) = path.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.and_then(|e| ls_colors.extensions.get(e))
|
||||
{
|
||||
Some(extension_style)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
fn is_executable(md: &fs::Metadata) -> bool {
|
||||
md.permissions().mode() & 0o111 != 0
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_executable(_: &fs::Metadata) -> bool {
|
||||
false
|
||||
let mut path_str = path.to_string_lossy();
|
||||
replace_path_separator(&config, &mut path_str);
|
||||
write!(stdout, "{}{}", path_str, separator)
|
||||
}
|
||||
|
||||
+255
-121
@@ -6,24 +6,27 @@
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
extern crate ctrlc;
|
||||
use crate::exec;
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::fshelper;
|
||||
use crate::internal::{opts::FdOptions, osstr_to_bytes, MAX_BUFFER_LENGTH};
|
||||
use crate::output;
|
||||
|
||||
use exec;
|
||||
use fshelper;
|
||||
use internal::{error, FdOptions, EXITCODE_SIGINT, MAX_BUFFER_LENGTH};
|
||||
use output;
|
||||
|
||||
use std::process;
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
use std::ffi::OsStr;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
use ignore::{self, WalkBuilder};
|
||||
use ignore::overrides::OverrideBuilder;
|
||||
use regex::Regex;
|
||||
use ignore::{self, WalkBuilder};
|
||||
use regex::bytes::Regex;
|
||||
|
||||
/// The receiver thread can either be buffering results or directly streaming to the console.
|
||||
enum ReceiverMode {
|
||||
@@ -35,12 +38,10 @@ enum ReceiverMode {
|
||||
Streaming,
|
||||
}
|
||||
|
||||
/// The types of file to search for.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum FileType {
|
||||
RegularFile,
|
||||
Directory,
|
||||
SymLink,
|
||||
/// The Worker threads can result in a valid entry having PathBuf or an error.
|
||||
pub enum WorkerResult {
|
||||
Entry(PathBuf),
|
||||
Error(ignore::Error),
|
||||
}
|
||||
|
||||
/// Recursively scan the given search path for files / pathnames matching the pattern.
|
||||
@@ -48,86 +49,137 @@ pub enum FileType {
|
||||
/// If the `--exec` argument was supplied, this will create a thread pool for executing
|
||||
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
|
||||
/// path will simply be written to standard output.
|
||||
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) -> ExitCode {
|
||||
let mut path_iter = path_vec.iter();
|
||||
let first_path_buf = path_iter
|
||||
.next()
|
||||
.expect("Error: Path vector can not be empty");
|
||||
let (tx, rx) = channel();
|
||||
let threads = config.threads;
|
||||
|
||||
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
|
||||
|
||||
for pattern in &config.exclude_patterns {
|
||||
let res = override_builder.add(pattern);
|
||||
if res.is_err() {
|
||||
error(&format!("Error: malformed exclude pattern '{}'", pattern));
|
||||
print_error_and_exit!("Malformed exclude pattern '{}'", pattern);
|
||||
}
|
||||
}
|
||||
let overrides = override_builder.build().unwrap_or_else(|_| {
|
||||
error("Mismatch in exclude patterns");
|
||||
print_error_and_exit!("Mismatch in exclude patterns");
|
||||
});
|
||||
|
||||
let mut walker = WalkBuilder::new(first_path_buf.as_path());
|
||||
walker
|
||||
.hidden(config.ignore_hidden)
|
||||
.ignore(config.read_ignore)
|
||||
.git_ignore(config.read_gitignore)
|
||||
.parents(config.read_ignore || config.read_gitignore)
|
||||
.git_global(config.read_gitignore)
|
||||
.git_exclude(config.read_gitignore)
|
||||
.ignore(config.read_fdignore)
|
||||
.parents(config.read_fdignore || config.read_vcsignore)
|
||||
.git_ignore(config.read_vcsignore)
|
||||
.git_global(config.read_vcsignore)
|
||||
.git_exclude(config.read_vcsignore)
|
||||
.overrides(overrides)
|
||||
.follow_links(config.follow_links)
|
||||
.max_depth(config.max_depth);
|
||||
|
||||
if config.read_fdignore {
|
||||
walker.add_custom_ignore_filename(".fdignore");
|
||||
}
|
||||
|
||||
for ignore_file in &config.ignore_files {
|
||||
let result = walker.add_ignore(ignore_file);
|
||||
match result {
|
||||
Some(ignore::Error::Partial(_)) => (),
|
||||
Some(err) => {
|
||||
print_error!(
|
||||
"{}",
|
||||
format!(
|
||||
"Malformed pattern in custom ignore file '{}': {}.",
|
||||
ignore_file.to_string_lossy(),
|
||||
err.description()
|
||||
)
|
||||
);
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
for path_entry in path_iter {
|
||||
walker.add(path_entry.as_path());
|
||||
}
|
||||
|
||||
let parallel_walker = walker.threads(threads).build_parallel();
|
||||
let parallel_walker = walker.threads(config.threads).build_parallel();
|
||||
|
||||
let wants_to_quit = Arc::new(AtomicBool::new(false));
|
||||
let receiver_wtq = Arc::clone(&wants_to_quit);
|
||||
let sender_wtq = Arc::clone(&wants_to_quit);
|
||||
if config.ls_colors.is_some() {
|
||||
let wq = Arc::clone(&receiver_wtq);
|
||||
if config.ls_colors.is_some() && config.command.is_none() {
|
||||
let wq = Arc::clone(&wants_to_quit);
|
||||
ctrlc::set_handler(move || {
|
||||
wq.store(true, Ordering::Relaxed);
|
||||
}).unwrap();
|
||||
if wq.load(Ordering::Relaxed) {
|
||||
// Ctrl-C has been pressed twice, exit NOW
|
||||
process::exit(ExitCode::KilledBySigint.into());
|
||||
} else {
|
||||
wq.store(true, Ordering::Relaxed);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Spawn the thread that receives all results through the channel.
|
||||
let rx_config = Arc::clone(&config);
|
||||
let receiver_thread = thread::spawn(move || {
|
||||
let receiver_thread = spawn_receiver(&config, &wants_to_quit, rx);
|
||||
|
||||
// Spawn the sender threads.
|
||||
spawn_senders(&config, &wants_to_quit, pattern, parallel_walker, tx);
|
||||
|
||||
// Wait for the receiver thread to print out all results.
|
||||
let exit_code = receiver_thread.join().unwrap();
|
||||
|
||||
if wants_to_quit.load(Ordering::Relaxed) {
|
||||
process::exit(ExitCode::KilledBySigint.into());
|
||||
}
|
||||
|
||||
exit_code
|
||||
}
|
||||
|
||||
fn spawn_receiver(
|
||||
config: &Arc<FdOptions>,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
rx: Receiver<WorkerResult>,
|
||||
) -> thread::JoinHandle<ExitCode> {
|
||||
let config = Arc::clone(config);
|
||||
let wants_to_quit = Arc::clone(wants_to_quit);
|
||||
|
||||
let show_filesystem_errors = config.show_filesystem_errors;
|
||||
let threads = config.threads;
|
||||
|
||||
thread::spawn(move || {
|
||||
// This will be set to `Some` if the `--exec` argument was supplied.
|
||||
if let Some(ref cmd) = rx_config.command {
|
||||
let shared_rx = Arc::new(Mutex::new(rx));
|
||||
if let Some(ref cmd) = config.command {
|
||||
if cmd.in_batch_mode() {
|
||||
exec::batch(rx, cmd, show_filesystem_errors)
|
||||
} else {
|
||||
let shared_rx = Arc::new(Mutex::new(rx));
|
||||
|
||||
let out_perm = Arc::new(Mutex::new(()));
|
||||
let out_perm = Arc::new(Mutex::new(()));
|
||||
|
||||
// TODO: the following line is a workaround to replace the `unsafe` block that was
|
||||
// previously used here to avoid the (unnecessary?) cloning of the command. The
|
||||
// `unsafe` block caused problems on some platforms (SIGILL instructions on Linux) and
|
||||
// therefore had to be removed.
|
||||
let cmd = Arc::new(cmd.clone());
|
||||
// Each spawned job will store it's thread handle in here.
|
||||
let mut handles = Vec::with_capacity(threads);
|
||||
for _ in 0..threads {
|
||||
let rx = Arc::clone(&shared_rx);
|
||||
let cmd = Arc::clone(cmd);
|
||||
let out_perm = Arc::clone(&out_perm);
|
||||
|
||||
// Each spawned job will store it's thread handle in here.
|
||||
let mut handles = Vec::with_capacity(threads);
|
||||
for _ in 0..threads {
|
||||
let rx = Arc::clone(&shared_rx);
|
||||
let cmd = Arc::clone(&cmd);
|
||||
let out_perm = Arc::clone(&out_perm);
|
||||
// Spawn a job thread that will listen for and execute inputs.
|
||||
let handle =
|
||||
thread::spawn(move || exec::job(rx, cmd, out_perm, show_filesystem_errors));
|
||||
|
||||
// Spawn a job thread that will listen for and execute inputs.
|
||||
let handle = thread::spawn(move || exec::job(rx, cmd, out_perm));
|
||||
// Push the handle of the spawned thread into the vector for later joining.
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// Push the handle of the spawned thread into the vector for later joining.
|
||||
handles.push(handle);
|
||||
}
|
||||
// Wait for all threads to exit before exiting the program.
|
||||
for h in handles {
|
||||
h.join().unwrap();
|
||||
}
|
||||
|
||||
// Wait for all threads to exit before exiting the program.
|
||||
for h in handles {
|
||||
h.join().unwrap();
|
||||
ExitCode::Success
|
||||
}
|
||||
} else {
|
||||
let start = time::Instant::now();
|
||||
@@ -138,31 +190,48 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
let mut mode = ReceiverMode::Buffering;
|
||||
|
||||
// Maximum time to wait before we start streaming to the console.
|
||||
let max_buffer_time = rx_config
|
||||
let max_buffer_time = config
|
||||
.max_buffer_time
|
||||
.unwrap_or_else(|| time::Duration::from_millis(100));
|
||||
|
||||
for value in rx {
|
||||
match mode {
|
||||
ReceiverMode::Buffering => {
|
||||
buffer.push(value);
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
// Have we reached the maximum buffer size or maximum buffering time?
|
||||
if buffer.len() > MAX_BUFFER_LENGTH
|
||||
|| time::Instant::now() - start > max_buffer_time
|
||||
{
|
||||
// Flush the buffer
|
||||
for v in &buffer {
|
||||
output::print_entry(v, &rx_config, &receiver_wtq);
|
||||
for worker_result in rx {
|
||||
match worker_result {
|
||||
WorkerResult::Entry(value) => {
|
||||
match mode {
|
||||
ReceiverMode::Buffering => {
|
||||
buffer.push(value);
|
||||
|
||||
// Have we reached the maximum buffer size or maximum buffering time?
|
||||
if buffer.len() > MAX_BUFFER_LENGTH
|
||||
|| time::Instant::now() - start > max_buffer_time
|
||||
{
|
||||
// Flush the buffer
|
||||
for v in &buffer {
|
||||
output::print_entry(
|
||||
&mut stdout,
|
||||
v,
|
||||
&config,
|
||||
&wants_to_quit,
|
||||
);
|
||||
}
|
||||
buffer.clear();
|
||||
|
||||
// Start streaming
|
||||
mode = ReceiverMode::Streaming;
|
||||
}
|
||||
}
|
||||
ReceiverMode::Streaming => {
|
||||
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
|
||||
}
|
||||
buffer.clear();
|
||||
|
||||
// Start streaming
|
||||
mode = ReceiverMode::Streaming;
|
||||
}
|
||||
}
|
||||
ReceiverMode::Streaming => {
|
||||
output::print_entry(&value, &rx_config, &receiver_wtq);
|
||||
WorkerResult::Error(err) => {
|
||||
if show_filesystem_errors {
|
||||
print_error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,18 +241,27 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
if !buffer.is_empty() {
|
||||
buffer.sort();
|
||||
for value in buffer {
|
||||
output::print_entry(&value, &rx_config, &receiver_wtq);
|
||||
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Spawn the sender threads.
|
||||
ExitCode::Success
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn spawn_senders(
|
||||
config: &Arc<FdOptions>,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
pattern: Arc<Regex>,
|
||||
parallel_walker: ignore::WalkParallel,
|
||||
tx: Sender<WorkerResult>,
|
||||
) {
|
||||
parallel_walker.run(|| {
|
||||
let config = Arc::clone(&config);
|
||||
let config = Arc::clone(config);
|
||||
let pattern = Arc::clone(&pattern);
|
||||
let tx_thread = tx.clone();
|
||||
let wants_to_quit = Arc::clone(&sender_wtq);
|
||||
let wants_to_quit = Arc::clone(wants_to_quit);
|
||||
|
||||
Box::new(move |entry_o| {
|
||||
if wants_to_quit.load(Ordering::Relaxed) {
|
||||
@@ -192,64 +270,120 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
|
||||
let entry = match entry_o {
|
||||
Ok(e) => e,
|
||||
Err(_) => return ignore::WalkState::Continue,
|
||||
Err(err) => {
|
||||
tx_thread.send(WorkerResult::Error(err)).unwrap();
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
};
|
||||
|
||||
let entry_path = entry.path();
|
||||
|
||||
if entry.depth() == 0 {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
|
||||
// Filter out unwanted file types.
|
||||
if (entry.file_type().map_or(false, |ft| ft.is_file())
|
||||
&& !config.file_types.contains(&FileType::RegularFile))
|
||||
|| (entry.file_type().map_or(false, |ft| ft.is_dir())
|
||||
&& !config.file_types.contains(&FileType::Directory))
|
||||
|| (entry.file_type().map_or(false, |ft| ft.is_symlink())
|
||||
&& !config.file_types.contains(&FileType::SymLink))
|
||||
{
|
||||
// Check the name first, since it doesn't require metadata
|
||||
let entry_path = entry.path();
|
||||
|
||||
let search_str: Cow<OsStr> = if config.search_full_path {
|
||||
match fshelper::path_absolute_form(entry_path) {
|
||||
Ok(path_abs_buf) => Cow::Owned(path_abs_buf.as_os_str().to_os_string()),
|
||||
Err(_) => {
|
||||
print_error_and_exit!("Unable to retrieve absolute path.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match entry_path.file_name() {
|
||||
Some(filename) => Cow::Borrowed(filename),
|
||||
None => unreachable!(
|
||||
"Encountered file system entry without a file name. This should only \
|
||||
happen for paths like 'foo/bar/..' or '/' which are not supposed to \
|
||||
appear in a file system traversal."
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
if !pattern.is_match(&osstr_to_bytes(search_str.as_ref())) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
|
||||
// Filter out unwanted extensions.
|
||||
if let Some(ref filter_exts) = config.extensions {
|
||||
let entry_ext = entry_path
|
||||
.extension()
|
||||
.map(|e| e.to_string_lossy().to_lowercase());
|
||||
if entry_ext.map_or(true, |ext| !filter_exts.contains(&ext)) {
|
||||
if let Some(ref exts_regex) = config.extensions {
|
||||
if let Some(path_str) = entry_path.file_name() {
|
||||
if !exts_regex.is_match(&osstr_to_bytes(path_str)) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
}
|
||||
|
||||
let search_str_o = if config.search_full_path {
|
||||
match fshelper::path_absolute_form(entry_path) {
|
||||
Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),
|
||||
Err(_) => error("Error: unable to get full path."),
|
||||
}
|
||||
} else {
|
||||
entry_path.file_name().map(|f| f.to_string_lossy())
|
||||
};
|
||||
|
||||
if let Some(search_str) = search_str_o {
|
||||
if pattern.is_match(&*search_str) {
|
||||
// TODO: take care of the unwrap call
|
||||
tx_thread.send(entry_path.to_owned()).unwrap()
|
||||
// Filter out unwanted file types.
|
||||
if let Some(ref file_types) = config.file_types {
|
||||
if let Some(ref entry_type) = entry.file_type() {
|
||||
if (!file_types.files && entry_type.is_file())
|
||||
|| (!file_types.directories && entry_type.is_dir())
|
||||
|| (!file_types.symlinks && entry_type.is_symlink())
|
||||
|| (file_types.executables_only
|
||||
&& !entry
|
||||
.metadata()
|
||||
.map(|m| fshelper::is_executable(&m))
|
||||
.unwrap_or(false))
|
||||
|| (file_types.empty_only && !fshelper::is_empty(&entry))
|
||||
{
|
||||
return ignore::WalkState::Continue;
|
||||
} else if !(entry_type.is_file()
|
||||
|| entry_type.is_dir()
|
||||
|| entry_type.is_symlink())
|
||||
{
|
||||
// This is probably a block device, char device, fifo or socket. Skip it.
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out unwanted sizes if it is a file and we have been given size constraints.
|
||||
if !config.size_constraints.is_empty() {
|
||||
if entry_path.is_file() {
|
||||
if let Ok(metadata) = entry_path.metadata() {
|
||||
let file_size = metadata.len();
|
||||
if config
|
||||
.size_constraints
|
||||
.iter()
|
||||
.any(|sc| !sc.is_within(file_size))
|
||||
{
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out unwanted modification times
|
||||
if !config.time_constraints.is_empty() {
|
||||
let mut matched = false;
|
||||
if let Ok(metadata) = entry_path.metadata() {
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
matched = config
|
||||
.time_constraints
|
||||
.iter()
|
||||
.all(|tf| tf.applies_to(&modified));
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: take care of the unwrap call
|
||||
tx_thread
|
||||
.send(WorkerResult::Entry(entry_path.to_owned()))
|
||||
.unwrap();
|
||||
|
||||
ignore::WalkState::Continue
|
||||
})
|
||||
});
|
||||
|
||||
// Drop the initial sender. If we don't do this, the receiver will block even
|
||||
// if all threads have finished, since there is still one sender around.
|
||||
drop(tx);
|
||||
|
||||
// Wait for the receiver thread to print out all results.
|
||||
receiver_thread.join().unwrap();
|
||||
|
||||
if wants_to_quit.load(Ordering::Relaxed) {
|
||||
process::exit(EXITCODE_SIGINT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
extern crate kernel32;
|
||||
extern crate winapi;
|
||||
|
||||
use self::kernel32::{GetConsoleMode, GetStdHandle, SetConsoleMode};
|
||||
use self::winapi::{INVALID_HANDLE_VALUE, STD_OUTPUT_HANDLE};
|
||||
|
||||
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example
|
||||
pub fn enable_colored_output() -> bool {
|
||||
unsafe {
|
||||
let handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if handle == INVALID_HANDLE_VALUE {
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/console/getconsolemode
|
||||
let mut mode = 0;
|
||||
if GetConsoleMode(handle, &mut mode) == 0 {
|
||||
return false;
|
||||
}
|
||||
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
||||
//
|
||||
// A console consists of an input buffer and one or more screen buffers. ... Setting the
|
||||
// output modes of one screen buffer does not affect the output modes of other screen
|
||||
// buffers.
|
||||
SetConsoleMode(handle, mode) != 0
|
||||
}
|
||||
}
|
||||
+65
-16
@@ -20,10 +20,7 @@ use std::os::unix;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows;
|
||||
|
||||
extern crate diff;
|
||||
extern crate tempdir;
|
||||
|
||||
use self::tempdir::TempDir;
|
||||
use tempdir::TempDir;
|
||||
|
||||
/// Environment for the integration tests.
|
||||
pub struct TestEnv {
|
||||
@@ -32,6 +29,9 @@ pub struct TestEnv {
|
||||
|
||||
/// Path to the *fd* executable.
|
||||
fd_exe: PathBuf,
|
||||
|
||||
/// Normalize each line by sorting the whitespace-separated words
|
||||
normalize_line: bool,
|
||||
}
|
||||
|
||||
/// Create the working directory and the test files.
|
||||
@@ -44,6 +44,9 @@ fn create_working_directory(
|
||||
{
|
||||
let root = temp_dir.path();
|
||||
|
||||
// Pretend that this is a Git repository in order for `.gitignore` files to be respected
|
||||
fs::create_dir_all(root.join(".git"))?;
|
||||
|
||||
for directory in directories {
|
||||
fs::create_dir_all(root.join(directory))?;
|
||||
}
|
||||
@@ -60,7 +63,7 @@ fn create_working_directory(
|
||||
#[cfg(windows)]
|
||||
windows::fs::symlink_dir(root.join("one/two"), root.join("symlink"))?;
|
||||
|
||||
fs::File::create(root.join(".ignore"))?.write_all(b"ignored.foo")?;
|
||||
fs::File::create(root.join(".fdignore"))?.write_all(b"fdignored.foo")?;
|
||||
|
||||
fs::File::create(root.join(".gitignore"))?.write_all(b"gitignored.foo")?;
|
||||
}
|
||||
@@ -118,19 +121,24 @@ fn format_output_error(args: &[&str], expected: &str, actual: &str) -> String {
|
||||
}
|
||||
|
||||
/// Normalize the output for comparison.
|
||||
fn normalize_output(s: &str, trim_left: bool) -> String {
|
||||
fn normalize_output(s: &str, trim_start: bool, normalize_line: bool) -> String {
|
||||
// Split into lines and normalize separators.
|
||||
let mut lines = s.replace('\0', "NULL\n")
|
||||
let mut lines = s
|
||||
.replace('\0', "NULL\n")
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let line =
|
||||
if trim_left { line.trim_left() } else { line };
|
||||
line.replace('/', &std::path::MAIN_SEPARATOR.to_string())
|
||||
let line = if trim_start { line.trim_start() } else { line };
|
||||
let line = line.replace('/', &std::path::MAIN_SEPARATOR.to_string());
|
||||
if normalize_line {
|
||||
let mut words: Vec<_> = line.split_whitespace().collect();
|
||||
words.sort();
|
||||
return words.join(" ");
|
||||
}
|
||||
line
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
lines.sort_by_key(|s| s.clone());
|
||||
|
||||
lines.sort();
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
@@ -140,8 +148,17 @@ impl TestEnv {
|
||||
let fd_exe = find_fd_exe();
|
||||
|
||||
TestEnv {
|
||||
temp_dir: temp_dir,
|
||||
fd_exe: fd_exe,
|
||||
temp_dir,
|
||||
fd_exe,
|
||||
normalize_line: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_line(self, normalize: bool) -> TestEnv {
|
||||
TestEnv {
|
||||
temp_dir: self.temp_dir,
|
||||
fd_exe: self.fd_exe,
|
||||
normalize_line: normalize,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,12 +200,44 @@ impl TestEnv {
|
||||
}
|
||||
|
||||
// Normalize both expected and actual output.
|
||||
let expected = normalize_output(expected, true);
|
||||
let actual = normalize_output(&String::from_utf8_lossy(&output.stdout), false);
|
||||
let expected = normalize_output(expected, true, self.normalize_line);
|
||||
let actual = normalize_output(
|
||||
&String::from_utf8_lossy(&output.stdout),
|
||||
false,
|
||||
self.normalize_line,
|
||||
);
|
||||
|
||||
// Compare actual output to expected output.
|
||||
if expected != actual {
|
||||
panic!(format_output_error(args, &expected, &actual));
|
||||
}
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* with the specified arguments produces the expected error.
|
||||
pub fn assert_error(&self, args: &[&str], expected: &str) {
|
||||
self.assert_error_subdirectory(".", args, expected)
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* in the specified path under the root working directory,
|
||||
/// and with the specified arguments produces an error with the expected message.
|
||||
fn assert_error_subdirectory<P: AsRef<Path>>(&self, path: P, args: &[&str], expected: &str) {
|
||||
// Setup *fd* command.
|
||||
let mut cmd = process::Command::new(&self.fd_exe);
|
||||
cmd.current_dir(self.temp_dir.path().join(path));
|
||||
cmd.args(args);
|
||||
|
||||
// Run *fd*.
|
||||
let output = cmd.output().expect("fd output");
|
||||
|
||||
// Check for exit status.
|
||||
if output.status.success() {
|
||||
panic!("error '{}' did not occur.", expected);
|
||||
}
|
||||
|
||||
// Compare actual output to expected output.
|
||||
let actual = String::from_utf8_lossy(&output.stderr);
|
||||
if !actual.starts_with(expected) {
|
||||
panic!(format_output_error(args, &expected, &actual));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+634
-36
@@ -8,12 +8,14 @@
|
||||
|
||||
//! Integration tests for the CLI interface of fd.
|
||||
|
||||
extern crate regex;
|
||||
|
||||
mod testenv;
|
||||
|
||||
use testenv::TestEnv;
|
||||
use crate::testenv::TestEnv;
|
||||
use regex::escape;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
static DEFAULT_DIRS: &'static [&'static str] = &["one/two/three", "one/two/three/directory_foo"];
|
||||
|
||||
@@ -23,14 +25,15 @@ static DEFAULT_FILES: &'static [&'static str] = &[
|
||||
"one/two/c.foo",
|
||||
"one/two/C.Foo2",
|
||||
"one/two/three/d.foo",
|
||||
"ignored.foo",
|
||||
"fdignored.foo",
|
||||
"gitignored.foo",
|
||||
".hidden.foo",
|
||||
"e1 e2",
|
||||
];
|
||||
|
||||
fn get_absolute_root_path(env: &TestEnv) -> String {
|
||||
let path = env.test_root()
|
||||
let path = env
|
||||
.test_root()
|
||||
.canonicalize()
|
||||
.expect("absolute path")
|
||||
.to_str()
|
||||
@@ -38,11 +41,25 @@ fn get_absolute_root_path(env: &TestEnv) -> String {
|
||||
.to_string();
|
||||
|
||||
#[cfg(windows)]
|
||||
let path = path.trim_left_matches(r"\\?\").to_string();
|
||||
let path = path.trim_start_matches(r"\\?\").to_string();
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn get_test_env_with_abs_path(dirs: &[&'static str], files: &[&'static str]) -> (TestEnv, String) {
|
||||
let env = TestEnv::new(dirs, files);
|
||||
let root_path = get_absolute_root_path(&env);
|
||||
(env, root_path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn create_file_with_size<P: AsRef<Path>>(path: P, size_in_bytes: usize) {
|
||||
let content = "#".repeat(size_in_bytes);
|
||||
let mut f = fs::File::create::<P>(path).unwrap();
|
||||
f.write(content.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
/// Simple tests
|
||||
#[test]
|
||||
fn test_simple() {
|
||||
@@ -222,6 +239,89 @@ fn test_case_insensitive() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Glob-based searches (--glob)
|
||||
#[test]
|
||||
fn test_glob_searches() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "*.foo"],
|
||||
"a.foo
|
||||
one/b.foo
|
||||
one/two/c.foo
|
||||
one/two/three/d.foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "[a-c].foo"],
|
||||
"a.foo
|
||||
one/b.foo
|
||||
one/two/c.foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "[a-c].foo*"],
|
||||
"a.foo
|
||||
one/b.foo
|
||||
one/two/C.Foo2
|
||||
one/two/c.foo",
|
||||
);
|
||||
}
|
||||
|
||||
/// Glob-based searches (--glob) in combination with full path searches (--full-path)
|
||||
#[cfg(not(windows))] // TODO: make this work on Windows
|
||||
#[test]
|
||||
fn test_full_path_glob_searches() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "--full-path", "**/one/**/*.foo"],
|
||||
"one/b.foo
|
||||
one/two/c.foo
|
||||
one/two/three/d.foo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_case_glob_searches() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "c.foo*"],
|
||||
"one/two/C.Foo2
|
||||
one/two/c.foo",
|
||||
);
|
||||
|
||||
te.assert_output(&["--glob", "C.Foo*"], "one/two/C.Foo2");
|
||||
}
|
||||
|
||||
/// Glob-based searches (--glob) in combination with --case-sensitive
|
||||
#[test]
|
||||
fn test_case_sensitive_glob_searches() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(&["--glob", "--case-sensitive", "c.foo*"], "one/two/c.foo");
|
||||
}
|
||||
|
||||
/// Glob-based searches (--glob) in combination with --extension
|
||||
#[test]
|
||||
fn test_glob_searches_with_extension() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "--extension", "foo2", "[a-z].*"],
|
||||
"one/two/C.Foo2",
|
||||
);
|
||||
}
|
||||
|
||||
/// Make sure that --regex overrides --glob
|
||||
#[test]
|
||||
fn test_regex_overrides_glob() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(&["--glob", "--regex", "Foo2$"], "one/two/C.Foo2");
|
||||
}
|
||||
|
||||
/// Full path search (--full-path)
|
||||
#[test]
|
||||
fn test_full_path() {
|
||||
@@ -257,6 +357,28 @@ fn test_hidden() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Hidden file attribute on Windows
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_hidden_file_attribute() {
|
||||
use std::os::windows::fs::OpenOptionsExt;
|
||||
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesa
|
||||
const FILE_ATTRIBUTE_HIDDEN: u32 = 2;
|
||||
|
||||
fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.attributes(FILE_ATTRIBUTE_HIDDEN)
|
||||
.open(te.test_root().join("hidden-file.txt"))
|
||||
.unwrap();
|
||||
|
||||
te.assert_output(&["--hidden", "hidden-file.txt"], "hidden-file.txt");
|
||||
te.assert_output(&["hidden-file.txt"], "");
|
||||
}
|
||||
|
||||
/// Ignored files (--no-ignore)
|
||||
#[test]
|
||||
fn test_no_ignore() {
|
||||
@@ -265,7 +387,7 @@ fn test_no_ignore() {
|
||||
te.assert_output(
|
||||
&["--no-ignore", "foo"],
|
||||
"a.foo
|
||||
ignored.foo
|
||||
fdignored.foo
|
||||
gitignored.foo
|
||||
one/b.foo
|
||||
one/two/c.foo
|
||||
@@ -278,7 +400,7 @@ fn test_no_ignore() {
|
||||
&["--hidden", "--no-ignore", "foo"],
|
||||
".hidden.foo
|
||||
a.foo
|
||||
ignored.foo
|
||||
fdignored.foo
|
||||
gitignored.foo
|
||||
one/b.foo
|
||||
one/two/c.foo
|
||||
@@ -288,6 +410,70 @@ fn test_no_ignore() {
|
||||
);
|
||||
}
|
||||
|
||||
/// .gitignore and .fdignore
|
||||
#[test]
|
||||
fn test_gitignore_and_fdignore() {
|
||||
let files = &[
|
||||
"ignored-by-nothing",
|
||||
"ignored-by-fdignore",
|
||||
"ignored-by-gitignore",
|
||||
"ignored-by-both",
|
||||
];
|
||||
let te = TestEnv::new(&[], files);
|
||||
|
||||
fs::File::create(te.test_root().join(".fdignore"))
|
||||
.unwrap()
|
||||
.write_all(b"ignored-by-fdignore\nignored-by-both")
|
||||
.unwrap();
|
||||
|
||||
fs::File::create(te.test_root().join(".gitignore"))
|
||||
.unwrap()
|
||||
.write_all(b"ignored-by-gitignore\nignored-by-both")
|
||||
.unwrap();
|
||||
|
||||
te.assert_output(&["ignored"], "ignored-by-nothing");
|
||||
|
||||
te.assert_output(
|
||||
&["--no-ignore-vcs", "ignored"],
|
||||
"ignored-by-nothing
|
||||
ignored-by-gitignore",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--no-ignore", "ignored"],
|
||||
"ignored-by-nothing
|
||||
ignored-by-fdignore
|
||||
ignored-by-gitignore
|
||||
ignored-by-both",
|
||||
);
|
||||
}
|
||||
|
||||
/// Precedence of .fdignore files
|
||||
#[test]
|
||||
fn test_custom_ignore_precedence() {
|
||||
let dirs = &["inner"];
|
||||
let files = &["inner/foo"];
|
||||
let te = TestEnv::new(dirs, files);
|
||||
|
||||
// Ignore 'foo' via .gitignore
|
||||
fs::File::create(te.test_root().join("inner/.gitignore"))
|
||||
.unwrap()
|
||||
.write_all(b"foo")
|
||||
.unwrap();
|
||||
|
||||
// Whitelist 'foo' via .fdignore
|
||||
fs::File::create(te.test_root().join(".fdignore"))
|
||||
.unwrap()
|
||||
.write_all(b"!foo")
|
||||
.unwrap();
|
||||
|
||||
te.assert_output(&["foo"], "inner/foo");
|
||||
|
||||
te.assert_output(&["--no-ignore-vcs", "foo"], "inner/foo");
|
||||
|
||||
te.assert_output(&["--no-ignore", "foo"], "inner/foo");
|
||||
}
|
||||
|
||||
/// VCS ignored files (--no-ignore-vcs)
|
||||
#[test]
|
||||
fn test_no_ignore_vcs() {
|
||||
@@ -305,6 +491,25 @@ fn test_no_ignore_vcs() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Custom ignore files (--ignore-file)
|
||||
#[test]
|
||||
fn test_custom_ignore_files() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
// Ignore 'C.Foo2' and everything in 'three'.
|
||||
fs::File::create(te.test_root().join("custom.ignore"))
|
||||
.unwrap()
|
||||
.write_all(b"C.Foo2\nthree")
|
||||
.unwrap();
|
||||
|
||||
te.assert_output(
|
||||
&["--ignore-file", "custom.ignore", "foo"],
|
||||
"a.foo
|
||||
one/b.foo
|
||||
one/two/c.foo",
|
||||
);
|
||||
}
|
||||
|
||||
/// Ignored files with ripgrep aliases (-u / -uu)
|
||||
#[test]
|
||||
fn test_no_ignore_aliases() {
|
||||
@@ -313,7 +518,7 @@ fn test_no_ignore_aliases() {
|
||||
te.assert_output(
|
||||
&["-u", "foo"],
|
||||
"a.foo
|
||||
ignored.foo
|
||||
fdignored.foo
|
||||
gitignored.foo
|
||||
one/b.foo
|
||||
one/two/c.foo
|
||||
@@ -326,7 +531,7 @@ fn test_no_ignore_aliases() {
|
||||
&["-uu", "foo"],
|
||||
".hidden.foo
|
||||
a.foo
|
||||
ignored.foo
|
||||
fdignored.foo
|
||||
gitignored.foo
|
||||
one/b.foo
|
||||
one/two/c.foo
|
||||
@@ -406,9 +611,7 @@ fn test_max_depth() {
|
||||
/// Absolute paths (--absolute-path)
|
||||
#[test]
|
||||
fn test_absolute_path() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
let abs_path = get_absolute_root_path(&te);
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--absolute-path"],
|
||||
@@ -440,6 +643,12 @@ fn test_absolute_path() {
|
||||
abs_path = &abs_path
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Show absolute paths if the path argument is absolute
|
||||
#[test]
|
||||
fn test_implicit_absolute_path() {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", &abs_path],
|
||||
@@ -455,6 +664,26 @@ fn test_absolute_path() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Absolute paths should be normalized
|
||||
#[test]
|
||||
fn test_normalized_absolute_path() {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output_subdirectory(
|
||||
"one",
|
||||
&["--absolute-path", "foo", ".."],
|
||||
&format!(
|
||||
"{abs_path}/a.foo
|
||||
{abs_path}/one/b.foo
|
||||
{abs_path}/one/two/c.foo
|
||||
{abs_path}/one/two/C.Foo2
|
||||
{abs_path}/one/two/three/d.foo
|
||||
{abs_path}/one/two/three/directory_foo",
|
||||
abs_path = &abs_path
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// File type filter (--type)
|
||||
#[test]
|
||||
fn test_type() {
|
||||
@@ -492,6 +721,60 @@ fn test_type() {
|
||||
te.assert_output(&["--type", "l"], "symlink");
|
||||
}
|
||||
|
||||
/// Test `--type executable`
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_type_executable() {
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.mode(0o777)
|
||||
.open(te.test_root().join("executable-file.sh"))
|
||||
.unwrap();
|
||||
|
||||
te.assert_output(&["--type", "executable"], "executable-file.sh");
|
||||
|
||||
te.assert_output(
|
||||
&["--type", "executable", "--type", "directory"],
|
||||
"executable-file.sh
|
||||
one
|
||||
one/two
|
||||
one/two/three
|
||||
one/two/three/directory_foo",
|
||||
);
|
||||
}
|
||||
|
||||
/// Test `--type empty`
|
||||
#[test]
|
||||
fn test_type_empty() {
|
||||
let te = TestEnv::new(&["dir_empty", "dir_nonempty"], &[]);
|
||||
|
||||
create_file_with_size(te.test_root().join("0_bytes.foo"), 0);
|
||||
create_file_with_size(te.test_root().join("5_bytes.foo"), 5);
|
||||
|
||||
create_file_with_size(te.test_root().join("dir_nonempty").join("2_bytes.foo"), 2);
|
||||
|
||||
te.assert_output(
|
||||
&["--type", "empty"],
|
||||
"0_bytes.foo
|
||||
dir_empty",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--type", "empty", "--type", "file", "--type", "directory"],
|
||||
"0_bytes.foo
|
||||
dir_empty",
|
||||
);
|
||||
|
||||
te.assert_output(&["--type", "empty", "--type", "file"], "0_bytes.foo");
|
||||
|
||||
te.assert_output(&["--type", "empty", "--type", "directory"], "dir_empty");
|
||||
}
|
||||
|
||||
/// File extension (--extension)
|
||||
#[test]
|
||||
fn test_extension() {
|
||||
@@ -525,15 +808,35 @@ fn test_extension() {
|
||||
te.assert_output(&["--extension", ".foo", "a"], "a.foo");
|
||||
|
||||
te.assert_output(&["--extension", "foo2"], "one/two/C.Foo2");
|
||||
|
||||
let te2 = TestEnv::new(&[], &["spam.bar.baz", "egg.bar.baz", "yolk.bar.baz.sig"]);
|
||||
|
||||
te2.assert_output(
|
||||
&["--extension", ".bar.baz"],
|
||||
"spam.bar.baz
|
||||
egg.bar.baz",
|
||||
);
|
||||
|
||||
te2.assert_output(&["--extension", "sig"], "yolk.bar.baz.sig");
|
||||
|
||||
te2.assert_output(&["--extension", "bar.baz.sig"], "yolk.bar.baz.sig");
|
||||
|
||||
let te3 = TestEnv::new(&[], &["latin1.e\u{301}xt", "smiley.☻"]);
|
||||
|
||||
te3.assert_output(&["--extension", "☻"], "smiley.☻");
|
||||
|
||||
te3.assert_output(&["--extension", ".e\u{301}xt"], "latin1.e\u{301}xt");
|
||||
|
||||
let te4 = TestEnv::new(&[], &[".hidden", "test.hidden"]);
|
||||
|
||||
te4.assert_output(&["--hidden", "--extension", ".hidden"], "test.hidden");
|
||||
}
|
||||
|
||||
/// Symlinks misc
|
||||
/// Symlink as search directory
|
||||
#[test]
|
||||
fn test_symlink() {
|
||||
fn test_symlink_as_root() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
let abs_path = get_absolute_root_path(&te);
|
||||
|
||||
// From: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html
|
||||
// The getcwd() function shall place an absolute pathname of the current working directory in
|
||||
// the array pointed to by buf, and return buf. The pathname shall contain no components that
|
||||
@@ -566,20 +869,29 @@ fn test_symlink() {
|
||||
dir = &parent_parent
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symlink_and_absolute_path() {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output_subdirectory(
|
||||
"symlink",
|
||||
&["--absolute-path"],
|
||||
&format!(
|
||||
"{abs_path}/{dir}/c.foo
|
||||
{abs_path}/{dir}/C.Foo2
|
||||
{abs_path}/{dir}/three
|
||||
{abs_path}/{dir}/three/d.foo
|
||||
{abs_path}/{dir}/three/directory_foo",
|
||||
dir = if cfg!(windows) { "symlink" } else { "one/two" },
|
||||
"{abs_path}/one/two/c.foo
|
||||
{abs_path}/one/two/C.Foo2
|
||||
{abs_path}/one/two/three
|
||||
{abs_path}/one/two/three/d.foo
|
||||
{abs_path}/one/two/three/directory_foo",
|
||||
abs_path = &abs_path
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symlink_as_absolute_root() {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["", &format!("{abs_path}/symlink", abs_path = abs_path)],
|
||||
@@ -592,7 +904,11 @@ fn test_symlink() {
|
||||
abs_path = &abs_path
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symlink_and_full_path() {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
let root = te.system_root();
|
||||
let prefix = escape(&root.to_string_lossy());
|
||||
|
||||
@@ -604,14 +920,19 @@ fn test_symlink() {
|
||||
&format!("^{prefix}.*three", prefix = prefix),
|
||||
],
|
||||
&format!(
|
||||
"{abs_path}/{dir}/three
|
||||
{abs_path}/{dir}/three/d.foo
|
||||
{abs_path}/{dir}/three/directory_foo",
|
||||
dir = if cfg!(windows) { "symlink" } else { "one/two" },
|
||||
"{abs_path}/one/two/three
|
||||
{abs_path}/one/two/three/d.foo
|
||||
{abs_path}/one/two/three/directory_foo",
|
||||
abs_path = &abs_path
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symlink_and_full_path_abs_path() {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
let root = te.system_root();
|
||||
let prefix = escape(&root.to_string_lossy());
|
||||
te.assert_output(
|
||||
&[
|
||||
"--full-path",
|
||||
@@ -680,14 +1001,28 @@ fn test_excludes() {
|
||||
/// Shell script execution (--exec)
|
||||
#[test]
|
||||
fn test_exec() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
assert_exec_output("--exec");
|
||||
}
|
||||
|
||||
let abs_path = get_absolute_root_path(&te);
|
||||
/// Shell script execution using -exec
|
||||
#[test]
|
||||
fn test_exec_substitution() {
|
||||
assert_exec_output("-exec");
|
||||
}
|
||||
|
||||
// Shell script execution using -x
|
||||
#[test]
|
||||
fn test_exec_short_arg() {
|
||||
assert_exec_output("-x");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn assert_exec_output(exec_style: &str) {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
// TODO Windows tests: D:file.txt \file.txt \\server\share\file.txt ...
|
||||
if !cfg!(windows) {
|
||||
te.assert_output(
|
||||
&["--absolute-path", "foo", "--exec", "echo"],
|
||||
&["--absolute-path", "foo", exec_style, "echo"],
|
||||
&format!(
|
||||
"{abs_path}/a.foo
|
||||
{abs_path}/one/b.foo
|
||||
@@ -700,7 +1035,7 @@ fn test_exec() {
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", "--exec", "echo", "{}"],
|
||||
&["foo", exec_style, "echo", "{}"],
|
||||
"a.foo
|
||||
one/b.foo
|
||||
one/two/C.Foo2
|
||||
@@ -710,7 +1045,7 @@ fn test_exec() {
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", "--exec", "echo", "{.}"],
|
||||
&["foo", exec_style, "echo", "{.}"],
|
||||
"a
|
||||
one/b
|
||||
one/two/C
|
||||
@@ -720,7 +1055,7 @@ fn test_exec() {
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", "--exec", "echo", "{/}"],
|
||||
&["foo", exec_style, "echo", "{/}"],
|
||||
"a.foo
|
||||
b.foo
|
||||
C.Foo2
|
||||
@@ -730,7 +1065,7 @@ fn test_exec() {
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", "--exec", "echo", "{/.}"],
|
||||
&["foo", exec_style, "echo", "{/.}"],
|
||||
"a
|
||||
b
|
||||
C
|
||||
@@ -740,7 +1075,7 @@ fn test_exec() {
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", "--exec", "echo", "{//}"],
|
||||
&["foo", exec_style, "echo", "{//}"],
|
||||
".
|
||||
one
|
||||
one/two
|
||||
@@ -749,6 +1084,269 @@ fn test_exec() {
|
||||
one/two/three",
|
||||
);
|
||||
|
||||
te.assert_output(&["e1", "--exec", "printf", "%s.%s\n"], "e1 e2.");
|
||||
te.assert_output(&["e1", exec_style, "printf", "%s.%s\n"], "e1 e2.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec_batch() {
|
||||
assert_exec_batch_output("--exec-batch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec_batch_short_arg() {
|
||||
assert_exec_batch_output("-X");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn assert_exec_batch_output(exec_style: &str) {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
let te = te.normalize_line(true);
|
||||
|
||||
// TODO Test for windows
|
||||
if !cfg!(windows) {
|
||||
te.assert_output(
|
||||
&["--absolute-path", "foo", exec_style, "echo"],
|
||||
&format!(
|
||||
"{abs_path}/a.foo {abs_path}/one/b.foo {abs_path}/one/two/C.Foo2 {abs_path}/one/two/c.foo {abs_path}/one/two/three/d.foo {abs_path}/one/two/three/directory_foo",
|
||||
abs_path = &abs_path
|
||||
),
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", exec_style, "echo", "{}"],
|
||||
"a.foo one/b.foo one/two/C.Foo2 one/two/c.foo one/two/three/d.foo one/two/three/directory_foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", exec_style, "echo", "{/}"],
|
||||
"a.foo b.foo C.Foo2 c.foo d.foo directory_foo",
|
||||
);
|
||||
|
||||
te.assert_output(&["no_match", exec_style, "echo", "Matched: ", "{/}"], "");
|
||||
|
||||
te.assert_error(
|
||||
&["foo", exec_style, "echo", "{}", "{}"],
|
||||
"[fd error]: Only one placeholder allowed for batch commands",
|
||||
);
|
||||
|
||||
te.assert_error(
|
||||
&["foo", exec_style, "echo", "{/}", ";", "-x", "echo"],
|
||||
"error: The argument '--exec <cmd>' cannot be used with '--exec-batch <cmd>'",
|
||||
);
|
||||
|
||||
te.assert_error(
|
||||
&["foo", exec_style],
|
||||
"error: The argument '--exec-batch <cmd>' requires a value but none was supplied",
|
||||
);
|
||||
|
||||
te.assert_error(
|
||||
&["foo", exec_style, "echo {}"],
|
||||
"[fd error]: First argument of exec-batch is expected to be a fixed executable",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Literal search (--fixed-strings)
|
||||
#[test]
|
||||
fn test_fixed_strings() {
|
||||
let dirs = &["test1", "test2"];
|
||||
let files = &["test1/a.foo", "test1/a_foo", "test2/Download (1).tar.gz"];
|
||||
let te = TestEnv::new(dirs, files);
|
||||
|
||||
// Regex search, dot is treated as "any character"
|
||||
te.assert_output(
|
||||
&["a.foo"],
|
||||
"test1/a.foo
|
||||
test1/a_foo",
|
||||
);
|
||||
|
||||
// Literal search, dot is treated as character
|
||||
te.assert_output(&["--fixed-strings", "a.foo"], "test1/a.foo");
|
||||
|
||||
// Regex search, parens are treated as group
|
||||
te.assert_output(&["download (1)"], "");
|
||||
|
||||
// Literal search, parens are treated as characters
|
||||
te.assert_output(
|
||||
&["--fixed-strings", "download (1)"],
|
||||
"test2/Download (1).tar.gz",
|
||||
);
|
||||
|
||||
// Combine with --case-sensitive
|
||||
te.assert_output(&["--fixed-strings", "--case-sensitive", "download (1)"], "");
|
||||
}
|
||||
|
||||
/// Filenames with invalid UTF-8 sequences
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn test_invalid_utf8() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let dirs = &["test1"];
|
||||
let files = &[];
|
||||
let te = TestEnv::new(dirs, files);
|
||||
|
||||
fs::File::create(
|
||||
te.test_root()
|
||||
.join(OsStr::from_bytes(b"test1/test_\xFEinvalid.txt")),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
te.assert_output(&["", "test1/"], "test1/test_�invalid.txt");
|
||||
|
||||
te.assert_output(&["invalid", "test1/"], "test1/test_�invalid.txt");
|
||||
|
||||
// Should not be found under a different extension
|
||||
te.assert_output(&["-e", "zip", "", "test1/"], "");
|
||||
}
|
||||
|
||||
/// Filtering for file size (--size)
|
||||
#[test]
|
||||
fn test_size() {
|
||||
let te = TestEnv::new(&[], &[]);
|
||||
|
||||
create_file_with_size(te.test_root().join("0_bytes.foo"), 0);
|
||||
create_file_with_size(te.test_root().join("11_bytes.foo"), 11);
|
||||
create_file_with_size(te.test_root().join("30_bytes.foo"), 30);
|
||||
create_file_with_size(te.test_root().join("3_kilobytes.foo"), 3 * 1000);
|
||||
create_file_with_size(te.test_root().join("4_kibibytes.foo"), 4 * 1024);
|
||||
|
||||
// Zero and non-zero sized files.
|
||||
te.assert_output(
|
||||
&["", "--size", "+0B"],
|
||||
"0_bytes.foo
|
||||
11_bytes.foo
|
||||
30_bytes.foo
|
||||
3_kilobytes.foo
|
||||
4_kibibytes.foo",
|
||||
);
|
||||
|
||||
// Zero sized files.
|
||||
te.assert_output(&["", "--size", "-0B"], "0_bytes.foo");
|
||||
|
||||
// Files with 2 bytes or more.
|
||||
te.assert_output(
|
||||
&["", "--size", "+2B"],
|
||||
"11_bytes.foo
|
||||
30_bytes.foo
|
||||
3_kilobytes.foo
|
||||
4_kibibytes.foo",
|
||||
);
|
||||
|
||||
// Files with 2 bytes or less.
|
||||
te.assert_output(&["", "--size", "-2B"], "0_bytes.foo");
|
||||
|
||||
// Files with size between 1 byte and 11 bytes.
|
||||
te.assert_output(&["", "--size", "+1B", "--size", "-11B"], "11_bytes.foo");
|
||||
|
||||
// Files with size between 1 byte and 30 bytes.
|
||||
te.assert_output(
|
||||
&["", "--size", "+1B", "--size", "-30B"],
|
||||
"11_bytes.foo
|
||||
30_bytes.foo",
|
||||
);
|
||||
|
||||
// Combine with a search pattern
|
||||
te.assert_output(&["^11_", "--size", "+1B", "--size", "-30B"], "11_bytes.foo");
|
||||
|
||||
// Files with size between 12 and 30 bytes.
|
||||
te.assert_output(&["", "--size", "+12B", "--size", "-30B"], "30_bytes.foo");
|
||||
|
||||
// Files with size between 31 and 100 bytes.
|
||||
te.assert_output(&["", "--size", "+31B", "--size", "-100B"], "");
|
||||
|
||||
// Files with size between 3 kibibytes and 5 kibibytes.
|
||||
te.assert_output(&["", "--size", "+3ki", "--size", "-5ki"], "4_kibibytes.foo");
|
||||
|
||||
// Files with size between 3 kilobytes and 5 kilobytes.
|
||||
te.assert_output(
|
||||
&["", "--size", "+3k", "--size", "-5k"],
|
||||
"3_kilobytes.foo
|
||||
4_kibibytes.foo",
|
||||
);
|
||||
|
||||
// Files with size greater than 3 kilobytes and less than 3 kibibytes.
|
||||
te.assert_output(&["", "--size", "+3k", "--size", "-3ki"], "3_kilobytes.foo");
|
||||
|
||||
// Files with size equal 4 kibibytes.
|
||||
te.assert_output(&["", "--size", "+4ki", "--size", "-4ki"], "4_kibibytes.foo");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn create_file_with_modified<P: AsRef<Path>>(path: P, duration_in_secs: u64) {
|
||||
let st = SystemTime::now() - Duration::from_secs(duration_in_secs);
|
||||
let ft = filetime::FileTime::from_system_time(st);
|
||||
fs::File::create(&path).expect("creation failed");
|
||||
filetime::set_file_times(&path, ft, ft).expect("time modification failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modified_relative() {
|
||||
let te = TestEnv::new(&[], &[]);
|
||||
create_file_with_modified(te.test_root().join("foo_0_now"), 0);
|
||||
create_file_with_modified(te.test_root().join("bar_1_min"), 60);
|
||||
create_file_with_modified(te.test_root().join("foo_10_min"), 600);
|
||||
create_file_with_modified(te.test_root().join("bar_1_h"), 60 * 60);
|
||||
create_file_with_modified(te.test_root().join("foo_2_h"), 2 * 60 * 60);
|
||||
create_file_with_modified(te.test_root().join("bar_1_day"), 24 * 60 * 60);
|
||||
|
||||
te.assert_output(
|
||||
&["", "--changed-within", "15min"],
|
||||
"foo_0_now
|
||||
bar_1_min
|
||||
foo_10_min",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["", "--change-older-than", "15min"],
|
||||
"bar_1_h
|
||||
foo_2_h
|
||||
bar_1_day",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", "--changed-within", "12h"],
|
||||
"foo_0_now
|
||||
foo_10_min
|
||||
foo_2_h",
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn change_file_modified<P: AsRef<Path>>(path: P, iso_date: &str) {
|
||||
let st = humantime::parse_rfc3339(iso_date).expect("invalid date");
|
||||
let ft = filetime::FileTime::from_system_time(st);
|
||||
filetime::set_file_times(path, ft, ft).expect("time modification failde");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modified_asolute() {
|
||||
let te = TestEnv::new(&[], &["15mar2018", "30dec2017"]);
|
||||
change_file_modified(te.test_root().join("15mar2018"), "2018-03-15T12:00:00Z");
|
||||
change_file_modified(te.test_root().join("30dec2017"), "2017-12-30T23:59:00Z");
|
||||
|
||||
te.assert_output(
|
||||
&["", "--change-newer-than", "2018-01-01 00:00:00"],
|
||||
"15mar2018",
|
||||
);
|
||||
te.assert_output(
|
||||
&["", "--changed-before", "2018-01-01 00:00:00"],
|
||||
"30dec2017",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_path_separator() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", "one", "--path-separator", "="],
|
||||
"one=b.foo
|
||||
one=two=c.foo
|
||||
one=two=C.Foo2
|
||||
one=two=three=d.foo
|
||||
one=two=three=directory_foo",
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user