mirror of
https://github.com/sharkdp/fd.git
synced 2026-09-01 04:28:07 +08:00
Compare commits
228 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5648597a61 | |||
| b3258e00df | |||
| 41d7cc33ca | |||
| a81fef9992 | |||
| 53c338d71f | |||
| d77daa89a2 | |||
| 78cde856b1 | |||
| a954944d69 | |||
| 27162c2729 | |||
| 1d1eefdb7b | |||
| c8746f6216 | |||
| c761d51dfb | |||
| ddee6aa8f3 | |||
| e3c77ef7f5 | |||
| c0592e276e | |||
| d41a5a9e24 | |||
| 726fede7cc | |||
| 9d3cb69935 | |||
| da9af8c1eb | |||
| dc120c547d | |||
| bbf3d0d430 | |||
| 4f4591bfdc | |||
| 28d6a4f518 | |||
| 42719624a8 | |||
| fef1b91feb | |||
| c5a311d3cc | |||
| ca2ef7ba82 | |||
| 65b65b32be | |||
| 3283a7b345 | |||
| 4cafb28951 | |||
| 79d5a5bdc5 | |||
| a9dc45ecb1 | |||
| 1a16003cca | |||
| 6a68ac8c27 | |||
| 09cdf9831d | |||
| 22a55361e0 | |||
| d7cc34e36f | |||
| 4f9a02400a | |||
| 30cb4adc61 | |||
| daf3046a35 | |||
| 151eaad043 | |||
| 4f4330167a | |||
| f31fb9bf68 | |||
| b22285ffe4 | |||
| db6fc44d05 | |||
| 90de7242c7 | |||
| 04bcd546b2 | |||
| 4b5e8941a7 | |||
| fe00860b87 | |||
| 0335cc362b | |||
| a06efe1418 | |||
| 1a96544b29 | |||
| ee1a9f567f | |||
| 17fbc2dc52 | |||
| 074de44caf | |||
| cb56add1d8 | |||
| 4287cddc00 | |||
| 0b7febc3b6 | |||
| d91100e354 | |||
| 0f5500adfc | |||
| 2685728695 | |||
| 789706c3ab | |||
| 4a0730a5fa | |||
| 3b42898a69 | |||
| d63c63be8c | |||
| 47974b6479 | |||
| 2bab4a2249 | |||
| 95eae00126 | |||
| ed024ceb54 | |||
| 6c9e743d43 | |||
| ea21df3f76 | |||
| 7e3c69c096 | |||
| 439548c25a | |||
| ed45d1c5af | |||
| e44f2f8540 | |||
| 2ea77e7cdc | |||
| a85f33cf98 | |||
| 4815ee416e | |||
| 53557ef471 | |||
| f9372b55d5 | |||
| 4590ae8535 | |||
| bce95274e3 | |||
| af9d8082a1 | |||
| 094516aec3 | |||
| 33eb857a81 | |||
| 6e96154d86 | |||
| 556c40e1f4 | |||
| b415d7234b | |||
| e0cba4b700 | |||
| 342d12db3f | |||
| c79d1638f5 | |||
| 9ef9bf7b80 | |||
| 809fd8fb78 | |||
| a3060f952e | |||
| 65096a653e | |||
| 9f738ad995 | |||
| 5c27ab1a06 | |||
| 2cfb1a2bb2 | |||
| 938dafda28 | |||
| e2a7d3446b | |||
| e42ddda5a6 | |||
| 289d6443a7 | |||
| 3066445e6b | |||
| 364555fd71 | |||
| 80b042ad47 | |||
| 4274075f6f | |||
| b9d0e72f70 | |||
| 33df48ebe4 | |||
| c9dc617711 | |||
| da5cd12b24 | |||
| 1714d416e2 | |||
| d43827fe57 | |||
| ee673c92d3 | |||
| decd7f2ea3 | |||
| f3e1ea150c | |||
| 12bd321a27 | |||
| 5fc341832d | |||
| dd07bc9ef1 | |||
| 2340bab32a | |||
| 4a237c591e | |||
| 44605d55dd | |||
| e5e14f31c4 | |||
| f69b59195a | |||
| 9c9bb12f23 | |||
| 70448b0006 | |||
| fc924d2e0a | |||
| cab4981b11 | |||
| 104600e642 | |||
| 3c6fab3439 | |||
| d05e7171d4 | |||
| 81dee25438 | |||
| bbf0f1cc1f | |||
| bfc8c42444 | |||
| 918dfc6ef0 | |||
| d6034119ae | |||
| 82e6562cfc | |||
| 8cea65c1b8 | |||
| 9d73402ef2 | |||
| 06434f229b | |||
| 0f2429cabc | |||
| 232e3937f2 | |||
| e23398e6d0 | |||
| a5fe138a25 | |||
| b23cfc383f | |||
| e46998278a | |||
| 7213f5a88e | |||
| fa7d49282b | |||
| 2f18903b29 | |||
| b63e43b40f | |||
| 56289041a6 | |||
| 8196f79fa4 | |||
| 320d7fca0a | |||
| f7d1938556 | |||
| 328c6640be | |||
| c001dd47a7 | |||
| a9c7696ecc | |||
| 9a34ba6a0c | |||
| cd2dc5714e | |||
| 2828d90f99 | |||
| ed6c184020 | |||
| 6f7d10870c | |||
| dea1fbe722 | |||
| 94993ca6c2 | |||
| 3e0325cc60 | |||
| e5ba75f3c8 | |||
| 8796de57b5 | |||
| 0f27485faf | |||
| 762f551ff4 | |||
| fb205f5057 | |||
| 32fdbb1eae | |||
| 19e5d6e36e | |||
| 0895027ed6 | |||
| c60cf9c1a8 | |||
| c14a004367 | |||
| a6ed0fc5ef | |||
| 266499800b | |||
| 9491670974 | |||
| d48aeda6b2 | |||
| 59c27baf0f | |||
| 33f385692d | |||
| b027b2c322 | |||
| 9350c8544d | |||
| f55033818d | |||
| 1ab4e87dda | |||
| 4e20803e7d | |||
| 5a154866e3 | |||
| ac3e0e18e4 | |||
| 9cc0c8ed57 | |||
| a991ed4488 | |||
| fe79b8ce0a | |||
| 25a728529d | |||
| 1056220421 | |||
| 08fcd7ce59 | |||
| 50fca2dca2 | |||
| 588888112a | |||
| eac20a8132 | |||
| d5da615c17 | |||
| 641594c2c6 | |||
| 2545aaabd2 | |||
| 3599a30ee2 | |||
| f14b854bc1 | |||
| 8f86a06c1a | |||
| 761c445263 | |||
| 588e344d5b | |||
| e3af9b0307 | |||
| 29bf9d731d | |||
| 3857fa8f62 | |||
| 24e108e1ad | |||
| 1d16cd855e | |||
| a0505bd4df | |||
| 630749f706 | |||
| 1daa454615 | |||
| 040c57e007 | |||
| 1ce2add943 | |||
| 418233193a | |||
| 0b04bd19df | |||
| ac2dae0427 | |||
| 53afcd8fc3 | |||
| d266fa3010 | |||
| e7d0a91986 | |||
| 801816ee6c | |||
| 5cbd8405ec | |||
| 35945c4a62 | |||
| 97908beb97 | |||
| f15540e501 | |||
| 074cfad3d4 | |||
| f3ec3d9cf0 | |||
| 46e865f1a2 |
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug.
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug you encountered:**
|
||||
|
||||
|
||||
**Describe what you expected to happen:**
|
||||
|
||||
|
||||
**What version of `fd` are you using?**
|
||||
<!-- paste the output of `fd --version` here -->
|
||||
|
||||
**Which operating system / distribution are you on?**
|
||||
<!--
|
||||
Unix: paste the output of `uname -srm` and `lsb_release -a` here.
|
||||
Windows: please tell us your Windows version
|
||||
-->
|
||||
@@ -0,0 +1,2 @@
|
||||
blank_issues_enabled: true
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project.
|
||||
title: ''
|
||||
labels: feature-request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about 'fd'.
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
**What version of `fd` are you using?**
|
||||
[paste the output of `fd --version` here]
|
||||
@@ -1,29 +0,0 @@
|
||||
name: fd
|
||||
version: git
|
||||
summary: "A simple, fast and user-friendly alternative to 'find'"
|
||||
description: |
|
||||
fd is a simple, fast and user-friendly alternative to find.
|
||||
- While it does not seek to mirror all of find's powerful functionality, it provides sensible (opinionated) defaults for 80% of the use cases.
|
||||
Features
|
||||
Convenient syntax: fd PATTERN instead of find -iname '*PATTERN*'.
|
||||
Colorized terminal output (similar to ls).
|
||||
It's fast (see benchmarks below).
|
||||
Smart case: the search is case-insensitive by default. It switches to case-sensitive if the pattern contains an uppercase character*.
|
||||
Ignores hidden directories and files, by default.
|
||||
Ignores patterns from your .gitignore, by default.
|
||||
Regular expressions.
|
||||
Unicode-awareness.
|
||||
The command name is 50% shorter* than find :-).
|
||||
Parallel command execution with a syntax similar to GNU Parallel.
|
||||
|
||||
grade: stable
|
||||
confinement: classic
|
||||
|
||||
parts:
|
||||
fd:
|
||||
plugin: rust
|
||||
source: ./
|
||||
|
||||
apps:
|
||||
fd:
|
||||
command: bin/fd
|
||||
+7
-47
@@ -1,8 +1,5 @@
|
||||
language: rust
|
||||
cache: cargo
|
||||
matrix:
|
||||
# allow_failures:
|
||||
# - rust: nightly
|
||||
include:
|
||||
# Stable channel.
|
||||
- os: linux
|
||||
@@ -18,12 +15,14 @@ matrix:
|
||||
rust: stable
|
||||
env: TARGET=i686-unknown-linux-musl
|
||||
- os: osx
|
||||
osx_image: xcode11.4 # Catalina
|
||||
rust: stable
|
||||
env: TARGET=x86_64-apple-darwin
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- TARGET=arm-unknown-linux-gnueabihf
|
||||
- CC_arm_unknown_linux_gnueabihf=/usr/bin/arm-linux-gnueabihf-gcc-4.8
|
||||
- CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
|
||||
- os: linux
|
||||
rust: stable
|
||||
@@ -35,63 +34,24 @@ matrix:
|
||||
- os: linux
|
||||
rust: beta
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
# Disabled to reduce total CI time
|
||||
# - os: linux
|
||||
# rust: beta
|
||||
# env: TARGET=x86_64-unknown-linux-musl
|
||||
# - os: linux
|
||||
# rust: beta
|
||||
# env: TARGET=i686-unknown-linux-gnu
|
||||
# - os: linux
|
||||
# rust: beta
|
||||
# env: TARGET=i686-unknown-linux-musl
|
||||
# - os: osx
|
||||
# rust: beta
|
||||
# env: TARGET=x86_64-apple-darwin
|
||||
|
||||
# Nightly channel.
|
||||
- os: linux
|
||||
rust: nightly
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
# Disabled to reduce total CI time
|
||||
# - os: linux
|
||||
# rust: nightly
|
||||
# env: TARGET=x86_64-unknown-linux-musl
|
||||
# - os: linux
|
||||
# rust: nightly
|
||||
# env: TARGET=i686-unknown-linux-gnu
|
||||
# - os: linux
|
||||
# rust: nightly
|
||||
# env: TARGET=i686-unknown-linux-musl
|
||||
# - os: osx
|
||||
# rust: nightly
|
||||
# env: TARGET=x86_64-apple-darwin
|
||||
|
||||
# Minimum Rust supported channel.
|
||||
- os: linux
|
||||
rust: 1.31.0
|
||||
rust: 1.36.0
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
- os: linux
|
||||
rust: 1.31.0
|
||||
rust: 1.36.0
|
||||
env: TARGET=x86_64-unknown-linux-musl
|
||||
- os: linux
|
||||
rust: 1.31.0
|
||||
rust: 1.36.0
|
||||
env: TARGET=i686-unknown-linux-gnu
|
||||
- os: linux
|
||||
rust: 1.31.0
|
||||
rust: 1.36.0
|
||||
env: TARGET=i686-unknown-linux-musl
|
||||
- os: osx
|
||||
rust: 1.31.0
|
||||
rust: 1.36.0
|
||||
env: TARGET=x86_64-apple-darwin
|
||||
|
||||
# Code formatting check
|
||||
- os: linux
|
||||
rust: nightly
|
||||
# skip the global install step
|
||||
install:
|
||||
- cargo install --debug --force rustfmt-nightly
|
||||
script: cargo fmt -- --check
|
||||
|
||||
sudo: required
|
||||
|
||||
before_install:
|
||||
|
||||
+481
@@ -0,0 +1,481 @@
|
||||
# Upcoming release
|
||||
|
||||
## Features
|
||||
## Bugfixes
|
||||
## Changes
|
||||
## Other
|
||||
|
||||
# v8.1.1
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Support colored output on older Windows versions if either (1) `--color=always` is set or (2) the `TERM` environment variable is set. See #469
|
||||
|
||||
# v8.1.0
|
||||
|
||||
## Features
|
||||
|
||||
- Add new `--owner [user][:group]` filter. See #307 (pull #581) (@alexmaco)
|
||||
- Add support for a global ignore file (`~/.config/fd/ignore` on Unix), see #575 (@soedirgo)
|
||||
- Do not exit immediately if one of the search paths is missing, see #587 (@DJRHails)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Reverted a change from fd 8.0 that enabled colors on all Windows terminals (see below) in order to support older Windows versions again, see #577. Unfortunately, this re-opens #469
|
||||
- Fix segfault caused by jemalloc on macOS Catalina, see #498
|
||||
- Fix `--glob` behavior with empty pattern, see #579 (@SeamusConnor)
|
||||
- Fix `--list-details` on FreeBSD, DragonFly BSD, OpenBSD and NetBSD. See #573 (@t6)
|
||||
|
||||
## Changes
|
||||
|
||||
- Updated documentation for `--size`, see #584
|
||||
|
||||
# v8.0.0
|
||||
|
||||
## Features
|
||||
|
||||
- Add a new `-l`/`--list-details` option to show more details about the search results. This is
|
||||
basically an alias for `--exec-batch ls -l` with some additional `ls` options.
|
||||
This can be used in order to:
|
||||
* see metadata like permissions, owner, file size, modification times (#491)
|
||||
* see symlink targets (#482)
|
||||
* achieve a deterministic output order (#324, #196, #159)
|
||||
- Add a new `--max-results=<count>` option to limit the number of search results, see #472, #476 and #555
|
||||
This can be useful to speed up searches in cases where you know that there are only N results.
|
||||
Using this option is also (slightly) faster than piping to `head -n <count>` where `fd` can only
|
||||
exit when it finds the search results `<count> + 1`.
|
||||
- Add the alias `-1` for `--max-results=1`, see #561. (@SimplyDanny).
|
||||
- Add new `--type socket` and `--type pipe` filters, see #511.
|
||||
- Add new `--min-depth <depth>` and `--exact-depth <depth>` options in addition to the existing option
|
||||
to limit the maximum depth. See #404.
|
||||
- Support additional ANSI font styles in `LS_COLORS`: faint, slow blink, rapid blink, dimmed, hidden and strikethrough.
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Preserve non-UTF8 filenames: invalid UTF-8 filenames are now properly passed to child-processes
|
||||
when using `--exec`, `--exec-batch` or `--list-details`. In `fd`'s output, we replace non-UTF-8
|
||||
sequences with the "�" character. However, if the output of `fd` goes to another process, we
|
||||
print the actual bytes of the filename. For more details, see #558 and #295.
|
||||
- `LS_COLORS` entries with unsupported font styles are not completely ignored, see #552
|
||||
|
||||
## Changes
|
||||
|
||||
- Colored output will now be enabled by default on older Windows versions.
|
||||
This allows the use of colored output if the terminal supports it (e.g.
|
||||
MinTTY, Git Bash). On the other hand, this will be a regression for users
|
||||
on older Windows versions with terminals that do not support ANSI escape
|
||||
sequences. Affected users can use an alias `fd="fd --color=never"` to
|
||||
continue using `fd` without colors. There is no change of behavior for
|
||||
Windows 10. See #469.
|
||||
- When using `--glob` in combination with `--full-path`, a `*` character does not match a path
|
||||
separation character (`/` or `\\`) anymore. You can use `**` for that. This allows things like
|
||||
`fd -p -g '/some/base/path/*/*/*.txt'` which would previously match to arbitrary depths (instead
|
||||
of exactly two folders below `/some/base/path`. See #404.
|
||||
- "Legacy" support to use `fd -exec` (with a single dash) has been removed. Use `fd -x` or
|
||||
`fd --exec` instead.
|
||||
- Overall improved error handling and error messages.
|
||||
|
||||
|
||||
## Other
|
||||
|
||||
- Korean translation of the README, see: [한국어](https://github.com/spearkkk/fd-kor) (@spearkkk)
|
||||
|
||||
|
||||
# v7.5.0
|
||||
|
||||
## Features
|
||||
|
||||
- Added `--one-file-system` (aliases: `--mount`, `--xdev`) to not cross file system boundaries on Unix and Windows, see #507 (@FallenWarrior2k).
|
||||
- Added `--base-directory` to change the working directory in which `fd` is run, see #509 and #475 (@hajdamak).
|
||||
- `fd` will not use colored output if the `NO_COLOR` environment variable is set, see #550 and #551 (@metadave).
|
||||
- `fd --exec` will return exit code 1 if one of the executed commands fails, see #526 and #531 (@fusillicode and @Giuffre)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Fixed 'command not found' error when using zsh completion, see #487 (@barskern).
|
||||
- `fd -L` should include broken symlinks, see #357 and #497 (@tommilligan, @neersighted and @sharkdp)
|
||||
- Display directories even if we don't have permission to enter, see #437 (@sharkdp)
|
||||
|
||||
## Changes
|
||||
|
||||
- A flag can now be passed multiple times without producing an error, see #488 and #496 (@rootbid).
|
||||
- Search results are sorted when using the `-X` option to match the behaviour of piping to `xargs`, see #441 and #524 (@Marcoleni @crash-g).
|
||||
|
||||
|
||||
# v7.4.0
|
||||
|
||||
## Performance improvements
|
||||
|
||||
- Reduce number of `stat` syscalls, improving the performance for searches where file metadata is
|
||||
required (`--type`, `--size`, `--changed-within`, …), see #434 (@tavianator)
|
||||
- Use jemalloc by default, improving the performance for almost all searches, see #481. Note that
|
||||
Windows and `*musl*` builds do not profit from this.
|
||||
|
||||
## Features
|
||||
|
||||
- Added a new `-g`/`--glob` option to switch to glob-based searches (instead of regular expression
|
||||
based searches). This is accompanied by a new `--regex` option that can be used to switch back,
|
||||
if users want to `alias fd="fd --glob"`. See #284
|
||||
- Added a new `--path-separator <sep>` option which can be useful for Windows users who
|
||||
want/need `fd` to use `/` instead of `\`, see #428 and #153 (@mookid)
|
||||
- Added support for hidden files on Windows, see #379
|
||||
- When `fd` is run with the `--exec-batch`/`-X` option, it now exposes the exit status of the
|
||||
command that was run, see #333.
|
||||
- Exit immediately when Ctrl-C has been pressed twice, see #423
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Make `--changed-within`/`--changed-before` work for directories, see #470
|
||||
|
||||
## Other
|
||||
|
||||
- Pre-built `fd` binaries should now be available for `armhf` targets, see #457 (@detly)
|
||||
- `fd` is now available on Alpine Linux, see #451 (@5paceToast)
|
||||
- `fd` is now in the officla FreeBSD repositories, see #412 (@t6)
|
||||
- Added OpenBSD install instructions, see #421 (@evitalis)
|
||||
- Added metadata to the Debian package, see #416 (@cathalgarvey)
|
||||
- `fd` can be installed via npm, see #438 (@pablopunk)
|
||||
|
||||
|
||||
# v7.3.0
|
||||
|
||||
## Features
|
||||
|
||||
- New `--exec-batch <cmd>`/`-X <cmd>` option for batch execution of commands, see #360 (@kimsnj).
|
||||
This allows you to do things like:
|
||||
``` bash
|
||||
fd … -X vim # open all search results in vim (or any other editor)
|
||||
fd … -X ls -l # view detailed stats about the search results with 'ls'
|
||||
fd -e svg -X inkscape # open all SVG files in Inkscape
|
||||
```
|
||||
- Support for 24-bit color codes (when specified via `LS_COLORS`) as well as
|
||||
different font styles (bold, italic, underline).
|
||||
|
||||
## Changes
|
||||
|
||||
- A few performance improvements, in particular when printing lots of colorized
|
||||
results to the console, see #370
|
||||
- The `LS_COLORS` handling has been "outsourced" to a separate crate (https://github.com/sharkdp/lscolors) that is now being used by other tools as well: [fselect](https://github.com/jhspetersson/fselect), [lsd](https://github.com/Peltoche/lsd/pull/84). For details, see #363.
|
||||
|
||||
## Other
|
||||
|
||||
- `fd` will be available in Ubuntu Disco DIngo (19.04), see #373 (@sylvestre)
|
||||
- This release should come with a static ARM binary (`arm-unknown-linux-musleabihf`), see #320 (@duncanfinney)
|
||||
- Various documentation improvements, see #389
|
||||
|
||||
## Thanks
|
||||
|
||||
Special thanks to @alexmaco for his awesome work on refactoring and code improvements! (see #401, #398, and #383)
|
||||
|
||||
# v7.2.0
|
||||
|
||||
## Features
|
||||
|
||||
* Added support for filtering by file modification time by adding two new options `--changed-before <date|duration>` and `--changed-within <..>`. For more details, see the `--help` text, the man page, the relevant issue #165 and the PR #339 (@kimsnj)
|
||||
* Added `--show-errors` option to enable the display of filesystem error messages such as "permission denied", see #311 (@psinghal20 and @majecty)
|
||||
* Added `--maxdepth` as a (hidden) alias for `--max-depth`, see #323 (@mqudsi)
|
||||
* Added `--search-path` option which can be supplied to replace the positional `path` argument at any position.
|
||||
|
||||
## Changes
|
||||
|
||||
* Loosen strict handling of missing `--ignore-file`, see #280 (@psinghal20)
|
||||
* Re-enabled `.ignore` files, see #156.
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* `fd` could previously get stuck when run from the root directory in the
|
||||
presence of zombie processes. This curious bug has been fixed in Rust 1.29 and higher. For more details, see #288, [rust-lang/rust#50619](https://github.com/rust-lang/rust/issues/50619) and [the fix](https://github.com/rust-lang/rust/pull/50630)
|
||||
|
||||
## Other
|
||||
|
||||
* `fd` has officially landed in Debian! See #345 for details. Thanks goes to @sylvestre, @paride and possibly others I don't know about.
|
||||
* Added Chinese translation of README (@chinanf-boy)
|
||||
|
||||
## Thanks
|
||||
|
||||
A special thanks goes to @joshleeb for his amazing improvements throughout
|
||||
the code base (new tests, refactoring work and various other things)!
|
||||
|
||||
|
||||
# v7.1.0
|
||||
|
||||
## Features
|
||||
|
||||
* Added `--size` filter option, see #276 (@stevepentland, @JonathanxD and @alexmaco)
|
||||
* Added `--type empty` (or `-t e`) to search for empty files and/or directories, see #273
|
||||
|
||||
## Changes
|
||||
|
||||
* With the new version, `.gitignore` files will only be respected in Git repositories, not outside.
|
||||
* A few performance improvements for `--type` searches, see 641976cf7ad311ba741571ca8b7f02b2654b6955 and 50a2bab5cd52d26d4a3bc786885a2c270ed3b227
|
||||
|
||||
## Other
|
||||
|
||||
* Starting with this release, we will offer pre-built ARM binaries, see #244
|
||||
* Added instructions on how to use `fd` with `emacs`, see #282 (@redguardtoo)
|
||||
* `fd` is now in the official openSUSE repositories, see #275 (@avindra)
|
||||
* `fd` is now available via MacPorts, see #291 (@raimue)
|
||||
|
||||
|
||||
# v7.0.0
|
||||
|
||||
## Features
|
||||
|
||||
* Added `--type executable` (or `-t x`) to search for executable files only, see #246 (@PramodBisht)
|
||||
* Added support for `.fdignore` files, see #156 and #241.
|
||||
* Added `--ignore-file` option to add custom ignore files, see #156.
|
||||
* Suggest `--fixed-strings` on invalid regular expressions, see #234 (@PramodBisht)
|
||||
* Detect when user supplied path instead of pattern, see #235.
|
||||
|
||||
## Changes
|
||||
|
||||
* `.ignore` and `.rgignore` files are not parsed anymore. Use `.fdignore` files
|
||||
or add custom files via `--ignore-file` instead.
|
||||
* Updated to `regex-syntax` 0.5 (@cuviper)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* Properly normalize absolute paths, see #268
|
||||
* Invalid utf8 filenames displayed when `-e` is used, see #250
|
||||
* If `--type` is used, fifos/sockets/etc. are always shown, see #260
|
||||
|
||||
## Other
|
||||
|
||||
* Packaging:
|
||||
* The Arch Linux package is now simply called `fd`.
|
||||
* There is now a `fd` ebuild for Gentoo Linux.
|
||||
* There is a `scoop` package for `fd` (Windows).
|
||||
* There is a `Chocolatey` package for `fd` (Windows).
|
||||
* There is a Fedora `copr` package for `fd`.
|
||||
|
||||
|
||||
# v6.3.0
|
||||
|
||||
## Features
|
||||
|
||||
* Files with multiple extensions can now be found via `--extension`/`-e`, see #214 (@althonos)
|
||||
``` bash
|
||||
> fd -e tar.gz
|
||||
```
|
||||
|
||||
* Added new `-F`/`--fixed-strings`/`--literal` option that treats the pattern as a literal string instead of a regular expression, see #157
|
||||
|
||||
``` bash
|
||||
> fd -F 'file(1).txt'
|
||||
```
|
||||
|
||||
* Allow `-exec` to work as `--exec`, see #226 (@stevepentland)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* Fixed `Ctrl-C` handling when using `--exec`, see #224 (@Doxterpepper)
|
||||
|
||||
* Fixed wrong file owner for files in deb package, see #213
|
||||
|
||||
## Other
|
||||
|
||||
* Replaced old gif by a fancy new SVG screencast (@marionebl)
|
||||
* Updated [benchmark results](https://github.com/sharkdp/fd#benchmark) (fd has become faster in the meantime!). There is a new repository that hosts several benchmarking scripts for fd: https://github.com/sharkdp/fd-benchmarks
|
||||
|
||||
|
||||
# v6.2.0
|
||||
|
||||
## Features
|
||||
|
||||
* Support for filtering by multiple file extensions and multiple file types, see #199 and #177
|
||||
(@tkadur).
|
||||
|
||||
For example, it's possible to search for C++ source or header files:
|
||||
``` bash
|
||||
> fd -e cpp -e c -e cxx -e h pattern
|
||||
```
|
||||
|
||||
## Changes
|
||||
|
||||
* The size of the output buffer (for sorting search results) is now limited to 1000 entries. This
|
||||
improves the search speed significantly if there are a lot of results, see #191 (@sharkdp).
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* Fix a bug where long-running searches could not be killed via Ctrl-C, see #210 (@Doxterpepper)
|
||||
* fd's exit codes are now in accordance with Unix standards, see #201 (@Doxterpepper)
|
||||
|
||||
## Other
|
||||
|
||||
* Bash, zsh and fish completion should now work with the Ubuntu `.deb` packages, see #195 and #209
|
||||
(@tmccombs and @sharkdp)
|
||||
* There is a new section on how to set up `fzf` to use `fd` in the
|
||||
[README](https://github.com/sharkdp/fd#using-fd-with-fzf), see #168.
|
||||
|
||||
|
||||
# v6.1.0
|
||||
|
||||
## Features
|
||||
|
||||
* Support for multiple search paths, see #166 (@Doxterpepper)
|
||||
* Added `--no-ignore-vcs` option to disable `.gitignore` and other VCS ignore files,
|
||||
without disabling `.ignore` files - see #156 (@ptzz).
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* Handle terminal signals, see #128 (@Doxterpepper)
|
||||
* Fixed hang on `--exec` when user input was required, see #178 and #193 (@reima)
|
||||
|
||||
## Other
|
||||
|
||||
* Debian packages are now created via Travis CI and should be available for this and all
|
||||
future releases (@tmccombs).
|
||||
* fd is now available on Void Linux (@maxice8)
|
||||
* The minimum required Rust version is now 1.20
|
||||
|
||||
## Thanks
|
||||
|
||||
@Doxterpepper deserves a special mention for his great work that is included in this release and
|
||||
for the support in ticket discussions and concerning Travis CI fixes. Thank you very much!
|
||||
|
||||
Thanks also go out to @tmccombs for the work on Debian packages and for reviewing a lot of pull requests!
|
||||
|
||||
# v6.0.0
|
||||
|
||||
## Changes
|
||||
|
||||
- The `--exec`/`-x` option does not spawn an intermediate shell anymore. This improves the
|
||||
performance of parallel command execution and fixes a whole class of (present and potentially
|
||||
future) problems with shell escaping. The drawback is that shell commands cannot directly be
|
||||
called with `--exec`. See #155 for the full discussion. These changes have been implemented by
|
||||
@reima (Thanks!).
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- `--exec` does not escape cmd.exe metacharacters on Windows (see #155, as above).
|
||||
|
||||
## Other
|
||||
|
||||
* *fd* is now available in the FreeBSD ports (@andoriyu)
|
||||
* The minimal `rustc` version is now checked when building with `cargo`, see #164 (@matematikaadit)
|
||||
* The output directory for the shell completion files is created if it does not exist (@andoriyu)
|
||||
|
||||
|
||||
# v5.0.0
|
||||
|
||||
## Features
|
||||
|
||||
* Added new `--exec`, `-x` option for parallel command execution (@mmstick, see #84 and #116). See the corresponding [README section](https://github.com/sharkdp/fd#parallel-command-execution) for an introduction.
|
||||
* Auto-disable color output on unsupported Windows shells like `cmd.exe` (@iology, see #129)
|
||||
* Added the `--exclude`, `-X` option to suppress certain files/directories in the search results
|
||||
(see #89).
|
||||
* Added ripgrep aliases `-u` and `-uu` for `--no-ignore` and `--no-ignore --hidden`, respectively
|
||||
(@unsignedint, see #92)
|
||||
* Added `-i`, `--ignore-case` (@iology, see #95)
|
||||
* Made smart case really smart (@reima, see #103)
|
||||
* Added RedoxOS support (@goyox86, see #131)
|
||||
|
||||
## Changes
|
||||
|
||||
* The dot `.` can now match newlines in file names (@iology, see #111)
|
||||
* The short `--type` argument for symlinks has been changed from `s` to `l` (@jcpetkovich, see #83)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* Various improvements in root-path and symlink handling (@iology, see #82, #107, and #113)
|
||||
* Fixed absolute path handling on Windows (@reima, #93)
|
||||
* Fixed: current directory not included when using relative path (see #81)
|
||||
* Fixed `--type` behavior for unknown file types (@iology, see #150)
|
||||
* Some fixes around `--exec` (@iology, see #142)
|
||||
|
||||
## Other
|
||||
|
||||
* Major updates and bugfixes to our continuous integration and deployment tooling on Travis
|
||||
(@matematikaadit, see #149, #145, #133)
|
||||
* Code style improvements & automatic style checking via `rustfmt` on Travis (@Detegr, see #99)
|
||||
* Added a man page (@pickfire, see #77)
|
||||
* *fd* has been relicensed under the dual license MIT/Apache-2.0 (@Detegr, see #105)
|
||||
* Major refactorings and code improvements (Big thanks to @gsquire, @reima, @iology)
|
||||
* First version of [`CONTRIBUTING`](https://github.com/sharkdp/fd/blob/master/CONTRIBUTING.md) guidelines
|
||||
* There is now a Nix package (@mehandes)
|
||||
* *fd* is now in the official Arch Linux repos (@cassava)
|
||||
* Improved tooling around shell completion files (@ImbaKnugel, see #124)
|
||||
* Updated tutorial in the [`README`](https://github.com/sharkdp/fd/blob/master/README.md)
|
||||
* The minimum required version of Rust has been bumped to 1.19.
|
||||
|
||||
## Thanks
|
||||
|
||||
A *lot* of things have happened since the last release and I'd like to thank all contributors for their great support. I'd also like to thank those that have contributed by reporting bugs and by posting feature requests.
|
||||
|
||||
I'd also like to take this chance to say a special Thank You to a few people that have stood out in one way or another: To @iology, for contributing a multitude of bugfixes, improvements and new features. To @reima and @Detegr for their continuing great support. To @mmstick, for implementing the most advanced new feature of *fd*. And to @matematikaadit for the CI/tooling upgrades.
|
||||
|
||||
|
||||
# v4.0.0
|
||||
|
||||
## Features
|
||||
|
||||
* Added filtering by file extension, for example `fd -e txt`, see #56 (@reima)
|
||||
* Add option to force colored output: `--color always`, see #49 (@Detegr)
|
||||
* Generate Shell completions for Bash, ZSH, Fish and Powershell, see #64 (@ImbaKnugel)
|
||||
* Better & extended `--help` text (@abaez and @Detegr)
|
||||
* Proper Windows support, see #70
|
||||
|
||||
## Changes
|
||||
|
||||
* The integration tests have been re-written in Rust :sparkles:, making them platform-independent and easily callable via `cargo test` - see #65 (many thanks to @reima!)
|
||||
* New tutorial in the README (@deg4uss3r)
|
||||
* Reduced number of `stat` syscalls for each result from 3 to 1, see #36.
|
||||
* Enabled Appveyor CI
|
||||
|
||||
# v3.1.0
|
||||
|
||||
## Features
|
||||
- Added file type filtering, e.g. `find --type directory` or `find -t f` (@exitium)
|
||||
|
||||
# v3.0.0
|
||||
|
||||
## Features
|
||||
- Directories are now traversed in parallel, leading to significant performance improvements (see [benchmarks](https://github.com/sharkdp/fd#benchmark))
|
||||
- Added `--print0` option (@michaelmior)
|
||||
- Added AUR packages (@wezm)
|
||||
|
||||
## Changes
|
||||
- Changed short flag for `--follow` from `-f` to `-L` (consistency with `ripgrep`)
|
||||
|
||||
# v2.0.0
|
||||
|
||||
* Changed `--sensitive` to `--case-sensitive`
|
||||
* Changed `--absolute` to `--absolute-path`
|
||||
* Throw an error if root directory is not existent, see #39
|
||||
* Use absolute paths if the root dir is an absolute path, see #40
|
||||
* Handle invalid UTF-8, see #34 #38
|
||||
* Support `-V`, `--version` by switching from `getopts` to `clap`.
|
||||
|
||||
Misc:
|
||||
* It's now possible to install `fd` via homebrew on macOS: `brew install fd`.
|
||||
|
||||
# v1.1.0
|
||||
|
||||
- Windows compatibility (@sebasv), see #29 #35
|
||||
- Safely exit on broken output pipes (e.g.: usage with `head`, `tail`, ..), see #24
|
||||
- Backport for rust 1.16, see #23
|
||||
|
||||
# v1.0.0
|
||||
|
||||
* Respect `.(git)ignore` files
|
||||
* Use `LS_COLORS` environment variable directly, instead of `~/.dir_colors` file.
|
||||
* Added unit and integration tests
|
||||
* Added optional second argument (search path)
|
||||
|
||||
# v0.3.0
|
||||
|
||||
- Parse dircolors files, closes #20
|
||||
- Colorize each path component, closes #19
|
||||
- Add short command line option for --hidden, see #18
|
||||
|
||||
# v0.2.0
|
||||
|
||||
- Option to follow symlinks, disable colors, closes #16, closes #17
|
||||
- `--filename` instead of `--full-path`
|
||||
- Option to search hidden directories, closes #12
|
||||
- Configurable search depth, closes #13
|
||||
- Detect interactive terminal, closes #11
|
||||
|
||||
# v0.1.0
|
||||
|
||||
Initial release
|
||||
@@ -17,4 +17,3 @@ give us the chance to discuss any potential changes first.
|
||||
* [Development section in the README](https://github.com/sharkdp/fd#development)
|
||||
* [fd on crates.io](https://crates.io/crates/fd-find)
|
||||
* [LICENSE-APACHE](https://github.com/sharkdp/fd/blob/master/LICENSE-APACHE) and [LICENSE-MIT](https://github.com/sharkdp/fd/blob/master/LICENSE-MIT)
|
||||
|
||||
|
||||
Generated
+315
-228
@@ -1,9 +1,11 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.6.9"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11,116 +13,195 @@ name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.11"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.48 (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.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.4"
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.29"
|
||||
version = "1.0.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.6"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.32.0"
|
||||
version = "2.33.1"
|
||||
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.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.3.8"
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.6.5"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.1.1"
|
||||
version = "3.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "fd-find"
|
||||
version = "7.3.0"
|
||||
name = "dirs"
|
||||
version = "2.0.2"
|
||||
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.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"filetime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ignore 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lscolors 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fd-find"
|
||||
version = "8.1.1"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ctrlc 3.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"diff 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"filetime 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"humantime 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ignore 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jemallocator 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lscolors 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.17 (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.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.4"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -129,117 +210,138 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.2"
|
||||
name = "getrandom"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.9 (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.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.2.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.6"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 1.0.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.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
name = "jemalloc-sys"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jemallocator"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"jemalloc-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.2.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.48"
|
||||
version = "0.2.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.6"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lscolors"
|
||||
version = "0.5.0"
|
||||
version = "0.7.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)",
|
||||
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.2.0"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.11.0"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.10.0"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (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.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -247,12 +349,12 @@ name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.0"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -265,64 +367,65 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.51"
|
||||
version = "0.1.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.1"
|
||||
name = "redox_users"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.1.0"
|
||||
version = "1.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.5 (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.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.5"
|
||||
version = "0.6.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-argon2"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.4"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -331,77 +434,57 @@ version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term_size"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
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.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.6"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ucd-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.5"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "1.0.0"
|
||||
name = "users"
|
||||
version = "0.10.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)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.1.5"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -411,33 +494,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.2.7"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.6"
|
||||
version = "0.3.8"
|
||||
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)",
|
||||
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
@@ -445,10 +523,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.2"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -457,60 +535,69 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
|
||||
"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
||||
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
|
||||
"checksum cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)" = "4390a3b5f4f6bce9c1d0c00128379df433e53777fdd30e92f16a529332baec4e"
|
||||
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
|
||||
"checksum crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0ed1a4de2235cabda8558ff5840bffb97fcb64c97827f354a451307df5f72b"
|
||||
"checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c"
|
||||
"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.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a2df5c1a8c4be27e7707789dc42ae65976e60b394afd293d1419ab915833e646"
|
||||
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||
"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
"checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
|
||||
"checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||
"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
||||
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
||||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
"checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
|
||||
"checksum bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931"
|
||||
"checksum cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)" = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
|
||||
"checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
|
||||
"checksum ctrlc 3.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7a4ba686dff9fa4c1c9636ce1010b0cf98ceb421361b0bb3d6faeec43bd217a7"
|
||||
"checksum diff 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
|
||||
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
|
||||
"checksum filetime 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695"
|
||||
"checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
"checksum fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674"
|
||||
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
"checksum globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4743617a7464bbda3c8aec8558ff2f9429047e025771037df561d383337ff865"
|
||||
"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
|
||||
"checksum ignore 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ad03ca67dc12474ecd91fdb94d758cbd20cb4e7a78ebe831df26a9b7511e1162"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
|
||||
"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047"
|
||||
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
|
||||
"checksum lscolors 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9938fd8c379393454f73ec4c9c5b40f3d8332d80b25a29da05e41ee0ecbb559"
|
||||
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
|
||||
"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
|
||||
"checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba"
|
||||
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
||||
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
||||
"checksum globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120"
|
||||
"checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71"
|
||||
"checksum humantime 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9b6c53306532d3c8e8087b44e6580e10db51a023cf9b433cea2ac38066b92da"
|
||||
"checksum ignore 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)" = "128b9e89d15a3faa642ee164c998fd4fae3d89d054463cddb2c25a7baad3a352"
|
||||
"checksum jemalloc-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45"
|
||||
"checksum jemallocator 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)" = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
"checksum lscolors 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f77452267149eac960ded529fe5f5460ddf792845a1d71b5d0cfcee5642e47e"
|
||||
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||
"checksum nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
|
||||
"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
|
||||
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
|
||||
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||
"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f"
|
||||
"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861"
|
||||
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
|
||||
"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
|
||||
"checksum smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "88aea073965ab29f6edb5493faf96ad662fb18aa9eeb186a3b7057951605ed15"
|
||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
"checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431"
|
||||
"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
|
||||
"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
|
||||
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
||||
"checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
|
||||
"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||
"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327"
|
||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
||||
"checksum textwrap 0.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.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
|
||||
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
||||
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||
"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
|
||||
"checksum term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
|
||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
|
||||
"checksum users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486"
|
||||
"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
|
||||
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
|
||||
"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
+21
-8
@@ -16,24 +16,25 @@ license = "MIT/Apache-2.0"
|
||||
name = "fd-find"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/sharkdp/fd"
|
||||
version = "7.3.0"
|
||||
version = "8.1.1"
|
||||
edition= "2018"
|
||||
|
||||
[[bin]]
|
||||
name = "fd"
|
||||
path = "src/main.rs"
|
||||
[badges.appveyor]
|
||||
repository = "sharkdp/fd"
|
||||
|
||||
[badges.travis-ci]
|
||||
repository = "sharkdp/fd"
|
||||
|
||||
[[bin]]
|
||||
name = "fd"
|
||||
path = "src/main.rs"
|
||||
|
||||
[build-dependencies]
|
||||
clap = "2.31.2"
|
||||
version_check = "0.1.3"
|
||||
version_check = "0.9"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.11"
|
||||
ansi_term = "0.12"
|
||||
atty = "0.2"
|
||||
ignore = "0.4.3"
|
||||
lazy_static = "1.1.0"
|
||||
@@ -41,16 +42,28 @@ num_cpus = "1.8"
|
||||
regex = "1.0.0"
|
||||
regex-syntax = "0.6"
|
||||
ctrlc = "3.1"
|
||||
humantime = "1.1.1"
|
||||
lscolors = "0.5"
|
||||
humantime = "2.0"
|
||||
lscolors = "0.7"
|
||||
globset = "0.4"
|
||||
anyhow = "1.0"
|
||||
dirs = "2.0"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "2.31.2"
|
||||
features = ["suggestions", "color", "wrap_help"]
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
users = "0.10.0"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
# FIXME: Re-enable jemalloc on macOS
|
||||
# jemalloc is currently disabled on macOS due to a bug in jemalloc in combination with macOS
|
||||
# Catalina. See https://github.com/sharkdp/fd/issues/498 for details.
|
||||
[target.'cfg(all(not(windows), not(target_os = "macos"), not(target_env = "musl")))'.dependencies]
|
||||
jemallocator = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
diff = "0.1"
|
||||
tempdir = "0.3"
|
||||
|
||||
+1
-1
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017 fd developers
|
||||
Copyright 2017-2020 fd developers
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+19
-21
@@ -1,23 +1,21 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
MIT License
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
Copyright (c) 2017-2020 The fd developers
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# fd
|
||||
[](https://travis-ci.org/sharkdp/fd)
|
||||
[](https://ci.appveyor.com/project/sharkdp/fd)
|
||||
[](https://ci.appveyor.com/project/sharkdp/fd/branch/master)
|
||||
[](https://crates.io/crates/fd-find)
|
||||
[中文](https://github.com/chinanf-boy/fd-zh)
|
||||
[한국어](https://github.com/spearkkk/fd-kor)
|
||||
|
||||
*fd* is a simple, fast and user-friendly alternative to
|
||||
[*find*](https://www.gnu.org/software/findutils/).
|
||||
@@ -41,7 +42,7 @@ Let's start with `find`:
|
||||
Benchmark #1: find ~ -iregex '.*[0-9]\.jpg$'
|
||||
|
||||
Time (mean ± σ): 7.236 s ± 0.090 s
|
||||
|
||||
|
||||
Range (min … max): 7.133 s … 7.385 s
|
||||
```
|
||||
|
||||
@@ -50,7 +51,7 @@ Benchmark #1: find ~ -iregex '.*[0-9]\.jpg$'
|
||||
Benchmark #2: find ~ -iname '*[0-9].jpg'
|
||||
|
||||
Time (mean ± σ): 3.914 s ± 0.027 s
|
||||
|
||||
|
||||
Range (min … max): 3.876 s … 3.964 s
|
||||
```
|
||||
|
||||
@@ -61,7 +62,7 @@ otherwise `fd` does not have to traverse hidden folders and ignored paths (see b
|
||||
Benchmark #3: fd -HI '.*[0-9]\.jpg$' ~
|
||||
|
||||
Time (mean ± σ): 811.6 ms ± 26.9 ms
|
||||
|
||||
|
||||
Range (min … max): 786.0 ms … 870.7 ms
|
||||
```
|
||||
For this particular example, `fd` is approximately nine times faster than `find -iregex`
|
||||
@@ -75,13 +76,13 @@ folders, it is almost an order of magnitude faster:
|
||||
Benchmark #4: fd '[0-9]\.jpg$' ~
|
||||
|
||||
Time (mean ± σ): 123.7 ms ± 6.0 ms
|
||||
|
||||
|
||||
Range (min … max): 118.8 ms … 140.0 ms
|
||||
```
|
||||
|
||||
**Note**: This is *one particular* benchmark on *one particular* machine. While I have
|
||||
performed quite a lot of different tests (and found consistent results), things might
|
||||
be different for you! I encourage everyone to try it out on their own. See
|
||||
be different for you! I encourage everyone to try it out on their own. See
|
||||
[this repository](https://github.com/sharkdp/fd-benchmarks) for all necessary scripts.
|
||||
|
||||
Concerning *fd*'s speed, the main credit goes to the `regex` and `ignore` crates that are also used
|
||||
@@ -97,6 +98,8 @@ complete (and more colorful) variants, see
|
||||
[here](https://github.com/seebi/dircolors-solarized) or
|
||||
[here](https://github.com/trapd00r/LS_COLORS).
|
||||
|
||||
`fd` also honors the [`NO_COLOR`](https://no-color.org/) environment variable.
|
||||
|
||||
## Parallel command execution
|
||||
If the `-x`/`--exec` option is specified alongside a command template, a job pool will be created
|
||||
for executing commands in parallel for each discovered path as the input. The syntax for generating
|
||||
@@ -123,17 +126,29 @@ fd -e flac -x ffmpeg -i {} -c:a libopus {.}.opus
|
||||
fd -x wc -l \; -e rs
|
||||
```
|
||||
|
||||
The number of threads used for command execution can be set with the `--threads`/`-j` option.
|
||||
|
||||
## Installation
|
||||
|
||||
[](https://repology.org/project/fd-find/versions)
|
||||
|
||||
### On Ubuntu
|
||||
*... and other Debian-based Linux distributions.*
|
||||
|
||||
Download the latest `.deb` package from the [release page](https://github.com/sharkdp/fd/releases) and install it via:
|
||||
``` bash
|
||||
sudo dpkg -i fd_7.3.0_amd64.deb # adapt version number and architecture
|
||||
If you run Ubuntu 19.04 (Disco Dingo) or newer, you can install the
|
||||
[officially maintained package](https://packages.ubuntu.com/disco/fd-find):
|
||||
```
|
||||
sudo apt install fd-find
|
||||
```
|
||||
Note that the binary is called `fdfind` as the binary name `fd` is already used by another package.
|
||||
It is recommended that you add an `alias fd=fdfind` to your shells initialization file, in order to
|
||||
use `fd` in the same way as in this documentation.
|
||||
|
||||
Note: `fd` will be officially available in Ubuntu Disco Dingo (19.04).
|
||||
If you use an older version of Ubuntu, you can download the latest `.deb` package from the
|
||||
[release page](https://github.com/sharkdp/fd/releases) and install it via:
|
||||
``` bash
|
||||
sudo dpkg -i fd_8.1.0_amd64.deb # adapt version number and architecture
|
||||
```
|
||||
|
||||
### On Debian
|
||||
|
||||
@@ -159,6 +174,14 @@ dnf copr enable keefle/fd
|
||||
dnf install fd
|
||||
```
|
||||
|
||||
### On Alpine Linux
|
||||
|
||||
You can install [the fd package](https://pkgs.alpinelinux.org/packages?name=fd)
|
||||
from the official sources, provided you have the appropriate repository enabled:
|
||||
```
|
||||
apk add fd
|
||||
```
|
||||
|
||||
### On Arch Linux
|
||||
|
||||
You can install [the fd package](https://www.archlinux.org/packages/community/x86_64/fd/) from the official repos:
|
||||
@@ -188,7 +211,7 @@ xbps-install -S fd
|
||||
|
||||
### On macOS
|
||||
|
||||
You can install `fd` with [Homebrew](http://braumeister.org/formula/fd):
|
||||
You can install `fd` with [Homebrew](https://formulae.brew.sh/formula/fd):
|
||||
```
|
||||
brew install fd
|
||||
```
|
||||
@@ -221,9 +244,17 @@ nix-env -i fd
|
||||
|
||||
### On FreeBSD
|
||||
|
||||
You can install `sysutils/fd` via portmaster:
|
||||
You can install [the fd-find package](https://www.freshports.org/sysutils/fd) from the official repo:
|
||||
```
|
||||
portmaster sysutils/fd
|
||||
pkg install fd-find
|
||||
```
|
||||
|
||||
### From NPM
|
||||
|
||||
On linux and macOS, you can install the [fd-find](https://npm.im/fd-find) package:
|
||||
|
||||
```
|
||||
npm install -g fd-find
|
||||
```
|
||||
|
||||
### From source
|
||||
@@ -232,7 +263,7 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can
|
||||
```
|
||||
cargo install fd-find
|
||||
```
|
||||
Note that rust version *1.31.0* or later is required.
|
||||
Note that rust version *1.36.0* or later is required.
|
||||
|
||||
### From binaries
|
||||
|
||||
@@ -261,11 +292,11 @@ USAGE:
|
||||
FLAGS:
|
||||
-H, --hidden Search hidden files and directories
|
||||
-I, --no-ignore Do not respect .(git|fd)ignore files
|
||||
--no-ignore-vcs Do not respect .gitignore files
|
||||
-s, --case-sensitive Case-sensitive search (default: smart case)
|
||||
-i, --ignore-case Case-insensitive search (default: smart case)
|
||||
-F, --fixed-strings Treat the pattern as a literal string
|
||||
-g, --glob Glob-based search (default: regular expression)
|
||||
-a, --absolute-path Show absolute instead of relative paths
|
||||
-l, --list-details Use a long listing format with file metadata
|
||||
-L, --follow Follow symbolic links
|
||||
-p, --full-path Search full path (default: file-/dirname only)
|
||||
-0, --print0 Separate results by the null character
|
||||
@@ -275,7 +306,7 @@ FLAGS:
|
||||
OPTIONS:
|
||||
-d, --max-depth <depth> Set maximum search depth (default: none)
|
||||
-t, --type <filetype>... Filter by type: file (f), directory (d), symlink (l),
|
||||
executable (x), empty (e)
|
||||
executable (x), empty (e), socket (s), pipe (p)
|
||||
-e, --extension <ext>... Filter by file extension
|
||||
-x, --exec <cmd> Execute a command for each search result
|
||||
-X, --exec-batch <cmd> Execute a command with all search results at once
|
||||
@@ -286,7 +317,7 @@ OPTIONS:
|
||||
--changed-before <date|dur> Filter by file modification time (older than)
|
||||
|
||||
ARGS:
|
||||
<pattern> the search pattern, a regular expression (optional)
|
||||
<pattern> the search pattern - a regular expression unless '--glob' is used (optional)
|
||||
<path>... the root directory for the filesystem search (optional)
|
||||
```
|
||||
|
||||
@@ -422,6 +453,10 @@ To make exclude-patterns like these permanent, you can create a `.fdignore` file
|
||||
```
|
||||
Note: `fd` also supports `.ignore` files that are used by other programs such as `rg` or `ag`.
|
||||
|
||||
If you want `fd` to ignore these patterns globally, you can put them in `fd`'s global ignore file.
|
||||
This is usually located in `~/.config/fd/ignore` in macOS or Linux, and `%APPDATA%\fd\ignore` in
|
||||
Windows.
|
||||
|
||||
### Using fd with `xargs` or `parallel`
|
||||
|
||||
If we want to run a command on all search results, we can pipe the output to `xargs`:
|
||||
@@ -431,6 +466,28 @@ 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.
|
||||
|
||||
### Deleting files
|
||||
|
||||
You can use `fd` to remove all files and directories that are matched by your search pattern.
|
||||
If you only want to remove files, you can use the `--exec-batch`/`-X` option to call `rm`. For
|
||||
example, to recursively remove all `.DS_Store` files, run:
|
||||
``` bash
|
||||
> fd -H '^\.DS_Store$' -tf -X rm
|
||||
```
|
||||
If you are unsure, always call `fd` without `-X rm` first. Alternatively, use `rm`s "interactive"
|
||||
option:
|
||||
``` bash
|
||||
> fd -H '^\.DS_Store$' -tf -X rm -i
|
||||
```
|
||||
|
||||
If you also want to remove a certain class of directories, you can use the same technique. You will
|
||||
have to use `rm`s `--recursive`/`-r` flag to remove directories.
|
||||
|
||||
Note: there are scenarios where using `fd … -X rm -r` can cause race conditions: if you have a
|
||||
path like `…/foo/bar/foo/…` and want to remove all directories named `foo`, you can end up in a
|
||||
situation where the outer `foo` directory is removed first, leading to (harmless) *"'foo/bar/foo':
|
||||
No such file or directory"* errors in the `rm` call.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### `fd` does not find my file!
|
||||
@@ -495,3 +552,33 @@ After installing `find-file-in-project`, add the line `(setq ffip-use-rust-fd t)
|
||||
|
||||
In emacs, run `M-x find-file-in-project-by-selected` to find matching files. Alternatively, run
|
||||
`M-x find-file-in-project` to list all available files in the project.
|
||||
|
||||
#### Printing fd's output as a tree
|
||||
|
||||
To format the output of `fd` similar to the `tree` command, install [`as-tree`] and pipe the output
|
||||
of `fd` to `as-tree`:
|
||||
```bash
|
||||
fd | as-tree
|
||||
```
|
||||
|
||||
This can be more useful than running `tree` by itself because `tree` does not ignore any files by
|
||||
default, nor does it support as rich a set of options as `fd` does to control what to print:
|
||||
```bash
|
||||
❯ fd --extension rs | as-tree
|
||||
.
|
||||
├── build.rs
|
||||
└── src
|
||||
├── app.rs
|
||||
└── error.rs
|
||||
```
|
||||
|
||||
For more information about `as-tree`, see [the `as-tree` README][`as-tree`].
|
||||
|
||||
[`as-tree`]: https://github.com/jez/as-tree
|
||||
|
||||
## License
|
||||
Copyright (c) 2017-2020 The fd developers
|
||||
|
||||
`fd` is distributed under the terms of both the MIT License and the Apache License 2.0.
|
||||
|
||||
See the [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) files for license details.
|
||||
|
||||
+1
-24
@@ -13,24 +13,6 @@ environment:
|
||||
CHANNEL: stable
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: stable
|
||||
# Beta channel (disabled for speed reasons)
|
||||
#- TARGET: i686-pc-windows-gnu
|
||||
# CHANNEL: beta
|
||||
#- TARGET: i686-pc-windows-msvc
|
||||
# CHANNEL: beta
|
||||
#- TARGET: x86_64-pc-windows-gnu
|
||||
# CHANNEL: beta
|
||||
#- TARGET: x86_64-pc-windows-msvc
|
||||
# CHANNEL: beta
|
||||
# Nightly channel (disabled for speed reasons)
|
||||
#- TARGET: i686-pc-windows-gnu
|
||||
# CHANNEL: nightly
|
||||
#- TARGET: i686-pc-windows-msvc
|
||||
# CHANNEL: nightly
|
||||
#- TARGET: x86_64-pc-windows-gnu
|
||||
# CHANNEL: nightly
|
||||
#- TARGET: x86_64-pc-windows-msvc
|
||||
# CHANNEL: nightly
|
||||
|
||||
# Install Rust and Cargo
|
||||
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
||||
@@ -86,9 +68,4 @@ branches:
|
||||
- master
|
||||
# IMPORTANT Regex to match tags. Required, or appveyor may not trigger deploys when a new tag
|
||||
# is pushed. This regex matches semantic versions like v1.2.3-rc4+2016.02.22
|
||||
- /^v\d+\.\d+\.\d+.*$/
|
||||
|
||||
# Disable caching, for now
|
||||
#cache:
|
||||
# - '%USERPROFILE%\.cargo'
|
||||
# - 'target -> Cargo.lock'
|
||||
- /^v\d+\.\d+\.\d+.*$/
|
||||
@@ -1,25 +1,18 @@
|
||||
// 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::fs;
|
||||
|
||||
use clap::Shell;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::process::exit;
|
||||
|
||||
include!("src/app.rs");
|
||||
|
||||
fn main() {
|
||||
match version_check::is_min_version("1.31") {
|
||||
Some((true, _)) => {}
|
||||
let min_version = "1.36";
|
||||
|
||||
match version_check::is_min_version(min_version) {
|
||||
Some(true) => {}
|
||||
// rustc version too small or can't figure it out
|
||||
_ => {
|
||||
writeln!(&mut io::stderr(), "'fd' requires rustc >= 1.31").unwrap();
|
||||
exit(1);
|
||||
eprintln!("'fd' requires rustc >= {}", min_version);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+53
-2
@@ -55,13 +55,25 @@ make_deb() {
|
||||
local version
|
||||
local dpkgname
|
||||
local conflictname
|
||||
local homepage
|
||||
local maintainer
|
||||
local gcc_prefix
|
||||
|
||||
homepage="https://github.com/sharkdp/fd"
|
||||
maintainer="David Peter <mail@david-peter.de>"
|
||||
|
||||
case $TARGET in
|
||||
x86_64*)
|
||||
architecture=amd64
|
||||
gcc_prefix=""
|
||||
;;
|
||||
i686*)
|
||||
architecture=i386
|
||||
gcc_prefix=""
|
||||
;;
|
||||
arm*hf)
|
||||
architecture=armhf
|
||||
gcc_prefix="arm-linux-gnueabihf-"
|
||||
;;
|
||||
*)
|
||||
echo "make_deb: skipping target '${TARGET}'" >&2
|
||||
@@ -81,7 +93,7 @@ make_deb() {
|
||||
|
||||
# copy the main binary
|
||||
install -Dm755 "target/$TARGET/release/$PROJECT_NAME" "$tempdir/usr/bin/$PROJECT_NAME"
|
||||
strip "$tempdir/usr/bin/$PROJECT_NAME"
|
||||
"${gcc_prefix}"strip "$tempdir/usr/bin/$PROJECT_NAME"
|
||||
|
||||
# manpage
|
||||
install -Dm644 "doc/$PROJECT_NAME.1" "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
|
||||
@@ -91,6 +103,44 @@ make_deb() {
|
||||
install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md"
|
||||
install -Dm644 LICENSE-MIT "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-MIT"
|
||||
install -Dm644 LICENSE-APACHE "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-APACHE"
|
||||
cat > "$tempdir/usr/share/doc/$PROJECT_NAME/copyright" <<EOF
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: $PROJECT_NAME
|
||||
Source: $homepage
|
||||
|
||||
Files: *
|
||||
Copyright: $maintainer
|
||||
License: Apache-2.0 or MIT
|
||||
|
||||
License: Apache-2.0
|
||||
On Debian systems, the complete text of the Apache-2.0 can be found in the
|
||||
file /usr/share/common-licenses/Apache-2.0.
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
EOF
|
||||
|
||||
# completions
|
||||
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.bash "$tempdir/usr/share/bash-completion/completions/${PROJECT_NAME}"
|
||||
@@ -104,10 +154,11 @@ Package: $dpkgname
|
||||
Version: $version
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: David Peter <mail@david-peter.de>
|
||||
Maintainer: $maintainer
|
||||
Architecture: $architecture
|
||||
Provides: $PROJECT_NAME
|
||||
Conflicts: $conflictname
|
||||
Homepage: $homepage
|
||||
Description: Simple, fast and user-friendly alternative to find
|
||||
While fd does not seek to mirror all of find's powerful functionality, it
|
||||
provides sensible (opinionated) defaults for 80% of the use cases.
|
||||
|
||||
@@ -33,10 +33,12 @@ Include hidden files and directories in the search results
|
||||
.B \-I, \-\-no\-ignore
|
||||
Show search results from files and directories that would otherwise be ignored by
|
||||
.IR .gitignore ,
|
||||
.I .ignore
|
||||
or
|
||||
.I .fdignore
|
||||
files.
|
||||
.IR .ignore ,
|
||||
.IR .fdignore ,
|
||||
or the global ignore file.
|
||||
.TP
|
||||
.B \-u, \-\-unrestricted
|
||||
Alias for '--no-ignore'. Can be repeated; '-uu' is an alias for '--no-ignore --hidden'.
|
||||
.TP
|
||||
.B \-\-no\-ignore\-vcs
|
||||
Show search results from files and directories that would otherwise be ignored by
|
||||
@@ -51,12 +53,24 @@ pattern contains an uppercase character (smart case).
|
||||
Perform a case-insensitive search. By default, fd uses case-insensitive searches, unless the
|
||||
pattern contains an uppercase character (smart case).
|
||||
.TP
|
||||
.B \-g, \-\-glob
|
||||
Perform a glob-based search instead of a regular expression search.
|
||||
.TP
|
||||
.B \-\-regex
|
||||
Perform a regular-expression based seach (default). This can be used to override --glob.
|
||||
.TP
|
||||
.B \-F, \-\-fixed\-strings
|
||||
Treat the pattern as a literal string instead of a regular expression.
|
||||
.TP
|
||||
.B \-a, \-\-absolute\-path
|
||||
Shows the full path starting from the root as opposed to relative paths.
|
||||
.TP
|
||||
.B \-l, \-\-list\-details
|
||||
Use a detailed listing format like 'ls -l'. This is basically an alias
|
||||
for '--exec-batch ls -l' with some additional 'ls' options. This can be used
|
||||
to see more metadata, to show symlink targets and to achieve a deterministic
|
||||
sort order.
|
||||
.TP
|
||||
.B \-L, \-\-follow
|
||||
By default, fd does not descend into symlinked directories. Using this flag, symbolic links are
|
||||
also traversed.
|
||||
@@ -71,10 +85,19 @@ is matched against the full path.
|
||||
Separate search results by the null character (instead of newlines). Useful for piping results to
|
||||
.IR xargs .
|
||||
.TP
|
||||
.B \-\-max\-results count
|
||||
Limit the number of search results to 'count' and quit immediately.
|
||||
.TP
|
||||
.B \-1
|
||||
Limit the search to a single result and quit immediately. This is an alias for '--max-results=1'.
|
||||
.TP
|
||||
.B \-\-show-errors
|
||||
Enable the display of filesystem errors for situations such as insufficient
|
||||
permissions or dead symlinks.
|
||||
.TP
|
||||
.B \-\-one\-file\-system, \-\-mount, \-\-xdev
|
||||
By default, fd will traverse the file system tree as far as other options dictate. With this flag, fd ensures that it does not descend into a different file system than the one it started in. Comparable to the -mount or -xdev filters of find(1).
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Print help information.
|
||||
.TP
|
||||
@@ -86,12 +109,18 @@ Limit directory traversal to at most
|
||||
.I d
|
||||
levels of depth. By default, there is no limit on the search depth.
|
||||
.TP
|
||||
.BI "\-\-min\-depth " d
|
||||
Only show search results starting at the given depth. See also: '--max-depth' and '--exact-depth'.
|
||||
.TP
|
||||
.BI "\-\-exact\-depth " d
|
||||
Only show search results at the exact given depth. This is an alias for '--min-depth <depth> --max-depth <depth>'.
|
||||
.TP
|
||||
.BI "\-t, \-\-type " filetype
|
||||
Filter search by type:
|
||||
.RS
|
||||
.IP "f, file"
|
||||
regular files
|
||||
.IP "d, directories"
|
||||
.IP "d, directory"
|
||||
directories
|
||||
.IP "l, symlink"
|
||||
symbolic links
|
||||
@@ -99,6 +128,10 @@ symbolic links
|
||||
executable (files)
|
||||
.IP "e, empty"
|
||||
empty files or directories
|
||||
.IP "s, socket"
|
||||
sockets
|
||||
.IP "p, pipe"
|
||||
named pipes (FIFOs)
|
||||
.RE
|
||||
|
||||
.RS
|
||||
@@ -133,7 +166,7 @@ Always colorize output.
|
||||
.RE
|
||||
.TP
|
||||
.BI "\-j, \-\-threads " num
|
||||
Number of threads to use for searching (default: number of available CPUs).
|
||||
Set number of threads to use for searching & executing (default: number of available CPU cores).
|
||||
.TP
|
||||
.BI "\-S, \-\-size " size
|
||||
Limit results based on the size of files using the format
|
||||
@@ -152,7 +185,7 @@ Allowed unit values:
|
||||
.IP 'b'
|
||||
bytes
|
||||
.IP 'k'
|
||||
kilobytes
|
||||
kilobytes (base ten, 10^3 = 1000 bytes)
|
||||
.IP 'm'
|
||||
megabytes
|
||||
.IP 'g'
|
||||
@@ -160,7 +193,7 @@ gigabytes
|
||||
.IP 't'
|
||||
terabytes
|
||||
.IP 'ki'
|
||||
kibibytes
|
||||
kibibytes (base two, 2^10 = 1024 bytes)
|
||||
.IP 'mi'
|
||||
mebibytes
|
||||
.IP 'gi'
|
||||
@@ -190,6 +223,15 @@ Examples:
|
||||
\-\-changed-before "2018-10-27 10:00:00"
|
||||
\-\-change-older-than 2weeks
|
||||
.TP
|
||||
.BI "-o, \-\-owner " [user][:group]
|
||||
Filter files by their user and/or group. Format: [(user|uid)][:(group|gid)]. Either side
|
||||
is optional. Precede either side with a '!' to exclude files instead.
|
||||
|
||||
Examples:
|
||||
\-\-owner john
|
||||
\-\-owner :students
|
||||
\-\-owner "!john:students"
|
||||
.TP
|
||||
.BI "\-x, \-\-exec " command
|
||||
Execute
|
||||
.I command
|
||||
@@ -224,11 +266,29 @@ path without file extension
|
||||
.IP {/.}
|
||||
basename without file extension
|
||||
.RE
|
||||
.SH PATTERN SYNTAX
|
||||
The regular expression syntax used by fd is documented here:
|
||||
|
||||
.UR https://docs.rs/regex/1.0.0/regex/#syntax
|
||||
.UE
|
||||
|
||||
The glob syntax is documented here:
|
||||
|
||||
.UR https://docs.rs/globset/#syntax
|
||||
.UE
|
||||
.SH ENVIRONMENT
|
||||
.TP
|
||||
.B LS_COLORS
|
||||
Determines how to colorize search results, see
|
||||
.BR dircolors (1) .
|
||||
.TP
|
||||
.B XDG_CONFIG_HOME, HOME
|
||||
Used to locate the global ignore file. If
|
||||
.B XDG_CONFIG_HOME
|
||||
is set, use
|
||||
.IR $XDG_CONFIG_HOME/fd/ignore .
|
||||
Otherwise, use
|
||||
.IR $HOME/.config/fd/ignore .
|
||||
.SH EXAMPLES
|
||||
.TP
|
||||
.RI "Find files and directories that match the pattern '" needle "':"
|
||||
|
||||
+439
-246
@@ -1,92 +1,222 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
use std::collections::HashMap;
|
||||
|
||||
use clap::{crate_version, App, AppSettings, Arg};
|
||||
|
||||
struct Help {
|
||||
short: &'static str,
|
||||
long: &'static str,
|
||||
}
|
||||
|
||||
macro_rules! doc {
|
||||
($map:expr, $name:expr, $short:expr) => {
|
||||
doc!($map, $name, $short, $short)
|
||||
};
|
||||
($map:expr, $name:expr, $short:expr, $long:expr) => {
|
||||
$map.insert(
|
||||
$name,
|
||||
Help {
|
||||
short: $short,
|
||||
long: concat!($long, "\n "),
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn build_app() -> App<'static, 'static> {
|
||||
let helps = usage();
|
||||
let arg = |name| {
|
||||
Arg::with_name(name)
|
||||
.help(helps[name].short)
|
||||
.long_help(helps[name].long)
|
||||
};
|
||||
|
||||
App::new("fd")
|
||||
let mut app = App::new("fd")
|
||||
.version(crate_version!())
|
||||
.usage("fd [FLAGS/OPTIONS] [<pattern>] [<path>...]")
|
||||
.setting(AppSettings::ColoredHelp)
|
||||
.setting(AppSettings::DeriveDisplayOrder)
|
||||
.after_help(
|
||||
"Note: `fd -h` prints a short and concise overview while `fd --help` \
|
||||
gives all details.",
|
||||
"Note: `fd -h` prints a short and concise overview while `fd --help` gives all \
|
||||
details.",
|
||||
)
|
||||
.arg(arg("hidden").long("hidden").short("H"))
|
||||
.arg(arg("no-ignore").long("no-ignore").short("I"))
|
||||
.arg(arg("no-ignore-vcs").long("no-ignore-vcs"))
|
||||
.arg(
|
||||
arg("rg-alias-hidden-ignore")
|
||||
Arg::with_name("hidden")
|
||||
.long("hidden")
|
||||
.short("H")
|
||||
.overrides_with("hidden")
|
||||
.help("Search hidden files and directories")
|
||||
.long_help(
|
||||
"Include hidden directories and files in the search results (default: \
|
||||
hidden files and directories are skipped). Files and directories are \
|
||||
considered to be hidden if their name starts with a `.` sign (dot).",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-ignore")
|
||||
.long("no-ignore")
|
||||
.short("I")
|
||||
.overrides_with("no-ignore")
|
||||
.help("Do not respect .(git|fd)ignore files")
|
||||
.long_help(
|
||||
"Show search results from files and directories that would otherwise be \
|
||||
ignored by '.gitignore', '.ignore', '.fdignore', or the global ignore file.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-ignore-vcs")
|
||||
.long("no-ignore-vcs")
|
||||
.overrides_with("no-ignore-vcs")
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"Show search results from files and directories that would otherwise be \
|
||||
ignored by '.gitignore' files.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-global-ignore-file")
|
||||
.long("no-global-ignore-file")
|
||||
.hidden(true)
|
||||
.long_help("Do not respect the global ignore file."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rg-alias-hidden-ignore")
|
||||
.short("u")
|
||||
.long("unrestricted")
|
||||
.multiple(true)
|
||||
.hidden(true),
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"Alias for '--no-ignore'. Can be repeated. '-uu' is an alias for \
|
||||
'--no-ignore --hidden'.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("case-sensitive")
|
||||
Arg::with_name("case-sensitive")
|
||||
.long("case-sensitive")
|
||||
.short("s")
|
||||
.overrides_with("ignore-case"),
|
||||
.overrides_with_all(&["ignore-case", "case-sensitive"])
|
||||
.help("Case-sensitive search (default: smart case)")
|
||||
.long_help(
|
||||
"Perform a case-sensitive search. By default, fd uses case-insensitive \
|
||||
searches, unless the pattern contains an uppercase character (smart \
|
||||
case).",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("ignore-case")
|
||||
Arg::with_name("ignore-case")
|
||||
.long("ignore-case")
|
||||
.short("i")
|
||||
.overrides_with("case-sensitive"),
|
||||
.overrides_with_all(&["case-sensitive", "ignore-case"])
|
||||
.help("Case-insensitive search (default: smart case)")
|
||||
.long_help(
|
||||
"Perform a case-insensitive search. By default, fd uses case-insensitive \
|
||||
searches, unless the pattern contains an uppercase character (smart \
|
||||
case).",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("fixed-strings")
|
||||
Arg::with_name("glob")
|
||||
.long("glob")
|
||||
.short("g")
|
||||
.conflicts_with("fixed-strings")
|
||||
.overrides_with("glob")
|
||||
.help("Glob-based search (default: regular expression)")
|
||||
.long_help("Perform a glob-based search instead of a regular expression search."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("regex")
|
||||
.long("regex")
|
||||
.overrides_with_all(&["glob", "regex"])
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"Perform a regular-expression based search (default). This can be used to \
|
||||
override --glob.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("fixed-strings")
|
||||
.long("fixed-strings")
|
||||
.short("F")
|
||||
.alias("literal"),
|
||||
.alias("literal")
|
||||
.overrides_with("fixed-strings")
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"Treat the pattern as a literal string instead of a regular expression.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("absolute-path")
|
||||
.long("absolute-path")
|
||||
.short("a")
|
||||
.overrides_with("absolute-path")
|
||||
.help("Show absolute instead of relative paths")
|
||||
.long_help(
|
||||
"Shows the full path starting from the root as opposed to relative paths.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("list-details")
|
||||
.long("list-details")
|
||||
.short("l")
|
||||
.conflicts_with("absolute-path")
|
||||
.help("Use a long listing format with file metadata")
|
||||
.long_help(
|
||||
"Use a detailed listing format like 'ls -l'. This is basically an alias \
|
||||
for '--exec-batch ls -l' with some additional 'ls' options. This can be \
|
||||
used to see more metadata, to show symlink targets and to achieve a \
|
||||
deterministic sort order.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("follow")
|
||||
.long("follow")
|
||||
.short("L")
|
||||
.alias("dereference")
|
||||
.overrides_with("follow")
|
||||
.help("Follow symbolic links")
|
||||
.long_help(
|
||||
"By default, fd does not descend into symlinked directories. Using this \
|
||||
flag, symbolic links are also traversed.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("full-path")
|
||||
.long("full-path")
|
||||
.short("p")
|
||||
.overrides_with("full-path")
|
||||
.help("Search full path (default: file-/dirname only)")
|
||||
.long_help(
|
||||
"By default, the search pattern is only matched against the filename (or \
|
||||
directory name). Using this flag, the pattern is matched against the \
|
||||
full path.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("null_separator")
|
||||
.long("print0")
|
||||
.short("0")
|
||||
.overrides_with("print0")
|
||||
.conflicts_with("list-details")
|
||||
.help("Separate results by the null character")
|
||||
.long_help(
|
||||
"Separate search results by the null character (instead of newlines). \
|
||||
Useful for piping results to 'xargs'.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("max-depth")
|
||||
.long("max-depth")
|
||||
.short("d")
|
||||
.takes_value(true)
|
||||
.value_name("depth")
|
||||
.help("Set maximum search depth (default: none)")
|
||||
.long_help(
|
||||
"Limit the directory traversal to a given depth. By default, there is no \
|
||||
limit on the search depth.",
|
||||
),
|
||||
)
|
||||
.arg(arg("absolute-path").long("absolute-path").short("a"))
|
||||
.arg(arg("follow").long("follow").short("L").alias("dereference"))
|
||||
.arg(arg("full-path").long("full-path").short("p"))
|
||||
.arg(arg("null_separator").long("print0").short("0"))
|
||||
.arg(arg("depth").long("max-depth").short("d").takes_value(true))
|
||||
// support --maxdepth as well, for compatibility with rg
|
||||
.arg(
|
||||
arg("rg-depth")
|
||||
Arg::with_name("rg-depth")
|
||||
.long("maxdepth")
|
||||
.hidden(true)
|
||||
.takes_value(true),
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
arg("file-type")
|
||||
Arg::with_name("min-depth")
|
||||
.long("min-depth")
|
||||
.takes_value(true)
|
||||
.value_name("depth")
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"Only show search results starting at the given depth. \
|
||||
See also: '--max-depth' and '--exact-depth'",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("exact-depth")
|
||||
.long("exact-depth")
|
||||
.takes_value(true)
|
||||
.value_name("depth")
|
||||
.hidden_short_help(true)
|
||||
.conflicts_with_all(&["max-depth", "min-depth"])
|
||||
.long_help(
|
||||
"Only show search results at the exact given depth. This is an alias for \
|
||||
'--min-depth <depth> --max-depth <depth>'.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("file-type")
|
||||
.long("type")
|
||||
.short("t")
|
||||
.multiple(true)
|
||||
@@ -104,276 +234,339 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
"executable",
|
||||
"e",
|
||||
"empty",
|
||||
"s",
|
||||
"socket",
|
||||
"p",
|
||||
"pipe",
|
||||
])
|
||||
.hide_possible_values(true),
|
||||
.hide_possible_values(true)
|
||||
.help(
|
||||
"Filter by type: file (f), directory (d), symlink (l),\nexecutable (x), \
|
||||
empty (e), socket (s), pipe (p)",
|
||||
)
|
||||
.long_help(
|
||||
"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\n \
|
||||
'e' or 'empty': empty files or directories\n \
|
||||
's' or 'socket': socket\n \
|
||||
'p' or 'pipe': named pipe (FIFO)",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("extension")
|
||||
Arg::with_name("extension")
|
||||
.long("extension")
|
||||
.short("e")
|
||||
.multiple(true)
|
||||
.number_of_values(1)
|
||||
.takes_value(true)
|
||||
.value_name("ext"),
|
||||
.value_name("ext")
|
||||
.help("Filter by file extension")
|
||||
.long_help(
|
||||
"(Additionally) filter search results by their file extension. Multiple \
|
||||
allowable file extensions can be specified.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("exec")
|
||||
Arg::with_name("exec")
|
||||
.long("exec")
|
||||
.short("x")
|
||||
.min_values(1)
|
||||
.allow_hyphen_values(true)
|
||||
.value_terminator(";")
|
||||
.value_name("cmd"),
|
||||
.value_name("cmd")
|
||||
.conflicts_with("list-details")
|
||||
.help("Execute a command for each search result")
|
||||
.long_help(
|
||||
"Execute a command for each search result.\n\
|
||||
All arguments following --exec are taken to be arguments to the command until the \
|
||||
argument ';' is encountered.\n\
|
||||
Each occurrence of the following placeholders is substituted by a path derived from the \
|
||||
current search result before the command is executed:\n \
|
||||
'{}': path\n \
|
||||
'{/}': basename\n \
|
||||
'{//}': parent directory\n \
|
||||
'{.}': path without file extension\n \
|
||||
'{/.}': basename without file extension",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("exec-batch")
|
||||
Arg::with_name("exec-batch")
|
||||
.long("exec-batch")
|
||||
.short("X")
|
||||
.min_values(1)
|
||||
.allow_hyphen_values(true)
|
||||
.value_terminator(";")
|
||||
.value_name("cmd")
|
||||
.conflicts_with("exec"),
|
||||
.conflicts_with_all(&["exec", "list-details"])
|
||||
.help("Execute a command with all search results at once")
|
||||
.long_help(
|
||||
"Execute a command with all search results at once.\n\
|
||||
All arguments following --exec-batch are taken to be arguments to the command until the \
|
||||
argument ';' is encountered.\n\
|
||||
A single occurrence of the following placeholders is authorized and substituted by the paths derived from the \
|
||||
search results before the command is executed:\n \
|
||||
'{}': path\n \
|
||||
'{/}': basename\n \
|
||||
'{//}': parent directory\n \
|
||||
'{.}': path without file extension\n \
|
||||
'{/.}': basename without file extension",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("exclude")
|
||||
Arg::with_name("exclude")
|
||||
.long("exclude")
|
||||
.short("E")
|
||||
.takes_value(true)
|
||||
.value_name("pattern")
|
||||
.number_of_values(1)
|
||||
.multiple(true),
|
||||
.multiple(true)
|
||||
.help("Exclude entries that match the given glob pattern")
|
||||
.long_help(
|
||||
"Exclude files/directories that match the given glob pattern. This \
|
||||
overrides any other ignore logic. Multiple exclude patterns can be \
|
||||
specified.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("ignore-file")
|
||||
Arg::with_name("ignore-file")
|
||||
.long("ignore-file")
|
||||
.takes_value(true)
|
||||
.value_name("path")
|
||||
.number_of_values(1)
|
||||
.multiple(true)
|
||||
.hidden_short_help(true),
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"Add a custom ignore-file in '.gitignore' format. These files have a low \
|
||||
precedence.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("color")
|
||||
Arg::with_name("color")
|
||||
.long("color")
|
||||
.short("c")
|
||||
.takes_value(true)
|
||||
.value_name("when")
|
||||
.possible_values(&["never", "auto", "always"])
|
||||
.hide_possible_values(true),
|
||||
.hide_possible_values(true)
|
||||
.help("When to use colors: never, *auto*, always")
|
||||
.long_help(
|
||||
"Declare when to use color for the pattern match output:\n \
|
||||
'auto': show colors if the output goes to an interactive console (default)\n \
|
||||
'never': do not use colorized output\n \
|
||||
'always': always use colorized output",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("threads")
|
||||
Arg::with_name("threads")
|
||||
.long("threads")
|
||||
.short("j")
|
||||
.takes_value(true)
|
||||
.value_name("num")
|
||||
.hidden_short_help(true),
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"Set number of threads to use for searching & executing (default: number \
|
||||
of available CPU cores)",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("size")
|
||||
Arg::with_name("size")
|
||||
.long("size")
|
||||
.short("S")
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.allow_hyphen_values(true)
|
||||
.multiple(true),
|
||||
.multiple(true)
|
||||
.help("Limit results based on the size of files.")
|
||||
.long_help(
|
||||
"Limit results based on the size of files using the format <+-><NUM><UNIT>.\n \
|
||||
'+': file size must be greater than or equal to this\n \
|
||||
'-': 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 (base ten, 10^3 = 1000 bytes)\n \
|
||||
'm': megabytes\n \
|
||||
'g': gigabytes\n \
|
||||
't': terabytes\n \
|
||||
'ki': kibibytes (base two, 2^10 = 1024 bytes)\n \
|
||||
'mi': mebibytes\n \
|
||||
'gi': gibibytes\n \
|
||||
'ti': tebibytes",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("max-buffer-time")
|
||||
Arg::with_name("max-buffer-time")
|
||||
.long("max-buffer-time")
|
||||
.takes_value(true)
|
||||
.hidden(true),
|
||||
.hidden(true)
|
||||
.long_help(
|
||||
"Amount of time in milliseconds to buffer, before streaming the search \
|
||||
results to the console.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("changed-within")
|
||||
Arg::with_name("changed-within")
|
||||
.long("changed-within")
|
||||
.alias("change-newer-than")
|
||||
.takes_value(true)
|
||||
.value_name("date|dur")
|
||||
.number_of_values(1),
|
||||
.number_of_values(1)
|
||||
.help("Filter by file modification time (newer than)")
|
||||
.long_help(
|
||||
"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'",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("changed-before")
|
||||
Arg::with_name("changed-before")
|
||||
.long("changed-before")
|
||||
.alias("change-older-than")
|
||||
.takes_value(true)
|
||||
.value_name("date|dur")
|
||||
.number_of_values(1),
|
||||
.number_of_values(1)
|
||||
.help("Filter by file modification time (older than)")
|
||||
.long_help(
|
||||
"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",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
arg("show-errors")
|
||||
Arg::with_name("max-results")
|
||||
.long("max-results")
|
||||
.takes_value(true)
|
||||
.value_name("count")
|
||||
// We currently do not support --max-results in combination with
|
||||
// program execution because the results that come up in a --max-results
|
||||
// search are non-deterministic. Users might think that they can run the
|
||||
// same search with `--exec rm` attached and get a reliable removal of
|
||||
// the files they saw in the previous search.
|
||||
.conflicts_with_all(&["exec", "exec-batch", "list-details"])
|
||||
.hidden_short_help(true)
|
||||
.long_help("Limit the number of search results to 'count' and quit immediately."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("max-one-result")
|
||||
.short("1")
|
||||
.hidden_short_help(true)
|
||||
.overrides_with("max-results")
|
||||
.conflicts_with_all(&["exec", "exec-batch", "list-details"])
|
||||
.long_help("Limit the search to a single result and quit immediately. \
|
||||
This is an alias for '--max-results=1'.")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("show-errors")
|
||||
.long("show-errors")
|
||||
.hidden_short_help(true),
|
||||
.hidden_short_help(true)
|
||||
.overrides_with("show-errors")
|
||||
.long_help(
|
||||
"Enable the display of filesystem errors for situations such as \
|
||||
insufficient permissions or dead symlinks.",
|
||||
),
|
||||
)
|
||||
.arg(arg("pattern"))
|
||||
.arg(arg("path").multiple(true))
|
||||
.arg(
|
||||
arg("search-path")
|
||||
Arg::with_name("base-directory")
|
||||
.long("base-directory")
|
||||
.takes_value(true)
|
||||
.value_name("path")
|
||||
.number_of_values(1)
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"Change the current working directory of fd to the provided path. The \
|
||||
means that search results will be shown with respect to the given base \
|
||||
path. Note that relative paths which are passed to fd via the positional \
|
||||
<path> argument or the '--search-path' option will also be resolved \
|
||||
relative to this directory.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("pattern").help(
|
||||
"the search pattern - a regular expression unless '--glob' is used (optional)",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("path-separator")
|
||||
.takes_value(true)
|
||||
.value_name("separator")
|
||||
.long("path-separator")
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"Set the path separator to use when printing file paths. The default is \
|
||||
the OS-specific separator ('/' on Unix, '\\' on Windows).",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("path")
|
||||
.multiple(true)
|
||||
.help("the root directory for the filesystem search (optional)")
|
||||
.long_help(
|
||||
"The directory where the filesystem search is rooted (optional). If \
|
||||
omitted, search the current working directory.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("search-path")
|
||||
.long("search-path")
|
||||
.takes_value(true)
|
||||
.conflicts_with("path")
|
||||
.multiple(true)
|
||||
.hidden_short_help(true)
|
||||
.number_of_values(1),
|
||||
)
|
||||
}
|
||||
.number_of_values(1)
|
||||
.long_help(
|
||||
"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>]`",
|
||||
),
|
||||
);
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn usage() -> HashMap<&'static str, Help> {
|
||||
let mut h = HashMap::new();
|
||||
doc!(h, "hidden"
|
||||
, "Search hidden files and directories"
|
||||
, "Include hidden directories and files in the search results (default: hidden files \
|
||||
and directories are skipped). Files and directories are considered to be hidden if \
|
||||
their name starts with a `.` sign (dot).");
|
||||
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', '.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 \
|
||||
'.gitignore' files.");
|
||||
doc!(h, "case-sensitive"
|
||||
, "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).");
|
||||
doc!(h, "ignore-case"
|
||||
, "Case-insensitive search (default: smart case)"
|
||||
, "Perform a case-insensitive search. By default, fd uses case-insensitive searches, \
|
||||
unless the pattern contains an uppercase character (smart case).");
|
||||
doc!(h, "fixed-strings"
|
||||
, "Treat the pattern as a literal string"
|
||||
, "Treat the pattern as a literal string instead of a regular expression.");
|
||||
doc!(h, "absolute-path"
|
||||
, "Show absolute instead of relative paths"
|
||||
, "Shows the full path starting from the root as opposed to relative paths.");
|
||||
doc!(h, "follow"
|
||||
, "Follow symbolic links"
|
||||
, "By default, fd does not descend into symlinked directories. Using this flag, symbolic \
|
||||
links are also traversed.");
|
||||
doc!(h, "full-path"
|
||||
, "Search full path (default: file-/dirname only)"
|
||||
, "By default, the search pattern is only matched against the filename (or directory \
|
||||
name). Using this flag, the pattern is matched against the full path.");
|
||||
doc!(h, "null_separator"
|
||||
, "Separate results by the null character"
|
||||
, "Separate search results by the null character (instead of newlines). Useful for \
|
||||
piping results to 'xargs'.");
|
||||
doc!(h, "depth"
|
||||
, "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), 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\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 \
|
||||
extensions can be specified.");
|
||||
doc!(h, "exec"
|
||||
, "Execute a command for each search result"
|
||||
, "Execute a command for each search result.\n\
|
||||
All arguments following --exec are taken to be arguments to the command until the \
|
||||
argument ';' is encountered.\n\
|
||||
Each occurrence of the following placeholders is substituted by a path derived from the \
|
||||
current search result before the command is executed:\n \
|
||||
'{}': path\n \
|
||||
'{/}': basename\n \
|
||||
'{//}': parent directory\n \
|
||||
'{.}': path without file extension\n \
|
||||
'{/.}': basename without file extension");
|
||||
doc!(h, "exec-batch"
|
||||
, "Execute a command with all search results at once"
|
||||
, "Execute a command with all search results at once.\n\
|
||||
All arguments following --exec-batch are taken to be arguments to the command until the \
|
||||
argument ';' is encountered.\n\
|
||||
A single occurence of the following placeholders is authorized and substituted by the paths derived from the \
|
||||
search results before the command is executed:\n \
|
||||
'{}': path\n \
|
||||
'{/}': basename\n \
|
||||
'{//}': parent directory\n \
|
||||
'{.}': path without file extension\n \
|
||||
'{/.}': basename without file extension");
|
||||
doc!(h, "exclude"
|
||||
, "Exclude entries that match the given glob pattern"
|
||||
, "Exclude files/directories that match the given glob pattern. This overrides any \
|
||||
other ignore logic. Multiple exclude patterns can be specified.");
|
||||
doc!(h, "ignore-file"
|
||||
, "Add a custom ignore-file in .gitignore format"
|
||||
, "Add a custom ignore-file in '.gitignore' format. These files have a low precedence.");
|
||||
doc!(h, "color"
|
||||
, "When to use colors: never, *auto*, always"
|
||||
, "Declare when to use color for the pattern match output:\n \
|
||||
'auto': show colors if the output goes to an interactive console (default)\n \
|
||||
'never': do not use colorized output\n \
|
||||
'always': always use colorized output");
|
||||
doc!(h, "threads"
|
||||
, "Set number of threads to use for searching & executing"
|
||||
, "Set number of threads to use for searching & executing (default: number of available \
|
||||
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 \
|
||||
the console.");
|
||||
doc!(h, "pattern"
|
||||
, "the search pattern, a regular expression (optional)");
|
||||
doc!(h, "path"
|
||||
, "the root directory for the filesystem search (optional)"
|
||||
, "The directory where the filesystem search is rooted (optional). \
|
||||
If omitted, search the current working directory.");
|
||||
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
|
||||
if cfg!(unix) {
|
||||
app = app.arg(
|
||||
Arg::with_name("owner")
|
||||
.long("owner")
|
||||
.short("o")
|
||||
.takes_value(true)
|
||||
.value_name("user:group")
|
||||
.help("Filter by owning user and/or group")
|
||||
.long_help(
|
||||
"Filter files by their user and/or group. \
|
||||
Format: [(user|uid)][:(group|gid)]. Either side is optional. \
|
||||
Precede either side with a '!' to exclude files instead.\n\
|
||||
Examples:\n \
|
||||
--owner john\n \
|
||||
--owner :students\n \
|
||||
--owner '!john:students'",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Make `--one-file-system` available only on Unix and Windows platforms, as per the
|
||||
// restrictions on the corresponding option in the `ignore` crate.
|
||||
// Provide aliases `mount` and `xdev` for people coming from `find`.
|
||||
if cfg!(any(unix, windows)) {
|
||||
app = app.arg(
|
||||
Arg::with_name("one-file-system")
|
||||
.long("one-file-system")
|
||||
.aliases(&["mount", "xdev"])
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"By default, fd will traverse the file system tree as far as other options \
|
||||
dictate. With this flag, fd ensures that it does not descend into a \
|
||||
different file system than the one it started in. Comparable to the -mount \
|
||||
or -xdev filters of find(1).",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
app
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn print_error(msg: impl Into<String>) {
|
||||
eprintln!("[fd error]: {}", msg.into());
|
||||
}
|
||||
+14
-11
@@ -1,18 +1,13 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::error::print_error;
|
||||
use crate::exit_codes::ExitCode;
|
||||
|
||||
/// Executes a command.
|
||||
pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) {
|
||||
pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) -> ExitCode {
|
||||
// Spawn the supplied command.
|
||||
let output = cmd.output();
|
||||
|
||||
@@ -28,12 +23,20 @@ pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) {
|
||||
|
||||
let _ = stdout.lock().write_all(&output.stdout);
|
||||
let _ = stderr.lock().write_all(&output.stderr);
|
||||
|
||||
if output.status.code() == Some(0) {
|
||||
ExitCode::Success
|
||||
} else {
|
||||
ExitCode::GeneralError
|
||||
}
|
||||
}
|
||||
Err(ref why) if why.kind() == io::ErrorKind::NotFound => {
|
||||
print_error!("Command not found: {:?}", cmd);
|
||||
print_error(format!("Command not found: {:?}", cmd));
|
||||
ExitCode::GeneralError
|
||||
}
|
||||
Err(why) => {
|
||||
print_error!("Problem while executing command: {}", why);
|
||||
print_error(format!("Problem while executing command: {}", why));
|
||||
ExitCode::GeneralError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-67
@@ -1,78 +1,40 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
use crate::filesystem::strip_current_dir;
|
||||
|
||||
/// Removes the parent component of the path
|
||||
pub fn basename(path: &str) -> &str {
|
||||
let mut index = 0;
|
||||
for (id, character) in path.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
index = id;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: On Windows, should return what for C:file.txt D:file.txt and \\server\share ?
|
||||
if index != 0 {
|
||||
return &path[index + 1..];
|
||||
}
|
||||
|
||||
path
|
||||
pub fn basename(path: &Path) -> &OsStr {
|
||||
path.file_name().unwrap_or_else(|| path.as_os_str())
|
||||
}
|
||||
|
||||
/// Removes the extension from the path
|
||||
pub fn remove_extension(path: &str) -> &str {
|
||||
let mut has_dir = false;
|
||||
let mut dir_index = 0;
|
||||
let mut ext_index = 0;
|
||||
pub fn remove_extension(path: &Path) -> OsString {
|
||||
let dirname = dirname(path);
|
||||
let stem = path.file_stem().unwrap_or_else(|| path.as_os_str());
|
||||
|
||||
for (id, character) in path.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
has_dir = true;
|
||||
dir_index = id;
|
||||
}
|
||||
if character == '.' {
|
||||
ext_index = id;
|
||||
}
|
||||
}
|
||||
let path = PathBuf::from(dirname).join(stem);
|
||||
|
||||
// Account for hidden files and directories
|
||||
if ext_index != 0 && (!has_dir || dir_index + 2 <= ext_index) {
|
||||
return &path[0..ext_index];
|
||||
}
|
||||
|
||||
path
|
||||
strip_current_dir(&path).to_owned().into_os_string()
|
||||
}
|
||||
|
||||
/// Removes the basename from the path.
|
||||
pub fn dirname(path: &str) -> &str {
|
||||
let mut has_dir = false;
|
||||
let mut index = 0;
|
||||
for (id, character) in path.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
has_dir = true;
|
||||
index = id;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: On Windows, return what for C:file.txt D:file.txt and \\server\share ?
|
||||
if !has_dir {
|
||||
"."
|
||||
} else if index == 0 {
|
||||
&path[..1]
|
||||
} else {
|
||||
&path[0..index]
|
||||
}
|
||||
pub fn dirname(path: &Path) -> OsString {
|
||||
path.parent()
|
||||
.map(|p| {
|
||||
if p == OsStr::new("") {
|
||||
OsString::from(".")
|
||||
} else {
|
||||
p.as_os_str().to_owned()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| path.as_os_str().to_owned())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod path_tests {
|
||||
use super::*;
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
|
||||
fn correct(input: &str) -> String {
|
||||
input.replace('/', &MAIN_SEPARATOR.to_string())
|
||||
@@ -83,7 +45,9 @@ mod path_tests {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($func(&correct($input)), correct($output));
|
||||
let input_path = PathBuf::from(&correct($input));
|
||||
let output_string = OsString::from(correct($output));
|
||||
assert_eq!($func(&input_path), output_string);
|
||||
}
|
||||
)+
|
||||
}
|
||||
@@ -106,16 +70,18 @@ mod path_tests {
|
||||
dirname_dir: dirname for "dir/foo.txt" => "dir"
|
||||
dirname_utf8_0: dirname for "💖/foo.txt" => "💖"
|
||||
dirname_utf8_1: dirname for "dir/💖.txt" => "dir"
|
||||
dirname_empty: dirname for "" => "."
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn dirname_root() {
|
||||
#[cfg(windows)]
|
||||
assert_eq!(dirname("C:\\"), "C:");
|
||||
#[cfg(windows)]
|
||||
assert_eq!(dirname("\\"), "\\");
|
||||
#[cfg(not(windows))]
|
||||
assert_eq!(dirname("/"), "/");
|
||||
assert_eq!(dirname(&PathBuf::from("C:")), OsString::from("C:"));
|
||||
assert_eq!(dirname(&PathBuf::from("\\")), OsString::from("\\"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn dirname_root() {
|
||||
assert_eq!(dirname(&PathBuf::from("/")), OsString::from("/"));
|
||||
}
|
||||
}
|
||||
|
||||
+21
-18
@@ -1,17 +1,13 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use super::CommandTemplate;
|
||||
use crate::walk::WorkerResult;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::error::print_error;
|
||||
use crate::exit_codes::{merge_exitcodes, ExitCode};
|
||||
use crate::walk::WorkerResult;
|
||||
|
||||
use super::CommandTemplate;
|
||||
|
||||
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
|
||||
/// generate a command with the supplied command template. The generated command will then
|
||||
/// be executed, and this process will continue until the receiver's sender has closed.
|
||||
@@ -20,7 +16,8 @@ pub fn job(
|
||||
cmd: Arc<CommandTemplate>,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
show_filesystem_errors: bool,
|
||||
) {
|
||||
) -> ExitCode {
|
||||
let mut results: Vec<ExitCode> = Vec::new();
|
||||
loop {
|
||||
// Create a lock on the shared receiver for this thread.
|
||||
let lock = rx.lock().unwrap();
|
||||
@@ -31,29 +28,35 @@ pub fn job(
|
||||
Ok(WorkerResult::Entry(val)) => val,
|
||||
Ok(WorkerResult::Error(err)) => {
|
||||
if show_filesystem_errors {
|
||||
print_error!("{}", err);
|
||||
print_error(err.to_string());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Err(_) => break,
|
||||
};
|
||||
|
||||
// Drop the lock so that other threads can read from the the receiver.
|
||||
// Drop the lock so that other threads can read from the receiver.
|
||||
drop(lock);
|
||||
// Generate a command and execute it.
|
||||
cmd.generate_and_execute(&value, Arc::clone(&out_perm));
|
||||
// Generate a command, execute it and store its exit code.
|
||||
results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm)))
|
||||
}
|
||||
// Returns error in case of any error.
|
||||
merge_exitcodes(&results)
|
||||
}
|
||||
|
||||
pub fn batch(rx: Receiver<WorkerResult>, cmd: &CommandTemplate, show_filesystem_errors: bool) {
|
||||
pub fn batch(
|
||||
rx: Receiver<WorkerResult>,
|
||||
cmd: &CommandTemplate,
|
||||
show_filesystem_errors: bool,
|
||||
) -> ExitCode {
|
||||
let paths = rx.iter().filter_map(|value| match value {
|
||||
WorkerResult::Entry(val) => Some(val),
|
||||
WorkerResult::Error(err) => {
|
||||
if show_filesystem_errors {
|
||||
print_error!("{}", err);
|
||||
print_error(err.to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
});
|
||||
cmd.generate_and_execute_batch(paths);
|
||||
cmd.generate_and_execute_batch(paths)
|
||||
}
|
||||
|
||||
+38
-43
@@ -1,25 +1,20 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
// TODO: Possible optimization could avoid pushing characters on a buffer.
|
||||
mod command;
|
||||
mod input;
|
||||
mod job;
|
||||
mod token;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::filesystem::strip_current_dir;
|
||||
|
||||
use self::command::execute_command;
|
||||
use self::input::{basename, dirname, remove_extension};
|
||||
pub use self::job::{batch, job};
|
||||
@@ -53,17 +48,19 @@ impl CommandTemplate {
|
||||
Self::build(input, ExecutionMode::OneByOne)
|
||||
}
|
||||
|
||||
pub fn new_batch<I, S>(input: I) -> Result<CommandTemplate, &'static str>
|
||||
pub fn new_batch<I, S>(input: I) -> Result<CommandTemplate>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let cmd = Self::build(input, ExecutionMode::Batch);
|
||||
if cmd.number_of_tokens() > 1 {
|
||||
return Err("Only one placeholder allowed for batch commands");
|
||||
return Err(anyhow!("Only one placeholder allowed for batch commands"));
|
||||
}
|
||||
if cmd.args[0].has_tokens() {
|
||||
return Err("First argument of exec-batch is expected to be a fixed executable");
|
||||
return Err(anyhow!(
|
||||
"First argument of exec-batch is expected to be a fixed executable"
|
||||
));
|
||||
}
|
||||
Ok(cmd)
|
||||
}
|
||||
@@ -100,7 +97,7 @@ impl CommandTemplate {
|
||||
"{/}" => tokens.push(Token::Basename),
|
||||
"{//}" => tokens.push(Token::Parent),
|
||||
"{/.}" => tokens.push(Token::BasenameNoExt),
|
||||
_ => panic!("Unhandled placeholder"),
|
||||
_ => unreachable!("Unhandled placeholder"),
|
||||
}
|
||||
|
||||
has_placeholder = true;
|
||||
@@ -132,24 +129,16 @@ impl CommandTemplate {
|
||||
self.args.iter().filter(|arg| arg.has_tokens()).count()
|
||||
}
|
||||
|
||||
fn prepare_path(input: &Path) -> String {
|
||||
input
|
||||
.strip_prefix(".")
|
||||
.unwrap_or(input)
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
/// Generates and executes a command.
|
||||
///
|
||||
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
|
||||
/// build. Once all arguments have been processed, the command is executed.
|
||||
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) {
|
||||
let input = Self::prepare_path(input);
|
||||
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) -> ExitCode {
|
||||
let input = strip_current_dir(input);
|
||||
|
||||
let mut cmd = Command::new(self.args[0].generate(&input).as_ref());
|
||||
let mut cmd = Command::new(self.args[0].generate(&input));
|
||||
for arg in &self.args[1..] {
|
||||
cmd.arg(arg.generate(&input).as_ref());
|
||||
cmd.arg(arg.generate(&input));
|
||||
}
|
||||
|
||||
execute_command(cmd, &out_perm)
|
||||
@@ -159,33 +148,37 @@ impl CommandTemplate {
|
||||
self.mode == ExecutionMode::Batch
|
||||
}
|
||||
|
||||
pub fn generate_and_execute_batch<I>(&self, paths: I)
|
||||
pub fn generate_and_execute_batch<I>(&self, paths: I) -> ExitCode
|
||||
where
|
||||
I: Iterator<Item = PathBuf>,
|
||||
{
|
||||
let mut cmd = Command::new(self.args[0].generate("").as_ref());
|
||||
let mut cmd = Command::new(self.args[0].generate(""));
|
||||
cmd.stdin(Stdio::inherit());
|
||||
cmd.stdout(Stdio::inherit());
|
||||
cmd.stderr(Stdio::inherit());
|
||||
|
||||
let mut paths = paths.map(|p| Self::prepare_path(&p));
|
||||
let mut paths: Vec<_> = paths.collect();
|
||||
let mut has_path = false;
|
||||
|
||||
for arg in &self.args[1..] {
|
||||
if arg.has_tokens() {
|
||||
paths.sort();
|
||||
|
||||
// A single `Tokens` is expected
|
||||
// So we can directy consume the iterator once and for all
|
||||
// So we can directly consume the iterator once and for all
|
||||
for path in &mut paths {
|
||||
cmd.arg(arg.generate(&path).as_ref());
|
||||
cmd.arg(arg.generate(strip_current_dir(path)));
|
||||
has_path = true;
|
||||
}
|
||||
} else {
|
||||
cmd.arg(arg.generate("").as_ref());
|
||||
cmd.arg(arg.generate(""));
|
||||
}
|
||||
}
|
||||
|
||||
if has_path {
|
||||
execute_command(cmd, &Mutex::new(()));
|
||||
execute_command(cmd, &Mutex::new(()))
|
||||
} else {
|
||||
ExitCode::Success
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,25 +201,27 @@ impl ArgumentTemplate {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate<'a>(&'a self, path: &str) -> Cow<'a, str> {
|
||||
pub fn generate(&self, path: impl AsRef<Path>) -> OsString {
|
||||
use self::Token::*;
|
||||
|
||||
match *self {
|
||||
ArgumentTemplate::Tokens(ref tokens) => {
|
||||
let mut s = String::new();
|
||||
let mut s = OsString::new();
|
||||
for token in tokens {
|
||||
match *token {
|
||||
Basename => s += basename(path),
|
||||
BasenameNoExt => s += remove_extension(basename(path)),
|
||||
NoExt => s += remove_extension(path),
|
||||
Parent => s += dirname(path),
|
||||
Placeholder => s += path,
|
||||
Text(ref string) => s += string,
|
||||
Basename => s.push(basename(path.as_ref())),
|
||||
BasenameNoExt => {
|
||||
s.push(remove_extension(&PathBuf::from(basename(path.as_ref()))))
|
||||
}
|
||||
NoExt => s.push(remove_extension(path.as_ref())),
|
||||
Parent => s.push(dirname(path.as_ref())),
|
||||
Placeholder => s.push(path.as_ref()),
|
||||
Text(ref string) => s.push(string),
|
||||
}
|
||||
}
|
||||
Cow::Owned(s)
|
||||
s
|
||||
}
|
||||
ArgumentTemplate::Text(ref text) => Cow::Borrowed(text),
|
||||
ArgumentTemplate::Text(ref text) => OsString::from(text),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
/// Designates what should be written to a buffer
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ExitCode {
|
||||
Success,
|
||||
GeneralError,
|
||||
KilledBySigint,
|
||||
}
|
||||
@@ -6,8 +8,65 @@ pub enum ExitCode {
|
||||
impl Into<i32> for ExitCode {
|
||||
fn into(self) -> i32 {
|
||||
match self {
|
||||
ExitCode::Success => 0,
|
||||
ExitCode::GeneralError => 1,
|
||||
ExitCode::KilledBySigint => 130,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExitCode {
|
||||
fn is_error(&self) -> bool {
|
||||
*self != ExitCode::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge_exitcodes(results: &[ExitCode]) -> ExitCode {
|
||||
if results.iter().any(ExitCode::is_error) {
|
||||
return ExitCode::GeneralError;
|
||||
}
|
||||
ExitCode::Success
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn success_when_no_results() {
|
||||
assert_eq!(merge_exitcodes(&[]), ExitCode::Success);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn general_error_if_at_least_one_error() {
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::GeneralError]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::KilledBySigint]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::KilledBySigint, ExitCode::Success]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::Success, ExitCode::GeneralError]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::GeneralError, ExitCode::KilledBySigint]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn success_if_no_error() {
|
||||
assert_eq!(merge_exitcodes(&[ExitCode::Success]), ExitCode::Success);
|
||||
assert_eq!(
|
||||
merge_exitcodes(&[ExitCode::Success, ExitCode::Success]),
|
||||
ExitCode::Success
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
use std::borrow::Cow;
|
||||
use std::env::current_dir;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
use std::os::unix::fs::{FileTypeExt, PermissionsExt};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::walk;
|
||||
|
||||
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
|
||||
if path.is_absolute() {
|
||||
return Ok(path.to_path_buf());
|
||||
}
|
||||
|
||||
let path = path.strip_prefix(".").unwrap_or(path);
|
||||
current_dir().map(|path_buf| path_buf.join(path))
|
||||
}
|
||||
|
||||
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
|
||||
let path_buf = path_absolute_form(path)?;
|
||||
|
||||
#[cfg(windows)]
|
||||
let path_buf = Path::new(
|
||||
path_buf
|
||||
.as_path()
|
||||
.to_string_lossy()
|
||||
.trim_start_matches(r"\\?\"),
|
||||
)
|
||||
.to_path_buf();
|
||||
|
||||
Ok(path_buf)
|
||||
}
|
||||
|
||||
// Path::is_dir() is not guaranteed to be intuitively correct for "." and ".."
|
||||
// See: https://github.com/rust-lang/rust/issues/45302
|
||||
pub fn is_dir(path: &Path) -> bool {
|
||||
path.is_dir() && (path.file_name().is_some() || path.canonicalize().is_ok())
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn is_executable(md: &fs::Metadata) -> bool {
|
||||
md.permissions().mode() & 0o111 != 0
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn is_executable(_: &fs::Metadata) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_empty(entry: &walk::DirEntry) -> bool {
|
||||
if let Some(file_type) = entry.file_type() {
|
||||
if file_type.is_dir() {
|
||||
if let Ok(mut entries) = fs::read_dir(entry.path()) {
|
||||
entries.next().is_none()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else if file_type.is_file() {
|
||||
entry.metadata().map(|m| m.len() == 0).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn is_socket(ft: &fs::FileType) -> bool {
|
||||
ft.is_socket()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn is_socket(_: &fs::FileType) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn is_pipe(ft: &fs::FileType) -> bool {
|
||||
ft.is_fifo()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn is_pipe(_: &fs::FileType) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn osstr_to_bytes(input: &OsStr) -> Cow<[u8]> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
Cow::Borrowed(input.as_bytes())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn osstr_to_bytes(input: &OsStr) -> Cow<[u8]> {
|
||||
let string = input.to_string_lossy();
|
||||
|
||||
match string {
|
||||
Cow::Owned(string) => Cow::Owned(string.into_bytes()),
|
||||
Cow::Borrowed(string) => Cow::Borrowed(string.as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the `./` prefix from a path.
|
||||
pub fn strip_current_dir(path: &Path) -> &Path {
|
||||
path.strip_prefix(".").unwrap_or(path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::strip_current_dir;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn strip_current_dir_basic() {
|
||||
assert_eq!(strip_current_dir(Path::new("./foo")), Path::new("foo"));
|
||||
assert_eq!(strip_current_dir(Path::new("foo")), Path::new("foo"));
|
||||
assert_eq!(
|
||||
strip_current_dir(Path::new("./foo/bar/baz")),
|
||||
Path::new("foo/bar/baz")
|
||||
);
|
||||
assert_eq!(
|
||||
strip_current_dir(Path::new("foo/bar/baz")),
|
||||
Path::new("foo/bar/baz")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ pub struct FileTypes {
|
||||
pub files: bool,
|
||||
pub directories: bool,
|
||||
pub symlinks: bool,
|
||||
pub sockets: bool,
|
||||
pub pipes: bool,
|
||||
pub executables_only: bool,
|
||||
pub empty_only: bool,
|
||||
}
|
||||
@@ -13,6 +15,8 @@ impl Default for FileTypes {
|
||||
files: false,
|
||||
directories: false,
|
||||
symlinks: false,
|
||||
sockets: false,
|
||||
pipes: false,
|
||||
executables_only: false,
|
||||
empty_only: false,
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
pub use self::size::SizeFilter;
|
||||
pub use self::time::TimeFilter;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use self::owner::OwnerFilter;
|
||||
|
||||
mod size;
|
||||
mod time;
|
||||
|
||||
#[cfg(unix)]
|
||||
mod owner;
|
||||
@@ -0,0 +1,123 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::fs;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct OwnerFilter {
|
||||
uid: Check<u32>,
|
||||
gid: Check<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum Check<T> {
|
||||
Equal(T),
|
||||
NotEq(T),
|
||||
Ignore,
|
||||
}
|
||||
|
||||
impl OwnerFilter {
|
||||
/// Parses an owner constraint
|
||||
/// Returns an error if the string is invalid
|
||||
/// Returns Ok(None) when string is acceptable but a noop (such as "" or ":")
|
||||
pub fn from_string(input: &str) -> Result<Option<Self>> {
|
||||
let mut it = input.split(':');
|
||||
let (fst, snd) = (it.next(), it.next());
|
||||
|
||||
if it.next().is_some() {
|
||||
return Err(anyhow!(
|
||||
"more than one ':' present in owner string '{}'. See 'fd --help'.",
|
||||
input
|
||||
));
|
||||
}
|
||||
|
||||
let uid = Check::parse(fst, |s| {
|
||||
s.parse()
|
||||
.ok()
|
||||
.or_else(|| users::get_user_by_name(s).map(|user| user.uid()))
|
||||
.ok_or_else(|| anyhow!("'{}' is not a recognized user name", s))
|
||||
})?;
|
||||
let gid = Check::parse(snd, |s| {
|
||||
s.parse()
|
||||
.ok()
|
||||
.or_else(|| users::get_group_by_name(s).map(|group| group.gid()))
|
||||
.ok_or_else(|| anyhow!("'{}' is not a recognized group name", s))
|
||||
})?;
|
||||
|
||||
if let (Check::Ignore, Check::Ignore) = (uid, gid) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(OwnerFilter { uid, gid }))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches(&self, md: &fs::Metadata) -> bool {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
self.uid.check(md.uid()) && self.gid.check(md.gid())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> Check<T> {
|
||||
fn check(&self, v: T) -> bool {
|
||||
match self {
|
||||
Check::Equal(x) => v == *x,
|
||||
Check::NotEq(x) => v != *x,
|
||||
Check::Ignore => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse<F>(s: Option<&str>, f: F) -> Result<Self>
|
||||
where
|
||||
F: Fn(&str) -> Result<T>,
|
||||
{
|
||||
let (s, equality) = match s {
|
||||
Some("") | None => return Ok(Check::Ignore),
|
||||
Some(s) if s.starts_with('!') => (&s[1..], false),
|
||||
Some(s) => (s, true),
|
||||
};
|
||||
|
||||
f(s).map(|x| {
|
||||
if equality {
|
||||
Check::Equal(x)
|
||||
} else {
|
||||
Check::NotEq(x)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod owner_parsing {
|
||||
use super::OwnerFilter;
|
||||
|
||||
macro_rules! owner_tests {
|
||||
($($name:ident: $value:expr => $result:pat,)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
let o = OwnerFilter::from_string($value);
|
||||
match o {
|
||||
$result => {},
|
||||
_ => panic!("{:?} does not match {}", o, stringify!($result)),
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
use super::Check::*;
|
||||
owner_tests! {
|
||||
empty: "" => Ok(None),
|
||||
uid_only: "5" => Ok(Some(OwnerFilter { uid: Equal(5), gid: Ignore })),
|
||||
uid_gid: "9:3" => Ok(Some(OwnerFilter { uid: Equal(9), gid: Equal(3) })),
|
||||
gid_only: ":8" => Ok(Some(OwnerFilter { uid: Ignore, gid: Equal(8) })),
|
||||
colon_only: ":" => Ok(None),
|
||||
trailing: "5:" => Ok(Some(OwnerFilter { uid: Equal(5), gid: Ignore })),
|
||||
|
||||
uid_negate: "!5" => Ok(Some(OwnerFilter { uid: NotEq(5), gid: Ignore })),
|
||||
both_negate:"!4:!3" => Ok(Some(OwnerFilter { uid: NotEq(4), gid: NotEq(3) })),
|
||||
uid_not_gid:"6:!8" => Ok(Some(OwnerFilter { uid: Equal(6), gid: NotEq(8) })),
|
||||
|
||||
more_colons:"3:5:" => Err(_),
|
||||
only_colons:"::" => Err(_),
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static! {
|
||||
static ref SIZE_CAPTURES: Regex = { Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap() };
|
||||
static ref SIZE_CAPTURES: Regex = Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@@ -37,13 +37,13 @@ impl SizeFilter {
|
||||
|
||||
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('k') => KILO,
|
||||
v if v.starts_with("mi") => MEBI,
|
||||
v if v.starts_with("m") => MEGA,
|
||||
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('g') => GIGA,
|
||||
v if v.starts_with("ti") => TEBI,
|
||||
v if v.starts_with("t") => TERA,
|
||||
v if v.starts_with('t') => TERA,
|
||||
"b" => 1,
|
||||
_ => return None,
|
||||
};
|
||||
@@ -80,8 +80,8 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
/// 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)
|
||||
// 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)),
|
||||
@@ -168,7 +168,7 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
/// Invalid parse data
|
||||
// Invalid parse data
|
||||
gen_size_filter_failure! {
|
||||
ensure_missing_symbol_returns_none: "10M",
|
||||
ensure_missing_number_returns_none: "+g",
|
||||
@@ -9,7 +9,6 @@ pub enum TimeFilter {
|
||||
|
||||
impl TimeFilter {
|
||||
fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
|
||||
use humantime;
|
||||
humantime::parse_duration(s)
|
||||
.map(|duration| *ref_time - duration)
|
||||
.or_else(|_| humantime::parse_rfc3339_weak(s))
|
||||
@@ -39,8 +38,6 @@ mod tests {
|
||||
|
||||
#[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()
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::env::current_dir;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use ignore::DirEntry;
|
||||
|
||||
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
|
||||
if path.is_absolute() {
|
||||
return Ok(path.to_path_buf());
|
||||
}
|
||||
|
||||
let path = path.strip_prefix(".").unwrap_or(path);
|
||||
current_dir().map(|path_buf| path_buf.join(path))
|
||||
}
|
||||
|
||||
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
|
||||
let path_buf = path_absolute_form(path)?;
|
||||
|
||||
#[cfg(windows)]
|
||||
let path_buf = Path::new(
|
||||
path_buf
|
||||
.as_path()
|
||||
.to_string_lossy()
|
||||
.trim_left_matches(r"\\?\"),
|
||||
)
|
||||
.to_path_buf();
|
||||
|
||||
Ok(path_buf)
|
||||
}
|
||||
|
||||
// Path::is_dir() is not guaranteed to be intuitively correct for "." and ".."
|
||||
// See: https://github.com/rust-lang/rust/issues/45302
|
||||
pub fn is_dir(path: &Path) -> bool {
|
||||
path.is_dir() && (path.file_name().is_some() || path.canonicalize().is_ok())
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn is_executable(md: &fs::Metadata) -> bool {
|
||||
md.permissions().mode() & 0o111 != 0
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn is_executable(_: &fs::Metadata) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_empty(entry: &DirEntry) -> bool {
|
||||
if let Some(file_type) = entry.file_type() {
|
||||
if file_type.is_dir() {
|
||||
if let Ok(mut entries) = fs::read_dir(entry.path()) {
|
||||
entries.next().is_none()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else if file_type.is_file() {
|
||||
entry.metadata().map(|m| m.len() == 0).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use regex_syntax::hir::Hir;
|
||||
use regex_syntax::Parser;
|
||||
use std::ffi::OsString;
|
||||
|
||||
pub use self::file_types::FileTypes;
|
||||
|
||||
mod file_types;
|
||||
pub mod filter;
|
||||
pub mod opts;
|
||||
|
||||
macro_rules! print_error {
|
||||
($($arg:tt)*) => (eprintln!("[fd error]: {}", format!($($arg)*)))
|
||||
}
|
||||
|
||||
macro_rules! print_error_and_exit {
|
||||
($($arg:tt)*) => {
|
||||
print_error!($($arg)*);
|
||||
::std::process::exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
/// Determine if a regex pattern contains a literal uppercase character.
|
||||
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
|
||||
Parser::new()
|
||||
.parse(pattern)
|
||||
.map(|hir| hir_has_uppercase_char(&hir))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Determine if a regex expression contains a literal uppercase character.
|
||||
fn hir_has_uppercase_char(hir: &Hir) -> bool {
|
||||
use regex_syntax::hir::*;
|
||||
|
||||
match *hir.kind() {
|
||||
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
|
||||
HirKind::Class(Class::Unicode(ref ranges)) => ranges
|
||||
.iter()
|
||||
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
|
||||
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
|
||||
hir_has_uppercase_char(hir)
|
||||
}
|
||||
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
|
||||
hirs.iter().any(hir_has_uppercase_char)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum size of the output buffer before flushing results to the console
|
||||
pub const MAX_BUFFER_LENGTH: usize = 1000;
|
||||
|
||||
/// Traverse args_os, looking for -exec and replacing it with --exec.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * The args, with substitution if required
|
||||
pub fn transform_args_with_exec<I>(original: I) -> Vec<OsString>
|
||||
where
|
||||
I: Iterator<Item = OsString>,
|
||||
{
|
||||
let mut in_exec_opt = false;
|
||||
let target = OsString::from("-exec");
|
||||
let long_start = OsString::from("--exec");
|
||||
let short_start = OsString::from("-x");
|
||||
let exec_end = OsString::from(";");
|
||||
|
||||
original.fold(vec![], |mut args, curr| {
|
||||
if in_exec_opt {
|
||||
if curr == exec_end {
|
||||
in_exec_opt = false;
|
||||
}
|
||||
args.push(curr);
|
||||
return args;
|
||||
}
|
||||
|
||||
if curr == target || curr == long_start || curr == short_start {
|
||||
args.push(if curr == target {
|
||||
OsString::from("--exec")
|
||||
} else {
|
||||
curr
|
||||
});
|
||||
in_exec_opt = true;
|
||||
} else {
|
||||
args.push(curr);
|
||||
}
|
||||
args
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn oss_vec(strs: &[&str]) -> Vec<OsString> {
|
||||
strs.into_iter().map(OsString::from).collect()
|
||||
}
|
||||
|
||||
/// Ensure that -exec gets transformed into --exec
|
||||
#[test]
|
||||
fn normal_exec_substitution() {
|
||||
let original = oss_vec(&["fd", "foo", "-exec", "cmd"]);
|
||||
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||
let actual = transform_args_with_exec(original.into_iter());
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
/// Ensure that --exec is not touched
|
||||
#[test]
|
||||
fn passthru_of_original_exec() {
|
||||
let original = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||
let actual = transform_args_with_exec(original.into_iter());
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
#[test]
|
||||
fn temp_check_that_exec_context_observed() {
|
||||
let original = oss_vec(&[
|
||||
"fd",
|
||||
"foo",
|
||||
"-exec",
|
||||
"cmd",
|
||||
"-exec",
|
||||
"ls",
|
||||
";",
|
||||
"-exec",
|
||||
"rm",
|
||||
";",
|
||||
"--exec",
|
||||
"find",
|
||||
"-exec",
|
||||
"rm",
|
||||
";",
|
||||
"-x",
|
||||
"foo",
|
||||
"-exec",
|
||||
"something",
|
||||
";",
|
||||
"-exec",
|
||||
]);
|
||||
let expected = oss_vec(&[
|
||||
"fd",
|
||||
"foo",
|
||||
"--exec",
|
||||
"cmd",
|
||||
"-exec",
|
||||
"ls",
|
||||
";",
|
||||
"--exec",
|
||||
"rm",
|
||||
";",
|
||||
"--exec",
|
||||
"find",
|
||||
"-exec",
|
||||
"rm",
|
||||
";",
|
||||
"-x",
|
||||
"foo",
|
||||
"-exec",
|
||||
"something",
|
||||
";",
|
||||
"--exec",
|
||||
]);
|
||||
let actual = transform_args_with_exec(original.into_iter());
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
}
|
||||
+274
-105
@@ -1,78 +1,116 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
#[macro_use]
|
||||
mod internal;
|
||||
|
||||
mod app;
|
||||
mod error;
|
||||
mod exec;
|
||||
mod exit_codes;
|
||||
pub mod fshelper;
|
||||
mod filesystem;
|
||||
mod filetypes;
|
||||
mod filter;
|
||||
mod options;
|
||||
mod output;
|
||||
mod regex_helper;
|
||||
mod walk;
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use atty::Stream;
|
||||
use globset::GlobBuilder;
|
||||
use lscolors::LsColors;
|
||||
use regex::{RegexBuilder, RegexSetBuilder};
|
||||
use regex::bytes::{RegexBuilder, RegexSetBuilder};
|
||||
|
||||
use crate::error::print_error;
|
||||
use crate::exec::CommandTemplate;
|
||||
use crate::internal::{
|
||||
filter::{SizeFilter, TimeFilter},
|
||||
opts::FdOptions,
|
||||
pattern_has_uppercase_char, transform_args_with_exec, FileTypes,
|
||||
};
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::filetypes::FileTypes;
|
||||
#[cfg(unix)]
|
||||
use crate::filter::OwnerFilter;
|
||||
use crate::filter::{SizeFilter, TimeFilter};
|
||||
use crate::options::Options;
|
||||
use crate::regex_helper::pattern_has_uppercase_char;
|
||||
|
||||
fn main() {
|
||||
let checked_args = transform_args_with_exec(env::args_os());
|
||||
let matches = app::build_app().get_matches_from(checked_args);
|
||||
// We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481
|
||||
// FIXME: re-enable jemalloc on macOS, see comment in Cargo.toml file for more infos
|
||||
#[cfg(all(not(windows), not(target_os = "macos"), not(target_env = "musl")))]
|
||||
#[global_allocator]
|
||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||
|
||||
// Get the search pattern
|
||||
let pattern = matches.value_of("pattern").unwrap_or("");
|
||||
fn run() -> Result<ExitCode> {
|
||||
let matches = app::build_app().get_matches_from(env::args_os());
|
||||
|
||||
// Get the current working directory
|
||||
let current_dir = Path::new(".");
|
||||
if !fshelper::is_dir(current_dir) {
|
||||
print_error_and_exit!("Could not get current directory.");
|
||||
// Set the current working directory of the process
|
||||
if let Some(base_directory) = matches.value_of_os("base-directory") {
|
||||
let base_directory = Path::new(base_directory);
|
||||
if !filesystem::is_dir(base_directory) {
|
||||
return Err(anyhow!(
|
||||
"The '--base-directory' path '{}' is not a directory.",
|
||||
base_directory.to_string_lossy()
|
||||
));
|
||||
}
|
||||
env::set_current_dir(base_directory).with_context(|| {
|
||||
format!(
|
||||
"Could not set '{}' as the current working directory.",
|
||||
base_directory.to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
let current_directory = Path::new(".");
|
||||
if !filesystem::is_dir(current_directory) {
|
||||
return Err(anyhow!(
|
||||
"Could not retrieve current directory (has it been deleted?)."
|
||||
));
|
||||
}
|
||||
|
||||
// Get the search pattern
|
||||
let pattern = matches
|
||||
.value_of_os("pattern")
|
||||
.map(|p| {
|
||||
p.to_str()
|
||||
.ok_or_else(|| anyhow!("The search pattern includes invalid UTF-8 sequences."))
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or("");
|
||||
|
||||
// Get one or more root directories to search.
|
||||
let mut dir_vec: Vec<_> = match matches
|
||||
.values_of("path")
|
||||
.or_else(|| matches.values_of("search-path"))
|
||||
{
|
||||
Some(paths) => paths
|
||||
.map(|path| {
|
||||
let path_buffer = PathBuf::from(path);
|
||||
if !fshelper::is_dir(&path_buffer) {
|
||||
print_error_and_exit!(
|
||||
"'{}' is not a directory.",
|
||||
path_buffer.to_string_lossy()
|
||||
);
|
||||
}
|
||||
path_buffer
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
None => vec![current_dir.to_path_buf()],
|
||||
let passed_arguments = matches
|
||||
.values_of_os("path")
|
||||
.or_else(|| matches.values_of_os("search-path"));
|
||||
|
||||
let mut search_paths = if let Some(paths) = passed_arguments {
|
||||
let mut directories = vec![];
|
||||
for path in paths {
|
||||
let path_buffer = PathBuf::from(path);
|
||||
if filesystem::is_dir(&path_buffer) {
|
||||
directories.push(path_buffer);
|
||||
} else {
|
||||
print_error(format!(
|
||||
"Search path '{}' is not a directory.",
|
||||
path_buffer.to_string_lossy()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
directories
|
||||
} else {
|
||||
vec![current_directory.to_path_buf()]
|
||||
};
|
||||
|
||||
// Check if we have no valid search paths.
|
||||
if search_paths.is_empty() {
|
||||
return Err(anyhow!("No valid search paths given."));
|
||||
}
|
||||
|
||||
if matches.is_present("absolute-path") {
|
||||
dir_vec = dir_vec
|
||||
search_paths = search_paths
|
||||
.iter()
|
||||
.map(|path_buffer| {
|
||||
path_buffer
|
||||
.canonicalize()
|
||||
.and_then(|pb| fshelper::absolute_path(pb.as_path()))
|
||||
.and_then(|pb| filesystem::absolute_path(pb.as_path()))
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
@@ -81,22 +119,25 @@ 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)
|
||||
&& fshelper::is_dir(Path::new(pattern))
|
||||
&& filesystem::is_dir(Path::new(pattern))
|
||||
{
|
||||
print_error_and_exit!(
|
||||
return Err(anyhow!(
|
||||
"The search pattern '{pattern}' contains a path-separation character ('{sep}') \
|
||||
and will not lead to any search results.\n\n\
|
||||
If you want to search for all files inside the '{pattern}' directory, use a match-all pattern:\n\n \
|
||||
fd . '{pattern}'\n\n\
|
||||
Instead, if you want to search for the pattern in the full path, use:\n\n \
|
||||
Instead, if you want your pattern to match the full file path, use:\n\n \
|
||||
fd --full-path '{pattern}'",
|
||||
pattern = pattern,
|
||||
sep = std::path::MAIN_SEPARATOR,
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
// Treat pattern as literal string if '--fixed-strings' is used
|
||||
let pattern_regex = if matches.is_present("fixed-strings") {
|
||||
let pattern_regex = if matches.is_present("glob") && !pattern.is_empty() {
|
||||
let glob = GlobBuilder::new(pattern).literal_separator(true).build()?;
|
||||
glob.regex().to_owned()
|
||||
} else if matches.is_present("fixed-strings") {
|
||||
// Treat pattern as literal string if '--fixed-strings' is used
|
||||
regex::escape(pattern)
|
||||
} else {
|
||||
String::from(pattern)
|
||||
@@ -107,14 +148,21 @@ fn main() {
|
||||
let case_sensitive = !matches.is_present("ignore-case")
|
||||
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
|
||||
|
||||
#[cfg(windows)]
|
||||
let ansi_colors_support =
|
||||
ansi_term::enable_ansi_support().is_ok() || std::env::var_os("TERM").is_some();
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let ansi_colors_support = true;
|
||||
|
||||
let interactive_terminal = atty::is(Stream::Stdout);
|
||||
let colored_output = match matches.value_of("color") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
_ => atty::is(Stream::Stdout),
|
||||
_ => ansi_colors_support && env::var_os("NO_COLOR").is_none() && interactive_terminal,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok();
|
||||
let path_separator = matches.value_of("path-separator").map(|str| str.to_owned());
|
||||
|
||||
let ls_colors = if colored_output {
|
||||
Some(LsColors::from_env().unwrap_or_default())
|
||||
@@ -122,29 +170,103 @@ fn main() {
|
||||
None
|
||||
};
|
||||
|
||||
let command = matches
|
||||
.values_of("exec")
|
||||
.map(CommandTemplate::new)
|
||||
.or_else(|| {
|
||||
matches.values_of("exec-batch").map(|m| {
|
||||
CommandTemplate::new_batch(m).unwrap_or_else(|e| {
|
||||
print_error_and_exit!("{}", e);
|
||||
})
|
||||
})
|
||||
});
|
||||
let command = if let Some(args) = matches.values_of("exec") {
|
||||
Some(CommandTemplate::new(args))
|
||||
} else if let Some(args) = matches.values_of("exec-batch") {
|
||||
Some(CommandTemplate::new_batch(args)?)
|
||||
} else if matches.is_present("list-details") {
|
||||
let color = matches.value_of("color").unwrap_or("auto");
|
||||
let color_arg = ["--color=", color].concat();
|
||||
|
||||
let size_limits: Vec<SizeFilter> = matches
|
||||
.values_of("size")
|
||||
.map(|v| {
|
||||
v.map(|sf| {
|
||||
if let Some(f) = SizeFilter::from_string(sf) {
|
||||
return f;
|
||||
#[allow(unused)]
|
||||
let gnu_ls = |command_name| {
|
||||
vec![
|
||||
command_name,
|
||||
"-l", // long listing format
|
||||
"--human-readable", // human readable file sizes
|
||||
"--directory", // list directories themselves, not their contents
|
||||
&color_arg,
|
||||
]
|
||||
};
|
||||
|
||||
let cmd: Vec<&str> = if cfg!(unix) {
|
||||
if !cfg!(any(
|
||||
target_os = "macos",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
)) {
|
||||
// Assume ls is GNU ls
|
||||
gnu_ls("ls")
|
||||
} else {
|
||||
// MacOS, DragonFlyBSD, FreeBSD
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// Use GNU ls, if available (support for --color=auto, better LS_COLORS support)
|
||||
let gnu_ls_exists = Command::new("gls")
|
||||
.arg("--version")
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.is_ok();
|
||||
|
||||
if gnu_ls_exists {
|
||||
gnu_ls("gls")
|
||||
} else {
|
||||
let mut cmd = vec![
|
||||
"ls", // BSD version of ls
|
||||
"-l", // long listing format
|
||||
"-h", // '--human-readable' is not available, '-h' is
|
||||
"-d", // '--directory' is not available, but '-d' is
|
||||
];
|
||||
|
||||
if !cfg!(any(target_os = "netbsd", target_os = "openbsd")) && colored_output {
|
||||
// -G is not available in NetBSD's and OpenBSD's ls
|
||||
cmd.push("-G");
|
||||
}
|
||||
|
||||
cmd
|
||||
}
|
||||
print_error_and_exit!("'{}' is not a valid size constraint. See 'fd --help'.", sf);
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
} else if cfg!(windows) {
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// Use GNU ls, if available
|
||||
let gnu_ls_exists = Command::new("ls")
|
||||
.arg("--version")
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.is_ok();
|
||||
|
||||
if gnu_ls_exists {
|
||||
gnu_ls("ls")
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"'fd --list-details' is not supported on Windows unless GNU 'ls' is installed."
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"'fd --list-details' is not supported on this platform."
|
||||
));
|
||||
};
|
||||
|
||||
Some(CommandTemplate::new_batch(&cmd).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let size_limits = if let Some(vs) = matches.values_of("size") {
|
||||
vs.map(|sf| {
|
||||
SizeFilter::from_string(sf)
|
||||
.ok_or_else(|| anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", sf))
|
||||
})
|
||||
.unwrap_or_else(|| vec![]);
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let now = time::SystemTime::now();
|
||||
let mut time_constraints: Vec<TimeFilter> = Vec::new();
|
||||
@@ -152,18 +274,31 @@ fn main() {
|
||||
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);
|
||||
return Err(anyhow!(
|
||||
"'{}' is not a valid date or duration. See 'fd --help'.",
|
||||
t
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(t) = matches.value_of("changed-before") {
|
||||
if let Some(f) = TimeFilter::before(&now, t) {
|
||||
time_constraints.push(f);
|
||||
} else {
|
||||
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
|
||||
return Err(anyhow!(
|
||||
"'{}' is not a valid date or duration. See 'fd --help'.",
|
||||
t
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let config = FdOptions {
|
||||
#[cfg(unix)]
|
||||
let owner_constraint = if let Some(s) = matches.value_of("owner") {
|
||||
OwnerFilter::from_string(s)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let config = Options {
|
||||
case_sensitive,
|
||||
search_full_path: matches.is_present("full-path"),
|
||||
ignore_hidden: !(matches.is_present("hidden")
|
||||
@@ -173,11 +308,20 @@ fn main() {
|
||||
read_vcsignore: !(matches.is_present("no-ignore")
|
||||
|| matches.is_present("rg-alias-hidden-ignore")
|
||||
|| matches.is_present("no-ignore-vcs")),
|
||||
read_global_ignore: !(matches.is_present("no-ignore")
|
||||
|| matches.is_present("rg-alias-hidden-ignore")
|
||||
|| matches.is_present("no-global-ignore-file")),
|
||||
follow_links: matches.is_present("follow"),
|
||||
one_file_system: matches.is_present("one-file-system"),
|
||||
null_separator: matches.is_present("null_separator"),
|
||||
max_depth: matches
|
||||
.value_of("depth")
|
||||
.value_of("max-depth")
|
||||
.or_else(|| matches.value_of("rg-depth"))
|
||||
.or_else(|| matches.value_of("exact-depth"))
|
||||
.and_then(|n| usize::from_str_radix(n, 10).ok()),
|
||||
min_depth: matches
|
||||
.value_of("min-depth")
|
||||
.or_else(|| matches.value_of("exact-depth"))
|
||||
.and_then(|n| usize::from_str_radix(n, 10).ok()),
|
||||
threads: std::cmp::max(
|
||||
matches
|
||||
@@ -191,6 +335,7 @@ fn main() {
|
||||
.and_then(|n| u64::from_str_radix(n, 10).ok())
|
||||
.map(time::Duration::from_millis),
|
||||
ls_colors,
|
||||
interactive_terminal,
|
||||
file_types: matches.values_of("file-type").map(|values| {
|
||||
let mut file_types = FileTypes::default();
|
||||
for value in values {
|
||||
@@ -202,9 +347,9 @@ fn main() {
|
||||
file_types.executables_only = true;
|
||||
file_types.files = true;
|
||||
}
|
||||
"e" | "empty" => {
|
||||
file_types.empty_only = true;
|
||||
}
|
||||
"e" | "empty" => file_types.empty_only = true,
|
||||
"s" | "socket" => file_types.sockets = true,
|
||||
"p" | "pipe" => file_types.pipes = true,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -217,20 +362,17 @@ fn main() {
|
||||
|
||||
file_types
|
||||
}),
|
||||
extensions: matches.values_of("extension").map(|exts| {
|
||||
let patterns = exts
|
||||
.map(|e| e.trim_left_matches('.'))
|
||||
.map(|e| format!(r".\.{}$", regex::escape(e)));
|
||||
match RegexSetBuilder::new(patterns)
|
||||
.case_insensitive(true)
|
||||
.build()
|
||||
{
|
||||
Ok(re) => re,
|
||||
Err(err) => {
|
||||
print_error_and_exit!("{}", err.description());
|
||||
}
|
||||
}
|
||||
}),
|
||||
extensions: matches
|
||||
.values_of("extension")
|
||||
.map(|exts| {
|
||||
let patterns = exts
|
||||
.map(|e| e.trim_start_matches('.'))
|
||||
.map(|e| format!(r".\.{}$", regex::escape(e)));
|
||||
RegexSetBuilder::new(patterns)
|
||||
.case_insensitive(true)
|
||||
.build()
|
||||
})
|
||||
.transpose()?,
|
||||
command: command.map(Arc::new),
|
||||
exclude_patterns: matches
|
||||
.values_of("exclude")
|
||||
@@ -242,21 +384,48 @@ fn main() {
|
||||
.unwrap_or_else(|| vec![]),
|
||||
size_constraints: size_limits,
|
||||
time_constraints,
|
||||
#[cfg(unix)]
|
||||
owner_constraint,
|
||||
show_filesystem_errors: matches.is_present("show-errors"),
|
||||
path_separator,
|
||||
max_results: matches
|
||||
.value_of("max-results")
|
||||
.and_then(|n| usize::from_str_radix(n, 10).ok())
|
||||
.filter(|&n| n != 0)
|
||||
.or_else(|| {
|
||||
if matches.is_present("max-one-result") {
|
||||
Some(1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
match RegexBuilder::new(&pattern_regex)
|
||||
let re = RegexBuilder::new(&pattern_regex)
|
||||
.case_insensitive(!config.case_sensitive)
|
||||
.dot_matches_new_line(true)
|
||||
.build()
|
||||
{
|
||||
Ok(re) => walk::scan(&dir_vec, Arc::new(re), Arc::new(config)),
|
||||
.map_err(|e| {
|
||||
anyhow!(
|
||||
"{}\n\nNote: You can use the '--fixed-strings' option to search for a \
|
||||
literal string instead of a regular expression. Alternatively, you can \
|
||||
also use the '--glob' option to match on a glob pattern.",
|
||||
e.to_string()
|
||||
)
|
||||
})?;
|
||||
|
||||
walk::scan(&search_paths, Arc::new(re), Arc::new(config))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = run();
|
||||
match result {
|
||||
Ok(exit_code) => {
|
||||
process::exit(exit_code.into());
|
||||
}
|
||||
Err(err) => {
|
||||
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()
|
||||
);
|
||||
eprintln!("[fd error]: {}", err);
|
||||
process::exit(ExitCode::GeneralError.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
use crate::exec::CommandTemplate;
|
||||
use crate::internal::{
|
||||
filter::{SizeFilter, TimeFilter},
|
||||
FileTypes,
|
||||
};
|
||||
use lscolors::LsColors;
|
||||
use regex::RegexSet;
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use lscolors::LsColors;
|
||||
use regex::bytes::RegexSet;
|
||||
|
||||
use crate::exec::CommandTemplate;
|
||||
use crate::filetypes::FileTypes;
|
||||
#[cfg(unix)]
|
||||
use crate::filter::OwnerFilter;
|
||||
use crate::filter::{SizeFilter, TimeFilter};
|
||||
|
||||
/// Configuration options for *fd*.
|
||||
pub struct FdOptions {
|
||||
pub struct Options {
|
||||
/// Whether the search is case-sensitive or case-insensitive.
|
||||
pub case_sensitive: bool,
|
||||
|
||||
@@ -25,9 +27,15 @@ pub struct FdOptions {
|
||||
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
|
||||
pub read_vcsignore: bool,
|
||||
|
||||
/// Whether to respect the global ignore file or not.
|
||||
pub read_global_ignore: bool,
|
||||
|
||||
/// Whether to follow symlinks or not.
|
||||
pub follow_links: bool,
|
||||
|
||||
/// Whether to limit the search to starting file system or not.
|
||||
pub one_file_system: bool,
|
||||
|
||||
/// Whether elements of output should be separated by a null character
|
||||
pub null_separator: bool,
|
||||
|
||||
@@ -37,6 +45,9 @@ pub struct FdOptions {
|
||||
/// all files under subdirectories of the current directory, etc.
|
||||
pub max_depth: Option<usize>,
|
||||
|
||||
/// The minimum depth for reported entries, or `None`.
|
||||
pub min_depth: Option<usize>,
|
||||
|
||||
/// The number of threads to use.
|
||||
pub threads: usize,
|
||||
|
||||
@@ -49,6 +60,9 @@ pub struct FdOptions {
|
||||
/// how to style different filetypes.
|
||||
pub ls_colors: Option<LsColors>,
|
||||
|
||||
/// Whether or not we are writing to an interactive terminal
|
||||
pub interactive_terminal: bool,
|
||||
|
||||
/// The type of file to search for. If set to `None`, all file types are displayed. If
|
||||
/// set to `Some(..)`, only the types that are specified are shown.
|
||||
pub file_types: Option<FileTypes>,
|
||||
@@ -73,6 +87,16 @@ pub struct FdOptions {
|
||||
/// Constraints on last modification time of files
|
||||
pub time_constraints: Vec<TimeFilter>,
|
||||
|
||||
#[cfg(unix)]
|
||||
/// User/group ownership constraint
|
||||
pub owner_constraint: Option<OwnerFilter>,
|
||||
|
||||
/// Whether or not to display filesystem errors
|
||||
pub show_filesystem_errors: bool,
|
||||
|
||||
/// The separator used to print file paths.
|
||||
pub path_separator: Option<String>,
|
||||
|
||||
/// The maximum number of search results
|
||||
pub max_results: Option<usize>,
|
||||
}
|
||||
+54
-28
@@ -1,37 +1,25 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::internal::opts::FdOptions;
|
||||
use lscolors::{LsColors, Style};
|
||||
|
||||
use std::io::{self, StdoutLock, Write};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use ansi_term;
|
||||
use lscolors::{LsColors, Style};
|
||||
|
||||
/// Remove the `./` prefix from a path.
|
||||
fn strip_current_dir(pathbuf: &PathBuf) -> &Path {
|
||||
let mut iter = pathbuf.components();
|
||||
let mut iter_next = iter.clone();
|
||||
if iter_next.next() == Some(Component::CurDir) {
|
||||
iter.next();
|
||||
}
|
||||
iter.as_path()
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::filesystem::strip_current_dir;
|
||||
use crate::options::Options;
|
||||
|
||||
pub fn replace_path_separator(path: &str, new_path_separator: &str) -> String {
|
||||
path.replace(std::path::MAIN_SEPARATOR, &new_path_separator)
|
||||
}
|
||||
|
||||
// TODO: this function is performance critical and can probably be optimized
|
||||
pub fn print_entry(
|
||||
stdout: &mut StdoutLock,
|
||||
entry: &PathBuf,
|
||||
config: &FdOptions,
|
||||
config: &Options,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
) {
|
||||
let path = if entry.is_absolute() {
|
||||
@@ -52,10 +40,11 @@ pub fn print_entry(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this function is performance critical and can probably be optimized
|
||||
fn print_entry_colorized(
|
||||
stdout: &mut StdoutLock,
|
||||
path: &Path,
|
||||
config: &FdOptions,
|
||||
config: &Options,
|
||||
ls_colors: &LsColors,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
) -> io::Result<()> {
|
||||
@@ -67,8 +56,13 @@ fn print_entry_colorized(
|
||||
.map(Style::to_ansi_term_style)
|
||||
.unwrap_or(default_style);
|
||||
|
||||
write!(stdout, "{}", style.paint(component.to_string_lossy()))?;
|
||||
let mut path_string = component.to_string_lossy();
|
||||
if let Some(ref separator) = config.path_separator {
|
||||
*path_string.to_mut() = replace_path_separator(&path_string, &separator);
|
||||
}
|
||||
write!(stdout, "{}", style.paint(path_string))?;
|
||||
|
||||
// TODO: can we move this out of the if-statement? Why do we call it that often?
|
||||
if wants_to_quit.load(Ordering::Relaxed) {
|
||||
writeln!(stdout)?;
|
||||
process::exit(ExitCode::KilledBySigint.into());
|
||||
@@ -82,13 +76,45 @@ fn print_entry_colorized(
|
||||
}
|
||||
}
|
||||
|
||||
fn print_entry_uncolorized(
|
||||
// TODO: this function is performance critical and can probably be optimized
|
||||
fn print_entry_uncolorized_base(
|
||||
stdout: &mut StdoutLock,
|
||||
path: &Path,
|
||||
config: &FdOptions,
|
||||
config: &Options,
|
||||
) -> io::Result<()> {
|
||||
let separator = if config.null_separator { "\0" } else { "\n" };
|
||||
|
||||
let path_str = path.to_string_lossy();
|
||||
write!(stdout, "{}{}", path_str, separator)
|
||||
let mut path_string = path.to_string_lossy();
|
||||
if let Some(ref separator) = config.path_separator {
|
||||
*path_string.to_mut() = replace_path_separator(&path_string, &separator);
|
||||
}
|
||||
write!(stdout, "{}{}", path_string, separator)
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn print_entry_uncolorized(
|
||||
stdout: &mut StdoutLock,
|
||||
path: &Path,
|
||||
config: &Options,
|
||||
) -> io::Result<()> {
|
||||
print_entry_uncolorized_base(stdout, path, config)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn print_entry_uncolorized(
|
||||
stdout: &mut StdoutLock,
|
||||
path: &Path,
|
||||
config: &Options,
|
||||
) -> io::Result<()> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
if config.interactive_terminal || config.path_separator.is_some() {
|
||||
// Fall back to the base implementation
|
||||
print_entry_uncolorized_base(stdout, path, config)
|
||||
} else {
|
||||
// Print path as raw bytes, allowing invalid UTF-8 filenames to be passed to other processes
|
||||
let separator = if config.null_separator { b"\0" } else { b"\n" };
|
||||
stdout.write_all(path.as_os_str().as_bytes())?;
|
||||
stdout.write_all(separator)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
use regex_syntax::hir::Hir;
|
||||
use regex_syntax::ParserBuilder;
|
||||
|
||||
/// Determine if a regex pattern contains a literal uppercase character.
|
||||
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
|
||||
let mut parser = ParserBuilder::new().allow_invalid_utf8(true).build();
|
||||
|
||||
parser
|
||||
.parse(pattern)
|
||||
.map(|hir| hir_has_uppercase_char(&hir))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Determine if a regex expression contains a literal uppercase character.
|
||||
fn hir_has_uppercase_char(hir: &Hir) -> bool {
|
||||
use regex_syntax::hir::*;
|
||||
|
||||
match *hir.kind() {
|
||||
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
|
||||
HirKind::Literal(Literal::Byte(b)) => char::from(b).is_uppercase(),
|
||||
HirKind::Class(Class::Unicode(ref ranges)) => ranges
|
||||
.iter()
|
||||
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
|
||||
HirKind::Class(Class::Bytes(ref ranges)) => ranges
|
||||
.iter()
|
||||
.any(|r| char::from(r.start()).is_uppercase() || char::from(r.end()).is_uppercase()),
|
||||
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
|
||||
hir_has_uppercase_char(hir)
|
||||
}
|
||||
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
|
||||
hirs.iter().any(hir_has_uppercase_char)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_has_uppercase_char_simple() {
|
||||
assert!(pattern_has_uppercase_char("A"));
|
||||
assert!(pattern_has_uppercase_char("foo.EXE"));
|
||||
|
||||
assert!(!pattern_has_uppercase_char("a"));
|
||||
assert!(!pattern_has_uppercase_char("foo.exe123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_has_uppercase_char_advanced() {
|
||||
assert!(pattern_has_uppercase_char("foo.[a-zA-Z]"));
|
||||
|
||||
assert!(!pattern_has_uppercase_char(r"\Acargo"));
|
||||
assert!(!pattern_has_uppercase_char(r"carg\x6F"));
|
||||
}
|
||||
+224
-95
@@ -1,20 +1,8 @@
|
||||
// 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 crate::exec;
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::fshelper;
|
||||
use crate::internal::{opts::FdOptions, MAX_BUFFER_LENGTH};
|
||||
use crate::output;
|
||||
|
||||
use std::error::Error;
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{FileType, Metadata};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
@@ -22,9 +10,17 @@ use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use ignore::overrides::OverrideBuilder;
|
||||
use ignore::{self, WalkBuilder};
|
||||
use regex::Regex;
|
||||
use regex::bytes::Regex;
|
||||
|
||||
use crate::error::print_error;
|
||||
use crate::exec;
|
||||
use crate::exit_codes::{merge_exitcodes, ExitCode};
|
||||
use crate::filesystem;
|
||||
use crate::options::Options;
|
||||
use crate::output;
|
||||
|
||||
/// The receiver thread can either be buffering results or directly streaming to the console.
|
||||
enum ReceiverMode {
|
||||
@@ -42,12 +38,15 @@ pub enum WorkerResult {
|
||||
Error(ignore::Error),
|
||||
}
|
||||
|
||||
/// Maximum size of the output buffer before flushing results to the console
|
||||
pub const MAX_BUFFER_LENGTH: usize = 1000;
|
||||
|
||||
/// Recursively scan the given search path for files / pathnames matching the pattern.
|
||||
///
|
||||
/// If the `--exec` argument was supplied, this will create a thread pool for executing
|
||||
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
|
||||
/// path will simply be written to standard output.
|
||||
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Options>) -> Result<ExitCode> {
|
||||
let mut path_iter = path_vec.iter();
|
||||
let first_path_buf = path_iter
|
||||
.next()
|
||||
@@ -57,14 +56,13 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
|
||||
|
||||
for pattern in &config.exclude_patterns {
|
||||
let res = override_builder.add(pattern);
|
||||
if res.is_err() {
|
||||
print_error_and_exit!("Malformed exclude pattern '{}'", pattern);
|
||||
}
|
||||
override_builder
|
||||
.add(pattern)
|
||||
.map_err(|e| anyhow!("Malformed exclude pattern: {}", e))?;
|
||||
}
|
||||
let overrides = override_builder.build().unwrap_or_else(|_| {
|
||||
print_error_and_exit!("Mismatch in exclude patterns");
|
||||
});
|
||||
let overrides = override_builder
|
||||
.build()
|
||||
.map_err(|_| anyhow!("Mismatch in exclude patterns"))?;
|
||||
|
||||
let mut walker = WalkBuilder::new(first_path_buf.as_path());
|
||||
walker
|
||||
@@ -76,25 +74,51 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
.git_exclude(config.read_vcsignore)
|
||||
.overrides(overrides)
|
||||
.follow_links(config.follow_links)
|
||||
// No need to check for supported platforms, option is unavailable on unsupported ones
|
||||
.same_file_system(config.one_file_system)
|
||||
.max_depth(config.max_depth);
|
||||
|
||||
if config.read_fdignore {
|
||||
walker.add_custom_ignore_filename(".fdignore");
|
||||
}
|
||||
|
||||
if config.read_global_ignore {
|
||||
#[cfg(target_os = "macos")]
|
||||
let config_dir_op = std::env::var_os("XDG_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|p| p.is_absolute())
|
||||
.or_else(|| dirs::home_dir().map(|d| d.join(".config")));
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let config_dir_op = dirs::config_dir();
|
||||
|
||||
if let Some(global_ignore_file) = config_dir_op
|
||||
.map(|p| p.join("fd").join("ignore"))
|
||||
.filter(|p| p.is_file())
|
||||
{
|
||||
let result = walker.add_ignore(global_ignore_file);
|
||||
match result {
|
||||
Some(ignore::Error::Partial(_)) => (),
|
||||
Some(err) => {
|
||||
print_error(format!(
|
||||
"Malformed pattern in global ignore file. {}.",
|
||||
err.to_string()
|
||||
));
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ignore_file in &config.ignore_files {
|
||||
let result = walker.add_ignore(ignore_file);
|
||||
match result {
|
||||
Some(ignore::Error::Partial(_)) => (),
|
||||
Some(err) => {
|
||||
print_error!(
|
||||
"{}",
|
||||
format!(
|
||||
"Malformed pattern in custom ignore file '{}': {}.",
|
||||
ignore_file.to_string_lossy(),
|
||||
err.description()
|
||||
)
|
||||
);
|
||||
print_error(format!(
|
||||
"Malformed pattern in custom ignore file. {}.",
|
||||
err.to_string()
|
||||
));
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
@@ -110,7 +134,12 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
if config.ls_colors.is_some() && config.command.is_none() {
|
||||
let wq = Arc::clone(&wants_to_quit);
|
||||
ctrlc::set_handler(move || {
|
||||
wq.store(true, Ordering::Relaxed);
|
||||
if wq.load(Ordering::Relaxed) {
|
||||
// Ctrl-C has been pressed twice, exit NOW
|
||||
process::exit(ExitCode::KilledBySigint.into());
|
||||
} else {
|
||||
wq.store(true, Ordering::Relaxed);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -122,18 +151,20 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
spawn_senders(&config, &wants_to_quit, pattern, parallel_walker, tx);
|
||||
|
||||
// Wait for the receiver thread to print out all results.
|
||||
receiver_thread.join().unwrap();
|
||||
let exit_code = receiver_thread.join().unwrap();
|
||||
|
||||
if wants_to_quit.load(Ordering::Relaxed) {
|
||||
process::exit(ExitCode::KilledBySigint.into());
|
||||
Ok(ExitCode::KilledBySigint)
|
||||
} else {
|
||||
Ok(exit_code)
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_receiver(
|
||||
config: &Arc<FdOptions>,
|
||||
config: &Arc<Options>,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
rx: Receiver<WorkerResult>,
|
||||
) -> thread::JoinHandle<()> {
|
||||
) -> thread::JoinHandle<ExitCode> {
|
||||
let config = Arc::clone(config);
|
||||
let wants_to_quit = Arc::clone(wants_to_quit);
|
||||
|
||||
@@ -144,7 +175,7 @@ fn spawn_receiver(
|
||||
// This will be set to `Some` if the `--exec` argument was supplied.
|
||||
if let Some(ref cmd) = config.command {
|
||||
if cmd.in_batch_mode() {
|
||||
exec::batch(rx, cmd, show_filesystem_errors);
|
||||
exec::batch(rx, cmd, show_filesystem_errors)
|
||||
} else {
|
||||
let shared_rx = Arc::new(Mutex::new(rx));
|
||||
|
||||
@@ -166,9 +197,12 @@ fn spawn_receiver(
|
||||
}
|
||||
|
||||
// Wait for all threads to exit before exiting the program.
|
||||
let mut results: Vec<ExitCode> = Vec::new();
|
||||
for h in handles {
|
||||
h.join().unwrap();
|
||||
results.push(h.join().unwrap());
|
||||
}
|
||||
|
||||
merge_exitcodes(&results)
|
||||
}
|
||||
} else {
|
||||
let start = time::Instant::now();
|
||||
@@ -186,6 +220,8 @@ fn spawn_receiver(
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
let mut num_results = 0;
|
||||
|
||||
for worker_result in rx {
|
||||
match worker_result {
|
||||
WorkerResult::Entry(value) => {
|
||||
@@ -216,13 +252,21 @@ fn spawn_receiver(
|
||||
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
|
||||
}
|
||||
}
|
||||
|
||||
num_results += 1;
|
||||
}
|
||||
WorkerResult::Error(err) => {
|
||||
if show_filesystem_errors {
|
||||
print_error!("{}", err);
|
||||
print_error(err.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(max_results) = config.max_results {
|
||||
if num_results >= max_results {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have finished fast enough (faster than max_buffer_time), we haven't streamed
|
||||
@@ -233,12 +277,51 @@ fn spawn_receiver(
|
||||
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
|
||||
}
|
||||
}
|
||||
|
||||
ExitCode::Success
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub enum DirEntry {
|
||||
Normal(ignore::DirEntry),
|
||||
BrokenSymlink(PathBuf),
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
pub fn path(&self) -> &Path {
|
||||
match self {
|
||||
DirEntry::Normal(e) => e.path(),
|
||||
DirEntry::BrokenSymlink(pathbuf) => pathbuf.as_path(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_type(&self) -> Option<FileType> {
|
||||
match self {
|
||||
DirEntry::Normal(e) => e.file_type(),
|
||||
DirEntry::BrokenSymlink(pathbuf) => {
|
||||
pathbuf.symlink_metadata().map(|m| m.file_type()).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> Option<Metadata> {
|
||||
match self {
|
||||
DirEntry::Normal(e) => e.metadata().ok(),
|
||||
DirEntry::BrokenSymlink(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn depth(&self) -> Option<usize> {
|
||||
match self {
|
||||
DirEntry::Normal(e) => Some(e.depth()),
|
||||
DirEntry::BrokenSymlink(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_senders(
|
||||
config: &Arc<FdOptions>,
|
||||
config: &Arc<Options>,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
pattern: Arc<Regex>,
|
||||
parallel_walker: ignore::WalkParallel,
|
||||
@@ -256,37 +339,72 @@ fn spawn_senders(
|
||||
}
|
||||
|
||||
let entry = match entry_o {
|
||||
Ok(e) => e,
|
||||
Ok(ref e) if e.depth() == 0 => {
|
||||
// Skip the root directory entry.
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
Ok(e) => DirEntry::Normal(e),
|
||||
Err(ignore::Error::WithPath {
|
||||
path,
|
||||
err: inner_err,
|
||||
}) => match inner_err.as_ref() {
|
||||
ignore::Error::Io(io_error)
|
||||
if io_error.kind() == io::ErrorKind::NotFound
|
||||
&& path
|
||||
.symlink_metadata()
|
||||
.ok()
|
||||
.map_or(false, |m| m.file_type().is_symlink()) =>
|
||||
{
|
||||
DirEntry::BrokenSymlink(path)
|
||||
}
|
||||
_ => {
|
||||
tx_thread
|
||||
.send(WorkerResult::Error(ignore::Error::WithPath {
|
||||
path,
|
||||
err: inner_err,
|
||||
}))
|
||||
.unwrap();
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
tx_thread.send(WorkerResult::Error(err)).unwrap();
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
};
|
||||
|
||||
if entry.depth() == 0 {
|
||||
if let Some(min_depth) = config.min_depth {
|
||||
if entry.depth().map_or(true, |d| d < min_depth) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the name first, since it doesn't require metadata
|
||||
let entry_path = entry.path();
|
||||
|
||||
let search_str: Cow<OsStr> = if config.search_full_path {
|
||||
let path_abs_buf = filesystem::path_absolute_form(entry_path)
|
||||
.expect("Retrieving absolute path succeeds");
|
||||
Cow::Owned(path_abs_buf.as_os_str().to_os_string())
|
||||
} else {
|
||||
match entry_path.file_name() {
|
||||
Some(filename) => Cow::Borrowed(filename),
|
||||
None => unreachable!(
|
||||
"Encountered file system entry without a file name. This should only \
|
||||
happen for paths like 'foo/bar/..' or '/' which are not supposed to \
|
||||
appear in a file system traversal."
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
if !pattern.is_match(&filesystem::osstr_to_bytes(search_str.as_ref())) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
|
||||
// Filter out unwanted file types.
|
||||
|
||||
if let Some(ref file_types) = config.file_types {
|
||||
if let Some(ref entry_type) = entry.file_type() {
|
||||
if (!file_types.files && entry_type.is_file())
|
||||
|| (!file_types.directories && entry_type.is_dir())
|
||||
|| (!file_types.symlinks && entry_type.is_symlink())
|
||||
|| (file_types.executables_only
|
||||
&& !entry
|
||||
.metadata()
|
||||
.map(|m| fshelper::is_executable(&m))
|
||||
.unwrap_or(false))
|
||||
|| (file_types.empty_only && !fshelper::is_empty(&entry))
|
||||
{
|
||||
return ignore::WalkState::Continue;
|
||||
} else if !(entry_type.is_file()
|
||||
|| entry_type.is_dir()
|
||||
|| entry_type.is_symlink())
|
||||
{
|
||||
// This is probably a block device, char device, fifo or socket. Skip it.
|
||||
// Filter out unwanted extensions.
|
||||
if let Some(ref exts_regex) = config.extensions {
|
||||
if let Some(path_str) = entry_path.file_name() {
|
||||
if !exts_regex.is_match(&filesystem::osstr_to_bytes(path_str)) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
@@ -294,12 +412,26 @@ fn spawn_senders(
|
||||
}
|
||||
}
|
||||
|
||||
let entry_path = entry.path();
|
||||
|
||||
// Filter out unwanted extensions.
|
||||
if let Some(ref exts_regex) = config.extensions {
|
||||
if let Some(path_str) = entry_path.file_name().and_then(|s| s.to_str()) {
|
||||
if !exts_regex.is_match(path_str) {
|
||||
// Filter out unwanted file types.
|
||||
if let Some(ref file_types) = config.file_types {
|
||||
if let Some(ref entry_type) = entry.file_type() {
|
||||
if (!file_types.files && entry_type.is_file())
|
||||
|| (!file_types.directories && entry_type.is_dir())
|
||||
|| (!file_types.symlinks && entry_type.is_symlink())
|
||||
|| (!file_types.sockets && filesystem::is_socket(entry_type))
|
||||
|| (!file_types.pipes && filesystem::is_pipe(entry_type))
|
||||
|| (file_types.executables_only
|
||||
&& !entry
|
||||
.metadata()
|
||||
.map(|m| filesystem::is_executable(&m))
|
||||
.unwrap_or(false))
|
||||
|| (file_types.empty_only && !filesystem::is_empty(&entry))
|
||||
|| !(entry_type.is_file()
|
||||
|| entry_type.is_dir()
|
||||
|| entry_type.is_symlink()
|
||||
|| filesystem::is_socket(entry_type)
|
||||
|| filesystem::is_pipe(entry_type))
|
||||
{
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
@@ -307,6 +439,19 @@ fn spawn_senders(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if let Some(ref owner_constraint) = config.owner_constraint {
|
||||
if let Ok(ref metadata) = entry_path.metadata() {
|
||||
if !owner_constraint.matches(&metadata) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out unwanted sizes if it is a file and we have been given size constraints.
|
||||
if !config.size_constraints.is_empty() {
|
||||
if entry_path.is_file() {
|
||||
@@ -330,14 +475,12 @@ fn spawn_senders(
|
||||
// Filter out unwanted modification times
|
||||
if !config.time_constraints.is_empty() {
|
||||
let mut matched = false;
|
||||
if entry_path.is_file() {
|
||||
if let Ok(metadata) = entry_path.metadata() {
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
matched = config
|
||||
.time_constraints
|
||||
.iter()
|
||||
.all(|tf| tf.applies_to(&modified));
|
||||
}
|
||||
if let Ok(metadata) = entry_path.metadata() {
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
matched = config
|
||||
.time_constraints
|
||||
.iter()
|
||||
.all(|tf| tf.applies_to(&modified));
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
@@ -345,24 +488,10 @@ fn spawn_senders(
|
||||
}
|
||||
}
|
||||
|
||||
let search_str_o = if config.search_full_path {
|
||||
match fshelper::path_absolute_form(entry_path) {
|
||||
Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),
|
||||
Err(_) => {
|
||||
print_error_and_exit!("Unable to retrieve absolute path.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
entry_path.file_name().map(|f| f.to_string_lossy())
|
||||
};
|
||||
let send_result = tx_thread.send(WorkerResult::Entry(entry_path.to_owned()));
|
||||
|
||||
if let Some(search_str) = search_str_o {
|
||||
if pattern.is_match(&*search_str) {
|
||||
// TODO: take care of the unwrap call
|
||||
tx_thread
|
||||
.send(WorkerResult::Entry(entry_path.to_owned()))
|
||||
.unwrap()
|
||||
}
|
||||
if send_result.is_err() {
|
||||
return ignore::WalkState::Quit;
|
||||
}
|
||||
|
||||
ignore::WalkState::Continue
|
||||
|
||||
+84
-39
@@ -1,24 +1,12 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
use std::io::{self, Write};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix;
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
use tempdir::TempDir;
|
||||
|
||||
@@ -121,13 +109,13 @@ fn format_output_error(args: &[&str], expected: &str, actual: &str) -> String {
|
||||
}
|
||||
|
||||
/// Normalize the output for comparison.
|
||||
fn normalize_output(s: &str, trim_left: bool, normalize_line: bool) -> String {
|
||||
fn normalize_output(s: &str, trim_start: bool, normalize_line: bool) -> String {
|
||||
// Split into lines and normalize separators.
|
||||
let mut lines = s
|
||||
.replace('\0', "NULL\n")
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let line = if trim_left { line.trim_left() } else { line };
|
||||
let line = if trim_start { line.trim_start() } else { line };
|
||||
let line = line.replace('/', &std::path::MAIN_SEPARATOR.to_string());
|
||||
if normalize_line {
|
||||
let mut words: Vec<_> = line.split_whitespace().collect();
|
||||
@@ -162,6 +150,25 @@ impl TestEnv {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a broken symlink at the given path in the temp_dir.
|
||||
pub fn create_broken_symlink<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
link_path: P,
|
||||
) -> Result<PathBuf, io::Error> {
|
||||
let root = self.test_root();
|
||||
let broken_symlink_link = root.join(link_path);
|
||||
{
|
||||
let temp_target_dir = TempDir::new("fd-tests-broken-symlink")?;
|
||||
let broken_symlink_target = temp_target_dir.path().join("broken_symlink_target");
|
||||
fs::File::create(&broken_symlink_target)?;
|
||||
#[cfg(unix)]
|
||||
unix::fs::symlink(&broken_symlink_target, &broken_symlink_link)?;
|
||||
#[cfg(windows)]
|
||||
windows::fs::symlink_file(&broken_symlink_target, &broken_symlink_link)?;
|
||||
}
|
||||
Ok(broken_symlink_link)
|
||||
}
|
||||
|
||||
/// Get the root directory for the tests.
|
||||
pub fn test_root(&self) -> PathBuf {
|
||||
self.temp_dir.path().to_path_buf()
|
||||
@@ -173,11 +180,42 @@ impl TestEnv {
|
||||
PathBuf::from(components.next().expect("root directory").as_os_str())
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* in the specified path under the root working directory,
|
||||
/// and with the specified arguments produces the expected output.
|
||||
pub fn assert_success_and_get_output<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
args: &[&str],
|
||||
) -> process::Output {
|
||||
// Setup *fd* command.
|
||||
let mut cmd = process::Command::new(&self.fd_exe);
|
||||
cmd.current_dir(self.temp_dir.path().join(path));
|
||||
cmd.arg("--no-global-ignore-file").args(args);
|
||||
|
||||
// Run *fd*.
|
||||
let output = cmd.output().expect("fd output");
|
||||
|
||||
// Check for exit status.
|
||||
if !output.status.success() {
|
||||
panic!(format_exit_error(args, &output));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* with the specified arguments produces the expected output.
|
||||
pub fn assert_output(&self, args: &[&str], expected: &str) {
|
||||
self.assert_output_subdirectory(".", args, expected)
|
||||
}
|
||||
|
||||
/// Similar to assert_output, but able to handle non-utf8 output
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub fn assert_output_raw(&self, args: &[&str], expected: &[u8]) {
|
||||
let output = self.assert_success_and_get_output(".", args);
|
||||
|
||||
assert_eq!(expected, &output.stdout[..]);
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* in the specified path under the root working directory,
|
||||
/// and with the specified arguments produces the expected output.
|
||||
pub fn assert_output_subdirectory<P: AsRef<Path>>(
|
||||
@@ -186,18 +224,7 @@ impl TestEnv {
|
||||
args: &[&str],
|
||||
expected: &str,
|
||||
) {
|
||||
// Setup *fd* command.
|
||||
let mut cmd = process::Command::new(&self.fd_exe);
|
||||
cmd.current_dir(self.temp_dir.path().join(path));
|
||||
cmd.args(args);
|
||||
|
||||
// Run *fd*.
|
||||
let output = cmd.output().expect("fd output");
|
||||
|
||||
// Check for exit status.
|
||||
if !output.status.success() {
|
||||
panic!(format_exit_error(args, &output));
|
||||
}
|
||||
let output = self.assert_success_and_get_output(path, args);
|
||||
|
||||
// Normalize both expected and actual output.
|
||||
let expected = normalize_output(expected, true, self.normalize_line);
|
||||
@@ -213,31 +240,49 @@ impl TestEnv {
|
||||
}
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* with the specified arguments produces the expected error,
|
||||
/// and does not succeed.
|
||||
pub fn assert_failure_with_error(&self, args: &[&str], expected: &str) {
|
||||
let status = self.assert_error_subdirectory(".", args, expected);
|
||||
if status.success() {
|
||||
panic!("error '{}' did not occur.", expected);
|
||||
}
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* with the specified arguments produces the expected error.
|
||||
pub fn assert_error(&self, args: &[&str], expected: &str) {
|
||||
pub fn assert_error(&self, args: &[&str], expected: &str) -> process::ExitStatus {
|
||||
self.assert_error_subdirectory(".", args, expected)
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* in the specified path under the root working directory,
|
||||
/// and with the specified arguments produces an error with the expected message.
|
||||
fn assert_error_subdirectory<P: AsRef<Path>>(&self, path: P, args: &[&str], expected: &str) {
|
||||
fn assert_error_subdirectory<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
args: &[&str],
|
||||
expected: &str,
|
||||
) -> process::ExitStatus {
|
||||
// Setup *fd* command.
|
||||
let mut cmd = process::Command::new(&self.fd_exe);
|
||||
cmd.current_dir(self.temp_dir.path().join(path));
|
||||
cmd.args(args);
|
||||
cmd.arg("--no-global-ignore-file").args(args);
|
||||
|
||||
// Run *fd*.
|
||||
let output = cmd.output().expect("fd output");
|
||||
|
||||
// Check for exit status.
|
||||
if output.status.success() {
|
||||
panic!("error '{}' did not occur.", expected);
|
||||
}
|
||||
// Normalize both expected and actual output.
|
||||
let expected_error = normalize_output(expected, true, self.normalize_line);
|
||||
let actual_err = normalize_output(
|
||||
&String::from_utf8_lossy(&output.stderr),
|
||||
false,
|
||||
self.normalize_line,
|
||||
);
|
||||
|
||||
// Compare actual output to expected output.
|
||||
let actual = String::from_utf8_lossy(&output.stderr);
|
||||
if !actual.starts_with(expected) {
|
||||
panic!(format_output_error(args, &expected, &actual));
|
||||
if !actual_err.trim_start().starts_with(&expected_error) {
|
||||
panic!(format_output_error(args, &expected_error, &actual_err));
|
||||
}
|
||||
|
||||
return output.status;
|
||||
}
|
||||
}
|
||||
|
||||
+459
-79
@@ -1,25 +1,17 @@
|
||||
// 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.
|
||||
|
||||
//! Integration tests for the CLI interface of fd.
|
||||
|
||||
mod testenv;
|
||||
|
||||
use crate::testenv::TestEnv;
|
||||
use regex::escape;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
static DEFAULT_DIRS: &'static [&'static str] = &["one/two/three", "one/two/three/directory_foo"];
|
||||
use regex::escape;
|
||||
|
||||
static DEFAULT_FILES: &'static [&'static str] = &[
|
||||
use crate::testenv::TestEnv;
|
||||
|
||||
static DEFAULT_DIRS: &[&str] = &["one/two/three", "one/two/three/directory_foo"];
|
||||
|
||||
static DEFAULT_FILES: &[&str] = &[
|
||||
"a.foo",
|
||||
"one/b.foo",
|
||||
"one/two/c.foo",
|
||||
@@ -41,7 +33,7 @@ fn get_absolute_root_path(env: &TestEnv) -> String {
|
||||
.to_string();
|
||||
|
||||
#[cfg(windows)]
|
||||
let path = path.trim_left_matches(r"\\?\").to_string();
|
||||
let path = path.trim_start_matches(r"\\?\").to_string();
|
||||
|
||||
path
|
||||
}
|
||||
@@ -57,10 +49,10 @@ fn get_test_env_with_abs_path(dirs: &[&'static str], files: &[&'static str]) ->
|
||||
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();
|
||||
f.write_all(content.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
/// Simple tests
|
||||
/// Simple test
|
||||
#[test]
|
||||
fn test_simple() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
@@ -78,21 +70,27 @@ fn test_simple() {
|
||||
one/two/three/d.foo
|
||||
one/two/three/directory_foo",
|
||||
);
|
||||
}
|
||||
|
||||
te.assert_output(
|
||||
&[],
|
||||
"a.foo
|
||||
e1 e2
|
||||
one
|
||||
one/b.foo
|
||||
one/two
|
||||
one/two/c.foo
|
||||
one/two/C.Foo2
|
||||
one/two/three
|
||||
one/two/three/d.foo
|
||||
one/two/three/directory_foo
|
||||
symlink",
|
||||
);
|
||||
/// Test each pattern type with an empty pattern.
|
||||
#[test]
|
||||
fn test_empty_pattern() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
let expected = "a.foo
|
||||
e1 e2
|
||||
one
|
||||
one/b.foo
|
||||
one/two
|
||||
one/two/c.foo
|
||||
one/two/C.Foo2
|
||||
one/two/three
|
||||
one/two/three/d.foo
|
||||
one/two/three/directory_foo
|
||||
symlink";
|
||||
|
||||
te.assert_output(&["--regex"], expected);
|
||||
te.assert_output(&["--fixed-strings"], expected);
|
||||
te.assert_output(&["--glob"], expected);
|
||||
}
|
||||
|
||||
/// Test multiple directory searches
|
||||
@@ -119,6 +117,45 @@ fn test_multi_file() {
|
||||
te.assert_output(&["b.foo", "test1", "test2"], "test1/b.foo");
|
||||
}
|
||||
|
||||
/// Test search over multiple directory with missing
|
||||
#[test]
|
||||
fn test_multi_file_with_missing() {
|
||||
let dirs = &["real"];
|
||||
let files = &["real/a.foo", "real/b.foo"];
|
||||
let te = TestEnv::new(dirs, files);
|
||||
te.assert_output(&["a.foo", "real", "fake"], "real/a.foo");
|
||||
|
||||
te.assert_error(
|
||||
&["a.foo", "real", "fake"],
|
||||
"[fd error]: Search path 'fake' is not a directory.",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["", "real", "fake"],
|
||||
"real/a.foo
|
||||
real/b.foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["", "real", "fake1", "fake2"],
|
||||
"real/a.foo
|
||||
real/b.foo",
|
||||
);
|
||||
|
||||
te.assert_error(
|
||||
&["", "real", "fake1", "fake2"],
|
||||
"[fd error]: Search path 'fake1' is not a directory.
|
||||
[fd error]: Search path 'fake2' is not a directory.",
|
||||
);
|
||||
|
||||
te.assert_failure_with_error(
|
||||
&["", "fake1", "fake2"],
|
||||
"[fd error]: Search path 'fake1' is not a directory.
|
||||
[fd error]: Search path 'fake2' is not a directory.
|
||||
[fd error]: No valid search paths given.",
|
||||
);
|
||||
}
|
||||
|
||||
/// Explicit root path
|
||||
#[test]
|
||||
fn test_explicit_root_path() {
|
||||
@@ -239,6 +276,99 @@ fn test_case_insensitive() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Glob-based searches (--glob)
|
||||
#[test]
|
||||
fn test_glob_searches() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "*.foo"],
|
||||
"a.foo
|
||||
one/b.foo
|
||||
one/two/c.foo
|
||||
one/two/three/d.foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "[a-c].foo"],
|
||||
"a.foo
|
||||
one/b.foo
|
||||
one/two/c.foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "[a-c].foo*"],
|
||||
"a.foo
|
||||
one/b.foo
|
||||
one/two/C.Foo2
|
||||
one/two/c.foo",
|
||||
);
|
||||
}
|
||||
|
||||
/// Glob-based searches (--glob) in combination with full path searches (--full-path)
|
||||
#[cfg(not(windows))] // TODO: make this work on Windows
|
||||
#[test]
|
||||
fn test_full_path_glob_searches() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "--full-path", "**/one/**/*.foo"],
|
||||
"one/b.foo
|
||||
one/two/c.foo
|
||||
one/two/three/d.foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "--full-path", "**/one/*/*.foo"],
|
||||
" one/two/c.foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "--full-path", "**/one/*/*/*.foo"],
|
||||
" one/two/three/d.foo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smart_case_glob_searches() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "c.foo*"],
|
||||
"one/two/C.Foo2
|
||||
one/two/c.foo",
|
||||
);
|
||||
|
||||
te.assert_output(&["--glob", "C.Foo*"], "one/two/C.Foo2");
|
||||
}
|
||||
|
||||
/// Glob-based searches (--glob) in combination with --case-sensitive
|
||||
#[test]
|
||||
fn test_case_sensitive_glob_searches() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(&["--glob", "--case-sensitive", "c.foo*"], "one/two/c.foo");
|
||||
}
|
||||
|
||||
/// Glob-based searches (--glob) in combination with --extension
|
||||
#[test]
|
||||
fn test_glob_searches_with_extension() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--glob", "--extension", "foo2", "[a-z].*"],
|
||||
"one/two/C.Foo2",
|
||||
);
|
||||
}
|
||||
|
||||
/// Make sure that --regex overrides --glob
|
||||
#[test]
|
||||
fn test_regex_overrides_glob() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(&["--glob", "--regex", "Foo2$"], "one/two/C.Foo2");
|
||||
}
|
||||
|
||||
/// Full path search (--full-path)
|
||||
#[test]
|
||||
fn test_full_path() {
|
||||
@@ -274,6 +404,28 @@ fn test_hidden() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Hidden file attribute on Windows
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_hidden_file_attribute() {
|
||||
use std::os::windows::fs::OpenOptionsExt;
|
||||
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesa
|
||||
const FILE_ATTRIBUTE_HIDDEN: u32 = 2;
|
||||
|
||||
fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.attributes(FILE_ATTRIBUTE_HIDDEN)
|
||||
.open(te.test_root().join("hidden-file.txt"))
|
||||
.unwrap();
|
||||
|
||||
te.assert_output(&["--hidden", "hidden-file.txt"], "hidden-file.txt");
|
||||
te.assert_output(&["hidden-file.txt"], "");
|
||||
}
|
||||
|
||||
/// Ignored files (--no-ignore)
|
||||
#[test]
|
||||
fn test_no_ignore() {
|
||||
@@ -450,6 +602,75 @@ fn test_follow() {
|
||||
);
|
||||
}
|
||||
|
||||
// File system boundaries (--one-file-system)
|
||||
// Limited to Unix because, to the best of my knowledge, there is no easy way to test a use case
|
||||
// file systems mounted into the tree on Windows.
|
||||
// Not limiting depth causes massive delay under Darwin, see BurntSushi/ripgrep#1429
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_file_system_boundaries() {
|
||||
// Helper function to get the device ID for a given path
|
||||
// Inspired by https://github.com/BurntSushi/ripgrep/blob/8892bf648cfec111e6e7ddd9f30e932b0371db68/ignore/src/walk.rs#L1693
|
||||
fn device_num(path: impl AsRef<Path>) -> u64 {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
path.as_ref().metadata().map(|md| md.dev()).unwrap()
|
||||
}
|
||||
|
||||
// Can't simulate file system boundaries
|
||||
let te = TestEnv::new(&[], &[]);
|
||||
|
||||
let dev_null = Path::new("/dev/null");
|
||||
|
||||
// /dev/null should exist in all sane Unixes. Skip if it doesn't exist for some reason.
|
||||
// Also skip should it be on the same device as the root partition for some reason.
|
||||
if !dev_null.is_file() || device_num(dev_null) == device_num("/") {
|
||||
return;
|
||||
}
|
||||
|
||||
te.assert_output(
|
||||
&["--full-path", "--max-depth", "2", "^/dev/null$", "/"],
|
||||
"/dev/null",
|
||||
);
|
||||
te.assert_output(
|
||||
&[
|
||||
"--one-file-system",
|
||||
"--full-path",
|
||||
"--max-depth",
|
||||
"2",
|
||||
"^/dev/null$",
|
||||
"/",
|
||||
],
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_follow_broken_symlink() {
|
||||
let mut te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
te.create_broken_symlink("broken_symlink")
|
||||
.expect("Failed to create broken symlink.");
|
||||
|
||||
te.assert_output(
|
||||
&["symlink"],
|
||||
"broken_symlink
|
||||
symlink",
|
||||
);
|
||||
te.assert_output(
|
||||
&["--type", "symlink", "symlink"],
|
||||
"broken_symlink
|
||||
symlink",
|
||||
);
|
||||
|
||||
te.assert_output(&["--type", "file", "symlink"], "");
|
||||
|
||||
te.assert_output(
|
||||
&["--follow", "--type", "symlink", "symlink"],
|
||||
"broken_symlink",
|
||||
);
|
||||
te.assert_output(&["--follow", "--type", "file", "symlink"], "");
|
||||
}
|
||||
|
||||
/// Null separator (--print0)
|
||||
#[test]
|
||||
fn test_print0() {
|
||||
@@ -503,6 +724,40 @@ fn test_max_depth() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Minimum depth (--min-depth)
|
||||
#[test]
|
||||
fn test_min_depth() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--min-depth", "3"],
|
||||
"one/two/c.foo
|
||||
one/two/C.Foo2
|
||||
one/two/three
|
||||
one/two/three/d.foo
|
||||
one/two/three/directory_foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--min-depth", "4"],
|
||||
"one/two/three/d.foo
|
||||
one/two/three/directory_foo",
|
||||
);
|
||||
}
|
||||
|
||||
/// Exact depth (--exact-depth)
|
||||
#[test]
|
||||
fn test_exact_depth() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--exact-depth", "3"],
|
||||
"one/two/c.foo
|
||||
one/two/C.Foo2
|
||||
one/two/three",
|
||||
);
|
||||
}
|
||||
|
||||
/// Absolute paths (--absolute-path)
|
||||
#[test]
|
||||
fn test_absolute_path() {
|
||||
@@ -730,7 +985,9 @@ fn test_extension() {
|
||||
/// Symlink as search directory
|
||||
#[test]
|
||||
fn test_symlink_as_root() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
let mut te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
te.create_broken_symlink("broken_symlink")
|
||||
.expect("Failed to create broken symlink.");
|
||||
|
||||
// From: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html
|
||||
// The getcwd() function shall place an absolute pathname of the current working directory in
|
||||
@@ -751,6 +1008,7 @@ fn test_symlink_as_root() {
|
||||
&["", parent_parent],
|
||||
&format!(
|
||||
"{dir}/a.foo
|
||||
{dir}/broken_symlink
|
||||
{dir}/e1 e2
|
||||
{dir}/one
|
||||
{dir}/one/b.foo
|
||||
@@ -842,7 +1100,6 @@ fn test_symlink_and_full_path_abs_path() {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Exclude patterns (--exclude)
|
||||
#[test]
|
||||
fn test_excludes() {
|
||||
@@ -896,28 +1153,11 @@ fn test_excludes() {
|
||||
/// Shell script execution (--exec)
|
||||
#[test]
|
||||
fn test_exec() {
|
||||
assert_exec_output("--exec");
|
||||
}
|
||||
|
||||
/// Shell script execution using -exec
|
||||
#[test]
|
||||
fn test_exec_substitution() {
|
||||
assert_exec_output("-exec");
|
||||
}
|
||||
|
||||
// Shell script execution using -x
|
||||
#[test]
|
||||
fn test_exec_short_arg() {
|
||||
assert_exec_output("-x");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn assert_exec_output(exec_style: &str) {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
// TODO Windows tests: D:file.txt \file.txt \\server\share\file.txt ...
|
||||
if !cfg!(windows) {
|
||||
te.assert_output(
|
||||
&["--absolute-path", "foo", exec_style, "echo"],
|
||||
&["--absolute-path", "foo", "--exec", "echo"],
|
||||
&format!(
|
||||
"{abs_path}/a.foo
|
||||
{abs_path}/one/b.foo
|
||||
@@ -930,7 +1170,7 @@ fn assert_exec_output(exec_style: &str) {
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", exec_style, "echo", "{}"],
|
||||
&["foo", "--exec", "echo", "{}"],
|
||||
"a.foo
|
||||
one/b.foo
|
||||
one/two/C.Foo2
|
||||
@@ -940,7 +1180,7 @@ fn assert_exec_output(exec_style: &str) {
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", exec_style, "echo", "{.}"],
|
||||
&["foo", "--exec", "echo", "{.}"],
|
||||
"a
|
||||
one/b
|
||||
one/two/C
|
||||
@@ -950,7 +1190,7 @@ fn assert_exec_output(exec_style: &str) {
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", exec_style, "echo", "{/}"],
|
||||
&["foo", "--exec", "echo", "{/}"],
|
||||
"a.foo
|
||||
b.foo
|
||||
C.Foo2
|
||||
@@ -960,7 +1200,7 @@ fn assert_exec_output(exec_style: &str) {
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", exec_style, "echo", "{/.}"],
|
||||
&["foo", "--exec", "echo", "{/.}"],
|
||||
"a
|
||||
b
|
||||
C
|
||||
@@ -970,7 +1210,7 @@ fn assert_exec_output(exec_style: &str) {
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", exec_style, "echo", "{//}"],
|
||||
&["foo", "--exec", "echo", "{//}"],
|
||||
".
|
||||
one
|
||||
one/two
|
||||
@@ -979,29 +1219,19 @@ fn assert_exec_output(exec_style: &str) {
|
||||
one/two/three",
|
||||
);
|
||||
|
||||
te.assert_output(&["e1", exec_style, "printf", "%s.%s\n"], "e1 e2.");
|
||||
te.assert_output(&["e1", "--exec", "printf", "%s.%s\n"], "e1 e2.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec_batch() {
|
||||
assert_exec_batch_output("--exec-batch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec_batch_short_arg() {
|
||||
assert_exec_batch_output("-X");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn assert_exec_batch_output(exec_style: &str) {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
let te = te.normalize_line(true);
|
||||
|
||||
// TODO Test for windows
|
||||
if !cfg!(windows) {
|
||||
te.assert_output(
|
||||
&["--absolute-path", "foo", exec_style, "echo"],
|
||||
&["--absolute-path", "foo", "--exec-batch", "echo"],
|
||||
&format!(
|
||||
"{abs_path}/a.foo {abs_path}/one/b.foo {abs_path}/one/two/C.Foo2 {abs_path}/one/two/c.foo {abs_path}/one/two/three/d.foo {abs_path}/one/two/three/directory_foo",
|
||||
abs_path = &abs_path
|
||||
@@ -1009,34 +1239,37 @@ fn assert_exec_batch_output(exec_style: &str) {
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", exec_style, "echo", "{}"],
|
||||
&["foo", "--exec-batch", "echo", "{}"],
|
||||
"a.foo one/b.foo one/two/C.Foo2 one/two/c.foo one/two/three/d.foo one/two/three/directory_foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", exec_style, "echo", "{/}"],
|
||||
&["foo", "--exec-batch", "echo", "{/}"],
|
||||
"a.foo b.foo C.Foo2 c.foo d.foo directory_foo",
|
||||
);
|
||||
|
||||
te.assert_output(&["no_match", exec_style, "echo", "Matched: ", "{/}"], "");
|
||||
te.assert_output(
|
||||
&["no_match", "--exec-batch", "echo", "Matched: ", "{/}"],
|
||||
"",
|
||||
);
|
||||
|
||||
te.assert_error(
|
||||
&["foo", exec_style, "echo", "{}", "{}"],
|
||||
te.assert_failure_with_error(
|
||||
&["foo", "--exec-batch", "echo", "{}", "{}"],
|
||||
"[fd error]: Only one placeholder allowed for batch commands",
|
||||
);
|
||||
|
||||
te.assert_error(
|
||||
&["foo", exec_style, "echo", "{/}", ";", "-x", "echo"],
|
||||
te.assert_failure_with_error(
|
||||
&["foo", "--exec-batch", "echo", "{/}", ";", "-x", "echo"],
|
||||
"error: The argument '--exec <cmd>' cannot be used with '--exec-batch <cmd>'",
|
||||
);
|
||||
|
||||
te.assert_error(
|
||||
&["foo", exec_style],
|
||||
te.assert_failure_with_error(
|
||||
&["foo", "--exec-batch"],
|
||||
"error: The argument '--exec-batch <cmd>' requires a value but none was supplied",
|
||||
);
|
||||
|
||||
te.assert_error(
|
||||
&["foo", exec_style, "echo {}"],
|
||||
te.assert_failure_with_error(
|
||||
&["foo", "--exec-batch", "echo {}"],
|
||||
"[fd error]: First argument of exec-batch is expected to be a fixed executable",
|
||||
);
|
||||
}
|
||||
@@ -1231,3 +1464,150 @@ fn test_modified_asolute() {
|
||||
"30dec2017",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_path_separator() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", "one", "--path-separator", "="],
|
||||
"one=b.foo
|
||||
one=two=c.foo
|
||||
one=two=C.Foo2
|
||||
one=two=three=d.foo
|
||||
one=two=three=directory_foo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_base_directory() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["--base-directory", "one"],
|
||||
"b.foo
|
||||
two
|
||||
two/c.foo
|
||||
two/C.Foo2
|
||||
two/three
|
||||
two/three/d.foo
|
||||
two/three/directory_foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--base-directory", "one/two", "foo"],
|
||||
"c.foo
|
||||
C.Foo2
|
||||
three/d.foo
|
||||
three/directory_foo",
|
||||
);
|
||||
|
||||
// Explicit root path
|
||||
te.assert_output(
|
||||
&["--base-directory", "one", "foo", "two"],
|
||||
"two/c.foo
|
||||
two/C.Foo2
|
||||
two/three/d.foo
|
||||
two/three/directory_foo",
|
||||
);
|
||||
|
||||
// Ignore base directory when absolute path is used
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
let abs_base_dir = &format!("{abs_path}/one/two", abs_path = &abs_path);
|
||||
te.assert_output(
|
||||
&["--base-directory", &abs_base_dir, "foo", &abs_path],
|
||||
&format!(
|
||||
"{abs_path}/a.foo
|
||||
{abs_path}/one/b.foo
|
||||
{abs_path}/one/two/c.foo
|
||||
{abs_path}/one/two/C.Foo2
|
||||
{abs_path}/one/two/three/d.foo
|
||||
{abs_path}/one/two/three/directory_foo",
|
||||
abs_path = &abs_path
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_results() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
// Unrestricted
|
||||
te.assert_output(
|
||||
&["--max-results=0", "c.foo"],
|
||||
"one/two/C.Foo2
|
||||
one/two/c.foo",
|
||||
);
|
||||
|
||||
// Limited to two results
|
||||
te.assert_output(
|
||||
&["--max-results=2", "c.foo"],
|
||||
"one/two/C.Foo2
|
||||
one/two/c.foo",
|
||||
);
|
||||
|
||||
// Limited to one result. We could find either C.Foo2 or c.foo
|
||||
let assert_just_one_result_with_option = |option| {
|
||||
let output = te.assert_success_and_get_output(".", &[option, "c.foo"]);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout)
|
||||
.trim()
|
||||
.replace(&std::path::MAIN_SEPARATOR.to_string(), "/");
|
||||
assert!(stdout == "one/two/C.Foo2" || stdout == "one/two/c.foo");
|
||||
};
|
||||
assert_just_one_result_with_option("--max-results=1");
|
||||
assert_just_one_result_with_option("-1");
|
||||
}
|
||||
|
||||
/// Filenames with non-utf8 paths are passed to the executed program unchanged
|
||||
///
|
||||
/// Note:
|
||||
/// - the test is disabled on Darwin/OSX, since it coerces file names to UTF-8,
|
||||
/// even when the requested file name is not valid UTF-8.
|
||||
/// - the test is currently disabled on Windows because I'm not sure how to create
|
||||
/// invalid UTF-8 files on Windows
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[test]
|
||||
fn test_exec_invalid_utf8() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let dirs = &["test1"];
|
||||
let files = &[];
|
||||
let te = TestEnv::new(dirs, files);
|
||||
|
||||
fs::File::create(
|
||||
te.test_root()
|
||||
.join(OsStr::from_bytes(b"test1/test_\xFEinvalid.txt")),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
te.assert_output_raw(
|
||||
&["", "test1/", "--exec", "echo", "{}"],
|
||||
b"test1/test_\xFEinvalid.txt\n",
|
||||
);
|
||||
|
||||
te.assert_output_raw(
|
||||
&["", "test1/", "--exec", "echo", "{/}"],
|
||||
b"test_\xFEinvalid.txt\n",
|
||||
);
|
||||
|
||||
te.assert_output_raw(&["", "test1/", "--exec", "echo", "{//}"], b"test1\n");
|
||||
|
||||
te.assert_output_raw(
|
||||
&["", "test1/", "--exec", "echo", "{.}"],
|
||||
b"test1/test_\xFEinvalid\n",
|
||||
);
|
||||
|
||||
te.assert_output_raw(
|
||||
&["", "test1/", "--exec", "echo", "{/.}"],
|
||||
b"test_\xFEinvalid\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_details() {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
// Make sure we can execute 'fd --list-details' without any errors.
|
||||
te.assert_success_and_get_output(".", &["--list-details"]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user