Compare commits

...

67 Commits

Author SHA1 Message Date
sharkdp f064ee5509 Properly use .ignore files, see #156 2018-10-27 18:11:50 +02:00
sharkdp 26c2371835 Bump version 2018-10-27 17:09:00 +02:00
sharkdp 8832e471bc Update man page 2018-10-27 17:07:09 +02:00
sharkdp daf5191aac Add note about .ignore files 2018-10-27 16:44:34 +02:00
sharkdp c0c38872c4 Update usage 2018-10-27 16:37:41 +02:00
sharkdp 81a27fa9bd Unify error messages, closes #342 2018-10-27 16:30:29 +02:00
sharkdp 6ff58abb6c Re-enable .ignore files, closes #156 2018-10-27 15:42:02 +02:00
sharkdp 974802e817 Hide a few options from short '-h' text 2018-10-27 15:38:26 +02:00
sharkdp deea31cbbc Add aliases, change help text 2018-10-27 15:34:10 +02:00
Josh Leeb-du Toit caad43fe6c Use oss_vec fn in internal/mod.rs tests 2018-10-24 16:31:16 +02:00
Park Juhyung 095bad550f Print errors when --verbose is set 2018-10-23 21:50:25 +02:00
Park Juhyung f7a96bd980 Fix typo at max-buffer-time's help message
"tothe" -> "to the"
2018-10-23 07:03:21 +02:00
Casey Primozic cdf737bf5c Add search-path option
* Multiple `--search-path` arguments can be supplied which replace the positional `path` argument for providing directories which will be searched.
 * When a `--search-path` argument is provided, positional `path` arguments will not be allowed
2018-10-22 19:26:36 +02:00
yobrave e2bb932f87 Doc: add chinese readme link (#347) 2018-10-19 22:05:15 +02:00
David Peter 437bd1188e Remove shell prompt 2018-10-14 19:27:03 +02:00
David Peter dae09ecf6d Add recommendation for "alias fd=fdfind" 2018-10-14 19:26:22 +02:00
Sylvestre Ledru d81cf34771 fd is now officially in Debian (#345)
* fd is now officially in Debian
2018-10-14 19:21:16 +02:00
Benjamin Fox b98f0783ff Enable link-time optimization in release builds
This gives a significant improvement to runtime performance, at the cost of somewhat worse compile times.
2018-10-14 18:47:25 +02:00
Josh Leeb-du Toit 8543ca645d Split internals.rs into module and multiple files
This PR splits `internals.rs` into the `internal` module with multiple
files, and moves `FdOptions` into `opts.rs`.

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

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

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

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

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

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

    fd --type empty
    fd -te

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

To search for empty files, use

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

To search for empty directories, use

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

closes #273
2018-08-19 17:05:04 +02:00
sharkdp 50a2bab5cd Use short-circuiting for --type searches
Reverses the order of boolean checks for `--type` searches,
making them about 10% to 50% faster(!).
2018-08-19 16:27:23 +02:00
sharkdp 641976cf7a Remove duplicated lstat syscall
Removes a unnecessary `lstat` syscall by calling `.metadata()` only
once. This makes `--type executable` searches about 15% faster.
2018-08-19 16:23:06 +02:00
sharkdp 6fb5004b85 Enable ARM cross-compilation
closes #244
2018-08-19 15:30:59 +02:00
kpcyrd e4f61bc795 Remove unused windows dependencies 2018-08-18 16:40:08 +02:00
sharkdp c428f52209 Run cargo fmt 2018-08-03 21:20:48 +02:00
sharkdp 59c9901cde Update dependencies, fix #313 2018-08-03 21:20:48 +02:00
Alexandru Macovei 4dee8b7940 fix build for rust 1.20.0 2018-07-02 19:55:03 +02:00
Alexandru Macovei 9a45947234 simplify SizeFilter as enum with value instead of struct
Also move the touched cfg(test) code into cfg(test) module and trim test names
2018-07-02 19:55:03 +02:00
sharkdp c1ef68662c Update for new rustfmt 2018-05-14 21:00:00 +02:00
Josh Sleeper 9628d7fdcb chore(crate-updates): updated all crates to latest versions 2018-05-05 08:56:04 +02:00
David Peter cb47be6497 Fedora is now in the official repositories 2018-05-02 20:54:08 +02:00
sharkdp a3200204dc Simplifications to --size tests 2018-04-29 22:26:10 +02:00
Jonathan e2b2701965 Add helper method to create sized files, use meaningful names and add kibi and kilo tests. 2018-04-29 21:56:31 +02:00
Jonathan 409cf0c7a1 Remove use (which broke the build) 2018-04-29 21:56:31 +02:00
Jonathan f01685c63f Add tests for SizeFilter (--size) fixes sharkdp/fd#289 2018-04-29 21:56:31 +02:00
David Peter 060d14b5dc Update README.md 2018-04-26 19:53:04 +02:00
Rainer Müller fe922688b3 README: Add install instructions for MacPorts 2018-04-26 19:51:04 +02:00
sharkdp 4172ed03f0 Do not include non-files when using --size 2018-04-25 23:08:05 +02:00
Steve Pentland 0bbc7f5219 Update regex and match, update comments 2018-04-25 08:14:12 +02:00
Steve Pentland ace3f512d3 Make compatible with min rust version 2018-04-25 08:14:12 +02:00
Steve Pentland 2f3b472dfd Changes from review 2018-04-25 08:14:12 +02:00
Steve Pentland 0207c1371e Initial impl of size constraints.
This adds find-style size constraints with + or - indicating greater
or less than, a numerical size, and a unit
2018-04-25 08:14:12 +02:00
David Peter fea85dbfb3 Re-enable nightly 2018-04-21 12:54:00 +02:00
sharkdp b5e5b132c8 Temporarily disable nightly, rustc-ap-rustc_errors is broken 2018-04-20 08:28:18 +02:00
sharkdp 6a9f16e159 Run latest version of rustfmt 2018-04-13 23:13:22 +02:00
sharkdp f02b3cba6a Fix typos 2018-04-13 23:13:22 +02:00
Chen Bin 666f90db87 Re-org fzf part. Doc on using fd with Emacs 2018-04-13 23:13:22 +02:00
Avindra Goolcharan 2a1ee0b17d README: openSUSE package available
add software.opensuse.org link and `zypper in fd`
2018-04-01 17:25:23 +02:00
29 changed files with 1613 additions and 640 deletions
+16 -16
View File
@@ -20,6 +20,11 @@ matrix:
- os: osx
rust: stable
env: TARGET=x86_64-apple-darwin
- os: linux
rust: stable
env:
- TARGET=arm-unknown-linux-gnueabihf
- CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
# Beta channel.
- os: linux
@@ -59,19 +64,19 @@ matrix:
# Minimum Rust supported channel.
- os: linux
rust: 1.20.0
rust: 1.29.0
env: TARGET=x86_64-unknown-linux-gnu
- os: linux
rust: 1.20.0
rust: 1.29.0
env: TARGET=x86_64-unknown-linux-musl
- os: linux
rust: 1.20.0
rust: 1.29.0
env: TARGET=i686-unknown-linux-gnu
- os: linux
rust: 1.20.0
rust: 1.29.0
env: TARGET=i686-unknown-linux-musl
- os: osx
rust: 1.20.0
rust: 1.29.0
env: TARGET=x86_64-apple-darwin
# Code formatting check
@@ -80,15 +85,12 @@ matrix:
# skip the global install step
install:
- cargo install --debug --force rustfmt-nightly
script: cargo fmt -- --write-mode=diff
script: cargo fmt -- --check
addons:
apt:
packages:
# needed for i686-unknown-linux-gnu target
- gcc-multilib
# needed to build deb packages
- fakeroot
sudo: required
before_install:
- ci/before_install.bash
env:
global:
@@ -103,9 +105,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
Generated
+139 -121
View File
@@ -1,6 +1,6 @@
[[package]]
name = "aho-corasick"
version = "0.6.4"
version = "0.6.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)",
@@ -11,47 +11,47 @@ name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.8"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "0.9.1"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.0.1"
name = "cc"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.2"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.31.1"
version = "2.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.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.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -61,12 +61,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ctrlc"
version = "3.1.0"
version = "3.1.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)",
"nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -76,23 +75,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fd-find"
version = "7.0.0"
version = "7.2.0"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.31.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.4.1 (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.39 (registry+https://github.com/rust-lang/crates.io-index)",
"filetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.6 (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)",
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "filetime"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -105,7 +114,7 @@ name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -116,31 +125,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "globset"
version = "0.3.0"
version = "0.4.1"
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.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "humantime"
version = "1.1.1"
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.4.1"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -154,20 +171,23 @@ dependencies = [
[[package]]
name = "lazy_static"
version = "1.0.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libc"
version = "0.2.39"
version = "0.2.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.4.1"
version = "0.4.4"
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)",
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -175,17 +195,18 @@ name = "memchr"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nix"
version = "0.9.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.9.1 (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.39 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -194,22 +215,27 @@ name = "num_cpus"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (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.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.37"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -217,24 +243,24 @@ name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "0.2.8"
version = "1.0.2"
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.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.5.2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -242,11 +268,10 @@ dependencies = [
[[package]]
name = "remove_dir_all"
version = "0.3.0"
version = "0.5.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)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -254,7 +279,7 @@ name = "same-file"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -264,11 +289,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "tempdir"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -277,7 +302,7 @@ 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.39 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -286,27 +311,26 @@ name = "termion"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.9.0"
version = "0.10.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.5 (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.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -316,17 +340,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.4"
version = "0.1.5"
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"
@@ -334,12 +350,12 @@ 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.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -349,11 +365,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "walkdir"
version = "2.1.4"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -363,7 +379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -386,51 +402,53 @@ 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 aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c1c6d463cbe7ed28720b5b489e7c083eeb8f90d08be2a0d6bb9e1ffea9ce1afa"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "af80143d6f7608d746df1520709e5d141c96f240b0e62b0aa41bdfb53374d9d4"
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
"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.31.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dc18f6f4005132120d9711636b32c46a233fad94df6217fa1d81c5e97a9f200"
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
"checksum cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "2119ea4867bd2b8ed3aecab467709720b2d55b1bcfe09f772fd68066eaf15275"
"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3"
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
"checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19"
"checksum ctrlc 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "653abc99aa905f693d89df4797fadc08085baee379db92be9f2496cefe8a6f2c"
"checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e"
"checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
"checksum filetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "da4b9849e77b13195302c174324b5ba73eec9b236b24c221a61000daefb95c5f"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum globset 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e96ab92362c06811385ae9a34d2698e8a1160745e0c78fbb434a44c8de3fabc"
"checksum ignore 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "245bea0ba52531a3739cb8ba99f8689eda13d7faf8c36b6a73ce4421aab42588"
"checksum globset 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8e49edbcc9c7fc5beb8c0a54e7319ff8bed353a2b55e85811c6281188c2a6c84"
"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e"
"checksum ignore 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3e9faa7c84064f07b40da27044af629f578bc7994b650d3e458d0c29183c1d91"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
"checksum libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "f54263ad99207254cf58b5f701ecb432c717445ea2ee8af387334bdd1a03fdff"
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7"
"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d"
"checksum log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cba860f648db8e6f269df990180c2217f333472b4a6e901e97446858487971e2"
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
"checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32"
"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd"
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0a33e44e4f5dbeb5619910f68b874d85b01e15f86fa7bb0fbc41bbd148883a48"
"checksum regex-syntax 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d8337992c33b62cf49462a1ce4d0eedbb021f48de017e40ec307cca445df0ca9"
"checksum remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d2f806b0fcdabd98acd380dc8daef485e22bcb7cddc811d1337967f2528cf5"
"checksum regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5bbbea44c5490a1e84357ff28b7d518b4619a159fed5d25f6c1de2d19cc42814"
"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d"
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e"
"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 textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
"checksum unicode-width 0.1.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 unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
"checksum vec_map 0.8.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 vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63636bd0eb3d00ccb8b9036381b526efac53caf112b7783b730ab3f8e44da369"
"checksum walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1b768ba943161a9226ccd59b26bcd901e5d60e6061f4fcad3034784e0c7372b"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+14 -12
View File
@@ -16,7 +16,7 @@ license = "MIT/Apache-2.0"
name = "fd-find"
readme = "README.md"
repository = "https://github.com/sharkdp/fd"
version = "7.0.0"
version = "7.2.0"
[[bin]]
name = "fd"
@@ -28,30 +28,32 @@ repository = "sharkdp/fd"
repository = "sharkdp/fd"
[build-dependencies]
clap = "2.30.0"
clap = "2.31.2"
version_check = "0.1.3"
[dependencies]
ansi_term = "0.11"
atty = "0.2"
ignore = "0.4.1"
lazy_static = "1"
ignore = "0.4.3"
lazy_static = "1.1.0"
num_cpus = "1.8"
regex = "0.2.7"
regex-syntax = "0.5"
ctrlc = "3.0"
regex = "1.0.0"
regex-syntax = "0.6"
ctrlc = "3.1"
humantime = "1.1.1"
[dependencies.clap]
version = "2.30.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"
[dev-dependencies]
diff = "0.1"
tempdir = "0.3"
filetime = "0.2.1"
[profile.release]
lto = true
codegen-units = 1
+60 -15
View File
@@ -2,6 +2,7 @@
[![Build Status](https://travis-ci.org/sharkdp/fd.svg?branch=master)](https://travis-ci.org/sharkdp/fd)
[![Build status](https://ci.appveyor.com/api/projects/status/21c4p5fwggc5gy3j?svg=true)](https://ci.appveyor.com/project/sharkdp/fd)
[![Version info](https://img.shields.io/crates/v/fd-find.svg)](https://crates.io/crates/fd-find)
[中文](https://github.com/chinanf-boy/fd-zh)
*fd* is a simple, fast and user-friendly alternative to
[*find*](https://www.gnu.org/software/findutils/).
@@ -129,12 +130,29 @@ fd -x wc -l \; -e rs
Download the latest `.deb` package from the [release page](https://github.com/sharkdp/fd/releases) and install it via:
``` bash
sudo dpkg -i fd_7.0.0_amd64.deb # adapt version number and architecture
sudo dpkg -i fd_7.2.0_amd64.deb # adapt version number and architecture
```
### On Fedora
You can use this [fedora copr](https://copr.fedorainfracloud.org/coprs/keefle/fd/) to install `fd`:
### On Debian
If you run Debian sid (and soon Debian Buster), fd is
[officially maintained by Debian](https://tracker.debian.org/pkg/rust-fd-find).
```
sudo apt-get install fd-find
```
The binary is called `fdfind` as the binary name `fd` is already used by another package.
It is recommended that you add an `alias fd=fdfind` to your shells initialization file,
in order to use `fd` in the same way as in this documentation.
### 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
```
@@ -152,6 +170,13 @@ You can use [the fd ebuild](https://packages.gentoo.org/packages/sys-apps/fd) fr
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
You can install `fd` via xbps-install:
@@ -161,11 +186,16 @@ 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).
@@ -200,7 +230,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.29.0* or later is required.
### From binaries
@@ -241,15 +271,16 @@ FLAGS:
-V, --version Prints version information
OPTIONS:
-d, --max-depth <depth> Set maximum search depth (default: none)
-t, --type <filetype>... Filter by type: file (f), directory (d), symlink (l),
executable (x)
-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
--ignore-file <path>... Add a custom ignore-file in .gitignore format
-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
-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)
@@ -374,6 +405,7 @@ To make exclude-patterns like these permanent, you can create a `.fdignore` file
/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`
@@ -384,7 +416,9 @@ If we want to run a command on all search results, we can pipe the output to `xa
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`
### 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
@@ -406,3 +440,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.
+4 -5
View File
@@ -11,19 +11,18 @@ 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
match version_check::is_min_version("1.29") {
Some((true, _)) => {}
// rustc < 1.20 or can't figure it out
// 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.29").unwrap();
exit(1);
}
}
+10 -3
View File
@@ -11,18 +11,25 @@ pack() {
local tempdir
local out_dir
local package_name
local gcc_prefix
tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
out_dir=$(pwd)
package_name="$PROJECT_NAME-$TRAVIS_TAG-$TARGET"
if [[ $TARGET == "arm-unknown-linux-gnueabihf" ]]; then
gcc_prefix="arm-linux-gnueabihf-"
else
gcc_prefix=""
fi
# create a "staging" directory
mkdir "$tempdir/$package_name"
mkdir "$tempdir/$package_name/autocomplete"
# copying the main binary
cp "target/$TARGET/release/$PROJECT_NAME" "$tempdir/$package_name/"
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"
@@ -57,8 +64,8 @@ make_deb() {
architecture=i386
;;
*)
echo "ERROR: unknown target" >&2
return 1
echo "make_deb: skipping target '${TARGET}'" >&2
return 0
;;
esac
version=${TRAVIS_TAG#v}
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -ex
if [ "$TRAVIS_OS_NAME" != linux ]; then
exit 0
fi
sudo apt-get update
# needed to build deb packages
sudo apt-get install -y fakeroot
# needed for i686 linux gnu target
if [[ $TARGET == i686-unknown-linux-gnu ]]; then
sudo apt-get install -y gcc-multilib
fi
# needed for cross-compiling for arm
if [[ $TARGET == arm-unknown-linux-gnueabihf ]]; then
sudo apt-get install -y \
gcc-4.8-arm-linux-gnueabihf \
binutils-arm-linux-gnueabihf \
libc6-armhf-cross \
libc6-dev-armhf-cross
fi
Executable
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -ex
# Incorporate TARGET env var to the build and test process
cargo build --target "$TARGET" --verbose
# We cannot run arm executables on linux
if [[ $TARGET != arm-unknown-linux-gnueabihf ]]; then
cargo test --target "$TARGET" --verbose
fi
+83 -15
View File
@@ -27,42 +27,53 @@ 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
or
.I .fdignore
and include ignored files in the search results.
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 \-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.
@@ -86,6 +97,8 @@ directories
symbolic links
.IP "x, executable"
executable (files)
.IP "e, empty"
empty files or directories
.RE
.RS
@@ -122,6 +135,61 @@ Always colorize output.
.BI "\-j, \-\-threads " num
Number of threads to use for searching (default: number of available CPUs).
.TP
.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 "\fR [args...] ;"
Execute
.I command
+103 -10
View File
@@ -5,7 +5,6 @@
// 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};
@@ -16,10 +15,10 @@ struct Help {
}
macro_rules! doc {
($map: expr, $name: expr, $short: expr) => {
($map:expr, $name:expr, $short:expr) => {
doc!($map, $name, $short, $short)
};
($map: expr, $name: expr, $short: expr, $long: expr) => {
($map:expr, $name:expr, $short:expr, $long:expr) => {
$map.insert(
$name,
Help {
@@ -75,6 +74,13 @@ pub fn build_app() -> App<'static, 'static> {
.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")
@@ -92,6 +98,8 @@ pub fn build_app() -> App<'static, 'static> {
"symlink",
"x",
"executable",
"e",
"empty",
])
.hide_possible_values(true),
)
@@ -128,7 +136,8 @@ pub fn build_app() -> App<'static, 'static> {
.takes_value(true)
.value_name("path")
.number_of_values(1)
.multiple(true),
.multiple(true)
.hidden_short_help(true),
)
.arg(
arg("color")
@@ -144,7 +153,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")
@@ -152,8 +171,38 @@ 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").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)]
@@ -166,7 +215,7 @@ fn usage() -> HashMap<&'static str, Help> {
doc!(h, "no-ignore"
, "Do not respect .(git|fd)ignore files"
, "Show search results from files and directories that would otherwise be ignored by \
'.gitignore' or '.fdignore' 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 \
@@ -201,13 +250,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: file (f), directory (d), symlink (l),\nexecutable (x)"
, "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\n \
'x' or 'executable': executables");
'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 \
@@ -243,7 +296,7 @@ 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)");
@@ -254,6 +307,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
}
+6 -7
View File
@@ -6,10 +6,10 @@
// 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::{Arc, Mutex};
/// Executes a command.
pub fn execute_command(mut cmd: Command, out_perm: Arc<Mutex<()>>) {
@@ -29,12 +29,11 @@ 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);
}
Err(ref why) if why.kind() == io::ErrorKind::NotFound => {
print_error!("Command not found: {:?}", cmd);
}
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);
}
}
}
+1 -1
View File
@@ -72,7 +72,7 @@ pub fn dirname(path: &str) -> &str {
#[cfg(test)]
mod tests {
use super::{basename, dirname, remove_extension, MAIN_SEPARATOR};
use super::*;
fn correct(input: &str) -> String {
input.replace('/', &MAIN_SEPARATOR.to_string())
+18 -7
View File
@@ -6,24 +6,35 @@
// 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 std::path::PathBuf;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex};
use walk::WorkerResult;
/// 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,
};
+58 -15
View File
@@ -8,9 +8,9 @@
// 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;
@@ -19,10 +19,10 @@ use std::sync::{Arc, Mutex};
use regex::Regex;
use self::input::{basename, dirname, remove_extension};
use self::command::execute_command;
use self::token::Token;
use self::input::{basename, dirname, remove_extension};
pub use self::job::job;
use self::token::Token;
/// Represents a template that is utilized to generate command strings.
///
@@ -150,20 +150,24 @@ 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]),
],
}
);
}
#[test]
fn tokens_with_no_extension() {
assert_eq!(
CommandTemplate::new(&["echo", "{.}"]),
CommandTemplate {
@@ -174,4 +178,43 @@ mod tests {
}
);
}
#[test]
fn tokens_with_basename() {
assert_eq!(
CommandTemplate::new(&["echo", "{/}"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Basename]),
],
}
);
}
#[test]
fn tokens_with_parent() {
assert_eq!(
CommandTemplate::new(&["echo", "{//}"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Parent]),
],
}
);
}
#[test]
fn tokens_with_basename_no_extension() {
assert_eq!(
CommandTemplate::new(&["echo", "{/.}"]),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
],
}
);
}
}
+13 -4
View File
@@ -1,4 +1,13 @@
/// 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 {
GeneralError,
KilledBySigint,
}
impl Into<i32> for ExitCode {
fn into(self) -> i32 {
match self {
ExitCode::GeneralError => 1,
ExitCode::KilledBySigint => 130,
}
}
}
+30 -13
View File
@@ -7,19 +7,21 @@
// according to those terms.
use std::env::current_dir;
use std::path::{Path, PathBuf};
use std::io;
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> {
@@ -31,19 +33,16 @@ pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
.as_path()
.to_string_lossy()
.trim_left_matches(r"\\?\"),
).to_path_buf();
)
.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()
} else {
path.is_dir() && path.canonicalize().is_ok()
}
path.is_dir() && (path.file_name().is_some() || path.canonicalize().is_ok())
}
#[cfg(any(unix, target_os = "redox"))]
@@ -55,3 +54,21 @@ pub fn is_executable(md: &fs::Metadata) -> bool {
pub fn is_executable(_: &fs::Metadata) -> bool {
false
}
pub fn is_empty(entry: &DirEntry) -> bool {
if let Some(file_type) = entry.file_type() {
if file_type.is_dir() {
if let Ok(mut entries) = fs::read_dir(entry.path()) {
entries.next().is_none()
} else {
false
}
} else if file_type.is_file() {
entry.metadata().map(|m| m.len() == 0).unwrap_or(false)
} else {
false
}
} else {
false
}
}
-257
View File
@@ -1,257 +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::ffi::OsString;
use std::process;
use std::time;
use std::io::Write;
use std::path::PathBuf;
use exec::CommandTemplate;
use lscolors::LsColors;
use regex_syntax::Parser;
use regex_syntax::hir::Hir;
use regex::RegexSet;
/// Whether or not to show
pub struct FileTypes {
pub files: bool,
pub directories: bool,
pub symlinks: bool,
pub executables_only: bool,
}
impl Default for FileTypes {
fn default() -> FileTypes {
FileTypes {
files: false,
directories: false,
symlinks: false,
executables_only: false,
}
}
}
/// 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<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. 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<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>,
}
/// 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 {
Parser::new()
.parse(pattern)
.map(|hir| hir_has_uppercase_char(&hir))
.unwrap_or(false)
}
/// Determine if a regex expression contains a literal uppercase character.
fn hir_has_uppercase_char(hir: &Hir) -> bool {
use regex_syntax::hir::*;
match *hir.kind() {
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
HirKind::Class(Class::Unicode(ref ranges)) => ranges
.iter()
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
hir_has_uppercase_char(hir)
}
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
hirs.iter().any(hir_has_uppercase_char)
}
_ => false,
}
}
/// Maximum size of the output buffer before flushing results to the console
pub const MAX_BUFFER_LENGTH: usize = 1000;
/// 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;
/// 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)]
fn oss(v: &str) -> OsString {
OsString::from(v)
}
/// Ensure that -exec gets transformed into --exec
#[test]
fn normal_exec_substitution() {
let original = vec![oss("fd"), oss("foo"), oss("-exec"), oss("cmd")];
let expected = vec![oss("fd"), oss("foo"), oss("--exec"), oss("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 = vec![oss("fd"), oss("foo"), oss("--exec"), oss("cmd")];
let expected = vec![oss("fd"), oss("foo"), oss("--exec"), oss("cmd")];
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
#[test]
fn temp_check_that_exec_context_observed() {
let original = vec![
oss("fd"),
oss("foo"),
oss("-exec"),
oss("cmd"),
oss("-exec"),
oss("ls"),
oss(";"),
oss("-exec"),
oss("rm"),
oss(";"),
oss("--exec"),
oss("find"),
oss("-exec"),
oss("rm"),
oss(";"),
oss("-x"),
oss("foo"),
oss("-exec"),
oss("something"),
oss(";"),
oss("-exec"),
];
let expected = vec![
oss("fd"),
oss("foo"),
oss("--exec"),
oss("cmd"),
oss("-exec"),
oss("ls"),
oss(";"),
oss("--exec"),
oss("rm"),
oss(";"),
oss("--exec"),
oss("find"),
oss("-exec"),
oss("rm"),
oss(";"),
oss("-x"),
oss("foo"),
oss("-exec"),
oss("something"),
oss(";"),
oss("--exec"),
];
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
+20
View File
@@ -0,0 +1,20 @@
/// Whether or not to show
pub struct FileTypes {
pub files: bool,
pub directories: bool,
pub symlinks: bool,
pub executables_only: bool,
pub empty_only: bool,
}
impl Default for FileTypes {
fn default() -> FileTypes {
FileTypes {
files: false,
directories: false,
symlinks: false,
executables_only: false,
empty_only: false,
}
}
}
+5
View File
@@ -0,0 +1,5 @@
pub use self::size::SizeFilter;
pub use self::time::TimeFilter;
mod size;
mod time;
+208
View File
@@ -0,0 +1,208 @@
use regex::Regex;
lazy_static! {
static ref SIZE_CAPTURES: Regex = { Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap() };
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SizeFilter {
Max(u64),
Min(u64),
}
// SI prefixes (powers of 10)
const KILO: u64 = 1000;
const MEGA: u64 = KILO * 1000;
const GIGA: u64 = MEGA * 1000;
const TERA: u64 = GIGA * 1000;
// Binary prefixes (powers of 2)
const KIBI: u64 = 1024;
const MEBI: u64 = KIBI * 1024;
const GIBI: u64 = MEBI * 1024;
const TEBI: u64 = GIBI * 1024;
impl SizeFilter {
pub fn from_string<'a>(s: &str) -> Option<Self> {
if !SIZE_CAPTURES.is_match(s) {
return None;
}
let captures = SIZE_CAPTURES.captures(s)?;
let limit_kind = captures.get(1).map_or("+", |m| m.as_str());
let quantity = captures
.get(2)
.and_then(|v| v.as_str().parse::<u64>().ok())?;
let multiplier = match &captures.get(3).map_or("b", |m| m.as_str()).to_lowercase()[..] {
v if v.starts_with("ki") => KIBI,
v if v.starts_with("k") => KILO,
v if v.starts_with("mi") => MEBI,
v if v.starts_with("m") => MEGA,
v if v.starts_with("gi") => GIBI,
v if v.starts_with("g") => GIGA,
v if v.starts_with("ti") => TEBI,
v if v.starts_with("t") => TERA,
"b" => 1,
_ => return None,
};
let size = quantity * multiplier;
Some(match limit_kind {
"+" => SizeFilter::Min(size),
_ => SizeFilter::Max(size),
})
}
pub fn is_within(&self, size: u64) -> bool {
match self {
&SizeFilter::Max(limit) => size <= limit,
&SizeFilter::Min(limit) => size >= limit,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! gen_size_filter_parse_test {
($($name: ident: $val: expr,)*) => {
$(
#[test]
fn $name() {
let (txt, expected) = $val;
let actual = SizeFilter::from_string(txt).unwrap();
assert_eq!(actual, expected);
}
)*
};
}
/// Parsing and size conversion tests data. Ensure that each type gets properly interpreted.
/// Call with higher base values to ensure expected multiplication (only need a couple)
gen_size_filter_parse_test! {
byte_plus: ("+1b", SizeFilter::Min(1)),
byte_plus_multiplier: ("+10b", SizeFilter::Min(10)),
byte_minus: ("-1b", SizeFilter::Max(1)),
kilo_plus: ("+1k", SizeFilter::Min(1000)),
kilo_plus_suffix: ("+1kb", SizeFilter::Min(1000)),
kilo_minus: ("-1k", SizeFilter::Max(1000)),
kilo_minus_multiplier: ("-100k", SizeFilter::Max(100000)),
kilo_minus_suffix: ("-1kb", SizeFilter::Max(1000)),
kilo_plus_upper: ("+1K", SizeFilter::Min(1000)),
kilo_plus_suffix_upper: ("+1KB", SizeFilter::Min(1000)),
kilo_minus_upper: ("-1K", SizeFilter::Max(1000)),
kilo_minus_suffix_upper: ("-1Kb", SizeFilter::Max(1000)),
kibi_plus: ("+1ki", SizeFilter::Min(1024)),
kibi_plus_multiplier: ("+10ki", SizeFilter::Min(10240)),
kibi_plus_suffix: ("+1kib", SizeFilter::Min(1024)),
kibi_minus: ("-1ki", SizeFilter::Max(1024)),
kibi_minus_multiplier: ("-100ki", SizeFilter::Max(102400)),
kibi_minus_suffix: ("-1kib", SizeFilter::Max(1024)),
kibi_plus_upper: ("+1KI", SizeFilter::Min(1024)),
kibi_plus_suffix_upper: ("+1KiB", SizeFilter::Min(1024)),
kibi_minus_upper: ("-1Ki", SizeFilter::Max(1024)),
kibi_minus_suffix_upper: ("-1KIB", SizeFilter::Max(1024)),
mega_plus: ("+1m", SizeFilter::Min(1000000)),
mega_plus_suffix: ("+1mb", SizeFilter::Min(1000000)),
mega_minus: ("-1m", SizeFilter::Max(1000000)),
mega_minus_suffix: ("-1mb", SizeFilter::Max(1000000)),
mega_plus_upper: ("+1M", SizeFilter::Min(1000000)),
mega_plus_suffix_upper: ("+1MB", SizeFilter::Min(1000000)),
mega_minus_upper: ("-1M", SizeFilter::Max(1000000)),
mega_minus_suffix_upper: ("-1Mb", SizeFilter::Max(1000000)),
mebi_plus: ("+1mi", SizeFilter::Min(1048576)),
mebi_plus_suffix: ("+1mib", SizeFilter::Min(1048576)),
mebi_minus: ("-1mi", SizeFilter::Max(1048576)),
mebi_minus_suffix: ("-1mib", SizeFilter::Max(1048576)),
mebi_plus_upper: ("+1MI", SizeFilter::Min(1048576)),
mebi_plus_suffix_upper: ("+1MiB", SizeFilter::Min(1048576)),
mebi_minus_upper: ("-1Mi", SizeFilter::Max(1048576)),
mebi_minus_suffix_upper: ("-1MIB", SizeFilter::Max(1048576)),
giga_plus: ("+1g", SizeFilter::Min(1000000000)),
giga_plus_suffix: ("+1gb", SizeFilter::Min(1000000000)),
giga_minus: ("-1g", SizeFilter::Max(1000000000)),
giga_minus_suffix: ("-1gb", SizeFilter::Max(1000000000)),
giga_plus_upper: ("+1G", SizeFilter::Min(1000000000)),
giga_plus_suffix_upper: ("+1GB", SizeFilter::Min(1000000000)),
giga_minus_upper: ("-1G", SizeFilter::Max(1000000000)),
giga_minus_suffix_upper: ("-1Gb", SizeFilter::Max(1000000000)),
gibi_plus: ("+1gi", SizeFilter::Min(1073741824)),
gibi_plus_suffix: ("+1gib", SizeFilter::Min(1073741824)),
gibi_minus: ("-1gi", SizeFilter::Max(1073741824)),
gibi_minus_suffix: ("-1gib", SizeFilter::Max(1073741824)),
gibi_plus_upper: ("+1GI", SizeFilter::Min(1073741824)),
gibi_plus_suffix_upper: ("+1GiB", SizeFilter::Min(1073741824)),
gibi_minus_upper: ("-1Gi", SizeFilter::Max(1073741824)),
gibi_minus_suffix_upper: ("-1GIB", SizeFilter::Max(1073741824)),
tera_plus: ("+1t", SizeFilter::Min(1000000000000)),
tera_plus_suffix: ("+1tb", SizeFilter::Min(1000000000000)),
tera_minus: ("-1t", SizeFilter::Max(1000000000000)),
tera_minus_suffix: ("-1tb", SizeFilter::Max(1000000000000)),
tera_plus_upper: ("+1T", SizeFilter::Min(1000000000000)),
tera_plus_suffix_upper: ("+1TB", SizeFilter::Min(1000000000000)),
tera_minus_upper: ("-1T", SizeFilter::Max(1000000000000)),
tera_minus_suffix_upper: ("-1Tb", SizeFilter::Max(1000000000000)),
tebi_plus: ("+1ti", SizeFilter::Min(1099511627776)),
tebi_plus_suffix: ("+1tib", SizeFilter::Min(1099511627776)),
tebi_minus: ("-1ti", SizeFilter::Max(1099511627776)),
tebi_minus_suffix: ("-1tib", SizeFilter::Max(1099511627776)),
tebi_plus_upper: ("+1TI", SizeFilter::Min(1099511627776)),
tebi_plus_suffix_upper: ("+1TiB", SizeFilter::Min(1099511627776)),
tebi_minus_upper: ("-1Ti", SizeFilter::Max(1099511627776)),
tebi_minus_suffix_upper: ("-1TIB", SizeFilter::Max(1099511627776)),
}
/// Invalid parse testing
macro_rules! gen_size_filter_failure {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
let i = SizeFilter::from_string($value);
assert!(i.is_none());
}
)*
};
}
/// Invalid parse data
gen_size_filter_failure! {
ensure_missing_symbol_returns_none: "10M",
ensure_missing_number_returns_none: "+g",
ensure_missing_unit_returns_none: "+18",
ensure_bad_format_returns_none_1: "$10M",
ensure_bad_format_returns_none_2: "badval",
ensure_bad_format_returns_none_3: "9999",
ensure_invalid_unit_returns_none_1: "+50a",
ensure_invalid_unit_returns_none_2: "-10v",
ensure_invalid_unit_returns_none_3: "+1Mv",
ensure_bib_format_returns_none: "+1bib",
ensure_bb_format_returns_none: "+1bb",
}
#[test]
fn is_within_less_than() {
let f = SizeFilter::from_string("-1k").unwrap();
assert!(f.is_within(999));
}
#[test]
fn is_within_less_than_equal() {
let f = SizeFilter::from_string("-1k").unwrap();
assert!(f.is_within(1000));
}
#[test]
fn is_within_greater_than() {
let f = SizeFilter::from_string("+1k").unwrap();
assert!(f.is_within(1001));
}
#[test]
fn is_within_greater_than_equal() {
let f = SizeFilter::from_string("+1K").unwrap();
assert!(f.is_within(1000));
}
}
+82
View File
@@ -0,0 +1,82 @@
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> {
use humantime;
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() {
use humantime;
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));
}
}
+173
View File
@@ -0,0 +1,173 @@
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use regex_syntax::hir::Hir;
use regex_syntax::Parser;
use std::ffi::OsString;
pub use self::file_types::FileTypes;
mod file_types;
pub mod filter;
pub mod opts;
macro_rules! print_error {
($($arg:tt)*) => (eprintln!("[fd error]: {}", format!($($arg)*)))
}
macro_rules! print_error_and_exit {
($($arg:tt)*) => {
print_error!($($arg)*);
::std::process::exit(1);
};
}
/// Determine if a regex pattern contains a literal uppercase character.
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
Parser::new()
.parse(pattern)
.map(|hir| hir_has_uppercase_char(&hir))
.unwrap_or(false)
}
/// Determine if a regex expression contains a literal uppercase character.
fn hir_has_uppercase_char(hir: &Hir) -> bool {
use regex_syntax::hir::*;
match *hir.kind() {
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
HirKind::Class(Class::Unicode(ref ranges)) => ranges
.iter()
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
hir_has_uppercase_char(hir)
}
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
hirs.iter().any(hir_has_uppercase_char)
}
_ => false,
}
}
/// Maximum size of the output buffer before flushing results to the console
pub const MAX_BUFFER_LENGTH: usize = 1000;
/// Traverse args_os, looking for -exec and replacing it with --exec.
///
/// # Returns
///
/// * The args, with substitution if required
pub fn transform_args_with_exec<I>(original: I) -> Vec<OsString>
where
I: Iterator<Item = OsString>,
{
let mut in_exec_opt = false;
let target = OsString::from("-exec");
let long_start = OsString::from("--exec");
let short_start = OsString::from("-x");
let exec_end = OsString::from(";");
original.fold(vec![], |mut args, curr| {
if in_exec_opt {
if curr == exec_end {
in_exec_opt = false;
}
args.push(curr);
return args;
}
if curr == target || curr == long_start || curr == short_start {
args.push(if curr == target {
OsString::from("--exec")
} else {
curr
});
in_exec_opt = true;
} else {
args.push(curr);
}
args
})
}
#[cfg(test)]
mod tests {
use super::*;
fn oss_vec(strs: &[&str]) -> Vec<OsString> {
strs.into_iter().map(OsString::from).collect()
}
/// Ensure that -exec gets transformed into --exec
#[test]
fn normal_exec_substitution() {
let original = oss_vec(&["fd", "foo", "-exec", "cmd"]);
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
/// Ensure that --exec is not touched
#[test]
fn passthru_of_original_exec() {
let original = oss_vec(&["fd", "foo", "--exec", "cmd"]);
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
#[test]
fn temp_check_that_exec_context_observed() {
let original = oss_vec(&[
"fd",
"foo",
"-exec",
"cmd",
"-exec",
"ls",
";",
"-exec",
"rm",
";",
"--exec",
"find",
"-exec",
"rm",
";",
"-x",
"foo",
"-exec",
"something",
";",
"-exec",
]);
let expected = oss_vec(&[
"fd",
"foo",
"--exec",
"cmd",
"-exec",
"ls",
";",
"--exec",
"rm",
";",
"--exec",
"find",
"-exec",
"rm",
";",
"-x",
"foo",
"-exec",
"something",
";",
"--exec",
]);
let actual = transform_args_with_exec(original.into_iter());
assert_eq!(expected, actual);
}
}
+78
View File
@@ -0,0 +1,78 @@
use exec::CommandTemplate;
use internal::{
filter::{SizeFilter, TimeFilter},
FileTypes,
};
use lscolors::LsColors;
use regex::RegexSet;
use std::{path::PathBuf, 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<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,
}
+68 -64
View File
@@ -6,10 +6,9 @@
// 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};
/// A parser for the `LS_COLORS` environment variable.
use std::collections::HashMap;
/// Maps file extensions to ANSI colors / styles.
pub type ExtensionStyles = HashMap<String, Style>;
@@ -168,65 +167,70 @@ impl 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")
);
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_simple() {
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31"));
}
#[test]
fn test_parse_decoration() {
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("00;31"));
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("03;34"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("01;36"));
}
#[test]
fn test_parse_decoration_backwards() {
assert_eq!(Some(Colour::Blue.italic()), LsColors::parse_style("34;03"));
assert_eq!(Some(Colour::Cyan.bold()), LsColors::parse_style("36;01"));
assert_eq!(Some(Colour::Red.normal()), LsColors::parse_style("31;00"));
}
#[test]
fn test_parse_256() {
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("38;5;115")
);
assert_eq!(
Some(Colour::Fixed(115).normal()),
LsColors::parse_style("00;38;5;115")
);
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("01;38;5;119")
);
assert_eq!(
Some(Colour::Fixed(119).bold()),
LsColors::parse_style("38;5;119;01")
);
}
#[test]
fn test_from_string() {
assert_eq!(LsColors::default(), LsColors::from_string(&String::new()));
let result = LsColors::from_string(&String::from(
"rs=0:di=03;34:ln=01;36:*.foo=01;35:*README=33",
));
assert_eq!(Colour::Blue.italic(), result.directory);
assert_eq!(Colour::Cyan.bold(), result.symlink);
assert_eq!(Some(&Colour::Purple.bold()), result.extensions.get("foo"));
assert_eq!(
Some(&Colour::Yellow.normal()),
result.filenames.get("README")
);
}
}
+78 -19
View File
@@ -13,17 +13,21 @@ extern crate clap;
extern crate ignore;
#[macro_use]
extern crate lazy_static;
extern crate humantime;
#[cfg(all(unix, not(target_os = "redox")))]
extern crate libc;
extern crate num_cpus;
extern crate regex;
extern crate regex_syntax;
pub mod fshelper;
pub mod lscolors;
#[macro_use]
mod internal;
mod app;
mod exec;
mod internal;
mod exit_codes;
pub mod fshelper;
pub mod lscolors;
mod output;
mod walk;
@@ -37,7 +41,11 @@ use atty::Stream;
use regex::{RegexBuilder, RegexSetBuilder};
use exec::CommandTemplate;
use internal::{error, pattern_has_uppercase_char, transform_args_with_exec, FdOptions, FileTypes};
use internal::{
filter::{SizeFilter, TimeFilter},
opts::FdOptions,
pattern_has_uppercase_char, transform_args_with_exec, FileTypes,
};
use lscolors::LsColors;
fn main() {
@@ -50,19 +58,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") {
let mut dir_vec: Vec<_> = match matches
.values_of("path")
.or(matches.values_of("search-path"))
{
Some(paths) => paths
.map(|path| {
let path_buffer = PathBuf::from(path);
if !fshelper::is_dir(&path_buffer) {
error(&format!(
"Error: '{}' is not a directory.",
print_error_and_exit!(
"'{}' is not a directory.",
path_buffer.to_string_lossy()
));
);
}
path_buffer
})
@@ -83,11 +94,12 @@ fn main() {
}
// 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)
if !matches.is_present("full-path")
&& pattern.contains(std::path::MAIN_SEPARATOR)
&& fshelper::is_dir(Path::new(pattern))
{
error(&format!(
"Error: The search pattern '{pattern}' contains a path-separation character ('{sep}') \
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\
@@ -95,7 +107,7 @@ fn main() {
fd --full-path '{pattern}'",
pattern = pattern,
sep = std::path::MAIN_SEPARATOR,
));
);
}
// Treat pattern as literal string if '--fixed-strings' is used
@@ -132,6 +144,36 @@ fn main() {
let command = matches.values_of("exec").map(CommandTemplate::new);
let size_limits: Vec<SizeFilter> = matches
.values_of("size")
.map(|v| {
v.map(|sf| {
if let Some(f) = SizeFilter::from_string(sf) {
return f;
}
print_error_and_exit!("'{}' is not a valid size constraint. See 'fd --help'.", sf);
})
.collect()
})
.unwrap_or_else(|| vec![]);
let now = time::SystemTime::now();
let mut time_constraints: Vec<TimeFilter> = Vec::new();
if let Some(t) = matches.value_of("changed-within") {
if let Some(f) = TimeFilter::after(&now, t) {
time_constraints.push(f);
} else {
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
}
}
if let Some(t) = matches.value_of("changed-before") {
if let Some(f) = TimeFilter::before(&now, t) {
time_constraints.push(f);
} else {
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
}
}
let config = FdOptions {
case_sensitive,
search_full_path: matches.is_present("full-path"),
@@ -146,6 +188,7 @@ fn main() {
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
@@ -170,20 +213,33 @@ fn main() {
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
}),
extensions: matches.values_of("extension").map(|exts| {
let patterns = exts.map(|e| e.trim_left_matches('.'))
let patterns = exts
.map(|e| e.trim_left_matches('.'))
.map(|e| format!(r".\.{}$", regex::escape(e)));
match RegexSetBuilder::new(patterns)
.case_insensitive(true)
.build()
{
Ok(re) => re,
Err(err) => error(err.description()),
Err(err) => {
print_error_and_exit!("{}", err.description());
}
}
}),
command,
@@ -195,6 +251,9 @@ fn main() {
.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"),
};
match RegexBuilder::new(&pattern_regex)
@@ -203,12 +262,12 @@ fn main() {
.build()
{
Ok(re) => walk::scan(&dir_vec, Arc::new(re), Arc::new(config)),
Err(err) => error(
format!(
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()
).as_str(),
),
);
}
}
}
+12 -8
View File
@@ -6,16 +6,17 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
use internal::{FdOptions, EXITCODE_ERROR, EXITCODE_SIGINT};
use exit_codes::ExitCode;
use fshelper::is_executable;
use internal::opts::FdOptions;
use lscolors::LsColors;
use std::process;
use std::io::{self, Write};
use std::ops::Deref;
use std::path::{self, Component, Path, PathBuf};
use std::sync::Arc;
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use ansi_term;
@@ -44,7 +45,7 @@ pub fn print_entry(entry: &PathBuf, config: &FdOptions, wants_to_quit: &Arc<Atom
if r.is_err() {
// Probably a broken pipe. Exit gracefully.
process::exit(EXITCODE_ERROR);
process::exit(ExitCode::GeneralError.into());
}
}
@@ -86,7 +87,7 @@ fn print_entry_colorized(
if wants_to_quit.load(Ordering::Relaxed) {
write!(handle, "\n")?;
process::exit(EXITCODE_SIGINT);
process::exit(ExitCode::KilledBySigint.into());
}
}
@@ -105,7 +106,8 @@ fn print_entry_uncolorized(path: &Path, config: &FdOptions) -> io::Result<()> {
}
fn get_path_style<'a>(path: &Path, ls_colors: &'a LsColors) -> Option<&'a ansi_term::Style> {
if path.symlink_metadata()
if path
.symlink_metadata()
.map(|md| md.file_type().is_symlink())
.unwrap_or(false)
{
@@ -118,12 +120,14 @@ fn get_path_style<'a>(path: &Path, ls_colors: &'a LsColors) -> Option<&'a ansi_t
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()
} 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()
} else if let Some(extension_style) = path
.extension()
.and_then(|e| e.to_str())
.and_then(|e| ls_colors.extensions.get(e))
{
+113 -42
View File
@@ -9,21 +9,22 @@
extern crate ctrlc;
use exec;
use exit_codes::ExitCode;
use fshelper;
use internal::{error, FdOptions, EXITCODE_SIGINT, MAX_BUFFER_LENGTH};
use internal::{opts::FdOptions, MAX_BUFFER_LENGTH};
use output;
use std::process;
use std::error::Error;
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::{Arc, Mutex};
use std::thread;
use std::time;
use ignore::{self, WalkBuilder};
use ignore::overrides::OverrideBuilder;
use ignore::{self, WalkBuilder};
use regex::Regex;
/// The receiver thread can either be buffering results or directly streaming to the console.
@@ -36,6 +37,12 @@ enum ReceiverMode {
Streaming,
}
/// 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.
///
/// If the `--exec` argument was supplied, this will create a thread pool for executing
@@ -48,23 +55,24 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
.expect("Error: Path vector can not be empty");
let (tx, rx) = channel();
let threads = config.threads;
let show_filesystem_errors = config.show_filesystem_errors;
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
for pattern in &config.exclude_patterns {
let res = override_builder.add(pattern);
if res.is_err() {
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(false)
.ignore(config.read_fdignore)
.parents(config.read_fdignore || config.read_vcsignore)
.git_ignore(config.read_vcsignore)
.git_global(config.read_vcsignore)
@@ -83,11 +91,14 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
match err {
ignore::Error::Partial(_) => (),
_ => {
error(&format!(
"Error while parsing custom ignore file '{}': {}.",
ignore_file.to_string_lossy(),
err.description()
));
print_error!(
"{}",
format!(
"Malformed pattern in custom ignore file '{}': {}.",
ignore_file.to_string_lossy(),
err.description()
)
);
}
}
}
@@ -106,7 +117,8 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
let wq = Arc::clone(&receiver_wtq);
ctrlc::set_handler(move || {
wq.store(true, Ordering::Relaxed);
}).unwrap();
})
.unwrap();
}
// Spawn the thread that receives all results through the channel.
@@ -132,7 +144,8 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
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));
let handle =
thread::spawn(move || exec::job(rx, cmd, out_perm, show_filesystem_errors));
// Push the handle of the spawned thread into the vector for later joining.
handles.push(handle);
@@ -155,27 +168,36 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
.max_buffer_time
.unwrap_or_else(|| time::Duration::from_millis(100));
for value in rx {
match mode {
ReceiverMode::Buffering => {
buffer.push(value);
for worker_result in rx {
match worker_result {
WorkerResult::Entry(value) => {
match mode {
ReceiverMode::Buffering => {
buffer.push(value);
// Have we reached the maximum buffer size or maximum buffering time?
if buffer.len() > MAX_BUFFER_LENGTH
|| time::Instant::now() - start > max_buffer_time
{
// Flush the buffer
for v in &buffer {
output::print_entry(v, &rx_config, &receiver_wtq);
// Have we reached the maximum buffer size or maximum buffering time?
if buffer.len() > MAX_BUFFER_LENGTH
|| time::Instant::now() - start > max_buffer_time
{
// Flush the buffer
for v in &buffer {
output::print_entry(v, &rx_config, &receiver_wtq);
}
buffer.clear();
// Start streaming
mode = ReceiverMode::Streaming;
}
}
ReceiverMode::Streaming => {
output::print_entry(&value, &rx_config, &receiver_wtq);
}
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);
}
}
}
}
@@ -205,7 +227,10 @@ 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;
}
};
if entry.depth() == 0 {
@@ -216,15 +241,19 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
if let Some(ref file_types) = config.file_types {
if let Some(ref entry_type) = entry.file_type() {
if (entry_type.is_file() && !file_types.files)
|| (entry_type.is_dir() && !file_types.directories)
|| (entry_type.is_symlink() && !file_types.symlinks)
|| (entry.metadata().is_ok()
&& !fshelper::is_executable(&entry.metadata().unwrap())
&& file_types.executables_only)
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()
} 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.
@@ -248,10 +277,50 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
}
}
// Filter out unwanted sizes if it is a file and we have been given size constraints.
if config.size_constraints.len() > 0 {
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 entry_path.is_file() {
if let Ok(metadata) = entry_path.metadata() {
if let Ok(modified) = metadata.modified() {
matched = config
.time_constraints
.iter()
.all(|tf| tf.applies_to(&modified));
}
}
}
if !matched {
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."),
Err(_) => {
print_error_and_exit!("Unable to retrieve absolute path.");
}
}
} else {
entry_path.file_name().map(|f| f.to_string_lossy())
@@ -260,7 +329,9 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
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()
tx_thread
.send(WorkerResult::Entry(entry_path.to_owned()))
.unwrap()
}
}
@@ -276,6 +347,6 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
receiver_thread.join().unwrap();
if wants_to_quit.load(Ordering::Relaxed) {
process::exit(EXITCODE_SIGINT);
process::exit(ExitCode::KilledBySigint.into());
}
}
+6 -3
View File
@@ -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))?;
}
@@ -120,11 +123,11 @@ 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 {
// 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 };
let line = if trim_left { line.trim_left() } else { line };
line.replace('/', &std::path::MAIN_SEPARATOR.to_string())
})
.collect::<Vec<_>>();
+178 -3
View File
@@ -8,14 +8,18 @@
//! Integration tests for the CLI interface of fd.
extern crate filetime;
extern crate humantime;
extern crate regex;
mod testenv;
use testenv::TestEnv;
use regex::escape;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::time::{Duration, SystemTime};
use testenv::TestEnv;
static DEFAULT_DIRS: &'static [&'static str] = &["one/two/three", "one/two/three/directory_foo"];
@@ -32,7 +36,8 @@ static DEFAULT_FILES: &'static [&'static str] = &[
];
fn get_absolute_root_path(env: &TestEnv) -> String {
let path = env.test_root()
let path = env
.test_root()
.canonicalize()
.expect("absolute path")
.to_str()
@@ -52,6 +57,13 @@ fn get_test_env_with_abs_path(dirs: &[&'static str], files: &[&'static str]) ->
(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() {
@@ -635,6 +647,33 @@ fn test_type_executable() {
);
}
/// 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() {
@@ -992,7 +1031,8 @@ fn test_invalid_utf8() {
fs::File::create(
te.test_root()
.join(OsStr::from_bytes(b"test1/test_\xFEinvalid.txt")),
).unwrap();
)
.unwrap();
te.assert_output(&["", "test1/"], "test1/test_invalid.txt");
@@ -1001,3 +1041,138 @@ fn test_invalid_utf8() {
// 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",
);
}