mirror of
https://github.com/sharkdp/fd.git
synced 2026-09-01 15:12:46 +08:00
Compare commits
349 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f58e8f706 | |||
| f1ab443dd0 | |||
| acd4910f2c | |||
| c83bcfbdfc | |||
| c2b46f247f | |||
| fe53af064b | |||
| 6aa87f3423 | |||
| 74e593c43c | |||
| df4227c614 | |||
| 5ade72a5e1 | |||
| d8bd5f9d51 | |||
| 85f9db0565 | |||
| 2672d84160 | |||
| 8cfdcf43f6 | |||
| 8c197d2866 | |||
| 051ff5987a | |||
| 630f173724 | |||
| 64e6ea9fe9 | |||
| 33feb511f0 | |||
| 985fab16bb | |||
| a17a3fe5ef | |||
| 04debe5c4e | |||
| ec99e5d34c | |||
| 047f1be792 | |||
| ecd5ec6387 | |||
| 3c87952c47 | |||
| 4b5efa3438 | |||
| afcdaf2e3d | |||
| 1abdf47731 | |||
| 92b5c9f71e | |||
| f09f038bd2 | |||
| 6b40a075cd | |||
| 45d1b15cff | |||
| 32ec7afc3a | |||
| 34b8c0df73 | |||
| 831f517f5a | |||
| f064ee5509 | |||
| 26c2371835 | |||
| 8832e471bc | |||
| daf5191aac | |||
| c0c38872c4 | |||
| 81a27fa9bd | |||
| 6ff58abb6c | |||
| 974802e817 | |||
| deea31cbbc | |||
| caad43fe6c | |||
| 095bad550f | |||
| f7a96bd980 | |||
| cdf737bf5c | |||
| e2bb932f87 | |||
| 437bd1188e | |||
| dae09ecf6d | |||
| d81cf34771 | |||
| b98f0783ff | |||
| 8543ca645d | |||
| 984f04f142 | |||
| abe8aa55c0 | |||
| 54c117d72f | |||
| 8691ab4bed | |||
| cb1cfa108b | |||
| 8bdd8f8e8f | |||
| f9c4e8d399 | |||
| adc467b2b2 | |||
| 56b6389dac | |||
| 046b0574dc | |||
| 6407dc4eb4 | |||
| 2ebef2d46f | |||
| 27caa33729 | |||
| 53226f82eb | |||
| 8fb25f15c9 | |||
| d375b5e1f5 | |||
| 2465cd1399 | |||
| 1c7e647f61 | |||
| 67e76b8b5b | |||
| aa70c5a446 | |||
| 50a2bab5cd | |||
| 641976cf7a | |||
| 6fb5004b85 | |||
| e4f61bc795 | |||
| c428f52209 | |||
| 59c9901cde | |||
| 4dee8b7940 | |||
| 9a45947234 | |||
| c1ef68662c | |||
| 9628d7fdcb | |||
| cb47be6497 | |||
| a3200204dc | |||
| e2b2701965 | |||
| 409cf0c7a1 | |||
| f01685c63f | |||
| 060d14b5dc | |||
| fe922688b3 | |||
| 4172ed03f0 | |||
| 0bbc7f5219 | |||
| ace3f512d3 | |||
| 2f3b472dfd | |||
| 0207c1371e | |||
| fea85dbfb3 | |||
| b5e5b132c8 | |||
| 6a9f16e159 | |||
| f02b3cba6a | |||
| 666f90db87 | |||
| 2a1ee0b17d | |||
| 388b90e624 | |||
| 011e09840a | |||
| 37483036e0 | |||
| 8c1b037279 | |||
| f9a32583a5 | |||
| 9d840dd485 | |||
| 4f6c43a6dc | |||
| 2cf8e7b8a5 | |||
| 67f6fdf6a7 | |||
| 8ce127e443 | |||
| b1e48efc4a | |||
| ab3ded8d27 | |||
| 5397787824 | |||
| ffd49c60bd | |||
| 89a8f5573d | |||
| d49b0aa977 | |||
| ddddc3f1db | |||
| 87d0ccd4c0 | |||
| c654159bf2 | |||
| 588b0dc7c9 | |||
| 88fb9d73d1 | |||
| 3386b854b0 | |||
| 47d95284aa | |||
| 631931b431 | |||
| 40d811c7be | |||
| e5ee5eb7b3 | |||
| 82eeeda504 | |||
| f16552f29c | |||
| e30bc16780 | |||
| b4be1f161c | |||
| 8805bd32e2 | |||
| 7a3994decb | |||
| 7e8c2248d0 | |||
| ad8256c217 | |||
| 1bf193af60 | |||
| 1c1be76f35 | |||
| 267598fba5 | |||
| 11b3e96d2d | |||
| a6086abe7f | |||
| 7f65e7a0d6 | |||
| 814d37030e | |||
| 089c183d30 | |||
| 370d9f081f | |||
| 86fe9977e8 | |||
| b1bb60ebb9 | |||
| b4d39270d6 | |||
| 6d83eea32b | |||
| 26beaf97a6 | |||
| 33d37d7623 | |||
| dce004afef | |||
| c1941cc7ed | |||
| 0a1c65b9ce | |||
| 4fb9d84ffc | |||
| 2a84eb5cea | |||
| 6dd59b33b1 | |||
| 4fea5f3a3c | |||
| b269f4902e | |||
| 33791d8400 | |||
| 4e737b4b44 | |||
| c985ba2e6f | |||
| 658a97a94a | |||
| bc2b7a33ae | |||
| 1045930dd9 | |||
| cc36543da2 | |||
| 050fa9c3b1 | |||
| 6be33fbf33 | |||
| 61a065dd92 | |||
| 941caeb053 | |||
| 32a34cf8c9 | |||
| 39fb41f05a | |||
| 26ad7da347 | |||
| c5b920b913 | |||
| 37613d6989 | |||
| b4ae0dca7e | |||
| 8dde7c3365 | |||
| a5f5ad6254 | |||
| aeb3722b69 | |||
| faf934da4b | |||
| 4d66c84109 | |||
| 2bc72a5eca | |||
| 8702133ee8 | |||
| 9192bc153f | |||
| b2797c0c45 | |||
| aaf9e024d5 | |||
| 5784823aca | |||
| 51aea57a6a | |||
| 8914b43845 | |||
| 59ea560d37 | |||
| c165ff0546 | |||
| e2b8a3cce2 | |||
| 9bd1d12c00 | |||
| c0bfc65d88 | |||
| 44ee5a0b85 | |||
| 941a1e3d21 | |||
| 0999f8e328 | |||
| 9be32fa6a7 | |||
| 5ed7514096 | |||
| 3c5d8a1e86 | |||
| ccdbc76f5a | |||
| 355a171557 | |||
| 5f4a1203b4 | |||
| 0f82fe7ec0 | |||
| c18caaa6f6 | |||
| bc88b8f80e | |||
| 18709b1ede | |||
| 1a6c92c475 | |||
| 3104729f29 | |||
| 673392045f | |||
| 26f71dd21a | |||
| c022528f54 | |||
| 46db4a3860 | |||
| b3f1e1844d | |||
| bba5c4f607 | |||
| 614f576c47 | |||
| dc29ef70c4 | |||
| d702d849ea | |||
| 6b232a82ee | |||
| 87ba4e3bb0 | |||
| 9d26b74c2a | |||
| 7b8d9322ba | |||
| 5ad69fb2fb | |||
| 19afb15a98 | |||
| f4ed2ba74d | |||
| e38b7d7bff | |||
| 1ec204caa8 | |||
| dbf1ba8b82 | |||
| 4bf7ab67d0 | |||
| 7ecb623950 | |||
| 5863a5e706 | |||
| d32fe0241e | |||
| e9cf8af911 | |||
| 15a3ca0c80 | |||
| 701b8f209b | |||
| 570ea78d24 | |||
| ce73c086b6 | |||
| 6a01faf6e1 | |||
| 200dfaa20a | |||
| 6dc704a18e | |||
| 715b07599b | |||
| d2d2c31307 | |||
| f7f5192139 | |||
| ddfa0873fb | |||
| b6c95a4800 | |||
| 8d85debc12 | |||
| 37def9bb83 | |||
| e0eab07881 | |||
| 16eed82ca1 | |||
| 2a23905af5 | |||
| 54d9dde0d4 | |||
| e649c8fa79 | |||
| fe5d8aa29e | |||
| ecfd5568cf | |||
| 08aa06a0ad | |||
| ad1b4f3b2b | |||
| 1bc58b2fbb | |||
| 718f723d31 | |||
| 884bd41cae | |||
| fb1cd3a322 | |||
| db5f2fc3ac | |||
| 137fe2e697 | |||
| 4a5a5faf4d | |||
| f2632d5fce | |||
| c38ef0e9b2 | |||
| be2238ddf4 | |||
| 7150c9a3a9 | |||
| 0b04f39398 | |||
| 0677e6331d | |||
| b441528067 | |||
| 8fc3a83d92 | |||
| 97e3110333 | |||
| 049b9ec06b | |||
| e075e1cf7c | |||
| 35e5ecd492 | |||
| 4f2d9e77b3 | |||
| ccb899a511 | |||
| affe41949d | |||
| 68a85c7e5b | |||
| b9b6964f30 | |||
| 0cd9d8c3a6 | |||
| 108fce3396 | |||
| a7f4a0a581 | |||
| b98e4c9931 | |||
| 43b124cf19 | |||
| 20e179c91d | |||
| d8da6ec83c | |||
| 4731dc670c | |||
| a84536a173 | |||
| 080e8a9b6e | |||
| 3b55edf5dd | |||
| 0aae4a3d46 | |||
| 34e4620e26 | |||
| 2403ac350a | |||
| 6f22957cdc | |||
| 00b57b50ae | |||
| 396b9baaa5 | |||
| 083b1cfd9b | |||
| 4ec33d064c | |||
| 7399558a06 | |||
| 4b61beaa75 | |||
| 2256affd8f | |||
| b0fb774c0e | |||
| 32a321aafd | |||
| 83db8673cf | |||
| 8073635177 | |||
| 60df14f0a3 | |||
| 042959a4af | |||
| 89124b317e | |||
| adaf4f6d73 | |||
| d8c588d805 | |||
| df44407b84 | |||
| c1b8d1eae1 | |||
| 2a3dd5b631 | |||
| fbce0d4f39 | |||
| 1397dbcc6f | |||
| 2def50bddd | |||
| 6c69b98b49 | |||
| fe26f71582 | |||
| 3afd216008 | |||
| f1f537b83a | |||
| a2349848c0 | |||
| 916b55fae1 | |||
| f41c7a4344 | |||
| 4884c23283 | |||
| d67b1bba61 | |||
| b34716051a | |||
| fde8f2e8e3 | |||
| a790f9bd62 | |||
| b4c8a8ba56 | |||
| 1268e982f6 | |||
| 31c76989ad | |||
| f92dfb86bb | |||
| c55b255a3e | |||
| 977e0ac694 | |||
| 550e3b3572 | |||
| e06189e654 | |||
| 2d1d24c38c | |||
| 10ea476e31 | |||
| 1586d653ae | |||
| fb5ba2c068 | |||
| f8b02ac5ae | |||
| 1b0c8f349a | |||
| 918e4a014a | |||
| f5261595a7 | |||
| d8d0cf5d6e | |||
| eb728756ed | |||
| 3f69c0fa1a |
@@ -0,0 +1,29 @@
|
|||||||
|
name: fd
|
||||||
|
version: git
|
||||||
|
summary: "A simple, fast and user-friendly alternative to 'find'"
|
||||||
|
description: |
|
||||||
|
fd is a simple, fast and user-friendly alternative to find.
|
||||||
|
- While it does not seek to mirror all of find's powerful functionality, it provides sensible (opinionated) defaults for 80% of the use cases.
|
||||||
|
Features
|
||||||
|
Convenient syntax: fd PATTERN instead of find -iname '*PATTERN*'.
|
||||||
|
Colorized terminal output (similar to ls).
|
||||||
|
It's fast (see benchmarks below).
|
||||||
|
Smart case: the search is case-insensitive by default. It switches to case-sensitive if the pattern contains an uppercase character*.
|
||||||
|
Ignores hidden directories and files, by default.
|
||||||
|
Ignores patterns from your .gitignore, by default.
|
||||||
|
Regular expressions.
|
||||||
|
Unicode-awareness.
|
||||||
|
The command name is 50% shorter* than find :-).
|
||||||
|
Parallel command execution with a syntax similar to GNU Parallel.
|
||||||
|
|
||||||
|
grade: stable
|
||||||
|
confinement: classic
|
||||||
|
|
||||||
|
parts:
|
||||||
|
fd:
|
||||||
|
plugin: rust
|
||||||
|
source: ./
|
||||||
|
|
||||||
|
apps:
|
||||||
|
fd:
|
||||||
|
command: bin/fd
|
||||||
+124
-18
@@ -1,39 +1,145 @@
|
|||||||
language: rust
|
language: rust
|
||||||
rust:
|
cache: cargo
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- nightly
|
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
# allow_failures:
|
||||||
- rust: nightly
|
# - rust: nightly
|
||||||
include:
|
include:
|
||||||
# Nightly channel.
|
# Stable channel.
|
||||||
- os: linux
|
- os: linux
|
||||||
rust: nightly
|
rust: stable
|
||||||
env: TARGET=i686-unknown-linux-musl
|
env: TARGET=x86_64-unknown-linux-gnu
|
||||||
- os: linux
|
- os: linux
|
||||||
rust: nightly
|
rust: stable
|
||||||
env: TARGET=x86_64-unknown-linux-musl
|
env: TARGET=x86_64-unknown-linux-musl
|
||||||
|
- os: linux
|
||||||
|
rust: stable
|
||||||
|
env: TARGET=i686-unknown-linux-gnu
|
||||||
|
- os: linux
|
||||||
|
rust: stable
|
||||||
|
env: TARGET=i686-unknown-linux-musl
|
||||||
- os: osx
|
- os: osx
|
||||||
rust: nightly
|
rust: stable
|
||||||
env: TARGET=x86_64-apple-darwin
|
env: TARGET=x86_64-apple-darwin
|
||||||
|
- os: linux
|
||||||
|
rust: stable
|
||||||
|
env:
|
||||||
|
- TARGET=arm-unknown-linux-gnueabihf
|
||||||
|
- CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
|
||||||
|
- os: linux
|
||||||
|
rust: stable
|
||||||
|
env:
|
||||||
|
- TARGET=arm-unknown-linux-musleabihf
|
||||||
|
- CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
|
||||||
|
|
||||||
# Beta channel.
|
# Beta channel.
|
||||||
- os: linux
|
|
||||||
rust: beta
|
|
||||||
env: TARGET=x86_64-unknown-linux-musl
|
|
||||||
- os: linux
|
- os: linux
|
||||||
rust: beta
|
rust: beta
|
||||||
env: TARGET=x86_64-unknown-linux-gnu
|
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.
|
# Minimum Rust supported channel.
|
||||||
- os: linux
|
- os: linux
|
||||||
rust: 1.16.0
|
rust: 1.31.0
|
||||||
env: TARGET=x86_64-unknown-linux-gnu
|
env: TARGET=x86_64-unknown-linux-gnu
|
||||||
- os: linux
|
- os: linux
|
||||||
rust: 1.16.0
|
rust: 1.31.0
|
||||||
env: TARGET=x86_64-unknown-linux-musl
|
env: TARGET=x86_64-unknown-linux-musl
|
||||||
|
- os: linux
|
||||||
|
rust: 1.31.0
|
||||||
|
env: TARGET=i686-unknown-linux-gnu
|
||||||
|
- os: linux
|
||||||
|
rust: 1.31.0
|
||||||
|
env: TARGET=i686-unknown-linux-musl
|
||||||
|
- os: osx
|
||||||
|
rust: 1.31.0
|
||||||
|
env: TARGET=x86_64-apple-darwin
|
||||||
|
|
||||||
after_success:
|
# Code formatting check
|
||||||
- bash tests/test.sh
|
- os: linux
|
||||||
|
rust: nightly
|
||||||
|
# skip the global install step
|
||||||
|
install:
|
||||||
|
- cargo install --debug --force rustfmt-nightly
|
||||||
|
script: cargo fmt -- --check
|
||||||
|
|
||||||
|
sudo: required
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- ci/before_install.bash
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
# Default target on travis-ci.
|
||||||
|
# Used as conditional check in the install stage
|
||||||
|
- HOST=x86_64-unknown-linux-gnu
|
||||||
|
# Used on the deployment script
|
||||||
|
- PROJECT_NAME=fd
|
||||||
|
|
||||||
|
install:
|
||||||
|
# prevent target re-add error from rustup
|
||||||
|
- if [[ $TRAVIS_OS_NAME = linux && $HOST != $TARGET ]]; then rustup target add $TARGET; fi
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ci/script.bash
|
||||||
|
|
||||||
|
before_deploy:
|
||||||
|
- bash ci/before_deploy.bash
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
provider: releases
|
||||||
|
# NOTE updating the `api_key.secure`
|
||||||
|
# - go to: https://github.com/settings/tokens/new
|
||||||
|
# - generate new token using `public_repo` scope
|
||||||
|
# - encrypt it using: `travis encrypt API_KEY_HERE`
|
||||||
|
# - paste the output below
|
||||||
|
api_key:
|
||||||
|
secure: "RyFdh2lpDmaNhPar7ezsb18Xz+6XFM40y7cZCDRML+Sk+eYK1xtDNfEhDRJU5Qo1ReVsByds/QJTSXr2KmZPk3lXwG3SiN7UtrLUxCxFr6qrcM/iujlKTf5UxeRklkzPXxnH95DEyEgxvgbVhWTGVDWoyMnrVQXZKDy6z1iAiYB5h2Zl1rs+MRb/Enlt5q6XIKAlG0ppGtl8CfYudq5ZiqfJaMWTt9SWm2YskC8FeMc0S3IM6/EhTvaNYLdaarFqVWQEVql+6oCuL3ayPzmGyxLdxM37tIMNQ0f97zxqWodacXTG5ULdRD8if1l/SmTujrtjbZ0KWRjsjOq4vBtxBJKGdprcSiB0xH/hToqqtTSO0z5FPXi5cB8UlK6YLDDHcP3kXNer8CYMLI1VPaUDLTF57/0/RPi2DZiiGfZsIAS6PsICbHdTQVzxQckM4lN1vnAGgkhXIMbztml21pv+QrGy98OZJ0ubf5ztgQhpT0WPH4JXT8M6htsoo8dZf8lQ5aLfmW9RKePJDqixQwPqmimPIkrlxRDTDGII0ZAZws7l779eOLmEcM2tH2HbsUKUCZIG/pRHLSlP45Jn2bULGzuXZ2daq70z6zvIbom0CUzSXIvdTXEZI2AM5RBvPYGGaKI8YlxgRdQvJp3h0BzPdFOXI3RAxscCY7PJpa/RdIg="
|
||||||
|
# for uploading multiple files
|
||||||
|
file_glob: true
|
||||||
|
# NOTE explanation on each env variable
|
||||||
|
# - PROJECT_NAME: name of the project, set on the `env.global` above
|
||||||
|
# - TRAVIS_TAG: tag name that the build is being deployed for, usually the version number
|
||||||
|
# - TARGET: target triple of the build
|
||||||
|
file:
|
||||||
|
- $PROJECT_NAME-$TRAVIS_TAG-$TARGET.*
|
||||||
|
- $PROJECT_NAME*.deb
|
||||||
|
# don't delete artifacts from previous stage
|
||||||
|
skip_cleanup: true
|
||||||
|
on:
|
||||||
|
# deploy only if we push a tag
|
||||||
|
tags: true
|
||||||
|
# deploy only on stable channel that has TARGET env variable sets
|
||||||
|
condition: $TRAVIS_RUST_VERSION = stable && $TARGET != ""
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email:
|
email:
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
## Contributing to *fd*
|
||||||
|
|
||||||
|
**Thank you very much for considering to contribute to this project!**
|
||||||
|
|
||||||
|
We welcome any form of contribution:
|
||||||
|
|
||||||
|
* New issues (feature requests, bug reports, questions, ideas, ...)
|
||||||
|
* Pull requests (documentation improvements, code improvements, new features, ...)
|
||||||
|
|
||||||
|
**Note**: Before you take the time to open a pull request, please open a ticket first. This will
|
||||||
|
give us the chance to discuss any potential changes first.
|
||||||
|
|
||||||
|
## Important links
|
||||||
|
|
||||||
|
* [Open issues](https://github.com/sharkdp/fd/issues)
|
||||||
|
* [Open pull requests](https://github.com/sharkdp/fd/pulls)
|
||||||
|
* [Development section in the README](https://github.com/sharkdp/fd#development)
|
||||||
|
* [fd on crates.io](https://crates.io/crates/fd-find)
|
||||||
|
* [LICENSE-APACHE](https://github.com/sharkdp/fd/blob/master/LICENSE-APACHE) and [LICENSE-MIT](https://github.com/sharkdp/fd/blob/master/LICENSE-MIT)
|
||||||
|
|
||||||
Generated
+412
-162
@@ -1,92 +1,168 @@
|
|||||||
[root]
|
|
||||||
name = "fd"
|
|
||||||
version = "2.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"ignore 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.6.3"
|
version = "0.6.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
version = "0.9.0"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.2"
|
version = "0.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
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)",
|
||||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "0.8.2"
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "2.24.2"
|
version = "2.32.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"strsim 0.6.0 (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.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"unicode-width 0.1.4 (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.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam"
|
name = "crossbeam-channel"
|
||||||
version = "0.2.10"
|
version = "0.3.8"
|
||||||
|
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"
|
||||||
|
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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctrlc"
|
||||||
|
version = "3.1.1"
|
||||||
|
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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diff"
|
||||||
|
version = "0.1.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fd-find"
|
||||||
|
version = "7.3.0"
|
||||||
|
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)",
|
||||||
|
"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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetime"
|
||||||
|
version = "0.2.4"
|
||||||
|
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)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.5"
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fuchsia-cprng"
|
||||||
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.2.0"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "1.2.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]]
|
[[package]]
|
||||||
name = "ignore"
|
name = "ignore"
|
||||||
version = "0.2.0"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"globset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 0.2.8 (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.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"walkdir 1.0.7 (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)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -100,99 +176,214 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "0.2.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "same-file"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "term_size"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thread-id"
|
|
||||||
version = "3.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thread_local"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-segmentation"
|
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.48"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.6"
|
||||||
|
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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lscolors"
|
||||||
|
version = "0.5.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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.11.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)",
|
||||||
|
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.10.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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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)",
|
||||||
|
"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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rdrand"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.1.51"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_termios"
|
||||||
|
version = "0.1.1"
|
||||||
|
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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.1.0"
|
||||||
|
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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.5"
|
||||||
|
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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.4"
|
||||||
|
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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempdir"
|
||||||
|
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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "term_size"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "0.3.6"
|
||||||
|
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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-util"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unreachable"
|
name = "unreachable"
|
||||||
version = "0.1.1"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -200,12 +391,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8-ranges"
|
name = "utf8-ranges"
|
||||||
version = "1.0.0"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vec_map"
|
name = "vec_map"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -215,12 +411,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "1.0.7"
|
version = "2.2.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -228,39 +424,93 @@ name = "winapi"
|
|||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.6"
|
||||||
|
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]]
|
[[package]]
|
||||||
name = "winapi-build"
|
name = "winapi-build"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
|
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
|
||||||
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
|
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||||
"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159"
|
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
||||||
"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4"
|
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
|
||||||
"checksum clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b8f69e518f967224e628896b54e41ff6acfb4dcfefc5076325c36525dac900f"
|
"checksum cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)" = "4390a3b5f4f6bce9c1d0c00128379df433e53777fdd30e92f16a529332baec4e"
|
||||||
"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97"
|
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||||
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
|
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
|
||||||
"checksum globset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "feeb1b6840809ef5efcf7a4a990bc4e1b7ee3df8cf9e2379a75aeb2ba42ac9c3"
|
"checksum crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0ed1a4de2235cabda8558ff5840bffb97fcb64c97827f354a451307df5f72b"
|
||||||
"checksum ignore 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9bda0eaa44a060e21581315efb449e7fabceb93cf38c8376dd1b7e0623e07844"
|
"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 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 kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||||
"checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf"
|
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
|
||||||
"checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502"
|
"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047"
|
||||||
"checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad"
|
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
|
||||||
"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
|
"checksum lscolors 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9938fd8c379393454f73ec4c9c5b40f3d8332d80b25a29da05e41ee0ecbb559"
|
||||||
"checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01"
|
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
|
||||||
"checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457"
|
"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
|
||||||
"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
|
"checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba"
|
||||||
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
|
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
||||||
"checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"
|
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||||
"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a"
|
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||||
"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7"
|
"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
|
||||||
"checksum unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8083c594e02b8ae1654ae26f0ade5158b119bd88ad0e8227a5d8fcd72407946"
|
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||||
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
|
"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
|
||||||
"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91"
|
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f"
|
||||||
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
|
"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 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 void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
"checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
|
"checksum walkdir 2.2.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.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 winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||||
|
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
|
||||||
|
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|||||||
+56
-6
@@ -1,11 +1,61 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fd"
|
|
||||||
version = "2.0.0"
|
|
||||||
authors = ["David Peter <mail@david-peter.de>"]
|
authors = ["David Peter <mail@david-peter.de>"]
|
||||||
|
build = "build.rs"
|
||||||
|
categories = ["command-line-utilities"]
|
||||||
|
description = "fd is a simple, fast and user-friendly alternative to find."
|
||||||
|
exclude = ["/benchmarks/*"]
|
||||||
|
homepage = "https://github.com/sharkdp/fd"
|
||||||
|
keywords = [
|
||||||
|
"search",
|
||||||
|
"find",
|
||||||
|
"file",
|
||||||
|
"filesystem",
|
||||||
|
"tool",
|
||||||
|
]
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
name = "fd-find"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/sharkdp/fd"
|
||||||
|
version = "7.3.0"
|
||||||
|
edition= "2018"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fd"
|
||||||
|
path = "src/main.rs"
|
||||||
|
[badges.appveyor]
|
||||||
|
repository = "sharkdp/fd"
|
||||||
|
|
||||||
|
[badges.travis-ci]
|
||||||
|
repository = "sharkdp/fd"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
clap = "2.31.2"
|
||||||
|
version_check = "0.1.3"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ansi_term = "0.9"
|
ansi_term = "0.11"
|
||||||
clap = "2.24.2"
|
|
||||||
atty = "0.2"
|
atty = "0.2"
|
||||||
regex = "0.2"
|
ignore = "0.4.3"
|
||||||
ignore = "0.2"
|
lazy_static = "1.1.0"
|
||||||
|
num_cpus = "1.8"
|
||||||
|
regex = "1.0.0"
|
||||||
|
regex-syntax = "0.6"
|
||||||
|
ctrlc = "3.1"
|
||||||
|
humantime = "1.1.1"
|
||||||
|
lscolors = "0.5"
|
||||||
|
|
||||||
|
[dependencies.clap]
|
||||||
|
version = "2.31.2"
|
||||||
|
features = ["suggestions", "color", "wrap_help"]
|
||||||
|
|
||||||
|
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
diff = "0.1"
|
||||||
|
tempdir = "0.3"
|
||||||
|
filetime = "0.2.1"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017 David Peter
|
|
||||||
|
|
||||||
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.
|
|
||||||
+201
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2017 fd developers
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
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,5 +1,8 @@
|
|||||||
# fd
|
# fd
|
||||||
[](https://travis-ci.org/sharkdp/fd)
|
[](https://travis-ci.org/sharkdp/fd)
|
||||||
|
[](https://ci.appveyor.com/project/sharkdp/fd)
|
||||||
|
[](https://crates.io/crates/fd-find)
|
||||||
|
[中文](https://github.com/chinanf-boy/fd-zh)
|
||||||
|
|
||||||
*fd* is a simple, fast and user-friendly alternative to
|
*fd* is a simple, fast and user-friendly alternative to
|
||||||
[*find*](https://www.gnu.org/software/findutils/).
|
[*find*](https://www.gnu.org/software/findutils/).
|
||||||
@@ -9,20 +12,80 @@ While it does not seek to mirror all of *find*'s powerful functionality, it prov
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
* Convenient syntax: `fd PATTERN` instead of `find -iname '*PATTERN*'`.
|
* Convenient syntax: `fd PATTERN` instead of `find -iname '*PATTERN*'`.
|
||||||
|
* Colorized terminal output (similar to *ls*).
|
||||||
|
* It's *fast* (see [benchmarks](#benchmark) below).
|
||||||
* Smart case: the search is case-insensitive by default. It switches to
|
* Smart case: the search is case-insensitive by default. It switches to
|
||||||
case-sensitive if the pattern contains an uppercase
|
case-sensitive if the pattern contains an uppercase
|
||||||
character[\*](http://vimdoc.sourceforge.net/htmldoc/options.html#'smartcase').
|
character[\*](http://vimdoc.sourceforge.net/htmldoc/options.html#'smartcase').
|
||||||
* Colorized terminal output (similar to *ls*).
|
|
||||||
* Ignores hidden directories and files, by default.
|
* Ignores hidden directories and files, by default.
|
||||||
* Ignores patterns from your `.gitignore`, by default.
|
* Ignores patterns from your `.gitignore`, by default.
|
||||||
* Regular expressions.
|
* Regular expressions.
|
||||||
* Unicode-awareness.
|
* Unicode-awareness.
|
||||||
* The command name is *50%* shorter[\*](https://github.com/ggreer/the_silver_searcher) than
|
* The command name is *50%* shorter[\*](https://github.com/ggreer/the_silver_searcher) than
|
||||||
`find` :-).
|
`find` :-).
|
||||||
|
* Parallel command execution with a syntax similar to GNU Parallel.
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Benchmark
|
||||||
|
|
||||||
|
Let's search my home folder for files that end in `[0-9].jpg`. It contains ~190.000
|
||||||
|
subdirectories and about a million files. For averaging and statistical analysis, I'm using
|
||||||
|
[hyperfine](https://github.com/sharkdp/hyperfine). The following benchmarks are performed
|
||||||
|
with a "warm"/pre-filled disk-cache (results for a "cold" disk-cache show the same trends).
|
||||||
|
|
||||||
|
Let's start with `find`:
|
||||||
|
```
|
||||||
|
Benchmark #1: find ~ -iregex '.*[0-9]\.jpg$'
|
||||||
|
|
||||||
|
Time (mean ± σ): 7.236 s ± 0.090 s
|
||||||
|
|
||||||
|
Range (min … max): 7.133 s … 7.385 s
|
||||||
|
```
|
||||||
|
|
||||||
|
`find` is much faster if it does not need to perform a regular-expression search:
|
||||||
|
```
|
||||||
|
Benchmark #2: find ~ -iname '*[0-9].jpg'
|
||||||
|
|
||||||
|
Time (mean ± σ): 3.914 s ± 0.027 s
|
||||||
|
|
||||||
|
Range (min … max): 3.876 s … 3.964 s
|
||||||
|
```
|
||||||
|
|
||||||
|
Now let's try the same for `fd`. Note that `fd` *always* performs a regular expression
|
||||||
|
search. The options `--hidden` and `--no-ignore` are needed for a fair comparison,
|
||||||
|
otherwise `fd` does not have to traverse hidden folders and ignored paths (see below):
|
||||||
|
```
|
||||||
|
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`
|
||||||
|
and about five times faster than `find -iname`. By the way, both tools found the exact
|
||||||
|
same 20880 files :smile:.
|
||||||
|
|
||||||
|
Finally, let's run `fd` without `--hidden` and `--no-ignore` (this can lead to different
|
||||||
|
search results, of course). If *fd* does not have to traverse the hidden and git-ignored
|
||||||
|
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
|
||||||
|
[this repository](https://github.com/sharkdp/fd-benchmarks) for all necessary scripts.
|
||||||
|
|
||||||
|
Concerning *fd*'s speed, the main credit goes to the `regex` and `ignore` crates that are also used
|
||||||
|
in [ripgrep](https://github.com/BurntSushi/ripgrep) (check it out!).
|
||||||
|
|
||||||
## Colorized output
|
## Colorized output
|
||||||
`fd` can colorize files by extension, just like `ls`. In order for this to work, the environment
|
`fd` can colorize files by extension, just like `ls`. In order for this to work, the environment
|
||||||
@@ -34,40 +97,401 @@ complete (and more colorful) variants, see
|
|||||||
[here](https://github.com/seebi/dircolors-solarized) or
|
[here](https://github.com/seebi/dircolors-solarized) or
|
||||||
[here](https://github.com/trapd00r/LS_COLORS).
|
[here](https://github.com/trapd00r/LS_COLORS).
|
||||||
|
|
||||||
## Benchmark
|
## Parallel command execution
|
||||||
A search in my home folder with ~150.000 subdirectories and ~1M files. The given options for
|
If the `-x`/`--exec` option is specified alongside a command template, a job pool will be created
|
||||||
`fd` are needed for a fair comparison (otherwise `fd` is even faster by a factor of 5 because it
|
for executing commands in parallel for each discovered path as the input. The syntax for generating
|
||||||
does not have to search hidden and ignored paths):
|
commands is similar to that of GNU Parallel:
|
||||||
```
|
|
||||||
benchmarking bench/fd --hidden --no-ignore --full-path '.*[0-9]\.jpg$' ~
|
|
||||||
time 2.800 s (2.722 s .. 2.895 s)
|
|
||||||
1.000 R² (1.000 R² .. 1.000 R²)
|
|
||||||
mean 2.821 s (2.810 s .. 2.831 s)
|
|
||||||
std dev 16.52 ms (0.0 s .. 17.02 ms)
|
|
||||||
variance introduced by outliers: 19% (moderately inflated)
|
|
||||||
|
|
||||||
benchmarking bench/find ~ -iregex '.*[0-9]\.jpg$'
|
- `{}`: A placeholder token that will be replaced with the path of the search result
|
||||||
time 5.593 s (5.412 s .. 5.798 s)
|
(`documents/images/party.jpg`).
|
||||||
1.000 R² (0.999 R² .. 1.000 R²)
|
- `{.}`: Like `{}`, but without the file extension (`documents/images/party`).
|
||||||
mean 5.542 s (5.502 s .. 5.567 s)
|
- `{/}`: A placeholder that will be replaced by the basename of the search result (`party.jpg`).
|
||||||
std dev 37.32 ms (0.0 s .. 42.77 ms)
|
- `{//}`: Uses the parent of the discovered path (`documents/images`).
|
||||||
variance introduced by outliers: 19% (moderately inflated)
|
- `{/.}`: Uses the basename, with the extension removed (`party`).
|
||||||
```
|
|
||||||
(benchmarking tool: [bench](https://github.com/Gabriel439/bench))
|
|
||||||
|
|
||||||
Both tools found the exact same 14030 files. Note that we have used the `-iregex` option for `find`
|
``` bash
|
||||||
in order for both tools to perform a regular expression search. Both tools are comparably fast if
|
# Convert all jpg files to png files:
|
||||||
`-iname '*[0-9].jpg'` is used for `find`.
|
fd -e jpg -x convert {} {.}.png
|
||||||
|
|
||||||
## Install
|
# Unpack all zip files (if no placeholder is given, the path is appended):
|
||||||
With [cargo](https://github.com/rust-lang/cargo), you can clone, build and install *fd* with a single command:
|
fd -e zip -x unzip
|
||||||
|
|
||||||
|
# Convert all flac files into opus files:
|
||||||
|
fd -e flac -x ffmpeg -i {} -c:a libopus {.}.opus
|
||||||
|
|
||||||
|
# Count the number of lines in Rust files (the command template can be terminated with ';'):
|
||||||
|
fd -x wc -l \; -e rs
|
||||||
```
|
```
|
||||||
cargo install --git https://github.com/sharkdp/fd
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### 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
|
||||||
```
|
```
|
||||||
Note that rust version *1.16.0* or later is required.
|
|
||||||
The release page of this repository also includes precompiled binaries for Linux.
|
Note: `fd` will be officially available in Ubuntu Disco Dingo (19.04).
|
||||||
|
|
||||||
|
### On Debian
|
||||||
|
|
||||||
|
If you run Debian Buster or newer, you can install the
|
||||||
|
[officially maintained Debian package](https://tracker.debian.org/pkg/rust-fd-find):
|
||||||
|
```
|
||||||
|
sudo apt-get install fd-find
|
||||||
|
```
|
||||||
|
Note that the binary is called `fdfind` as the binary name `fd` is already used by another package.
|
||||||
|
It is recommended that you add an `alias fd=fdfind` to your shells initialization file, in order to
|
||||||
|
use `fd` in the same way as in this documentation.
|
||||||
|
|
||||||
|
### On Fedora
|
||||||
|
|
||||||
|
Starting with Fedora 28, you can install `fd` from the official package sources:
|
||||||
|
``` bash
|
||||||
|
dnf install fd-find
|
||||||
|
```
|
||||||
|
|
||||||
|
For older versions, you can use this [Fedora copr](https://copr.fedorainfracloud.org/coprs/keefle/fd/) to install `fd`:
|
||||||
|
``` bash
|
||||||
|
dnf copr enable keefle/fd
|
||||||
|
dnf install fd
|
||||||
|
```
|
||||||
|
|
||||||
|
### On Arch Linux
|
||||||
|
|
||||||
|
You can install [the fd package](https://www.archlinux.org/packages/community/x86_64/fd/) from the official repos:
|
||||||
|
```
|
||||||
|
pacman -S fd
|
||||||
|
```
|
||||||
|
### On Gentoo Linux
|
||||||
|
|
||||||
|
You can use [the fd ebuild](https://packages.gentoo.org/packages/sys-apps/fd) from the official repo:
|
||||||
|
```
|
||||||
|
emerge -av fd
|
||||||
|
```
|
||||||
|
|
||||||
|
### On openSUSE Linux
|
||||||
|
|
||||||
|
You can install [the fd package](https://software.opensuse.org/package/fd) from the official repo:
|
||||||
|
```
|
||||||
|
zypper in fd
|
||||||
|
```
|
||||||
|
|
||||||
|
### On Void Linux
|
||||||
|
|
||||||
|
You can install `fd` via xbps-install:
|
||||||
|
```
|
||||||
|
xbps-install -S fd
|
||||||
|
```
|
||||||
|
|
||||||
|
### On macOS
|
||||||
|
|
||||||
|
You can install `fd` with [Homebrew](http://braumeister.org/formula/fd):
|
||||||
|
```
|
||||||
|
brew install fd
|
||||||
|
```
|
||||||
|
|
||||||
|
… or with MacPorts:
|
||||||
|
```
|
||||||
|
sudo port install fd
|
||||||
|
```
|
||||||
|
|
||||||
|
### On Windows
|
||||||
|
|
||||||
|
You can download pre-built binaries from the [release page](https://github.com/sharkdp/fd/releases).
|
||||||
|
|
||||||
|
Alternatively, you can install `fd` via [Scoop](http://scoop.sh):
|
||||||
|
```
|
||||||
|
scoop install fd
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via [Chocolatey](https://chocolatey.org):
|
||||||
|
```
|
||||||
|
choco install fd
|
||||||
|
```
|
||||||
|
|
||||||
|
### On NixOS / via Nix
|
||||||
|
|
||||||
|
You can use the [Nix package manager](https://nixos.org/nix/) to install `fd`:
|
||||||
|
```
|
||||||
|
nix-env -i fd
|
||||||
|
```
|
||||||
|
|
||||||
|
### On FreeBSD
|
||||||
|
|
||||||
|
You can install `sysutils/fd` via portmaster:
|
||||||
|
```
|
||||||
|
portmaster sysutils/fd
|
||||||
|
```
|
||||||
|
|
||||||
|
### From source
|
||||||
|
|
||||||
|
With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can install *fd* via:
|
||||||
|
```
|
||||||
|
cargo install fd-find
|
||||||
|
```
|
||||||
|
Note that rust version *1.31.0* or later is required.
|
||||||
|
|
||||||
|
### From binaries
|
||||||
|
|
||||||
|
The [release page](https://github.com/sharkdp/fd/releases) includes precompiled binaries for Linux, macOS and Windows.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
```bash
|
```bash
|
||||||
cargo build --release
|
git clone https://github.com/sharkdp/fd
|
||||||
|
|
||||||
|
# Build
|
||||||
|
cd fd
|
||||||
|
cargo build
|
||||||
|
|
||||||
|
# Run unit tests and integration tests
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Install
|
||||||
|
cargo install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Command-line options
|
||||||
|
```
|
||||||
|
USAGE:
|
||||||
|
fd [FLAGS/OPTIONS] [<pattern>] [<path>...]
|
||||||
|
|
||||||
|
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
|
||||||
|
-a, --absolute-path Show absolute instead of relative paths
|
||||||
|
-L, --follow Follow symbolic links
|
||||||
|
-p, --full-path Search full path (default: file-/dirname only)
|
||||||
|
-0, --print0 Separate results by the null character
|
||||||
|
-h, --help Prints help information
|
||||||
|
-V, --version Prints version information
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-d, --max-depth <depth> Set maximum search depth (default: none)
|
||||||
|
-t, --type <filetype>... Filter by type: file (f), directory (d), symlink (l),
|
||||||
|
executable (x), empty (e)
|
||||||
|
-e, --extension <ext>... Filter by file extension
|
||||||
|
-x, --exec <cmd> Execute a command for each search result
|
||||||
|
-X, --exec-batch <cmd> Execute a command with all search results at once
|
||||||
|
-E, --exclude <pattern>... Exclude entries that match the given glob pattern
|
||||||
|
-c, --color <when> When to use colors: never, *auto*, always
|
||||||
|
-S, --size <size>... Limit results based on the size of files.
|
||||||
|
--changed-within <date|dur> Filter by file modification time (newer than)
|
||||||
|
--changed-before <date|dur> Filter by file modification time (older than)
|
||||||
|
|
||||||
|
ARGS:
|
||||||
|
<pattern> the search pattern, a regular expression (optional)
|
||||||
|
<path>... the root directory for the filesystem search (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the output of `fd -h`. To see the full set of command-line options, use `fd --help` which
|
||||||
|
also includes a much more detailed help text.
|
||||||
|
|
||||||
|
## Tutorial
|
||||||
|
|
||||||
|
First, to get an overview of all available command line options, you can either run
|
||||||
|
`fd -h` for a concise help message (see above) or `fd --help` for a more detailed
|
||||||
|
version.
|
||||||
|
|
||||||
|
### Simple search
|
||||||
|
|
||||||
|
*fd* is designed to find entries in your filesystem. The most basic search you can perform is to
|
||||||
|
run *fd* with a single argument: the search pattern. For example, assume that you want to find an
|
||||||
|
old script of yours (the name included `netflix`):
|
||||||
|
``` bash
|
||||||
|
> fd netfl
|
||||||
|
Software/python/imdb-ratings/netflix-details.py
|
||||||
|
```
|
||||||
|
If called with just a single argument like this, *fd* searches the current directory recursively
|
||||||
|
for any entries that *contain* the pattern `netfl`.
|
||||||
|
|
||||||
|
### Regular expression search
|
||||||
|
|
||||||
|
The search pattern is treated as a regular expression. Here, we search for entries that start
|
||||||
|
with `x` and end with `rc`:
|
||||||
|
``` bash
|
||||||
|
> cd /etc
|
||||||
|
> fd '^x.*rc$'
|
||||||
|
X11/xinit/xinitrc
|
||||||
|
X11/xinit/xserverrc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Specifying the root directory
|
||||||
|
|
||||||
|
If we want to search a specific directory, it can be given as a second argument to *fd*:
|
||||||
|
``` bash
|
||||||
|
> fd passwd /etc
|
||||||
|
/etc/default/passwd
|
||||||
|
/etc/pam.d/passwd
|
||||||
|
/etc/passwd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running *fd* without any arguments
|
||||||
|
|
||||||
|
*fd* can be called with no arguments. This is very useful to get a quick overview of all entries
|
||||||
|
in the current directory, recursively (similar to `ls -R`):
|
||||||
|
``` bash
|
||||||
|
> cd fd/tests
|
||||||
|
> fd
|
||||||
|
testenv
|
||||||
|
testenv/mod.rs
|
||||||
|
tests.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to use this functionality to list all files in a given directory, you have to use
|
||||||
|
a catch-all pattern such as `.` or `^`:
|
||||||
|
``` bash
|
||||||
|
> fd . fd/tests/
|
||||||
|
testenv
|
||||||
|
testenv/mod.rs
|
||||||
|
tests.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Searching for a particular file extension
|
||||||
|
|
||||||
|
Often, we are interested in all files of a particular type. This can be done with the `-e` (or
|
||||||
|
`--extension`) option. Here, we search for all Markdown files in the fd repository:
|
||||||
|
``` bash
|
||||||
|
> cd fd
|
||||||
|
> fd -e md
|
||||||
|
CONTRIBUTING.md
|
||||||
|
README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-e` option can be used in combination with a search pattern:
|
||||||
|
``` bash
|
||||||
|
> fd -e rs mod
|
||||||
|
src/fshelper/mod.rs
|
||||||
|
src/lscolors/mod.rs
|
||||||
|
tests/testenv/mod.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hidden and ignored files
|
||||||
|
By default, *fd* does not search hidden directories and does not show hidden files in the
|
||||||
|
search results. To disable this behavior, we can use the `-H` (or `--hidden`) option:
|
||||||
|
``` bash
|
||||||
|
> fd pre-commit
|
||||||
|
> fd -H pre-commit
|
||||||
|
.git/hooks/pre-commit.sample
|
||||||
|
```
|
||||||
|
|
||||||
|
If we work in a directory that is a Git repository (or includes Git repositories), *fd* does not
|
||||||
|
search folders (and does not show files) that match one of the `.gitignore` patterns. To disable
|
||||||
|
this behavior, we can use the `-I` (or `--no-ignore`) option:
|
||||||
|
``` bash
|
||||||
|
> fd num_cpu
|
||||||
|
> fd -I num_cpu
|
||||||
|
target/debug/deps/libnum_cpus-f5ce7ef99006aa05.rlib
|
||||||
|
```
|
||||||
|
|
||||||
|
To really search *all* files and directories, simply combine the hidden and ignore features to show
|
||||||
|
everything (`-HI`).
|
||||||
|
|
||||||
|
### Excluding specific files or directories
|
||||||
|
|
||||||
|
Sometimes we want to ignore search results from a specific subdirectory. For example, we might
|
||||||
|
want to search all hidden files and directories (`-H`) but exclude all matches from `.git`
|
||||||
|
directories. We can use the `-E` (or `--exclude`) option for this. It takes an arbitrary glob
|
||||||
|
pattern as an argument:
|
||||||
|
``` bash
|
||||||
|
> fd -H -E .git …
|
||||||
|
```
|
||||||
|
|
||||||
|
We can also use this to skip mounted directories:
|
||||||
|
``` bash
|
||||||
|
> fd -E /mnt/external-drive …
|
||||||
|
```
|
||||||
|
|
||||||
|
.. or to skip certain file types:
|
||||||
|
``` bash
|
||||||
|
> fd -E '*.bak' …
|
||||||
|
```
|
||||||
|
|
||||||
|
To make exclude-patterns like these permanent, you can create a `.fdignore` file. They work like
|
||||||
|
`.gitignore` files, but are specific to `fd`. For example:
|
||||||
|
``` bash
|
||||||
|
> cat ~/.fdignore
|
||||||
|
/mnt/external-drive
|
||||||
|
*.bak
|
||||||
|
```
|
||||||
|
Note: `fd` also supports `.ignore` files that are used by other programs such as `rg` or `ag`.
|
||||||
|
|
||||||
|
### Using fd with `xargs` or `parallel`
|
||||||
|
|
||||||
|
If we want to run a command on all search results, we can pipe the output to `xargs`:
|
||||||
|
``` bash
|
||||||
|
> fd -0 -e rs | xargs -0 wc -l
|
||||||
|
```
|
||||||
|
Here, the `-0` option tells *fd* to separate search results by the NULL character (instead of
|
||||||
|
newlines). In the same way, the `-0` option of `xargs` tells it to read the input in this way.
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
#### `fd` does not find my file!
|
||||||
|
|
||||||
|
Remember that `fd` ignores hidden directories and files by default. It also ignores patterns
|
||||||
|
from `.gitignore` files. If you want to make sure to find absolutely every possible file, always
|
||||||
|
use the options `-H` and `-I` to disable these two features:
|
||||||
|
``` bash
|
||||||
|
> fd -HI …
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `fd` doesn't seem to interpret my regex pattern correctly
|
||||||
|
|
||||||
|
A lot of special regex characters (like `[]`, `^`, `$`, ..) are also special characters in your
|
||||||
|
shell. If in doubt, always make sure to put single quotes around the regex pattern:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
> fd '^[A-Z][0-9]+$'
|
||||||
|
```
|
||||||
|
|
||||||
|
If your pattern starts with a dash, you have to add `--` to signal the end of command line
|
||||||
|
options. Otherwise, the pattern will be interpreted as a command-line option. Alternatively,
|
||||||
|
use a character class with a single hyphen character:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
> fd -- '-pattern'
|
||||||
|
> fd '[-]pattern'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration with other programs
|
||||||
|
|
||||||
|
#### Using fd with `fzf`
|
||||||
|
|
||||||
|
You can use *fd* to generate input for the command-line fuzzy finder [fzf](https://github.com/junegunn/fzf):
|
||||||
|
``` bash
|
||||||
|
export FZF_DEFAULT_COMMAND='fd --type file'
|
||||||
|
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you can type `vim <Ctrl-T>` on your terminal to open fzf and search through the fd-results.
|
||||||
|
|
||||||
|
Alternatively, you might like to follow symbolic links and include hidden files (but exclude `.git` folders):
|
||||||
|
``` bash
|
||||||
|
export FZF_DEFAULT_COMMAND='fd --type file --follow --hidden --exclude .git'
|
||||||
|
```
|
||||||
|
|
||||||
|
You can even use fd's colored output inside fzf by setting:
|
||||||
|
``` bash
|
||||||
|
export FZF_DEFAULT_COMMAND="fd --type file --color=always"
|
||||||
|
export FZF_DEFAULT_OPTS="--ansi"
|
||||||
|
```
|
||||||
|
|
||||||
|
For more details, see the [Tips section](https://github.com/junegunn/fzf#tips) of the fzf README.
|
||||||
|
|
||||||
|
#### Using fd with `emacs`
|
||||||
|
|
||||||
|
The emacs package [find-file-in-project](https://github.com/technomancy/find-file-in-project) can
|
||||||
|
use *fd* to find files.
|
||||||
|
|
||||||
|
After installing `find-file-in-project`, add the line `(setq ffip-use-rust-fd t)` to your
|
||||||
|
`~/.emacs` or `~/.emacs.d/init.el` file.
|
||||||
|
|
||||||
|
In emacs, run `M-x find-file-in-project-by-selected` to find matching files. Alternatively, run
|
||||||
|
`M-x find-file-in-project` to list all available files in the project.
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# Source: https://github.com/starkat99/appveyor-rust/
|
||||||
|
|
||||||
|
environment:
|
||||||
|
global:
|
||||||
|
PROJECT_NAME: fd
|
||||||
|
matrix:
|
||||||
|
# Stable channel
|
||||||
|
- TARGET: i686-pc-windows-gnu
|
||||||
|
CHANNEL: stable
|
||||||
|
- TARGET: i686-pc-windows-msvc
|
||||||
|
CHANNEL: stable
|
||||||
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
|
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)
|
||||||
|
install:
|
||||||
|
- curl -sSf -o rustup-init.exe https://win.rustup.rs
|
||||||
|
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
|
||||||
|
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||||
|
- rustc -Vv
|
||||||
|
- cargo -V
|
||||||
|
|
||||||
|
# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents
|
||||||
|
# the "directory does not contain a project or solution file" error.
|
||||||
|
# source: https://github.com/starkat99/appveyor-rust/blob/master/appveyor.yml#L113
|
||||||
|
build: false
|
||||||
|
|
||||||
|
# Equivalent to Travis' `script` phase
|
||||||
|
test_script:
|
||||||
|
- cargo build --verbose
|
||||||
|
- cargo run
|
||||||
|
- cargo test
|
||||||
|
|
||||||
|
before_deploy:
|
||||||
|
# Generate artifacts for release
|
||||||
|
- cargo build --release
|
||||||
|
- mkdir staging
|
||||||
|
- copy target\release\fd.exe staging
|
||||||
|
- ps: copy target\release\build\fd-find*\out\_fd.ps1 staging
|
||||||
|
- cd staging
|
||||||
|
# release zipfile will look like 'rust-everywhere-v1.2.3-x86_64-pc-windows-msvc'
|
||||||
|
- 7z a ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip *
|
||||||
|
- appveyor PushArtifact ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
description: 'Windows release'
|
||||||
|
# All the zipped artifacts will be deployed
|
||||||
|
artifact: /.*\.zip/
|
||||||
|
# Here's how:
|
||||||
|
# - Go to 'https://github.com/settings/tokens/new' and generate a Token with only the
|
||||||
|
# `public_repo` scope enabled
|
||||||
|
# - Then go to 'https://ci.appveyor.com/tools/encrypt' and enter the newly generated token.
|
||||||
|
# - Enter the "encrypted value" below
|
||||||
|
auth_token:
|
||||||
|
secure: mWJ8ieZdGEgHf232fdMyzb9T1rKbkJivYbp/REMo8ax3X5vkQJDHhFjWeuWA3wIC
|
||||||
|
provider: GitHub
|
||||||
|
# deploy when a new tag is pushed and only on the stable channel
|
||||||
|
on:
|
||||||
|
# channel to use to produce the release artifacts
|
||||||
|
CHANNEL: stable
|
||||||
|
appveyor_repo_tag: true
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- 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'
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// 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 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, _)) => {}
|
||||||
|
// rustc version too small or can't figure it out
|
||||||
|
_ => {
|
||||||
|
writeln!(&mut io::stderr(), "'fd' requires rustc >= 1.31").unwrap();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let var = std::env::var_os("SHELL_COMPLETIONS_DIR").or(std::env::var_os("OUT_DIR"));
|
||||||
|
let outdir = match var {
|
||||||
|
None => return,
|
||||||
|
Some(outdir) => outdir,
|
||||||
|
};
|
||||||
|
fs::create_dir_all(&outdir).unwrap();
|
||||||
|
|
||||||
|
let mut app = build_app();
|
||||||
|
app.gen_completions("fd", Shell::Bash, &outdir);
|
||||||
|
app.gen_completions("fd", Shell::Fish, &outdir);
|
||||||
|
app.gen_completions("fd", Shell::Zsh, &outdir);
|
||||||
|
app.gen_completions("fd", Shell::PowerShell, &outdir);
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
* linguist-vendored
|
||||||
+128
@@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Building and packaging for release
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cargo build --target "$TARGET" --release --verbose
|
||||||
|
}
|
||||||
|
|
||||||
|
pack() {
|
||||||
|
local tempdir
|
||||||
|
local out_dir
|
||||||
|
local package_name
|
||||||
|
local gcc_prefix
|
||||||
|
|
||||||
|
tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
|
||||||
|
out_dir=$(pwd)
|
||||||
|
package_name="$PROJECT_NAME-$TRAVIS_TAG-$TARGET"
|
||||||
|
|
||||||
|
if [[ $TARGET == arm-unknown-linux-* ]]; then
|
||||||
|
gcc_prefix="arm-linux-gnueabihf-"
|
||||||
|
else
|
||||||
|
gcc_prefix=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# create a "staging" directory
|
||||||
|
mkdir "$tempdir/$package_name"
|
||||||
|
mkdir "$tempdir/$package_name/autocomplete"
|
||||||
|
|
||||||
|
# copying the main binary
|
||||||
|
cp "target/$TARGET/release/$PROJECT_NAME" "$tempdir/$package_name/"
|
||||||
|
"${gcc_prefix}"strip "$tempdir/$package_name/$PROJECT_NAME"
|
||||||
|
|
||||||
|
# manpage, readme and license
|
||||||
|
cp "doc/$PROJECT_NAME.1" "$tempdir/$package_name"
|
||||||
|
cp README.md "$tempdir/$package_name"
|
||||||
|
cp LICENSE-MIT "$tempdir/$package_name"
|
||||||
|
cp LICENSE-APACHE "$tempdir/$package_name"
|
||||||
|
|
||||||
|
# various autocomplete
|
||||||
|
cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".bash "$tempdir/$package_name/autocomplete/${PROJECT_NAME}.bash-completion"
|
||||||
|
cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".fish "$tempdir/$package_name/autocomplete"
|
||||||
|
cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/_"$PROJECT_NAME" "$tempdir/$package_name/autocomplete"
|
||||||
|
|
||||||
|
# archiving
|
||||||
|
pushd "$tempdir"
|
||||||
|
tar czf "$out_dir/$package_name.tar.gz" "$package_name"/*
|
||||||
|
popd
|
||||||
|
rm -r "$tempdir"
|
||||||
|
}
|
||||||
|
|
||||||
|
make_deb() {
|
||||||
|
local tempdir
|
||||||
|
local architecture
|
||||||
|
local version
|
||||||
|
local dpkgname
|
||||||
|
local conflictname
|
||||||
|
|
||||||
|
case $TARGET in
|
||||||
|
x86_64*)
|
||||||
|
architecture=amd64
|
||||||
|
;;
|
||||||
|
i686*)
|
||||||
|
architecture=i386
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "make_deb: skipping target '${TARGET}'" >&2
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
version=${TRAVIS_TAG#v}
|
||||||
|
if [[ $TARGET = *musl* ]]; then
|
||||||
|
dpkgname=$PROJECT_NAME-musl
|
||||||
|
conflictname=$PROJECT_NAME
|
||||||
|
else
|
||||||
|
dpkgname=$PROJECT_NAME
|
||||||
|
conflictname=$PROJECT_NAME-musl
|
||||||
|
fi
|
||||||
|
|
||||||
|
tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
|
||||||
|
|
||||||
|
# copy the main binary
|
||||||
|
install -Dm755 "target/$TARGET/release/$PROJECT_NAME" "$tempdir/usr/bin/$PROJECT_NAME"
|
||||||
|
strip "$tempdir/usr/bin/$PROJECT_NAME"
|
||||||
|
|
||||||
|
# manpage
|
||||||
|
install -Dm644 "doc/$PROJECT_NAME.1" "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
|
||||||
|
gzip --best "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
|
||||||
|
|
||||||
|
# readme and license
|
||||||
|
install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md"
|
||||||
|
install -Dm644 LICENSE-MIT "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-MIT"
|
||||||
|
install -Dm644 LICENSE-APACHE "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-APACHE"
|
||||||
|
|
||||||
|
# completions
|
||||||
|
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.bash "$tempdir/usr/share/bash-completion/completions/${PROJECT_NAME}"
|
||||||
|
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.fish "$tempdir/usr/share/fish/completions/$PROJECT_NAME.fish"
|
||||||
|
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/_$PROJECT_NAME "$tempdir/usr/share/zsh/vendor-completions/_$PROJECT_NAME"
|
||||||
|
|
||||||
|
# Control file
|
||||||
|
mkdir "$tempdir/DEBIAN"
|
||||||
|
cat > "$tempdir/DEBIAN/control" <<EOF
|
||||||
|
Package: $dpkgname
|
||||||
|
Version: $version
|
||||||
|
Section: utils
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: David Peter <mail@david-peter.de>
|
||||||
|
Architecture: $architecture
|
||||||
|
Provides: $PROJECT_NAME
|
||||||
|
Conflicts: $conflictname
|
||||||
|
Description: Simple, fast and user-friendly alternative to find
|
||||||
|
While fd does not seek to mirror all of find's powerful functionality, it
|
||||||
|
provides sensible (opinionated) defaults for 80% of the use cases.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
fakeroot dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
main() {
|
||||||
|
build
|
||||||
|
pack
|
||||||
|
if [[ $TARGET = *linux* ]]; then
|
||||||
|
make_deb
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
if [ "$TRAVIS_OS_NAME" != linux ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo apt-get update
|
||||||
|
|
||||||
|
# needed to build deb packages
|
||||||
|
sudo apt-get install -y fakeroot
|
||||||
|
|
||||||
|
# needed for i686 linux gnu target
|
||||||
|
if [[ $TARGET == i686-unknown-linux-gnu ]]; then
|
||||||
|
sudo apt-get install -y gcc-multilib
|
||||||
|
fi
|
||||||
|
|
||||||
|
# needed for cross-compiling for arm
|
||||||
|
if [[ $TARGET == arm-unknown-linux-* ]]; then
|
||||||
|
sudo apt-get install -y \
|
||||||
|
gcc-4.8-arm-linux-gnueabihf \
|
||||||
|
binutils-arm-linux-gnueabihf \
|
||||||
|
libc6-armhf-cross \
|
||||||
|
libc6-dev-armhf-cross
|
||||||
|
fi
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
# Incorporate TARGET env var to the build and test process
|
||||||
|
cargo build --target "$TARGET" --verbose
|
||||||
|
|
||||||
|
# We cannot run arm executables on linux
|
||||||
|
if [[ $TARGET != arm-unknown-linux-* ]]; then
|
||||||
|
cargo test --target "$TARGET" --verbose
|
||||||
|
fi
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
* linguist-vendored
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
.TH FD 1
|
||||||
|
.SH NAME
|
||||||
|
fd \- find entries in the filesystem
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B fd
|
||||||
|
.RB [ \-HIEsiaLp0hV ]
|
||||||
|
.RB [ \-d
|
||||||
|
.IR depth ]
|
||||||
|
.RB [ \-t
|
||||||
|
.IR filetype ]
|
||||||
|
.RB [ \-e
|
||||||
|
.IR ext ]
|
||||||
|
.RB [ \-E
|
||||||
|
.IR exclude ]
|
||||||
|
.RB [ \-c
|
||||||
|
.IR when ]
|
||||||
|
.RB [ \-j
|
||||||
|
.IR num ]
|
||||||
|
.RB [ \-x
|
||||||
|
.IR cmd ]
|
||||||
|
.RI [ pattern ]
|
||||||
|
.RI [ path... ]
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.B fd
|
||||||
|
is a simple, fast and user-friendly alternative to
|
||||||
|
.BR find (1).
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
.B \-H, \-\-hidden
|
||||||
|
Include hidden files and directories in the search results
|
||||||
|
(default: hidden files and directories are skipped).
|
||||||
|
.TP
|
||||||
|
.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.
|
||||||
|
.TP
|
||||||
|
.B \-\-no\-ignore\-vcs
|
||||||
|
Show search results from files and directories that would otherwise be ignored by
|
||||||
|
.I .gitignore
|
||||||
|
files.
|
||||||
|
.TP
|
||||||
|
.B \-s, \-\-case\-sensitive
|
||||||
|
Perform a case-sensitive search. By default, fd uses case-insensitive searches, unless the
|
||||||
|
pattern contains an uppercase character (smart case).
|
||||||
|
.TP
|
||||||
|
.B \-i, \-\-ignore\-case
|
||||||
|
Perform a case-insensitive search. By default, fd uses case-insensitive searches, unless the
|
||||||
|
pattern contains an uppercase character (smart case).
|
||||||
|
.TP
|
||||||
|
.B \-F, \-\-fixed\-strings
|
||||||
|
Treat the pattern as a literal string instead of a regular expression.
|
||||||
|
.TP
|
||||||
|
.B \-a, \-\-absolute\-path
|
||||||
|
Shows the full path starting from the root as opposed to relative paths.
|
||||||
|
.TP
|
||||||
|
.B \-L, \-\-follow
|
||||||
|
By default, fd does not descend into symlinked directories. Using this flag, symbolic links are
|
||||||
|
also traversed.
|
||||||
|
.TP
|
||||||
|
.B \-p, \-\-full\-path
|
||||||
|
By default, the search pattern is only matched against the filename (or directory name). Using
|
||||||
|
this flag, the
|
||||||
|
.I pattern
|
||||||
|
is matched against the full path.
|
||||||
|
.TP
|
||||||
|
.B \-0, \-\-print0
|
||||||
|
Separate search results by the null character (instead of newlines). Useful for piping results to
|
||||||
|
.IR xargs .
|
||||||
|
.TP
|
||||||
|
.B \-\-show-errors
|
||||||
|
Enable the display of filesystem errors for situations such as insufficient
|
||||||
|
permissions or dead symlinks.
|
||||||
|
.TP
|
||||||
|
.B \-h, \-\-help
|
||||||
|
Print help information.
|
||||||
|
.TP
|
||||||
|
.B \-V, \-\-version
|
||||||
|
Print version information.
|
||||||
|
.TP
|
||||||
|
.BI "\-d, \-\-max\-depth " d
|
||||||
|
Limit directory traversal to at most
|
||||||
|
.I d
|
||||||
|
levels of depth. By default, there is no limit on the search depth.
|
||||||
|
.TP
|
||||||
|
.BI "\-t, \-\-type " filetype
|
||||||
|
Filter search by type:
|
||||||
|
.RS
|
||||||
|
.IP "f, file"
|
||||||
|
regular files
|
||||||
|
.IP "d, directories"
|
||||||
|
directories
|
||||||
|
.IP "l, symlink"
|
||||||
|
symbolic links
|
||||||
|
.IP "x, executable"
|
||||||
|
executable (files)
|
||||||
|
.IP "e, empty"
|
||||||
|
empty files or directories
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.RS
|
||||||
|
This option can be used repeatedly to allow for multiple file types.
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
|
.BI "\-e, \-\-extension " ext
|
||||||
|
Filter search results by file extension
|
||||||
|
.IR ext .
|
||||||
|
This option can be used repeatedly to allow for multiple possible file extensions.
|
||||||
|
.TP
|
||||||
|
.BI "\-E, \-\-exclude " pattern
|
||||||
|
Exclude files/directories that match the given glob pattern.
|
||||||
|
This overrides any other ignore logic.
|
||||||
|
Multiple exclude patterns can be specified.
|
||||||
|
.TP
|
||||||
|
.BI "\-\-ignore-file " path
|
||||||
|
Add a custom ignore-file in '.gitignore' format.
|
||||||
|
These files have a low precedence.
|
||||||
|
.TP
|
||||||
|
.BI "\-c, \-\-color " when
|
||||||
|
Declare
|
||||||
|
.I when
|
||||||
|
to colorize search results:
|
||||||
|
.RS
|
||||||
|
.IP auto
|
||||||
|
Colorize output when standard output is connected to terminal (default).
|
||||||
|
.IP never
|
||||||
|
Do not colorize output.
|
||||||
|
.IP always
|
||||||
|
Always colorize output.
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
|
.BI "\-j, \-\-threads " num
|
||||||
|
Number of threads to use for searching (default: number of available CPUs).
|
||||||
|
.TP
|
||||||
|
.BI "\-S, \-\-size " size
|
||||||
|
Limit results based on the size of files using the format
|
||||||
|
.I <+-><NUM><UNIT>
|
||||||
|
.RS
|
||||||
|
.IP '+'
|
||||||
|
file size must be greater than or equal to this
|
||||||
|
.IP '-'
|
||||||
|
file size must be less than or equal to this
|
||||||
|
.IP 'NUM'
|
||||||
|
The numeric size (e.g. 500)
|
||||||
|
.IP 'UNIT'
|
||||||
|
The units for NUM. They are not case-sensitive.
|
||||||
|
Allowed unit values:
|
||||||
|
.RS
|
||||||
|
.IP 'b'
|
||||||
|
bytes
|
||||||
|
.IP 'k'
|
||||||
|
kilobytes
|
||||||
|
.IP 'm'
|
||||||
|
megabytes
|
||||||
|
.IP 'g'
|
||||||
|
gigabytes
|
||||||
|
.IP 't'
|
||||||
|
terabytes
|
||||||
|
.IP 'ki'
|
||||||
|
kibibytes
|
||||||
|
.IP 'mi'
|
||||||
|
mebibytes
|
||||||
|
.IP 'gi'
|
||||||
|
gibibytes
|
||||||
|
.IP 'ti'
|
||||||
|
tebibytes
|
||||||
|
.RE
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
|
.BI "\-\-changed-within " date|duration
|
||||||
|
Filter results based on the file modification time. The argument can be provided as a specific
|
||||||
|
point in time (\fIYYYY-MM-DD HH:MM:SS\fR) or as a duration (\fI10h, 1d, 35min\fR).
|
||||||
|
.B --change-newer-than
|
||||||
|
can be used as an alias.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
\-\-changed-within 2weeks
|
||||||
|
\-\-change-newer-than "2018-10-27 10:00:00"
|
||||||
|
.TP
|
||||||
|
.BI "\-\-changed-before " date|duration
|
||||||
|
Filter results based on the file modification time. The argument can be provided as a specific
|
||||||
|
point in time (\fIYYYY-MM-DD HH:MM:SS\fR) or as a duration (\fI10h, 1d, 35min\fR).
|
||||||
|
.B --change-older-than
|
||||||
|
can be used as an alias.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
\-\-changed-before "2018-10-27 10:00:00"
|
||||||
|
\-\-change-older-than 2weeks
|
||||||
|
.TP
|
||||||
|
.BI "\-x, \-\-exec " command
|
||||||
|
Execute
|
||||||
|
.I command
|
||||||
|
for each search result. The following placeholders are substituted by a path derived from the current search result:
|
||||||
|
.RS
|
||||||
|
.IP {}
|
||||||
|
path
|
||||||
|
.IP {/}
|
||||||
|
basename
|
||||||
|
.IP {//}
|
||||||
|
parent directory
|
||||||
|
.IP {.}
|
||||||
|
path without file extension
|
||||||
|
.IP {/.}
|
||||||
|
basename without file extension
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
|
.BI "\-X, \-\-exec-batch " command
|
||||||
|
Execute
|
||||||
|
.I command
|
||||||
|
with all search results at once.
|
||||||
|
A single occurence of the following placeholders is authorized and substituted by the paths derived from the search results before the command is executed:
|
||||||
|
.RS
|
||||||
|
.IP {}
|
||||||
|
path
|
||||||
|
.IP {/}
|
||||||
|
basename
|
||||||
|
.IP {//}
|
||||||
|
parent directory
|
||||||
|
.IP {.}
|
||||||
|
path without file extension
|
||||||
|
.IP {/.}
|
||||||
|
basename without file extension
|
||||||
|
.RE
|
||||||
|
.SH ENVIRONMENT
|
||||||
|
.TP
|
||||||
|
.B LS_COLORS
|
||||||
|
Determines how to colorize search results, see
|
||||||
|
.BR dircolors (1) .
|
||||||
|
.SH EXAMPLES
|
||||||
|
.TP
|
||||||
|
.RI "Find files and directories that match the pattern '" needle "':"
|
||||||
|
$ fd needle
|
||||||
|
.TP
|
||||||
|
.RI "Start a search in a given directory (" /var/log "):"
|
||||||
|
$ fd nginx /var/log
|
||||||
|
.TP
|
||||||
|
.RI "Find all Python files (all files with the extention " .py ") in the current directory:"
|
||||||
|
$ fd -e py
|
||||||
|
.TP
|
||||||
|
.RI "Open all search results with vim:"
|
||||||
|
$ fd pattern -X vim
|
||||||
|
.SH SEE ALSO
|
||||||
|
.BR find (1)
|
||||||
Vendored
+60
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Designed to be executed via svg-term from the fd root directory:
|
||||||
|
# svg-term --command="bash doc/screencast.sh" --out doc/screencast.svg --padding=10
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
|
||||||
|
PROMPT="▶"
|
||||||
|
|
||||||
|
enter() {
|
||||||
|
INPUT=$1
|
||||||
|
DELAY=1
|
||||||
|
|
||||||
|
prompt
|
||||||
|
sleep "$DELAY"
|
||||||
|
type "$INPUT"
|
||||||
|
sleep 0.5
|
||||||
|
printf '%b' "\\n"
|
||||||
|
eval "$INPUT"
|
||||||
|
type "\\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt() {
|
||||||
|
printf '%b ' $PROMPT | pv -q
|
||||||
|
}
|
||||||
|
|
||||||
|
type() {
|
||||||
|
printf '%b' "$1" | pv -qL $((10+(-2 + RANDOM%5)))
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
IFS='%'
|
||||||
|
|
||||||
|
enter "fd"
|
||||||
|
|
||||||
|
enter "fd app"
|
||||||
|
|
||||||
|
enter "fd sh"
|
||||||
|
|
||||||
|
enter "fd sh --type f"
|
||||||
|
|
||||||
|
enter "fd -e md"
|
||||||
|
|
||||||
|
enter "fd -e md --exec wc -l"
|
||||||
|
|
||||||
|
enter "fd '^[A-Z]'"
|
||||||
|
|
||||||
|
enter "fd --exclude src"
|
||||||
|
|
||||||
|
enter "fd --hidden sample"
|
||||||
|
|
||||||
|
prompt
|
||||||
|
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
unset IFS
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
Vendored
+1
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 115 KiB |
+379
@@ -0,0 +1,379 @@
|
|||||||
|
// 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")
|
||||||
|
.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.",
|
||||||
|
)
|
||||||
|
.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")
|
||||||
|
.short("u")
|
||||||
|
.multiple(true)
|
||||||
|
.hidden(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("case-sensitive")
|
||||||
|
.long("case-sensitive")
|
||||||
|
.short("s")
|
||||||
|
.overrides_with("ignore-case"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("ignore-case")
|
||||||
|
.long("ignore-case")
|
||||||
|
.short("i")
|
||||||
|
.overrides_with("case-sensitive"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("fixed-strings")
|
||||||
|
.long("fixed-strings")
|
||||||
|
.short("F")
|
||||||
|
.alias("literal"),
|
||||||
|
)
|
||||||
|
.arg(arg("absolute-path").long("absolute-path").short("a"))
|
||||||
|
.arg(arg("follow").long("follow").short("L").alias("dereference"))
|
||||||
|
.arg(arg("full-path").long("full-path").short("p"))
|
||||||
|
.arg(arg("null_separator").long("print0").short("0"))
|
||||||
|
.arg(arg("depth").long("max-depth").short("d").takes_value(true))
|
||||||
|
// support --maxdepth as well, for compatibility with rg
|
||||||
|
.arg(
|
||||||
|
arg("rg-depth")
|
||||||
|
.long("maxdepth")
|
||||||
|
.hidden(true)
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("file-type")
|
||||||
|
.long("type")
|
||||||
|
.short("t")
|
||||||
|
.multiple(true)
|
||||||
|
.number_of_values(1)
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("filetype")
|
||||||
|
.possible_values(&[
|
||||||
|
"f",
|
||||||
|
"file",
|
||||||
|
"d",
|
||||||
|
"directory",
|
||||||
|
"l",
|
||||||
|
"symlink",
|
||||||
|
"x",
|
||||||
|
"executable",
|
||||||
|
"e",
|
||||||
|
"empty",
|
||||||
|
])
|
||||||
|
.hide_possible_values(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("extension")
|
||||||
|
.long("extension")
|
||||||
|
.short("e")
|
||||||
|
.multiple(true)
|
||||||
|
.number_of_values(1)
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("ext"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("exec")
|
||||||
|
.long("exec")
|
||||||
|
.short("x")
|
||||||
|
.min_values(1)
|
||||||
|
.allow_hyphen_values(true)
|
||||||
|
.value_terminator(";")
|
||||||
|
.value_name("cmd"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("exec-batch")
|
||||||
|
.long("exec-batch")
|
||||||
|
.short("X")
|
||||||
|
.min_values(1)
|
||||||
|
.allow_hyphen_values(true)
|
||||||
|
.value_terminator(";")
|
||||||
|
.value_name("cmd")
|
||||||
|
.conflicts_with("exec"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("exclude")
|
||||||
|
.long("exclude")
|
||||||
|
.short("E")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("pattern")
|
||||||
|
.number_of_values(1)
|
||||||
|
.multiple(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("ignore-file")
|
||||||
|
.long("ignore-file")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("path")
|
||||||
|
.number_of_values(1)
|
||||||
|
.multiple(true)
|
||||||
|
.hidden_short_help(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("color")
|
||||||
|
.long("color")
|
||||||
|
.short("c")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("when")
|
||||||
|
.possible_values(&["never", "auto", "always"])
|
||||||
|
.hide_possible_values(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("threads")
|
||||||
|
.long("threads")
|
||||||
|
.short("j")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("num")
|
||||||
|
.hidden_short_help(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("size")
|
||||||
|
.long("size")
|
||||||
|
.short("S")
|
||||||
|
.takes_value(true)
|
||||||
|
.number_of_values(1)
|
||||||
|
.allow_hyphen_values(true)
|
||||||
|
.multiple(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("max-buffer-time")
|
||||||
|
.long("max-buffer-time")
|
||||||
|
.takes_value(true)
|
||||||
|
.hidden(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("changed-within")
|
||||||
|
.long("changed-within")
|
||||||
|
.alias("change-newer-than")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("date|dur")
|
||||||
|
.number_of_values(1),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("changed-before")
|
||||||
|
.long("changed-before")
|
||||||
|
.alias("change-older-than")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("date|dur")
|
||||||
|
.number_of_values(1),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
arg("show-errors")
|
||||||
|
.long("show-errors")
|
||||||
|
.hidden_short_help(true),
|
||||||
|
)
|
||||||
|
.arg(arg("pattern"))
|
||||||
|
.arg(arg("path").multiple(true))
|
||||||
|
.arg(
|
||||||
|
arg("search-path")
|
||||||
|
.long("search-path")
|
||||||
|
.takes_value(true)
|
||||||
|
.conflicts_with("path")
|
||||||
|
.multiple(true)
|
||||||
|
.hidden_short_help(true)
|
||||||
|
.number_of_values(1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
/// Executes a command.
|
||||||
|
pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) {
|
||||||
|
// Spawn the supplied command.
|
||||||
|
let output = cmd.output();
|
||||||
|
|
||||||
|
// Then wait for the command to exit, if it was spawned.
|
||||||
|
match output {
|
||||||
|
Ok(output) => {
|
||||||
|
// While this lock is active, this thread will be the only thread allowed
|
||||||
|
// to write its outputs.
|
||||||
|
let _lock = out_perm.lock().unwrap();
|
||||||
|
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let stderr = io::stderr();
|
||||||
|
|
||||||
|
let _ = stdout.lock().write_all(&output.stdout);
|
||||||
|
let _ = stderr.lock().write_all(&output.stderr);
|
||||||
|
}
|
||||||
|
Err(ref why) if why.kind() == io::ErrorKind::NotFound => {
|
||||||
|
print_error!("Command not found: {:?}", cmd);
|
||||||
|
}
|
||||||
|
Err(why) => {
|
||||||
|
print_error!("Problem while executing command: {}", why);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
// 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::path::MAIN_SEPARATOR;
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
for (id, character) in path.char_indices() {
|
||||||
|
if character == MAIN_SEPARATOR {
|
||||||
|
has_dir = true;
|
||||||
|
dir_index = id;
|
||||||
|
}
|
||||||
|
if character == '.' {
|
||||||
|
ext_index = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account for hidden files and directories
|
||||||
|
if ext_index != 0 && (!has_dir || dir_index + 2 <= ext_index) {
|
||||||
|
return &path[0..ext_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod path_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn correct(input: &str) -> String {
|
||||||
|
input.replace('/', &MAIN_SEPARATOR.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! func_tests {
|
||||||
|
($($name:ident: $func:ident for $input:expr => $output:expr)+) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
assert_eq!($func(&correct($input)), correct($output));
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func_tests! {
|
||||||
|
remove_ext_simple: remove_extension for "foo.txt" => "foo"
|
||||||
|
remove_ext_dir: remove_extension for "dir/foo.txt" => "dir/foo"
|
||||||
|
hidden: remove_extension for ".foo" => ".foo"
|
||||||
|
remove_ext_utf8: remove_extension for "💖.txt" => "💖"
|
||||||
|
remove_ext_empty: remove_extension for "" => ""
|
||||||
|
|
||||||
|
basename_simple: basename for "foo.txt" => "foo.txt"
|
||||||
|
basename_dir: basename for "dir/foo.txt" => "foo.txt"
|
||||||
|
basename_empty: basename for "" => ""
|
||||||
|
basename_utf8_0: basename for "💖/foo.txt" => "foo.txt"
|
||||||
|
basename_utf8_1: basename for "dir/💖.txt" => "💖.txt"
|
||||||
|
|
||||||
|
dirname_simple: dirname for "foo.txt" => "."
|
||||||
|
dirname_dir: dirname for "dir/foo.txt" => "dir"
|
||||||
|
dirname_utf8_0: dirname for "💖/foo.txt" => "💖"
|
||||||
|
dirname_utf8_1: dirname for "dir/💖.txt" => "dir"
|
||||||
|
dirname_empty: dirname for "" => "."
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dirname_root() {
|
||||||
|
#[cfg(windows)]
|
||||||
|
assert_eq!(dirname("C:\\"), "C:");
|
||||||
|
#[cfg(windows)]
|
||||||
|
assert_eq!(dirname("\\"), "\\");
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
assert_eq!(dirname("/"), "/");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// 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};
|
||||||
|
|
||||||
|
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
|
||||||
|
/// generate a command with the supplied command template. The generated command will then
|
||||||
|
/// be executed, and this process will continue until the receiver's sender has closed.
|
||||||
|
pub fn job(
|
||||||
|
rx: Arc<Mutex<Receiver<WorkerResult>>>,
|
||||||
|
cmd: Arc<CommandTemplate>,
|
||||||
|
out_perm: Arc<Mutex<()>>,
|
||||||
|
show_filesystem_errors: bool,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
// Create a lock on the shared receiver for this thread.
|
||||||
|
let lock = rx.lock().unwrap();
|
||||||
|
|
||||||
|
// Obtain the next result from the receiver, else if the channel
|
||||||
|
// has closed, exit from the loop
|
||||||
|
let value: PathBuf = match lock.recv() {
|
||||||
|
Ok(WorkerResult::Entry(val)) => val,
|
||||||
|
Ok(WorkerResult::Error(err)) => {
|
||||||
|
if show_filesystem_errors {
|
||||||
|
print_error!("{}", err);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(_) => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Drop the lock so that other threads can read from the the receiver.
|
||||||
|
drop(lock);
|
||||||
|
// Generate a command and execute it.
|
||||||
|
cmd.generate_and_execute(&value, Arc::clone(&out_perm));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn batch(rx: Receiver<WorkerResult>, cmd: &CommandTemplate, show_filesystem_errors: bool) {
|
||||||
|
let paths = rx.iter().filter_map(|value| match value {
|
||||||
|
WorkerResult::Entry(val) => Some(val),
|
||||||
|
WorkerResult::Error(err) => {
|
||||||
|
if show_filesystem_errors {
|
||||||
|
print_error!("{}", err);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cmd.generate_and_execute_batch(paths);
|
||||||
|
}
|
||||||
+345
@@ -0,0 +1,345 @@
|
|||||||
|
// 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::path::{Path, PathBuf};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
use self::command::execute_command;
|
||||||
|
use self::input::{basename, dirname, remove_extension};
|
||||||
|
pub use self::job::{batch, job};
|
||||||
|
use self::token::Token;
|
||||||
|
|
||||||
|
/// Execution mode of the command
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum ExecutionMode {
|
||||||
|
/// Command is executed for each search result
|
||||||
|
OneByOne,
|
||||||
|
/// Command is run for a batch of results at once
|
||||||
|
Batch,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a template that is utilized to generate command strings.
|
||||||
|
///
|
||||||
|
/// The template is meant to be coupled with an input in order to generate a command. The
|
||||||
|
/// `generate_and_execute()` method will be used to generate a command and execute it.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct CommandTemplate {
|
||||||
|
args: Vec<ArgumentTemplate>,
|
||||||
|
mode: ExecutionMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandTemplate {
|
||||||
|
pub fn new<I, S>(input: I) -> CommandTemplate
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = S>,
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
Self::build(input, ExecutionMode::OneByOne)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_batch<I, S>(input: I) -> Result<CommandTemplate, &'static str>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = S>,
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
let cmd = Self::build(input, ExecutionMode::Batch);
|
||||||
|
if cmd.number_of_tokens() > 1 {
|
||||||
|
return Err("Only one placeholder allowed for batch commands");
|
||||||
|
}
|
||||||
|
if cmd.args[0].has_tokens() {
|
||||||
|
return Err("First argument of exec-batch is expected to be a fixed executable");
|
||||||
|
}
|
||||||
|
Ok(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build<I, S>(input: I, mode: ExecutionMode) -> CommandTemplate
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = S>,
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
lazy_static! {
|
||||||
|
static ref PLACEHOLDER_PATTERN: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut args = Vec::new();
|
||||||
|
let mut has_placeholder = false;
|
||||||
|
|
||||||
|
for arg in input {
|
||||||
|
let arg = arg.as_ref();
|
||||||
|
|
||||||
|
let mut tokens = Vec::new();
|
||||||
|
let mut start = 0;
|
||||||
|
|
||||||
|
for placeholder in PLACEHOLDER_PATTERN.find_iter(arg) {
|
||||||
|
// Leading text before the placeholder.
|
||||||
|
if placeholder.start() > start {
|
||||||
|
tokens.push(Token::Text(arg[start..placeholder.start()].to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
start = placeholder.end();
|
||||||
|
|
||||||
|
match placeholder.as_str() {
|
||||||
|
"{}" => tokens.push(Token::Placeholder),
|
||||||
|
"{.}" => tokens.push(Token::NoExt),
|
||||||
|
"{/}" => tokens.push(Token::Basename),
|
||||||
|
"{//}" => tokens.push(Token::Parent),
|
||||||
|
"{/.}" => tokens.push(Token::BasenameNoExt),
|
||||||
|
_ => panic!("Unhandled placeholder"),
|
||||||
|
}
|
||||||
|
|
||||||
|
has_placeholder = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without a placeholder, the argument is just fixed text.
|
||||||
|
if tokens.is_empty() {
|
||||||
|
args.push(ArgumentTemplate::Text(arg.to_owned()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if start < arg.len() {
|
||||||
|
// Trailing text after last placeholder.
|
||||||
|
tokens.push(Token::Text(arg[start..].to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(ArgumentTemplate::Tokens(tokens));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a placeholder token was not supplied, append one at the end of the command.
|
||||||
|
if !has_placeholder {
|
||||||
|
args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder]));
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandTemplate { args, mode }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn number_of_tokens(&self) -> usize {
|
||||||
|
self.args.iter().filter(|arg| arg.has_tokens()).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_path(input: &Path) -> String {
|
||||||
|
input
|
||||||
|
.strip_prefix(".")
|
||||||
|
.unwrap_or(input)
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates and executes a command.
|
||||||
|
///
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
let mut cmd = Command::new(self.args[0].generate(&input).as_ref());
|
||||||
|
for arg in &self.args[1..] {
|
||||||
|
cmd.arg(arg.generate(&input).as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
execute_command(cmd, &out_perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn in_batch_mode(&self) -> bool {
|
||||||
|
self.mode == ExecutionMode::Batch
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_and_execute_batch<I>(&self, paths: I)
|
||||||
|
where
|
||||||
|
I: Iterator<Item = PathBuf>,
|
||||||
|
{
|
||||||
|
let mut cmd = Command::new(self.args[0].generate("").as_ref());
|
||||||
|
cmd.stdin(Stdio::inherit());
|
||||||
|
cmd.stdout(Stdio::inherit());
|
||||||
|
cmd.stderr(Stdio::inherit());
|
||||||
|
|
||||||
|
let mut paths = paths.map(|p| Self::prepare_path(&p));
|
||||||
|
let mut has_path = false;
|
||||||
|
|
||||||
|
for arg in &self.args[1..] {
|
||||||
|
if arg.has_tokens() {
|
||||||
|
// A single `Tokens` is expected
|
||||||
|
// So we can directy consume the iterator once and for all
|
||||||
|
for path in &mut paths {
|
||||||
|
cmd.arg(arg.generate(&path).as_ref());
|
||||||
|
has_path = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmd.arg(arg.generate("").as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_path {
|
||||||
|
execute_command(cmd, &Mutex::new(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a template for a single command argument.
|
||||||
|
///
|
||||||
|
/// The argument is either a collection of `Token`s including at least one placeholder variant, or
|
||||||
|
/// a fixed text.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
enum ArgumentTemplate {
|
||||||
|
Tokens(Vec<Token>),
|
||||||
|
Text(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArgumentTemplate {
|
||||||
|
pub fn has_tokens(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ArgumentTemplate::Tokens(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate<'a>(&'a self, path: &str) -> Cow<'a, str> {
|
||||||
|
use self::Token::*;
|
||||||
|
|
||||||
|
match *self {
|
||||||
|
ArgumentTemplate::Tokens(ref tokens) => {
|
||||||
|
let mut s = String::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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Cow::Owned(s)
|
||||||
|
}
|
||||||
|
ArgumentTemplate::Text(ref text) => Cow::Borrowed(text),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokens_with_placeholder() {
|
||||||
|
assert_eq!(
|
||||||
|
CommandTemplate::new(&[&"echo", &"${SHELL}:"]),
|
||||||
|
CommandTemplate {
|
||||||
|
args: vec![
|
||||||
|
ArgumentTemplate::Text("echo".into()),
|
||||||
|
ArgumentTemplate::Text("${SHELL}:".into()),
|
||||||
|
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
|
||||||
|
],
|
||||||
|
mode: ExecutionMode::OneByOne,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokens_with_no_extension() {
|
||||||
|
assert_eq!(
|
||||||
|
CommandTemplate::new(&["echo", "{.}"]),
|
||||||
|
CommandTemplate {
|
||||||
|
args: vec![
|
||||||
|
ArgumentTemplate::Text("echo".into()),
|
||||||
|
ArgumentTemplate::Tokens(vec![Token::NoExt]),
|
||||||
|
],
|
||||||
|
mode: ExecutionMode::OneByOne,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokens_with_basename() {
|
||||||
|
assert_eq!(
|
||||||
|
CommandTemplate::new(&["echo", "{/}"]),
|
||||||
|
CommandTemplate {
|
||||||
|
args: vec![
|
||||||
|
ArgumentTemplate::Text("echo".into()),
|
||||||
|
ArgumentTemplate::Tokens(vec![Token::Basename]),
|
||||||
|
],
|
||||||
|
mode: ExecutionMode::OneByOne,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokens_with_parent() {
|
||||||
|
assert_eq!(
|
||||||
|
CommandTemplate::new(&["echo", "{//}"]),
|
||||||
|
CommandTemplate {
|
||||||
|
args: vec![
|
||||||
|
ArgumentTemplate::Text("echo".into()),
|
||||||
|
ArgumentTemplate::Tokens(vec![Token::Parent]),
|
||||||
|
],
|
||||||
|
mode: ExecutionMode::OneByOne,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokens_with_basename_no_extension() {
|
||||||
|
assert_eq!(
|
||||||
|
CommandTemplate::new(&["echo", "{/.}"]),
|
||||||
|
CommandTemplate {
|
||||||
|
args: vec![
|
||||||
|
ArgumentTemplate::Text("echo".into()),
|
||||||
|
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
|
||||||
|
],
|
||||||
|
mode: ExecutionMode::OneByOne,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokens_multiple() {
|
||||||
|
assert_eq!(
|
||||||
|
CommandTemplate::new(&["cp", "{}", "{/.}.ext"]),
|
||||||
|
CommandTemplate {
|
||||||
|
args: vec![
|
||||||
|
ArgumentTemplate::Text("cp".into()),
|
||||||
|
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
|
||||||
|
ArgumentTemplate::Tokens(vec![
|
||||||
|
Token::BasenameNoExt,
|
||||||
|
Token::Text(".ext".into())
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
mode: ExecutionMode::OneByOne,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokens_single_batch() {
|
||||||
|
assert_eq!(
|
||||||
|
CommandTemplate::new_batch(&["echo", "{.}"]).unwrap(),
|
||||||
|
CommandTemplate {
|
||||||
|
args: vec![
|
||||||
|
ArgumentTemplate::Text("echo".into()),
|
||||||
|
ArgumentTemplate::Tokens(vec![Token::NoExt]),
|
||||||
|
],
|
||||||
|
mode: ExecutionMode::Batch,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokens_multiple_batch() {
|
||||||
|
assert!(CommandTemplate::new_batch(&["echo", "{.}", "{}"]).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// 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
|
||||||
|
///
|
||||||
|
/// Each `Token` contains either text, or a placeholder variant, which will be used to generate
|
||||||
|
/// commands after all tokens for a given command template have been collected.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Token {
|
||||||
|
Placeholder,
|
||||||
|
Basename,
|
||||||
|
Parent,
|
||||||
|
NoExt,
|
||||||
|
BasenameNoExt,
|
||||||
|
Text(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Token {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Token::Placeholder => f.write_str("{}")?,
|
||||||
|
Token::Basename => f.write_str("{/}")?,
|
||||||
|
Token::Parent => f.write_str("{//}")?,
|
||||||
|
Token::NoExt => f.write_str("{.}")?,
|
||||||
|
Token::BasenameNoExt => f.write_str("{/.}")?,
|
||||||
|
Token::Text(ref string) => f.write_str(string)?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
pub enum ExitCode {
|
||||||
|
GeneralError,
|
||||||
|
KilledBySigint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<i32> for ExitCode {
|
||||||
|
fn into(self) -> i32 {
|
||||||
|
match self {
|
||||||
|
ExitCode::GeneralError => 1,
|
||||||
|
ExitCode::KilledBySigint => 130,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+66
-35
@@ -1,43 +1,74 @@
|
|||||||
|
// 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 std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// Get a relative path with respect to a certain base path.
|
use ignore::DirEntry;
|
||||||
/// See: https://stackoverflow.com/a/39343127/704831
|
|
||||||
pub fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
|
|
||||||
use std::path::Component;
|
|
||||||
|
|
||||||
if path.is_absolute() != base.is_absolute() {
|
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
|
||||||
if path.is_absolute() {
|
if path.is_absolute() {
|
||||||
Some(PathBuf::from(path))
|
return Ok(path.to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = path.strip_prefix(".").unwrap_or(path);
|
||||||
|
current_dir().map(|path_buf| path_buf.join(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
|
||||||
|
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 {
|
} else {
|
||||||
None
|
false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut ita = path.components();
|
false
|
||||||
let mut itb = base.components();
|
|
||||||
let mut comps: Vec<Component> = vec![];
|
|
||||||
loop {
|
|
||||||
match (ita.next(), itb.next()) {
|
|
||||||
(None, None) => break,
|
|
||||||
(Some(a), None) => {
|
|
||||||
comps.push(a);
|
|
||||||
comps.extend(ita.by_ref());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
(None, _) => comps.push(Component::ParentDir),
|
|
||||||
(Some(a), Some(b)) if comps.is_empty() && a == b => (),
|
|
||||||
(Some(a), Some(b)) if b == Component::CurDir => comps.push(a),
|
|
||||||
(Some(_), Some(b)) if b == Component::ParentDir => return None,
|
|
||||||
(Some(a), Some(_)) => {
|
|
||||||
comps.push(Component::ParentDir);
|
|
||||||
for _ in itb {
|
|
||||||
comps.push(Component::ParentDir);
|
|
||||||
}
|
|
||||||
comps.push(a);
|
|
||||||
comps.extend(ita.by_ref());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(comps.iter().map(|c| c.as_os_str()).collect())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/// Whether or not to show
|
||||||
|
pub struct FileTypes {
|
||||||
|
pub files: bool,
|
||||||
|
pub directories: bool,
|
||||||
|
pub symlinks: bool,
|
||||||
|
pub executables_only: bool,
|
||||||
|
pub empty_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FileTypes {
|
||||||
|
fn default() -> FileTypes {
|
||||||
|
FileTypes {
|
||||||
|
files: false,
|
||||||
|
directories: false,
|
||||||
|
symlinks: false,
|
||||||
|
executables_only: false,
|
||||||
|
empty_only: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
pub use self::size::SizeFilter;
|
||||||
|
pub use self::time::TimeFilter;
|
||||||
|
|
||||||
|
mod size;
|
||||||
|
mod time;
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref SIZE_CAPTURES: Regex = { Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap() };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum SizeFilter {
|
||||||
|
Max(u64),
|
||||||
|
Min(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
// SI prefixes (powers of 10)
|
||||||
|
const KILO: u64 = 1000;
|
||||||
|
const MEGA: u64 = KILO * 1000;
|
||||||
|
const GIGA: u64 = MEGA * 1000;
|
||||||
|
const TERA: u64 = GIGA * 1000;
|
||||||
|
|
||||||
|
// Binary prefixes (powers of 2)
|
||||||
|
const KIBI: u64 = 1024;
|
||||||
|
const MEBI: u64 = KIBI * 1024;
|
||||||
|
const GIBI: u64 = MEBI * 1024;
|
||||||
|
const TEBI: u64 = GIBI * 1024;
|
||||||
|
|
||||||
|
impl SizeFilter {
|
||||||
|
pub fn from_string(s: &str) -> Option<Self> {
|
||||||
|
if !SIZE_CAPTURES.is_match(s) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let captures = SIZE_CAPTURES.captures(s)?;
|
||||||
|
let limit_kind = captures.get(1).map_or("+", |m| m.as_str());
|
||||||
|
let quantity = captures
|
||||||
|
.get(2)
|
||||||
|
.and_then(|v| v.as_str().parse::<u64>().ok())?;
|
||||||
|
|
||||||
|
let multiplier = match &captures.get(3).map_or("b", |m| m.as_str()).to_lowercase()[..] {
|
||||||
|
v if v.starts_with("ki") => KIBI,
|
||||||
|
v if v.starts_with("k") => KILO,
|
||||||
|
v if v.starts_with("mi") => MEBI,
|
||||||
|
v if v.starts_with("m") => MEGA,
|
||||||
|
v if v.starts_with("gi") => GIBI,
|
||||||
|
v if v.starts_with("g") => GIGA,
|
||||||
|
v if v.starts_with("ti") => TEBI,
|
||||||
|
v if v.starts_with("t") => TERA,
|
||||||
|
"b" => 1,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = quantity * multiplier;
|
||||||
|
Some(match limit_kind {
|
||||||
|
"+" => SizeFilter::Min(size),
|
||||||
|
_ => SizeFilter::Max(size),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_within(&self, size: u64) -> bool {
|
||||||
|
match *self {
|
||||||
|
SizeFilter::Max(limit) => size <= limit,
|
||||||
|
SizeFilter::Min(limit) => size >= limit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
macro_rules! gen_size_filter_parse_test {
|
||||||
|
($($name: ident: $val: expr,)*) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
let (txt, expected) = $val;
|
||||||
|
let actual = SizeFilter::from_string(txt).unwrap();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parsing and size conversion tests data. Ensure that each type gets properly interpreted.
|
||||||
|
/// Call with higher base values to ensure expected multiplication (only need a couple)
|
||||||
|
gen_size_filter_parse_test! {
|
||||||
|
byte_plus: ("+1b", SizeFilter::Min(1)),
|
||||||
|
byte_plus_multiplier: ("+10b", SizeFilter::Min(10)),
|
||||||
|
byte_minus: ("-1b", SizeFilter::Max(1)),
|
||||||
|
kilo_plus: ("+1k", SizeFilter::Min(1000)),
|
||||||
|
kilo_plus_suffix: ("+1kb", SizeFilter::Min(1000)),
|
||||||
|
kilo_minus: ("-1k", SizeFilter::Max(1000)),
|
||||||
|
kilo_minus_multiplier: ("-100k", SizeFilter::Max(100000)),
|
||||||
|
kilo_minus_suffix: ("-1kb", SizeFilter::Max(1000)),
|
||||||
|
kilo_plus_upper: ("+1K", SizeFilter::Min(1000)),
|
||||||
|
kilo_plus_suffix_upper: ("+1KB", SizeFilter::Min(1000)),
|
||||||
|
kilo_minus_upper: ("-1K", SizeFilter::Max(1000)),
|
||||||
|
kilo_minus_suffix_upper: ("-1Kb", SizeFilter::Max(1000)),
|
||||||
|
kibi_plus: ("+1ki", SizeFilter::Min(1024)),
|
||||||
|
kibi_plus_multiplier: ("+10ki", SizeFilter::Min(10240)),
|
||||||
|
kibi_plus_suffix: ("+1kib", SizeFilter::Min(1024)),
|
||||||
|
kibi_minus: ("-1ki", SizeFilter::Max(1024)),
|
||||||
|
kibi_minus_multiplier: ("-100ki", SizeFilter::Max(102400)),
|
||||||
|
kibi_minus_suffix: ("-1kib", SizeFilter::Max(1024)),
|
||||||
|
kibi_plus_upper: ("+1KI", SizeFilter::Min(1024)),
|
||||||
|
kibi_plus_suffix_upper: ("+1KiB", SizeFilter::Min(1024)),
|
||||||
|
kibi_minus_upper: ("-1Ki", SizeFilter::Max(1024)),
|
||||||
|
kibi_minus_suffix_upper: ("-1KIB", SizeFilter::Max(1024)),
|
||||||
|
mega_plus: ("+1m", SizeFilter::Min(1000000)),
|
||||||
|
mega_plus_suffix: ("+1mb", SizeFilter::Min(1000000)),
|
||||||
|
mega_minus: ("-1m", SizeFilter::Max(1000000)),
|
||||||
|
mega_minus_suffix: ("-1mb", SizeFilter::Max(1000000)),
|
||||||
|
mega_plus_upper: ("+1M", SizeFilter::Min(1000000)),
|
||||||
|
mega_plus_suffix_upper: ("+1MB", SizeFilter::Min(1000000)),
|
||||||
|
mega_minus_upper: ("-1M", SizeFilter::Max(1000000)),
|
||||||
|
mega_minus_suffix_upper: ("-1Mb", SizeFilter::Max(1000000)),
|
||||||
|
mebi_plus: ("+1mi", SizeFilter::Min(1048576)),
|
||||||
|
mebi_plus_suffix: ("+1mib", SizeFilter::Min(1048576)),
|
||||||
|
mebi_minus: ("-1mi", SizeFilter::Max(1048576)),
|
||||||
|
mebi_minus_suffix: ("-1mib", SizeFilter::Max(1048576)),
|
||||||
|
mebi_plus_upper: ("+1MI", SizeFilter::Min(1048576)),
|
||||||
|
mebi_plus_suffix_upper: ("+1MiB", SizeFilter::Min(1048576)),
|
||||||
|
mebi_minus_upper: ("-1Mi", SizeFilter::Max(1048576)),
|
||||||
|
mebi_minus_suffix_upper: ("-1MIB", SizeFilter::Max(1048576)),
|
||||||
|
giga_plus: ("+1g", SizeFilter::Min(1000000000)),
|
||||||
|
giga_plus_suffix: ("+1gb", SizeFilter::Min(1000000000)),
|
||||||
|
giga_minus: ("-1g", SizeFilter::Max(1000000000)),
|
||||||
|
giga_minus_suffix: ("-1gb", SizeFilter::Max(1000000000)),
|
||||||
|
giga_plus_upper: ("+1G", SizeFilter::Min(1000000000)),
|
||||||
|
giga_plus_suffix_upper: ("+1GB", SizeFilter::Min(1000000000)),
|
||||||
|
giga_minus_upper: ("-1G", SizeFilter::Max(1000000000)),
|
||||||
|
giga_minus_suffix_upper: ("-1Gb", SizeFilter::Max(1000000000)),
|
||||||
|
gibi_plus: ("+1gi", SizeFilter::Min(1073741824)),
|
||||||
|
gibi_plus_suffix: ("+1gib", SizeFilter::Min(1073741824)),
|
||||||
|
gibi_minus: ("-1gi", SizeFilter::Max(1073741824)),
|
||||||
|
gibi_minus_suffix: ("-1gib", SizeFilter::Max(1073741824)),
|
||||||
|
gibi_plus_upper: ("+1GI", SizeFilter::Min(1073741824)),
|
||||||
|
gibi_plus_suffix_upper: ("+1GiB", SizeFilter::Min(1073741824)),
|
||||||
|
gibi_minus_upper: ("-1Gi", SizeFilter::Max(1073741824)),
|
||||||
|
gibi_minus_suffix_upper: ("-1GIB", SizeFilter::Max(1073741824)),
|
||||||
|
tera_plus: ("+1t", SizeFilter::Min(1000000000000)),
|
||||||
|
tera_plus_suffix: ("+1tb", SizeFilter::Min(1000000000000)),
|
||||||
|
tera_minus: ("-1t", SizeFilter::Max(1000000000000)),
|
||||||
|
tera_minus_suffix: ("-1tb", SizeFilter::Max(1000000000000)),
|
||||||
|
tera_plus_upper: ("+1T", SizeFilter::Min(1000000000000)),
|
||||||
|
tera_plus_suffix_upper: ("+1TB", SizeFilter::Min(1000000000000)),
|
||||||
|
tera_minus_upper: ("-1T", SizeFilter::Max(1000000000000)),
|
||||||
|
tera_minus_suffix_upper: ("-1Tb", SizeFilter::Max(1000000000000)),
|
||||||
|
tebi_plus: ("+1ti", SizeFilter::Min(1099511627776)),
|
||||||
|
tebi_plus_suffix: ("+1tib", SizeFilter::Min(1099511627776)),
|
||||||
|
tebi_minus: ("-1ti", SizeFilter::Max(1099511627776)),
|
||||||
|
tebi_minus_suffix: ("-1tib", SizeFilter::Max(1099511627776)),
|
||||||
|
tebi_plus_upper: ("+1TI", SizeFilter::Min(1099511627776)),
|
||||||
|
tebi_plus_suffix_upper: ("+1TiB", SizeFilter::Min(1099511627776)),
|
||||||
|
tebi_minus_upper: ("-1Ti", SizeFilter::Max(1099511627776)),
|
||||||
|
tebi_minus_suffix_upper: ("-1TIB", SizeFilter::Max(1099511627776)),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invalid parse testing
|
||||||
|
macro_rules! gen_size_filter_failure {
|
||||||
|
($($name:ident: $value:expr,)*) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
let i = SizeFilter::from_string($value);
|
||||||
|
assert!(i.is_none());
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invalid parse data
|
||||||
|
gen_size_filter_failure! {
|
||||||
|
ensure_missing_symbol_returns_none: "10M",
|
||||||
|
ensure_missing_number_returns_none: "+g",
|
||||||
|
ensure_missing_unit_returns_none: "+18",
|
||||||
|
ensure_bad_format_returns_none_1: "$10M",
|
||||||
|
ensure_bad_format_returns_none_2: "badval",
|
||||||
|
ensure_bad_format_returns_none_3: "9999",
|
||||||
|
ensure_invalid_unit_returns_none_1: "+50a",
|
||||||
|
ensure_invalid_unit_returns_none_2: "-10v",
|
||||||
|
ensure_invalid_unit_returns_none_3: "+1Mv",
|
||||||
|
ensure_bib_format_returns_none: "+1bib",
|
||||||
|
ensure_bb_format_returns_none: "+1bb",
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_within_less_than() {
|
||||||
|
let f = SizeFilter::from_string("-1k").unwrap();
|
||||||
|
assert!(f.is_within(999));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_within_less_than_equal() {
|
||||||
|
let f = SizeFilter::from_string("-1k").unwrap();
|
||||||
|
assert!(f.is_within(1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_within_greater_than() {
|
||||||
|
let f = SizeFilter::from_string("+1k").unwrap();
|
||||||
|
assert!(f.is_within(1001));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_within_greater_than_equal() {
|
||||||
|
let f = SizeFilter::from_string("+1K").unwrap();
|
||||||
|
assert!(f.is_within(1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
/// Filter based on time ranges.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum TimeFilter {
|
||||||
|
Before(SystemTime),
|
||||||
|
After(SystemTime),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeFilter {
|
||||||
|
fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
|
||||||
|
use humantime;
|
||||||
|
humantime::parse_duration(s)
|
||||||
|
.map(|duration| *ref_time - duration)
|
||||||
|
.or_else(|_| humantime::parse_rfc3339_weak(s))
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn before(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
|
||||||
|
TimeFilter::from_str(ref_time, s).map(TimeFilter::Before)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn after(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
|
||||||
|
TimeFilter::from_str(ref_time, s).map(TimeFilter::After)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn applies_to(&self, t: &SystemTime) -> bool {
|
||||||
|
match self {
|
||||||
|
TimeFilter::Before(limit) => t <= limit,
|
||||||
|
TimeFilter::After(limit) => t >= limit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_time_filter_applicable() {
|
||||||
|
use humantime;
|
||||||
|
|
||||||
|
let ref_time = humantime::parse_rfc3339("2010-10-10T10:10:10Z").unwrap();
|
||||||
|
assert!(TimeFilter::after(&ref_time, "1min")
|
||||||
|
.unwrap()
|
||||||
|
.applies_to(&ref_time));
|
||||||
|
assert!(!TimeFilter::before(&ref_time, "1min")
|
||||||
|
.unwrap()
|
||||||
|
.applies_to(&ref_time));
|
||||||
|
|
||||||
|
let t1m_ago = ref_time - Duration::from_secs(60);
|
||||||
|
assert!(!TimeFilter::after(&ref_time, "30sec")
|
||||||
|
.unwrap()
|
||||||
|
.applies_to(&t1m_ago));
|
||||||
|
assert!(TimeFilter::after(&ref_time, "2min")
|
||||||
|
.unwrap()
|
||||||
|
.applies_to(&t1m_ago));
|
||||||
|
|
||||||
|
assert!(TimeFilter::before(&ref_time, "30sec")
|
||||||
|
.unwrap()
|
||||||
|
.applies_to(&t1m_ago));
|
||||||
|
assert!(!TimeFilter::before(&ref_time, "2min")
|
||||||
|
.unwrap()
|
||||||
|
.applies_to(&t1m_ago));
|
||||||
|
|
||||||
|
let t10s_before = "2010-10-10 10:10:00";
|
||||||
|
assert!(!TimeFilter::before(&ref_time, t10s_before)
|
||||||
|
.unwrap()
|
||||||
|
.applies_to(&ref_time));
|
||||||
|
assert!(TimeFilter::before(&ref_time, t10s_before)
|
||||||
|
.unwrap()
|
||||||
|
.applies_to(&t1m_ago));
|
||||||
|
|
||||||
|
assert!(TimeFilter::after(&ref_time, t10s_before)
|
||||||
|
.unwrap()
|
||||||
|
.applies_to(&ref_time));
|
||||||
|
assert!(!TimeFilter::after(&ref_time, t10s_before)
|
||||||
|
.unwrap()
|
||||||
|
.applies_to(&t1m_ago));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
// Copyright (c) 2017 fd developers
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||||
|
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||||
|
// at your option. All files in the project carrying such
|
||||||
|
// notice may not be copied, modified, or distributed except
|
||||||
|
// according to those terms.
|
||||||
|
|
||||||
|
use regex_syntax::hir::Hir;
|
||||||
|
use regex_syntax::Parser;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
pub use self::file_types::FileTypes;
|
||||||
|
|
||||||
|
mod file_types;
|
||||||
|
pub mod filter;
|
||||||
|
pub mod opts;
|
||||||
|
|
||||||
|
macro_rules! print_error {
|
||||||
|
($($arg:tt)*) => (eprintln!("[fd error]: {}", format!($($arg)*)))
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! print_error_and_exit {
|
||||||
|
($($arg:tt)*) => {
|
||||||
|
print_error!($($arg)*);
|
||||||
|
::std::process::exit(1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine if a regex pattern contains a literal uppercase character.
|
||||||
|
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
|
||||||
|
Parser::new()
|
||||||
|
.parse(pattern)
|
||||||
|
.map(|hir| hir_has_uppercase_char(&hir))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine if a regex expression contains a literal uppercase character.
|
||||||
|
fn hir_has_uppercase_char(hir: &Hir) -> bool {
|
||||||
|
use regex_syntax::hir::*;
|
||||||
|
|
||||||
|
match *hir.kind() {
|
||||||
|
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
|
||||||
|
HirKind::Class(Class::Unicode(ref ranges)) => ranges
|
||||||
|
.iter()
|
||||||
|
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
|
||||||
|
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
|
||||||
|
hir_has_uppercase_char(hir)
|
||||||
|
}
|
||||||
|
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
|
||||||
|
hirs.iter().any(hir_has_uppercase_char)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maximum size of the output buffer before flushing results to the console
|
||||||
|
pub const MAX_BUFFER_LENGTH: usize = 1000;
|
||||||
|
|
||||||
|
/// Traverse args_os, looking for -exec and replacing it with --exec.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * The args, with substitution if required
|
||||||
|
pub fn transform_args_with_exec<I>(original: I) -> Vec<OsString>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = OsString>,
|
||||||
|
{
|
||||||
|
let mut in_exec_opt = false;
|
||||||
|
let target = OsString::from("-exec");
|
||||||
|
let long_start = OsString::from("--exec");
|
||||||
|
let short_start = OsString::from("-x");
|
||||||
|
let exec_end = OsString::from(";");
|
||||||
|
|
||||||
|
original.fold(vec![], |mut args, curr| {
|
||||||
|
if in_exec_opt {
|
||||||
|
if curr == exec_end {
|
||||||
|
in_exec_opt = false;
|
||||||
|
}
|
||||||
|
args.push(curr);
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
if curr == target || curr == long_start || curr == short_start {
|
||||||
|
args.push(if curr == target {
|
||||||
|
OsString::from("--exec")
|
||||||
|
} else {
|
||||||
|
curr
|
||||||
|
});
|
||||||
|
in_exec_opt = true;
|
||||||
|
} else {
|
||||||
|
args.push(curr);
|
||||||
|
}
|
||||||
|
args
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn oss_vec(strs: &[&str]) -> Vec<OsString> {
|
||||||
|
strs.into_iter().map(OsString::from).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that -exec gets transformed into --exec
|
||||||
|
#[test]
|
||||||
|
fn normal_exec_substitution() {
|
||||||
|
let original = oss_vec(&["fd", "foo", "-exec", "cmd"]);
|
||||||
|
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||||
|
let actual = transform_args_with_exec(original.into_iter());
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
/// Ensure that --exec is not touched
|
||||||
|
#[test]
|
||||||
|
fn passthru_of_original_exec() {
|
||||||
|
let original = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||||
|
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||||
|
let actual = transform_args_with_exec(original.into_iter());
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn temp_check_that_exec_context_observed() {
|
||||||
|
let original = oss_vec(&[
|
||||||
|
"fd",
|
||||||
|
"foo",
|
||||||
|
"-exec",
|
||||||
|
"cmd",
|
||||||
|
"-exec",
|
||||||
|
"ls",
|
||||||
|
";",
|
||||||
|
"-exec",
|
||||||
|
"rm",
|
||||||
|
";",
|
||||||
|
"--exec",
|
||||||
|
"find",
|
||||||
|
"-exec",
|
||||||
|
"rm",
|
||||||
|
";",
|
||||||
|
"-x",
|
||||||
|
"foo",
|
||||||
|
"-exec",
|
||||||
|
"something",
|
||||||
|
";",
|
||||||
|
"-exec",
|
||||||
|
]);
|
||||||
|
let expected = oss_vec(&[
|
||||||
|
"fd",
|
||||||
|
"foo",
|
||||||
|
"--exec",
|
||||||
|
"cmd",
|
||||||
|
"-exec",
|
||||||
|
"ls",
|
||||||
|
";",
|
||||||
|
"--exec",
|
||||||
|
"rm",
|
||||||
|
";",
|
||||||
|
"--exec",
|
||||||
|
"find",
|
||||||
|
"-exec",
|
||||||
|
"rm",
|
||||||
|
";",
|
||||||
|
"-x",
|
||||||
|
"foo",
|
||||||
|
"-exec",
|
||||||
|
"something",
|
||||||
|
";",
|
||||||
|
"--exec",
|
||||||
|
]);
|
||||||
|
let actual = transform_args_with_exec(original.into_iter());
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
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};
|
||||||
|
|
||||||
|
/// Configuration options for *fd*.
|
||||||
|
pub struct FdOptions {
|
||||||
|
/// Whether the search is case-sensitive or case-insensitive.
|
||||||
|
pub case_sensitive: bool,
|
||||||
|
|
||||||
|
/// Whether to search within the full file path or just the base name (filename or directory
|
||||||
|
/// name).
|
||||||
|
pub search_full_path: bool,
|
||||||
|
|
||||||
|
/// Whether to ignore hidden files and directories (or not).
|
||||||
|
pub ignore_hidden: bool,
|
||||||
|
|
||||||
|
/// Whether to respect `.fdignore` files or not.
|
||||||
|
pub read_fdignore: bool,
|
||||||
|
|
||||||
|
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
|
||||||
|
pub read_vcsignore: bool,
|
||||||
|
|
||||||
|
/// Whether to follow symlinks or not.
|
||||||
|
pub follow_links: bool,
|
||||||
|
|
||||||
|
/// Whether elements of output should be separated by a null character
|
||||||
|
pub null_separator: bool,
|
||||||
|
|
||||||
|
/// The maximum search depth, or `None` if no maximum search depth should be set.
|
||||||
|
///
|
||||||
|
/// A depth of `1` includes all files under the current directory, a depth of `2` also includes
|
||||||
|
/// all files under subdirectories of the current directory, etc.
|
||||||
|
pub max_depth: Option<usize>,
|
||||||
|
|
||||||
|
/// The number of threads to use.
|
||||||
|
pub threads: usize,
|
||||||
|
|
||||||
|
/// Time to buffer results internally before streaming to the console. This is useful to
|
||||||
|
/// provide a sorted output, in case the total execution time is shorter than
|
||||||
|
/// `max_buffer_time`.
|
||||||
|
pub max_buffer_time: Option<Duration>,
|
||||||
|
|
||||||
|
/// `None` if the output should not be colorized. Otherwise, a `LsColors` instance that defines
|
||||||
|
/// how to style different filetypes.
|
||||||
|
pub ls_colors: Option<LsColors>,
|
||||||
|
|
||||||
|
/// The type of file to search for. If set to `None`, all file types are displayed. If
|
||||||
|
/// set to `Some(..)`, only the types that are specified are shown.
|
||||||
|
pub file_types: Option<FileTypes>,
|
||||||
|
|
||||||
|
/// The extension to search for. Only entries matching the extension will be included.
|
||||||
|
///
|
||||||
|
/// The value (if present) will be a lowercase string without leading dots.
|
||||||
|
pub extensions: Option<RegexSet>,
|
||||||
|
|
||||||
|
/// If a value is supplied, each item found will be used to generate and execute commands.
|
||||||
|
pub command: Option<Arc<CommandTemplate>>,
|
||||||
|
|
||||||
|
/// A list of glob patterns that should be excluded from the search.
|
||||||
|
pub exclude_patterns: Vec<String>,
|
||||||
|
|
||||||
|
/// A list of custom ignore files.
|
||||||
|
pub ignore_files: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// The given constraints on the size of returned files
|
||||||
|
pub size_constraints: Vec<SizeFilter>,
|
||||||
|
|
||||||
|
/// Constraints on last modification time of files
|
||||||
|
pub time_constraints: Vec<TimeFilter>,
|
||||||
|
|
||||||
|
/// Whether or not to display filesystem errors
|
||||||
|
pub show_filesystem_errors: bool,
|
||||||
|
}
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
/// A parser for the `LS_COLORS` environment variable.
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use ansi_term::{Style, Colour};
|
|
||||||
|
|
||||||
/// Maps file extensions to ANSI colors / styles.
|
|
||||||
pub type ExtensionStyles = HashMap<String, Style>;
|
|
||||||
|
|
||||||
/// Maps filenames to ANSI colors / styles.
|
|
||||||
pub type FilenameStyles = HashMap<String, Style>;
|
|
||||||
|
|
||||||
const LS_CODES: &'static [&'static str] =
|
|
||||||
&["no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
|
|
||||||
"so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec",
|
|
||||||
"ec", "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", "ca", "mh",
|
|
||||||
"cl"];
|
|
||||||
|
|
||||||
/// Defines how different file system entries should be colorized / styled.
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct LsColors {
|
|
||||||
/// ANSI Style for directories.
|
|
||||||
pub directory: Style,
|
|
||||||
|
|
||||||
/// ANSI style for symbolic links.
|
|
||||||
pub symlink: Style,
|
|
||||||
|
|
||||||
/// ANSI style for executable files.
|
|
||||||
pub executable: Style,
|
|
||||||
|
|
||||||
/// A map that defines ANSI styles for different file extensions.
|
|
||||||
pub extensions: ExtensionStyles,
|
|
||||||
|
|
||||||
/// A map that defines ANSI styles for different specific filenames.
|
|
||||||
pub filenames: FilenameStyles,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for LsColors {
|
|
||||||
/// Get a default LsColors structure.
|
|
||||||
fn default() -> LsColors {
|
|
||||||
LsColors {
|
|
||||||
directory: Colour::Blue.bold(),
|
|
||||||
symlink: Colour::Cyan.normal(),
|
|
||||||
executable: Colour::Red.bold(),
|
|
||||||
extensions: HashMap::new(),
|
|
||||||
filenames: HashMap::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LsColors {
|
|
||||||
/// Parse a single text-decoration code (normal, bold, italic, ...).
|
|
||||||
fn parse_decoration(code: &str) -> Option<fn(Colour) -> Style> {
|
|
||||||
match code {
|
|
||||||
"0" | "00" => Some(Colour::normal),
|
|
||||||
"1" | "01" => Some(Colour::bold),
|
|
||||||
"3" | "03" => Some(Colour::italic),
|
|
||||||
"4" | "04" => Some(Colour::underline),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse ANSI escape sequences like `38;5;10;1`.
|
|
||||||
fn parse_style(code: &str) -> Option<Style> {
|
|
||||||
let mut split = code.split(';');
|
|
||||||
|
|
||||||
if let Some(first) = split.next() {
|
|
||||||
// Try to match the first part as a text-decoration argument
|
|
||||||
let mut decoration = LsColors::parse_decoration(first);
|
|
||||||
|
|
||||||
let c1 = if decoration.is_none() { Some(first) } else { split.next() };
|
|
||||||
let c2 = split.next();
|
|
||||||
let c3 = split.next();
|
|
||||||
|
|
||||||
let color =
|
|
||||||
if c1 == Some("38") && c2 == Some("5") {
|
|
||||||
let n_white = 7;
|
|
||||||
let n = if let Some(num) = c3 {
|
|
||||||
u8::from_str_radix(num, 10).unwrap_or(n_white)
|
|
||||||
} else {
|
|
||||||
n_white
|
|
||||||
};
|
|
||||||
|
|
||||||
Colour::Fixed(n)
|
|
||||||
} else if let Some(color_s) = c1 {
|
|
||||||
match color_s {
|
|
||||||
"30" => Colour::Black,
|
|
||||||
"31" => Colour::Red,
|
|
||||||
"32" => Colour::Green,
|
|
||||||
"33" => Colour::Yellow,
|
|
||||||
"34" => Colour::Blue,
|
|
||||||
"35" => Colour::Purple,
|
|
||||||
"36" => Colour::Cyan,
|
|
||||||
_ => Colour::White
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Colour::White
|
|
||||||
};
|
|
||||||
|
|
||||||
if decoration.is_none() {
|
|
||||||
// Try to find a decoration somewhere in the sequence
|
|
||||||
decoration = code.split(';')
|
|
||||||
.flat_map(LsColors::parse_decoration)
|
|
||||||
.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
let ansi_style = decoration.unwrap_or(Colour::normal)(color);
|
|
||||||
|
|
||||||
Some(ansi_style)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a new `LS_COLORS` entry.
|
|
||||||
fn add_entry(&mut self, input: &str) {
|
|
||||||
let mut parts = input.trim().split('=');
|
|
||||||
if let Some(pattern) = parts.next() {
|
|
||||||
if let Some(style_code) = parts.next() {
|
|
||||||
// Ensure that the input was split into exactly two parts:
|
|
||||||
if !parts.next().is_none() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(style) = LsColors::parse_style(style_code) {
|
|
||||||
// Try to match against one of the known codes
|
|
||||||
let res = LS_CODES.iter().find(|&&c| c == pattern);
|
|
||||||
|
|
||||||
if let Some(code) = res {
|
|
||||||
match code.as_ref() {
|
|
||||||
"di" => self.directory = style,
|
|
||||||
"ln" => self.symlink = style,
|
|
||||||
"ex" => self.executable = style,
|
|
||||||
_ => return
|
|
||||||
}
|
|
||||||
} else if pattern.starts_with("*.") {
|
|
||||||
let extension = String::from(pattern).split_off(2);
|
|
||||||
self.extensions.insert(extension, style);
|
|
||||||
}
|
|
||||||
else if pattern.starts_with('*') {
|
|
||||||
let filename = String::from(pattern).split_off(1);
|
|
||||||
self.filenames.insert(filename, style);
|
|
||||||
} else {
|
|
||||||
// Unknown/corrupt pattern
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a `LsColors` structure from a string.
|
|
||||||
pub fn from_string(input: &str) -> LsColors {
|
|
||||||
let mut lscolors = LsColors::default();
|
|
||||||
|
|
||||||
for s in input.split(':') {
|
|
||||||
lscolors.add_entry(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
lscolors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_simple() {
|
|
||||||
assert_eq!(Some(Colour::Red.normal()),
|
|
||||||
LsColors::parse_style("31"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_decoration() {
|
|
||||||
assert_eq!(Some(Colour::Red.normal()),
|
|
||||||
LsColors::parse_style("00;31"));
|
|
||||||
|
|
||||||
assert_eq!(Some(Colour::Blue.italic()),
|
|
||||||
LsColors::parse_style("03;34"));
|
|
||||||
|
|
||||||
assert_eq!(Some(Colour::Cyan.bold()),
|
|
||||||
LsColors::parse_style("01;36"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_decoration_backwards() {
|
|
||||||
assert_eq!(Some(Colour::Blue.italic()),
|
|
||||||
LsColors::parse_style("34;03"));
|
|
||||||
|
|
||||||
assert_eq!(Some(Colour::Cyan.bold()),
|
|
||||||
LsColors::parse_style("36;01"));
|
|
||||||
|
|
||||||
assert_eq!(Some(Colour::Red.normal()),
|
|
||||||
LsColors::parse_style("31;00"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_256() {
|
|
||||||
assert_eq!(Some(Colour::Fixed(115).normal()),
|
|
||||||
LsColors::parse_style("38;5;115"));
|
|
||||||
|
|
||||||
assert_eq!(Some(Colour::Fixed(115).normal()),
|
|
||||||
LsColors::parse_style("00;38;5;115"));
|
|
||||||
|
|
||||||
assert_eq!(Some(Colour::Fixed(119).bold()),
|
|
||||||
LsColors::parse_style("01;38;5;119"));
|
|
||||||
|
|
||||||
assert_eq!(Some(Colour::Fixed(119).bold()),
|
|
||||||
LsColors::parse_style("38;5;119;01"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_from_string() {
|
|
||||||
assert_eq!(LsColors::default(), LsColors::from_string(&String::new()));
|
|
||||||
|
|
||||||
let result = LsColors::from_string(
|
|
||||||
&String::from("rs=0:di=03;34:ln=01;36:*.foo=01;35:*README=33"));
|
|
||||||
|
|
||||||
assert_eq!(Colour::Blue.italic(), result.directory);
|
|
||||||
assert_eq!(Colour::Cyan.bold(), result.symlink);
|
|
||||||
assert_eq!(Some(&Colour::Purple.bold()), result.extensions.get("foo"));
|
|
||||||
assert_eq!(Some(&Colour::Yellow.normal()), result.filenames.get("README"));
|
|
||||||
}
|
|
||||||
+232
-301
@@ -1,331 +1,262 @@
|
|||||||
|
// 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]
|
#[macro_use]
|
||||||
extern crate clap;
|
mod internal;
|
||||||
extern crate ansi_term;
|
|
||||||
extern crate atty;
|
|
||||||
extern crate regex;
|
|
||||||
extern crate ignore;
|
|
||||||
|
|
||||||
pub mod lscolors;
|
mod app;
|
||||||
|
mod exec;
|
||||||
|
mod exit_codes;
|
||||||
pub mod fshelper;
|
pub mod fshelper;
|
||||||
|
mod output;
|
||||||
|
mod walk;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::Write;
|
use std::path::{Path, PathBuf};
|
||||||
use std::ops::Deref;
|
use std::sync::Arc;
|
||||||
#[cfg(target_family = "unix")]
|
use std::time;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
use std::path::{Path, Component};
|
|
||||||
use std::process;
|
|
||||||
|
|
||||||
use clap::{App, AppSettings, Arg};
|
|
||||||
use atty::Stream;
|
use atty::Stream;
|
||||||
use regex::{Regex, RegexBuilder};
|
|
||||||
use ignore::WalkBuilder;
|
|
||||||
|
|
||||||
use lscolors::LsColors;
|
use lscolors::LsColors;
|
||||||
|
use regex::{RegexBuilder, RegexSetBuilder};
|
||||||
|
|
||||||
/// Defines how to display search result paths.
|
use crate::exec::CommandTemplate;
|
||||||
#[derive(PartialEq)]
|
use crate::internal::{
|
||||||
enum PathDisplay {
|
filter::{SizeFilter, TimeFilter},
|
||||||
/// As an absolute path
|
opts::FdOptions,
|
||||||
Absolute,
|
pattern_has_uppercase_char, transform_args_with_exec, FileTypes,
|
||||||
|
};
|
||||||
/// As a relative path
|
|
||||||
Relative
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration options for *fd*.
|
|
||||||
struct FdOptions {
|
|
||||||
/// Determines whether the regex search is case-sensitive or case-insensitive.
|
|
||||||
case_sensitive: bool,
|
|
||||||
|
|
||||||
/// Whether to search within the full file path or just the base name (filename or directory
|
|
||||||
/// name).
|
|
||||||
search_full_path: bool,
|
|
||||||
|
|
||||||
/// Whether to ignore hidden files and directories (or not).
|
|
||||||
ignore_hidden: bool,
|
|
||||||
|
|
||||||
/// Whether to respect VCS ignore files (`.gitignore`, `.ignore`, ..) or not.
|
|
||||||
read_ignore: bool,
|
|
||||||
|
|
||||||
/// Whether to follow symlinks or not.
|
|
||||||
follow_links: bool,
|
|
||||||
|
|
||||||
/// The maximum search depth, or `None` if no maximum search depth should be set.
|
|
||||||
///
|
|
||||||
/// A depth of `1` includes all files under the current directory, a depth of `2` also includes
|
|
||||||
/// all files under subdirectories of the current directory, etc.
|
|
||||||
max_depth: Option<usize>,
|
|
||||||
|
|
||||||
/// Display results as relative or absolute path.
|
|
||||||
path_display: PathDisplay,
|
|
||||||
|
|
||||||
/// `None` if the output should not be colorized. Otherwise, a `LsColors` instance that defines
|
|
||||||
/// how to style different filetypes.
|
|
||||||
ls_colors: Option<LsColors>
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Root directory
|
|
||||||
static ROOT_DIR : &'static str = "/";
|
|
||||||
|
|
||||||
/// Parent directory
|
|
||||||
static PARENT_DIR : &'static str = "..";
|
|
||||||
|
|
||||||
/// Print a search result to the console.
|
|
||||||
fn print_entry(base: &Path, entry: &Path, config: &FdOptions) {
|
|
||||||
let path_full = base.join(entry);
|
|
||||||
|
|
||||||
let path_str = entry.to_string_lossy();
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
let is_executable = |p: &std::path::PathBuf| {
|
|
||||||
p.metadata()
|
|
||||||
.ok()
|
|
||||||
.map(|f| f.permissions().mode() & 0o111 != 0)
|
|
||||||
.unwrap_or(false)
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(target_family = "unix"))]
|
|
||||||
let is_executable = |p: &std::path::PathBuf| {false};
|
|
||||||
|
|
||||||
if let Some(ref ls_colors) = config.ls_colors {
|
|
||||||
let default_style = ansi_term::Style::default();
|
|
||||||
|
|
||||||
let mut component_path = base.to_path_buf();
|
|
||||||
|
|
||||||
if config.path_display == PathDisplay::Absolute {
|
|
||||||
print!("{}", ls_colors.directory.paint(ROOT_DIR));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traverse the path and colorize each component
|
|
||||||
for component in entry.components() {
|
|
||||||
let comp_str = match component {
|
|
||||||
Component::Normal(p) => p.to_string_lossy(),
|
|
||||||
Component::ParentDir => Cow::from(PARENT_DIR),
|
|
||||||
_ => error("Error: unexpected path component.")
|
|
||||||
};
|
|
||||||
|
|
||||||
component_path.push(Path::new(comp_str.deref()));
|
|
||||||
|
|
||||||
let style =
|
|
||||||
if component_path.symlink_metadata()
|
|
||||||
.map(|md| md.file_type().is_symlink())
|
|
||||||
.unwrap_or(false) {
|
|
||||||
&ls_colors.symlink
|
|
||||||
} else if component_path.is_dir() {
|
|
||||||
&ls_colors.directory
|
|
||||||
} else if is_executable(&component_path) {
|
|
||||||
&ls_colors.executable
|
|
||||||
} else {
|
|
||||||
// Look up file name
|
|
||||||
let o_style =
|
|
||||||
component_path.file_name()
|
|
||||||
.and_then(|n| n.to_str())
|
|
||||||
.and_then(|n| ls_colors.filenames.get(n));
|
|
||||||
|
|
||||||
match o_style {
|
|
||||||
Some(s) => s,
|
|
||||||
None =>
|
|
||||||
// Look up file extension
|
|
||||||
component_path.extension()
|
|
||||||
.and_then(|e| e.to_str())
|
|
||||||
.and_then(|e| ls_colors.extensions.get(e))
|
|
||||||
.unwrap_or(&default_style)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
print!("{}", style.paint(comp_str));
|
|
||||||
|
|
||||||
if component_path.is_dir() && component_path != path_full {
|
|
||||||
let sep = std::path::MAIN_SEPARATOR.to_string();
|
|
||||||
print!("{}", style.paint(sep));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
} else {
|
|
||||||
// Uncolorized output
|
|
||||||
|
|
||||||
let prefix = if config.path_display == PathDisplay::Absolute { ROOT_DIR } else { "" };
|
|
||||||
|
|
||||||
let r = writeln!(&mut std::io::stdout(), "{}{}", prefix, path_str);
|
|
||||||
|
|
||||||
if r.is_err() {
|
|
||||||
// Probably a broken pipe. Exit gracefully.
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recursively scan the given search path and search for files / pathnames matching the pattern.
|
|
||||||
fn scan(root: &Path, pattern: &Regex, base: &Path, config: &FdOptions) {
|
|
||||||
let walker = WalkBuilder::new(root)
|
|
||||||
.hidden(config.ignore_hidden)
|
|
||||||
.ignore(config.read_ignore)
|
|
||||||
.git_ignore(config.read_ignore)
|
|
||||||
.parents(config.read_ignore)
|
|
||||||
.git_global(config.read_ignore)
|
|
||||||
.git_exclude(config.read_ignore)
|
|
||||||
.follow_links(config.follow_links)
|
|
||||||
.max_depth(config.max_depth)
|
|
||||||
.build()
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|e| e.ok())
|
|
||||||
.filter(|e| e.path() != root);
|
|
||||||
|
|
||||||
for entry in walker {
|
|
||||||
let path_rel_buf = match fshelper::path_relative_from(entry.path(), base) {
|
|
||||||
Some(p) => p,
|
|
||||||
None => error("Error: could not get relative path for directory entry.")
|
|
||||||
};
|
|
||||||
let path_rel = path_rel_buf.as_path();
|
|
||||||
|
|
||||||
let search_str_o =
|
|
||||||
if config.search_full_path {
|
|
||||||
Some(path_rel.to_string_lossy())
|
|
||||||
} else {
|
|
||||||
path_rel.file_name()
|
|
||||||
.map(|f| f.to_string_lossy())
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(search_str) = search_str_o {
|
|
||||||
pattern.find(&*search_str)
|
|
||||||
.map(|_| print_entry(base, path_rel, config));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print error message to stderr and exit with status `1`.
|
|
||||||
fn error(message: &str) -> ! {
|
|
||||||
writeln!(&mut std::io::stderr(), "{}", message)
|
|
||||||
.expect("Failed writing to stderr");
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let matches =
|
let checked_args = transform_args_with_exec(env::args_os());
|
||||||
App::new("fd")
|
let matches = app::build_app().get_matches_from(checked_args);
|
||||||
.version(crate_version!())
|
|
||||||
.usage("fd [FLAGS/OPTIONS] [<pattern>] [<path>]")
|
|
||||||
.setting(AppSettings::ColoredHelp)
|
|
||||||
.setting(AppSettings::DeriveDisplayOrder)
|
|
||||||
.arg(Arg::with_name("case-sensitive")
|
|
||||||
.long("case-sensitive")
|
|
||||||
.short("s")
|
|
||||||
.help("Case-sensitive search (default: smart case)"))
|
|
||||||
.arg(Arg::with_name("full-path")
|
|
||||||
.long("full-path")
|
|
||||||
.short("p")
|
|
||||||
.help("Search full path (default: file-/dirname only)"))
|
|
||||||
.arg(Arg::with_name("hidden")
|
|
||||||
.long("hidden")
|
|
||||||
.short("H")
|
|
||||||
.help("Search hidden files and directories"))
|
|
||||||
.arg(Arg::with_name("no-ignore")
|
|
||||||
.long("no-ignore")
|
|
||||||
.short("I")
|
|
||||||
.help("Do not respect .(git)ignore files"))
|
|
||||||
.arg(Arg::with_name("follow")
|
|
||||||
.long("follow")
|
|
||||||
.short("f")
|
|
||||||
.help("Follow symlinks"))
|
|
||||||
.arg(Arg::with_name("absolute-path")
|
|
||||||
.long("absolute-path")
|
|
||||||
.short("a")
|
|
||||||
.help("Show absolute instead of relative paths"))
|
|
||||||
.arg(Arg::with_name("no-color")
|
|
||||||
.long("no-color")
|
|
||||||
.short("n")
|
|
||||||
.help("Do not colorize output"))
|
|
||||||
.arg(Arg::with_name("depth")
|
|
||||||
.long("max-depth")
|
|
||||||
.short("d")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Set maximum search depth (default: none)"))
|
|
||||||
.arg(Arg::with_name("pattern")
|
|
||||||
.help("the search pattern, a regular expression (optional)"))
|
|
||||||
.arg(Arg::with_name("path")
|
|
||||||
.help("the root directory for the filesystem search (optional)"))
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
// Get the search pattern
|
// Get the search pattern
|
||||||
let empty_pattern = String::new();
|
let pattern = matches.value_of("pattern").unwrap_or("");
|
||||||
let pattern = matches.value_of("pattern").unwrap_or(&empty_pattern);
|
|
||||||
|
|
||||||
// Get the current working directory
|
// Get the current working directory
|
||||||
let current_dir_buf = match env::current_dir() {
|
let current_dir = Path::new(".");
|
||||||
Ok(cd) => cd,
|
if !fshelper::is_dir(current_dir) {
|
||||||
Err(_) => error("Error: could not get current directory.")
|
print_error_and_exit!("Could not get current directory.");
|
||||||
};
|
|
||||||
let current_dir = current_dir_buf.as_path();
|
|
||||||
|
|
||||||
// Get the root directory for the search
|
|
||||||
let mut root_dir_is_absolute = false;
|
|
||||||
let root_dir_buf = if let Some(rd) = matches.value_of("path") {
|
|
||||||
let path = Path::new(rd);
|
|
||||||
|
|
||||||
root_dir_is_absolute = path.is_absolute();
|
|
||||||
|
|
||||||
path.canonicalize().unwrap_or_else(
|
|
||||||
|_| error(&format!("Error: could not find directory '{}'.", rd))
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
current_dir_buf.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
if !root_dir_buf.is_dir() {
|
|
||||||
error(&format!("Error: '{}' is not a directory.", root_dir_buf.to_string_lossy()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let root_dir = root_dir_buf.as_path();
|
// Get one or more root directories to search.
|
||||||
|
let mut dir_vec: Vec<_> = match matches
|
||||||
|
.values_of("path")
|
||||||
|
.or_else(|| matches.values_of("search-path"))
|
||||||
|
{
|
||||||
|
Some(paths) => paths
|
||||||
|
.map(|path| {
|
||||||
|
let path_buffer = PathBuf::from(path);
|
||||||
|
if !fshelper::is_dir(&path_buffer) {
|
||||||
|
print_error_and_exit!(
|
||||||
|
"'{}' is not a directory.",
|
||||||
|
path_buffer.to_string_lossy()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
path_buffer
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
None => vec![current_dir.to_path_buf()],
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches.is_present("absolute-path") {
|
||||||
|
dir_vec = dir_vec
|
||||||
|
.iter()
|
||||||
|
.map(|path_buffer| {
|
||||||
|
path_buffer
|
||||||
|
.canonicalize()
|
||||||
|
.and_then(|pb| fshelper::absolute_path(pb.as_path()))
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect if the user accidentally supplied a path instead of a search pattern
|
||||||
|
if !matches.is_present("full-path")
|
||||||
|
&& pattern.contains(std::path::MAIN_SEPARATOR)
|
||||||
|
&& fshelper::is_dir(Path::new(pattern))
|
||||||
|
{
|
||||||
|
print_error_and_exit!(
|
||||||
|
"The search pattern '{pattern}' contains a path-separation character ('{sep}') \
|
||||||
|
and will not lead to any search results.\n\n\
|
||||||
|
If you want to search for all files inside the '{pattern}' directory, use a match-all pattern:\n\n \
|
||||||
|
fd . '{pattern}'\n\n\
|
||||||
|
Instead, if you want to search for the pattern in the full path, use:\n\n \
|
||||||
|
fd --full-path '{pattern}'",
|
||||||
|
pattern = pattern,
|
||||||
|
sep = std::path::MAIN_SEPARATOR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Treat pattern as literal string if '--fixed-strings' is used
|
||||||
|
let pattern_regex = if matches.is_present("fixed-strings") {
|
||||||
|
regex::escape(pattern)
|
||||||
|
} else {
|
||||||
|
String::from(pattern)
|
||||||
|
};
|
||||||
|
|
||||||
// The search will be case-sensitive if the command line flag is set or
|
// The search will be case-sensitive if the command line flag is set or
|
||||||
// if the pattern has an uppercase character (smart case).
|
// if the pattern has an uppercase character (smart case).
|
||||||
let case_sensitive = matches.is_present("case-sensitive") ||
|
let case_sensitive = !matches.is_present("ignore-case")
|
||||||
pattern.chars().any(char::is_uppercase);
|
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
|
||||||
|
|
||||||
let colored_output = !matches.is_present("no-color") &&
|
let colored_output = match matches.value_of("color") {
|
||||||
atty::is(Stream::Stdout);
|
Some("always") => true,
|
||||||
|
Some("never") => false,
|
||||||
|
_ => atty::is(Stream::Stdout),
|
||||||
|
};
|
||||||
|
|
||||||
let ls_colors =
|
#[cfg(windows)]
|
||||||
if colored_output {
|
let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok();
|
||||||
Some(
|
|
||||||
env::var("LS_COLORS")
|
let ls_colors = if colored_output {
|
||||||
.ok()
|
Some(LsColors::from_env().unwrap_or_default())
|
||||||
.map(|val| LsColors::from_string(&val))
|
} else {
|
||||||
.unwrap_or_default()
|
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 size_limits: Vec<SizeFilter> = matches
|
||||||
|
.values_of("size")
|
||||||
|
.map(|v| {
|
||||||
|
v.map(|sf| {
|
||||||
|
if let Some(f) = SizeFilter::from_string(sf) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
print_error_and_exit!("'{}' is not a valid size constraint. See 'fd --help'.", sf);
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| vec![]);
|
||||||
|
|
||||||
|
let now = time::SystemTime::now();
|
||||||
|
let mut time_constraints: Vec<TimeFilter> = Vec::new();
|
||||||
|
if let Some(t) = matches.value_of("changed-within") {
|
||||||
|
if let Some(f) = TimeFilter::after(&now, t) {
|
||||||
|
time_constraints.push(f);
|
||||||
} else {
|
} else {
|
||||||
None
|
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
if let Some(t) = matches.value_of("changed-before") {
|
||||||
|
if let Some(f) = TimeFilter::before(&now, t) {
|
||||||
|
time_constraints.push(f);
|
||||||
|
} else {
|
||||||
|
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let config = FdOptions {
|
let config = FdOptions {
|
||||||
case_sensitive: case_sensitive,
|
case_sensitive,
|
||||||
search_full_path: matches.is_present("full-path"),
|
search_full_path: matches.is_present("full-path"),
|
||||||
ignore_hidden: !matches.is_present("hidden"),
|
ignore_hidden: !(matches.is_present("hidden")
|
||||||
read_ignore: !matches.is_present("no-ignore"),
|
|| matches.occurrences_of("rg-alias-hidden-ignore") >= 2),
|
||||||
follow_links: matches.is_present("follow"),
|
read_fdignore: !(matches.is_present("no-ignore")
|
||||||
max_depth: matches.value_of("depth")
|
|| matches.is_present("rg-alias-hidden-ignore")),
|
||||||
.and_then(|ds| usize::from_str_radix(ds, 10).ok()),
|
read_vcsignore: !(matches.is_present("no-ignore")
|
||||||
path_display: if matches.is_present("absolute-path") || root_dir_is_absolute {
|
|| matches.is_present("rg-alias-hidden-ignore")
|
||||||
PathDisplay::Absolute
|
|| matches.is_present("no-ignore-vcs")),
|
||||||
} else {
|
follow_links: matches.is_present("follow"),
|
||||||
PathDisplay::Relative
|
null_separator: matches.is_present("null_separator"),
|
||||||
},
|
max_depth: matches
|
||||||
ls_colors: ls_colors
|
.value_of("depth")
|
||||||
|
.or_else(|| matches.value_of("rg-depth"))
|
||||||
|
.and_then(|n| usize::from_str_radix(n, 10).ok()),
|
||||||
|
threads: std::cmp::max(
|
||||||
|
matches
|
||||||
|
.value_of("threads")
|
||||||
|
.and_then(|n| usize::from_str_radix(n, 10).ok())
|
||||||
|
.unwrap_or_else(num_cpus::get),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
max_buffer_time: matches
|
||||||
|
.value_of("max-buffer-time")
|
||||||
|
.and_then(|n| u64::from_str_radix(n, 10).ok())
|
||||||
|
.map(time::Duration::from_millis),
|
||||||
|
ls_colors,
|
||||||
|
file_types: matches.values_of("file-type").map(|values| {
|
||||||
|
let mut file_types = FileTypes::default();
|
||||||
|
for value in values {
|
||||||
|
match value {
|
||||||
|
"f" | "file" => file_types.files = true,
|
||||||
|
"d" | "directory" => file_types.directories = true,
|
||||||
|
"l" | "symlink" => file_types.symlinks = true,
|
||||||
|
"x" | "executable" => {
|
||||||
|
file_types.executables_only = true;
|
||||||
|
file_types.files = true;
|
||||||
|
}
|
||||||
|
"e" | "empty" => {
|
||||||
|
file_types.empty_only = true;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If only 'empty' was specified, search for both files and directories:
|
||||||
|
if file_types.empty_only && !(file_types.files || file_types.directories) {
|
||||||
|
file_types.files = true;
|
||||||
|
file_types.directories = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_types
|
||||||
|
}),
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
command: command.map(Arc::new),
|
||||||
|
exclude_patterns: matches
|
||||||
|
.values_of("exclude")
|
||||||
|
.map(|v| v.map(|p| String::from("!") + p).collect())
|
||||||
|
.unwrap_or_else(|| vec![]),
|
||||||
|
ignore_files: matches
|
||||||
|
.values_of("ignore-file")
|
||||||
|
.map(|vs| vs.map(PathBuf::from).collect())
|
||||||
|
.unwrap_or_else(|| vec![]),
|
||||||
|
size_constraints: size_limits,
|
||||||
|
time_constraints,
|
||||||
|
show_filesystem_errors: matches.is_present("show-errors"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let root = Path::new(ROOT_DIR);
|
match RegexBuilder::new(&pattern_regex)
|
||||||
let base = match config.path_display {
|
.case_insensitive(!config.case_sensitive)
|
||||||
PathDisplay::Relative => current_dir,
|
.dot_matches_new_line(true)
|
||||||
PathDisplay::Absolute => root
|
.build()
|
||||||
};
|
{
|
||||||
|
Ok(re) => walk::scan(&dir_vec, Arc::new(re), Arc::new(config)),
|
||||||
match RegexBuilder::new(pattern)
|
Err(err) => {
|
||||||
.case_insensitive(!config.case_sensitive)
|
print_error_and_exit!(
|
||||||
.build() {
|
"{}\nHint: You can use the '--fixed-strings' option to search for a \
|
||||||
Ok(re) => scan(root_dir, &re, base, &config),
|
literal string instead of a regular expression",
|
||||||
Err(err) => error(err.description())
|
err.description()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// 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::process;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use ansi_term;
|
||||||
|
|
||||||
|
/// Remove the `./` prefix from a path.
|
||||||
|
fn strip_current_dir(pathbuf: &PathBuf) -> &Path {
|
||||||
|
let mut iter = pathbuf.components();
|
||||||
|
let mut iter_next = iter.clone();
|
||||||
|
if iter_next.next() == Some(Component::CurDir) {
|
||||||
|
iter.next();
|
||||||
|
}
|
||||||
|
iter.as_path()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_entry(
|
||||||
|
stdout: &mut StdoutLock,
|
||||||
|
entry: &PathBuf,
|
||||||
|
config: &FdOptions,
|
||||||
|
wants_to_quit: &Arc<AtomicBool>,
|
||||||
|
) {
|
||||||
|
let path = if entry.is_absolute() {
|
||||||
|
entry.as_path()
|
||||||
|
} else {
|
||||||
|
strip_current_dir(entry)
|
||||||
|
};
|
||||||
|
|
||||||
|
let r = if let Some(ref ls_colors) = config.ls_colors {
|
||||||
|
print_entry_colorized(stdout, path, config, ls_colors, &wants_to_quit)
|
||||||
|
} else {
|
||||||
|
print_entry_uncolorized(stdout, path, config)
|
||||||
|
};
|
||||||
|
|
||||||
|
if r.is_err() {
|
||||||
|
// Probably a broken pipe. Exit gracefully.
|
||||||
|
process::exit(ExitCode::GeneralError.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_entry_colorized(
|
||||||
|
stdout: &mut StdoutLock,
|
||||||
|
path: &Path,
|
||||||
|
config: &FdOptions,
|
||||||
|
ls_colors: &LsColors,
|
||||||
|
wants_to_quit: &Arc<AtomicBool>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let default_style = ansi_term::Style::default();
|
||||||
|
|
||||||
|
// Traverse the path and colorize each component
|
||||||
|
for (component, style) in ls_colors.style_for_path_components(path) {
|
||||||
|
let style = style
|
||||||
|
.map(Style::to_ansi_term_style)
|
||||||
|
.unwrap_or(default_style);
|
||||||
|
|
||||||
|
write!(stdout, "{}", style.paint(component.to_string_lossy()))?;
|
||||||
|
|
||||||
|
if wants_to_quit.load(Ordering::Relaxed) {
|
||||||
|
writeln!(stdout)?;
|
||||||
|
process::exit(ExitCode::KilledBySigint.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.null_separator {
|
||||||
|
write!(stdout, "\0")
|
||||||
|
} else {
|
||||||
|
writeln!(stdout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_entry_uncolorized(
|
||||||
|
stdout: &mut StdoutLock,
|
||||||
|
path: &Path,
|
||||||
|
config: &FdOptions,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let separator = if config.null_separator { "\0" } else { "\n" };
|
||||||
|
|
||||||
|
let path_str = path.to_string_lossy();
|
||||||
|
write!(stdout, "{}{}", path_str, separator)
|
||||||
|
}
|
||||||
+371
@@ -0,0 +1,371 @@
|
|||||||
|
// 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::io;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time;
|
||||||
|
|
||||||
|
use ignore::overrides::OverrideBuilder;
|
||||||
|
use ignore::{self, WalkBuilder};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
/// The receiver thread can either be buffering results or directly streaming to the console.
|
||||||
|
enum ReceiverMode {
|
||||||
|
/// Receiver is still buffering in order to sort the results, if the search finishes fast
|
||||||
|
/// enough.
|
||||||
|
Buffering,
|
||||||
|
|
||||||
|
/// Receiver is directly printing results to the output.
|
||||||
|
Streaming,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Worker threads can result in a valid entry having PathBuf or an error.
|
||||||
|
pub enum WorkerResult {
|
||||||
|
Entry(PathBuf),
|
||||||
|
Error(ignore::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively scan the given search path for files / pathnames matching the pattern.
|
||||||
|
///
|
||||||
|
/// If the `--exec` argument was supplied, this will create a thread pool for executing
|
||||||
|
/// 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>) {
|
||||||
|
let mut path_iter = path_vec.iter();
|
||||||
|
let first_path_buf = path_iter
|
||||||
|
.next()
|
||||||
|
.expect("Error: Path vector can not be empty");
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let overrides = override_builder.build().unwrap_or_else(|_| {
|
||||||
|
print_error_and_exit!("Mismatch in exclude patterns");
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut walker = WalkBuilder::new(first_path_buf.as_path());
|
||||||
|
walker
|
||||||
|
.hidden(config.ignore_hidden)
|
||||||
|
.ignore(config.read_fdignore)
|
||||||
|
.parents(config.read_fdignore || config.read_vcsignore)
|
||||||
|
.git_ignore(config.read_vcsignore)
|
||||||
|
.git_global(config.read_vcsignore)
|
||||||
|
.git_exclude(config.read_vcsignore)
|
||||||
|
.overrides(overrides)
|
||||||
|
.follow_links(config.follow_links)
|
||||||
|
.max_depth(config.max_depth);
|
||||||
|
|
||||||
|
if config.read_fdignore {
|
||||||
|
walker.add_custom_ignore_filename(".fdignore");
|
||||||
|
}
|
||||||
|
|
||||||
|
for ignore_file in &config.ignore_files {
|
||||||
|
let result = walker.add_ignore(ignore_file);
|
||||||
|
match result {
|
||||||
|
Some(ignore::Error::Partial(_)) => (),
|
||||||
|
Some(err) => {
|
||||||
|
print_error!(
|
||||||
|
"{}",
|
||||||
|
format!(
|
||||||
|
"Malformed pattern in custom ignore file '{}': {}.",
|
||||||
|
ignore_file.to_string_lossy(),
|
||||||
|
err.description()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for path_entry in path_iter {
|
||||||
|
walker.add(path_entry.as_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
let parallel_walker = walker.threads(config.threads).build_parallel();
|
||||||
|
|
||||||
|
let wants_to_quit = Arc::new(AtomicBool::new(false));
|
||||||
|
if config.ls_colors.is_some() && config.command.is_none() {
|
||||||
|
let wq = Arc::clone(&wants_to_quit);
|
||||||
|
ctrlc::set_handler(move || {
|
||||||
|
wq.store(true, Ordering::Relaxed);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn the thread that receives all results through the channel.
|
||||||
|
let receiver_thread = spawn_receiver(&config, &wants_to_quit, rx);
|
||||||
|
|
||||||
|
// Spawn the sender threads.
|
||||||
|
spawn_senders(&config, &wants_to_quit, pattern, parallel_walker, tx);
|
||||||
|
|
||||||
|
// Wait for the receiver thread to print out all results.
|
||||||
|
receiver_thread.join().unwrap();
|
||||||
|
|
||||||
|
if wants_to_quit.load(Ordering::Relaxed) {
|
||||||
|
process::exit(ExitCode::KilledBySigint.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_receiver(
|
||||||
|
config: &Arc<FdOptions>,
|
||||||
|
wants_to_quit: &Arc<AtomicBool>,
|
||||||
|
rx: Receiver<WorkerResult>,
|
||||||
|
) -> thread::JoinHandle<()> {
|
||||||
|
let config = Arc::clone(config);
|
||||||
|
let wants_to_quit = Arc::clone(wants_to_quit);
|
||||||
|
|
||||||
|
let show_filesystem_errors = config.show_filesystem_errors;
|
||||||
|
let threads = config.threads;
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
// This will be set to `Some` if the `--exec` argument was supplied.
|
||||||
|
if let Some(ref cmd) = config.command {
|
||||||
|
if cmd.in_batch_mode() {
|
||||||
|
exec::batch(rx, cmd, show_filesystem_errors);
|
||||||
|
} else {
|
||||||
|
let shared_rx = Arc::new(Mutex::new(rx));
|
||||||
|
|
||||||
|
let out_perm = Arc::new(Mutex::new(()));
|
||||||
|
|
||||||
|
// Each spawned job will store it's thread handle in here.
|
||||||
|
let mut handles = Vec::with_capacity(threads);
|
||||||
|
for _ in 0..threads {
|
||||||
|
let rx = Arc::clone(&shared_rx);
|
||||||
|
let cmd = Arc::clone(cmd);
|
||||||
|
let out_perm = Arc::clone(&out_perm);
|
||||||
|
|
||||||
|
// Spawn a job thread that will listen for and execute inputs.
|
||||||
|
let handle =
|
||||||
|
thread::spawn(move || exec::job(rx, cmd, out_perm, show_filesystem_errors));
|
||||||
|
|
||||||
|
// Push the handle of the spawned thread into the vector for later joining.
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all threads to exit before exiting the program.
|
||||||
|
for h in handles {
|
||||||
|
h.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let start = time::Instant::now();
|
||||||
|
|
||||||
|
let mut buffer = vec![];
|
||||||
|
|
||||||
|
// Start in buffering mode
|
||||||
|
let mut mode = ReceiverMode::Buffering;
|
||||||
|
|
||||||
|
// Maximum time to wait before we start streaming to the console.
|
||||||
|
let max_buffer_time = config
|
||||||
|
.max_buffer_time
|
||||||
|
.unwrap_or_else(|| time::Duration::from_millis(100));
|
||||||
|
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut stdout = stdout.lock();
|
||||||
|
|
||||||
|
for worker_result in rx {
|
||||||
|
match worker_result {
|
||||||
|
WorkerResult::Entry(value) => {
|
||||||
|
match mode {
|
||||||
|
ReceiverMode::Buffering => {
|
||||||
|
buffer.push(value);
|
||||||
|
|
||||||
|
// Have we reached the maximum buffer size or maximum buffering time?
|
||||||
|
if buffer.len() > MAX_BUFFER_LENGTH
|
||||||
|
|| time::Instant::now() - start > max_buffer_time
|
||||||
|
{
|
||||||
|
// Flush the buffer
|
||||||
|
for v in &buffer {
|
||||||
|
output::print_entry(
|
||||||
|
&mut stdout,
|
||||||
|
v,
|
||||||
|
&config,
|
||||||
|
&wants_to_quit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
// Start streaming
|
||||||
|
mode = ReceiverMode::Streaming;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReceiverMode::Streaming => {
|
||||||
|
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WorkerResult::Error(err) => {
|
||||||
|
if show_filesystem_errors {
|
||||||
|
print_error!("{}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have finished fast enough (faster than max_buffer_time), we haven't streamed
|
||||||
|
// anything to the console, yet. In this case, sort the results and print them:
|
||||||
|
if !buffer.is_empty() {
|
||||||
|
buffer.sort();
|
||||||
|
for value in buffer {
|
||||||
|
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_senders(
|
||||||
|
config: &Arc<FdOptions>,
|
||||||
|
wants_to_quit: &Arc<AtomicBool>,
|
||||||
|
pattern: Arc<Regex>,
|
||||||
|
parallel_walker: ignore::WalkParallel,
|
||||||
|
tx: Sender<WorkerResult>,
|
||||||
|
) {
|
||||||
|
parallel_walker.run(|| {
|
||||||
|
let config = Arc::clone(config);
|
||||||
|
let pattern = Arc::clone(&pattern);
|
||||||
|
let tx_thread = tx.clone();
|
||||||
|
let wants_to_quit = Arc::clone(wants_to_quit);
|
||||||
|
|
||||||
|
Box::new(move |entry_o| {
|
||||||
|
if wants_to_quit.load(Ordering::Relaxed) {
|
||||||
|
return ignore::WalkState::Quit;
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = match entry_o {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(err) => {
|
||||||
|
tx_thread.send(WorkerResult::Error(err)).unwrap();
|
||||||
|
return ignore::WalkState::Continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if entry.depth() == 0 {
|
||||||
|
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.
|
||||||
|
return ignore::WalkState::Continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ignore::WalkState::Continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return ignore::WalkState::Continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ignore::WalkState::Continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out unwanted sizes if it is a file and we have been given size constraints.
|
||||||
|
if !config.size_constraints.is_empty() {
|
||||||
|
if entry_path.is_file() {
|
||||||
|
if let Ok(metadata) = entry_path.metadata() {
|
||||||
|
let file_size = metadata.len();
|
||||||
|
if config
|
||||||
|
.size_constraints
|
||||||
|
.iter()
|
||||||
|
.any(|sc| !sc.is_within(file_size))
|
||||||
|
{
|
||||||
|
return ignore::WalkState::Continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ignore::WalkState::Continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ignore::WalkState::Continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out unwanted modification times
|
||||||
|
if !config.time_constraints.is_empty() {
|
||||||
|
let mut matched = false;
|
||||||
|
if entry_path.is_file() {
|
||||||
|
if let Ok(metadata) = entry_path.metadata() {
|
||||||
|
if let Ok(modified) = metadata.modified() {
|
||||||
|
matched = config
|
||||||
|
.time_constraints
|
||||||
|
.iter()
|
||||||
|
.all(|tf| tf.applies_to(&modified));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
return ignore::WalkState::Continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let search_str_o = if config.search_full_path {
|
||||||
|
match fshelper::path_absolute_form(entry_path) {
|
||||||
|
Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),
|
||||||
|
Err(_) => {
|
||||||
|
print_error_and_exit!("Unable to retrieve absolute path.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entry_path.file_name().map(|f| f.to_string_lossy())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(search_str) = search_str_o {
|
||||||
|
if pattern.is_match(&*search_str) {
|
||||||
|
// TODO: take care of the unwrap call
|
||||||
|
tx_thread
|
||||||
|
.send(WorkerResult::Entry(entry_path.to_owned()))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore::WalkState::Continue
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
-213
@@ -1,213 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
|
|
||||||
fd="${SCRIPT_DIR}/../target/debug/fd"
|
|
||||||
|
|
||||||
export reset='\x1b[0m'
|
|
||||||
export bold='\x1b[01m'
|
|
||||||
export green='\x1b[32;01m'
|
|
||||||
export red='\x1b[31;01m'
|
|
||||||
|
|
||||||
set -eou pipefail
|
|
||||||
|
|
||||||
suite() {
|
|
||||||
echo
|
|
||||||
echo -e "${bold}$1${reset}"
|
|
||||||
echo
|
|
||||||
}
|
|
||||||
|
|
||||||
expect() {
|
|
||||||
expected_output="$1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
tmp_expected="$(mktemp)"
|
|
||||||
tmp_output="$(mktemp)"
|
|
||||||
|
|
||||||
echo "$expected_output" > "$tmp_expected"
|
|
||||||
|
|
||||||
"$fd" "$@" | sort > "$tmp_output"
|
|
||||||
|
|
||||||
echo -ne " ${bold}▶${reset} Testing 'fd $*' ... "
|
|
||||||
|
|
||||||
if diff -q "$tmp_expected" "$tmp_output" > /dev/null; then
|
|
||||||
echo -e "${green}✓ okay${reset}"
|
|
||||||
else
|
|
||||||
echo -e "${red}❌FAILED${reset}"
|
|
||||||
|
|
||||||
echo -ne "\nShowing diff between ${red}expected${reset} and "
|
|
||||||
echo -e "${green}actual${reset} output:\n"
|
|
||||||
|
|
||||||
diff -C3 --label expected --label actual --color \
|
|
||||||
"$tmp_expected" "$tmp_output" || true
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
root=$(mktemp --directory)
|
|
||||||
|
|
||||||
cd "$root"
|
|
||||||
|
|
||||||
# Setup test environment
|
|
||||||
|
|
||||||
mkdir -p one/two/three
|
|
||||||
|
|
||||||
touch a.foo
|
|
||||||
touch one/b.foo
|
|
||||||
touch one/two/c.foo
|
|
||||||
touch one/two/C.Foo
|
|
||||||
touch one/two/three/d.foo
|
|
||||||
mkdir one/two/three/directory_foo
|
|
||||||
touch ignored.foo
|
|
||||||
touch .hidden.foo
|
|
||||||
ln -s one/two symlink
|
|
||||||
|
|
||||||
echo "ignored.foo" > .ignore
|
|
||||||
|
|
||||||
|
|
||||||
# Run the tests
|
|
||||||
|
|
||||||
suite "Simple tests"
|
|
||||||
expect "a.foo" a.foo
|
|
||||||
expect "one/b.foo" b.foo
|
|
||||||
expect "one/two/three/d.foo" d.foo
|
|
||||||
expect "a.foo
|
|
||||||
one/b.foo
|
|
||||||
one/two/c.foo
|
|
||||||
one/two/C.Foo
|
|
||||||
one/two/three/d.foo
|
|
||||||
one/two/three/directory_foo" foo
|
|
||||||
expect "a.foo
|
|
||||||
one
|
|
||||||
one/b.foo
|
|
||||||
one/two
|
|
||||||
one/two/c.foo
|
|
||||||
one/two/C.Foo
|
|
||||||
one/two/three
|
|
||||||
one/two/three/d.foo
|
|
||||||
one/two/three/directory_foo
|
|
||||||
symlink" # run 'fd' without arguments
|
|
||||||
|
|
||||||
suite "Explicit root path"
|
|
||||||
expect "one/b.foo
|
|
||||||
one/two/c.foo
|
|
||||||
one/two/C.Foo
|
|
||||||
one/two/three/d.foo
|
|
||||||
one/two/three/directory_foo" foo one
|
|
||||||
expect "one/two/three/d.foo
|
|
||||||
one/two/three/directory_foo" foo one/two/three
|
|
||||||
(
|
|
||||||
cd one/two
|
|
||||||
expect "../../a.foo
|
|
||||||
../b.foo
|
|
||||||
c.foo
|
|
||||||
C.Foo
|
|
||||||
three/d.foo
|
|
||||||
three/directory_foo" foo ../../
|
|
||||||
)
|
|
||||||
|
|
||||||
suite "Regex searches"
|
|
||||||
expect "a.foo
|
|
||||||
one/b.foo
|
|
||||||
one/two/c.foo
|
|
||||||
one/two/C.Foo" '[a-c].foo'
|
|
||||||
expect "a.foo
|
|
||||||
one/b.foo
|
|
||||||
one/two/c.foo" --case-sensitive '[a-c].foo'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
suite "Smart case"
|
|
||||||
expect "one/two/c.foo
|
|
||||||
one/two/C.Foo" c.foo
|
|
||||||
expect "one/two/C.Foo" C.Foo
|
|
||||||
expect "one/two/C.Foo" Foo
|
|
||||||
|
|
||||||
|
|
||||||
suite "Case-sensitivity (--case-sensitive)"
|
|
||||||
expect "one/two/c.foo" --case-sensitive c.foo
|
|
||||||
expect "one/two/C.Foo" --case-sensitive C.Foo
|
|
||||||
|
|
||||||
|
|
||||||
suite "Full path search (--full-path)"
|
|
||||||
expect "one/two/three/d.foo
|
|
||||||
one/two/three/directory_foo" --full-path 'three.*foo'
|
|
||||||
expect "a.foo" --full-path '^a\.foo$'
|
|
||||||
|
|
||||||
|
|
||||||
suite "Hidden files (--hidden)"
|
|
||||||
expect "a.foo
|
|
||||||
.hidden.foo
|
|
||||||
one/b.foo
|
|
||||||
one/two/c.foo
|
|
||||||
one/two/C.Foo
|
|
||||||
one/two/three/d.foo
|
|
||||||
one/two/three/directory_foo" --hidden foo
|
|
||||||
|
|
||||||
|
|
||||||
suite "Ignored files (--no-ignore)"
|
|
||||||
expect "a.foo
|
|
||||||
ignored.foo
|
|
||||||
one/b.foo
|
|
||||||
one/two/c.foo
|
|
||||||
one/two/C.Foo
|
|
||||||
one/two/three/d.foo
|
|
||||||
one/two/three/directory_foo" --no-ignore foo
|
|
||||||
|
|
||||||
expect "a.foo
|
|
||||||
.hidden.foo
|
|
||||||
ignored.foo
|
|
||||||
one/b.foo
|
|
||||||
one/two/c.foo
|
|
||||||
one/two/C.Foo
|
|
||||||
one/two/three/d.foo
|
|
||||||
one/two/three/directory_foo" --hidden --no-ignore foo
|
|
||||||
|
|
||||||
|
|
||||||
suite "Symlinks (--follow)"
|
|
||||||
expect "one/two/c.foo
|
|
||||||
one/two/C.Foo
|
|
||||||
symlink/c.foo
|
|
||||||
symlink/C.Foo" --follow c.foo
|
|
||||||
|
|
||||||
|
|
||||||
suite "Maximum depth (--max-depth)"
|
|
||||||
expect "a.foo
|
|
||||||
one
|
|
||||||
one/b.foo
|
|
||||||
one/two
|
|
||||||
one/two/c.foo
|
|
||||||
one/two/C.Foo
|
|
||||||
one/two/three
|
|
||||||
symlink" --max-depth 3
|
|
||||||
expect "a.foo
|
|
||||||
one
|
|
||||||
one/b.foo
|
|
||||||
one/two
|
|
||||||
symlink" --max-depth 2
|
|
||||||
expect "a.foo
|
|
||||||
one
|
|
||||||
symlink" --max-depth 1
|
|
||||||
|
|
||||||
|
|
||||||
suite "Absolute paths (--absolute-path)"
|
|
||||||
expect "$root/a.foo
|
|
||||||
$root/one/b.foo
|
|
||||||
$root/one/two/c.foo
|
|
||||||
$root/one/two/C.Foo
|
|
||||||
$root/one/two/three/d.foo
|
|
||||||
$root/one/two/three/directory_foo" --absolute-path foo
|
|
||||||
expect "$root/a.foo
|
|
||||||
$root/one/b.foo
|
|
||||||
$root/one/two/c.foo
|
|
||||||
$root/one/two/C.Foo
|
|
||||||
$root/one/two/three/d.foo
|
|
||||||
$root/one/two/three/directory_foo" foo "$root"
|
|
||||||
|
|
||||||
|
|
||||||
suite "Invalid UTF-8"
|
|
||||||
touch "$(printf 'test-invalid-utf8-\xc3.txt')"
|
|
||||||
expect "$(printf 'test-invalid-utf8-\ufffd.txt')" test-invalid-utf8
|
|
||||||
|
|
||||||
# All done
|
|
||||||
echo
|
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::os::windows;
|
||||||
|
|
||||||
|
use tempdir::TempDir;
|
||||||
|
|
||||||
|
/// Environment for the integration tests.
|
||||||
|
pub struct TestEnv {
|
||||||
|
/// Temporary working directory.
|
||||||
|
temp_dir: TempDir,
|
||||||
|
|
||||||
|
/// Path to the *fd* executable.
|
||||||
|
fd_exe: PathBuf,
|
||||||
|
|
||||||
|
/// Normalize each line by sorting the whitespace-separated words
|
||||||
|
normalize_line: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create the working directory and the test files.
|
||||||
|
fn create_working_directory(
|
||||||
|
directories: &[&'static str],
|
||||||
|
files: &[&'static str],
|
||||||
|
) -> Result<TempDir, io::Error> {
|
||||||
|
let temp_dir = TempDir::new("fd-tests")?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let root = temp_dir.path();
|
||||||
|
|
||||||
|
// Pretend that this is a Git repository in order for `.gitignore` files to be respected
|
||||||
|
fs::create_dir_all(root.join(".git"))?;
|
||||||
|
|
||||||
|
for directory in directories {
|
||||||
|
fs::create_dir_all(root.join(directory))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for file in files {
|
||||||
|
fs::File::create(root.join(file))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
unix::fs::symlink(root.join("one/two"), root.join("symlink"))?;
|
||||||
|
|
||||||
|
// Note: creating symlinks on Windows requires the `SeCreateSymbolicLinkPrivilege` which
|
||||||
|
// is by default only granted for administrators.
|
||||||
|
#[cfg(windows)]
|
||||||
|
windows::fs::symlink_dir(root.join("one/two"), root.join("symlink"))?;
|
||||||
|
|
||||||
|
fs::File::create(root.join(".fdignore"))?.write_all(b"fdignored.foo")?;
|
||||||
|
|
||||||
|
fs::File::create(root.join(".gitignore"))?.write_all(b"gitignored.foo")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(temp_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the *fd* executable.
|
||||||
|
fn find_fd_exe() -> PathBuf {
|
||||||
|
// Tests exe is in target/debug/deps, the *fd* exe is in target/debug
|
||||||
|
let root = env::current_exe()
|
||||||
|
.expect("tests executable")
|
||||||
|
.parent()
|
||||||
|
.expect("tests executable directory")
|
||||||
|
.parent()
|
||||||
|
.expect("fd executable directory")
|
||||||
|
.to_path_buf();
|
||||||
|
|
||||||
|
let exe_name = if cfg!(windows) { "fd.exe" } else { "fd" };
|
||||||
|
|
||||||
|
root.join(exe_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format an error message for when *fd* did not exit successfully.
|
||||||
|
fn format_exit_error(args: &[&str], output: &process::Output) -> String {
|
||||||
|
format!(
|
||||||
|
"`fd {}` did not exit successfully.\nstdout:\n---\n{}---\nstderr:\n---\n{}---",
|
||||||
|
args.join(" "),
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format an error message for when the output of *fd* did not match the expected output.
|
||||||
|
fn format_output_error(args: &[&str], expected: &str, actual: &str) -> String {
|
||||||
|
// Generate diff text.
|
||||||
|
let diff_text = diff::lines(expected, actual)
|
||||||
|
.into_iter()
|
||||||
|
.map(|diff| match diff {
|
||||||
|
diff::Result::Left(l) => format!("-{}", l),
|
||||||
|
diff::Result::Both(l, _) => format!(" {}", l),
|
||||||
|
diff::Result::Right(r) => format!("+{}", r),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
format!(
|
||||||
|
concat!(
|
||||||
|
"`fd {}` did not produce the expected output.\n",
|
||||||
|
"Showing diff between expected and actual:\n{}\n"
|
||||||
|
),
|
||||||
|
args.join(" "),
|
||||||
|
diff_text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalize the output for comparison.
|
||||||
|
fn normalize_output(s: &str, trim_left: 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 = line.replace('/', &std::path::MAIN_SEPARATOR.to_string());
|
||||||
|
if normalize_line {
|
||||||
|
let mut words: Vec<_> = line.split_whitespace().collect();
|
||||||
|
words.sort();
|
||||||
|
return words.join(" ");
|
||||||
|
}
|
||||||
|
line
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
lines.sort();
|
||||||
|
lines.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestEnv {
|
||||||
|
pub fn new(directories: &[&'static str], files: &[&'static str]) -> TestEnv {
|
||||||
|
let temp_dir = create_working_directory(directories, files).expect("working directory");
|
||||||
|
let fd_exe = find_fd_exe();
|
||||||
|
|
||||||
|
TestEnv {
|
||||||
|
temp_dir,
|
||||||
|
fd_exe,
|
||||||
|
normalize_line: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize_line(self, normalize: bool) -> TestEnv {
|
||||||
|
TestEnv {
|
||||||
|
temp_dir: self.temp_dir,
|
||||||
|
fd_exe: self.fd_exe,
|
||||||
|
normalize_line: normalize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the root directory for the tests.
|
||||||
|
pub fn test_root(&self) -> PathBuf {
|
||||||
|
self.temp_dir.path().to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the root directory of the file system.
|
||||||
|
pub fn system_root(&self) -> PathBuf {
|
||||||
|
let mut components = self.temp_dir.path().components();
|
||||||
|
PathBuf::from(components.next().expect("root directory").as_os_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>>(
|
||||||
|
&self,
|
||||||
|
path: P,
|
||||||
|
args: &[&str],
|
||||||
|
expected: &str,
|
||||||
|
) {
|
||||||
|
// Setup *fd* command.
|
||||||
|
let mut cmd = process::Command::new(&self.fd_exe);
|
||||||
|
cmd.current_dir(self.temp_dir.path().join(path));
|
||||||
|
cmd.args(args);
|
||||||
|
|
||||||
|
// Run *fd*.
|
||||||
|
let output = cmd.output().expect("fd output");
|
||||||
|
|
||||||
|
// Check for exit status.
|
||||||
|
if !output.status.success() {
|
||||||
|
panic!(format_exit_error(args, &output));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize both expected and actual output.
|
||||||
|
let expected = normalize_output(expected, true, self.normalize_line);
|
||||||
|
let actual = normalize_output(
|
||||||
|
&String::from_utf8_lossy(&output.stdout),
|
||||||
|
false,
|
||||||
|
self.normalize_line,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compare actual output to expected output.
|
||||||
|
if expected != actual {
|
||||||
|
panic!(format_output_error(args, &expected, &actual));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that calling *fd* with the specified arguments produces the expected error.
|
||||||
|
pub fn assert_error(&self, args: &[&str], expected: &str) {
|
||||||
|
self.assert_error_subdirectory(".", args, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that calling *fd* in the specified path under the root working directory,
|
||||||
|
/// and with the specified arguments produces an error with the expected message.
|
||||||
|
fn assert_error_subdirectory<P: AsRef<Path>>(&self, path: P, args: &[&str], expected: &str) {
|
||||||
|
// Setup *fd* command.
|
||||||
|
let mut cmd = process::Command::new(&self.fd_exe);
|
||||||
|
cmd.current_dir(self.temp_dir.path().join(path));
|
||||||
|
cmd.args(args);
|
||||||
|
|
||||||
|
// Run *fd*.
|
||||||
|
let output = cmd.output().expect("fd output");
|
||||||
|
|
||||||
|
// Check for exit status.
|
||||||
|
if output.status.success() {
|
||||||
|
panic!("error '{}' did not occur.", expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare actual output to expected output.
|
||||||
|
let actual = String::from_utf8_lossy(&output.stderr);
|
||||||
|
if !actual.starts_with(expected) {
|
||||||
|
panic!(format_output_error(args, &expected, &actual));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1233
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user