mirror of
https://github.com/sharkdp/fd.git
synced 2026-08-31 20:03:34 +08:00
Compare commits
483 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f5ed8534e | |||
| c48d02e0a9 | |||
| b3399c239b | |||
| 4377954cf9 | |||
| 125cb81a5b | |||
| 2e9be3e3f5 | |||
| 43f276e073 | |||
| ec38e23d58 | |||
| 56060a5b6b | |||
| 03548a847f | |||
| 06cd391845 | |||
| ef4abfc1d2 | |||
| 72895675ef | |||
| ccc6cc54b2 | |||
| aa1c0250b7 | |||
| d5f740c9d3 | |||
| aa6933d9e3 | |||
| c800c79976 | |||
| 075c1af7d8 | |||
| fbebaa299d | |||
| f6bbeb35f5 | |||
| 0dc49b07a0 | |||
| c2e5075316 | |||
| e7f192f1c6 | |||
| e5145ffb98 | |||
| 30add71233 | |||
| dade9736ac | |||
| 639ed34791 | |||
| da7ea79034 | |||
| b7e077320d | |||
| 81669f4c10 | |||
| 5ea7cb7a05 | |||
| 4baefb2f5a | |||
| 97a8825b00 | |||
| 7fe4bfaacb | |||
| 1a6638ba23 | |||
| 8772708aa7 | |||
| a4bb734482 | |||
| 5fbfdfadb8 | |||
| 16ae03c3b4 | |||
| 52f22557a5 | |||
| 72cc9a876c | |||
| 5d06491370 | |||
| 67aaf82d37 | |||
| f219da4b3a | |||
| e990a13405 | |||
| 7b7876e701 | |||
| feac240eb5 | |||
| fba6db5096 | |||
| f2eb1be678 | |||
| b211ded5de | |||
| 92bd7850d0 | |||
| 06c58b99f5 | |||
| 776f8d0f11 | |||
| 47421a49b8 | |||
| f347379fb0 | |||
| 4d7e34ad59 | |||
| 359c0269ba | |||
| 13cdfb32f8 | |||
| 0484486f3f | |||
| f32060b0de | |||
| fe992706ae | |||
| 2b1bf471b1 | |||
| cab31e280b | |||
| 088e42deef | |||
| ecdaf58e7f | |||
| fc2a972082 | |||
| 66e3ccc5e1 | |||
| 21fd013073 | |||
| a539181f1f | |||
| cdc6a37ed6 | |||
| 2570fbd04e | |||
| 690976380d | |||
| 828649a30d | |||
| 20dc7a984d | |||
| 653bc0e55d | |||
| e8fe1508e2 | |||
| fd493eb709 | |||
| f1b39d49c1 | |||
| 1236b1dbcf | |||
| 17dd2a6dfe | |||
| bf9e6fd36e | |||
| 46db1c4ef3 | |||
| b9cb5d54a4 | |||
| a41021b0d2 | |||
| 82f04755bf | |||
| c42bf4b904 | |||
| fbc836b553 | |||
| 91860bf682 | |||
| a4a4709320 | |||
| b6f8bc8ff8 | |||
| 782fc278aa | |||
| 12650a0913 | |||
| 2e115df9e7 | |||
| 953f586f26 | |||
| 08d913f167 | |||
| 6b92c5db90 | |||
| 7b5b3ec47b | |||
| 02e9850112 | |||
| b8c575cc8f | |||
| 3de948ae0d | |||
| 45a86459b2 | |||
| a64a607fd8 | |||
| 00eb6461cb | |||
| feb969881b | |||
| c06efe1317 | |||
| 04fac46c5c | |||
| e0d03d170e | |||
| fd1c3d376e | |||
| c3f786db43 | |||
| a5f17db53a | |||
| f1a5aa0887 | |||
| 3c619afe30 | |||
| 476d404938 | |||
| 43f5c8adc9 | |||
| c749c95136 | |||
| 3ebd78cf02 | |||
| 8da936abd8 | |||
| 668af05dca | |||
| 3ba90dd768 | |||
| 87caef9513 | |||
| 78e21395b8 | |||
| 1b52948e42 | |||
| 334488cab7 | |||
| 1c72f80ff5 | |||
| 5b4869a940 | |||
| 9bc8fabba0 | |||
| 8984e57dd6 | |||
| 049232439a | |||
| e4730603d0 | |||
| 154cd6e329 | |||
| 394e967b5f | |||
| 0f0fa76c81 | |||
| 1795e0e9dd | |||
| 500f404c4f | |||
| 6a18b36037 | |||
| 115ae93df9 | |||
| c06c9952b6 | |||
| 42dce35dd7 | |||
| c2e4be3d12 | |||
| 37852aa388 | |||
| 25d7c4801f | |||
| d34e18a58c | |||
| ba60a163fe | |||
| 4e04d13215 | |||
| c37592b0b7 | |||
| 224b7f2354 | |||
| f8ae334ca9 | |||
| b5344dac30 | |||
| 018556a367 | |||
| aeff525c30 | |||
| 2d398dc4a7 | |||
| 6e44828cc8 | |||
| b507449146 | |||
| d9697d1486 | |||
| 515e0ee469 | |||
| a0ca460901 | |||
| 693325bcf2 | |||
| ab81cad3b5 | |||
| 70850629e9 | |||
| 8b0db189a1 | |||
| ad1b267601 | |||
| 04829c287d | |||
| ca92bea0d7 | |||
| 252beb8df7 | |||
| b80764b19a | |||
| 148fa64a73 | |||
| 6ce55624f7 | |||
| 708efaa812 | |||
| a7a4499d1d | |||
| 2a2126c40a | |||
| db30bfc2b7 | |||
| 81a051f07c | |||
| 95d6470956 | |||
| e59a2001cc | |||
| c60338a28b | |||
| 16464a35b1 | |||
| 1d85e31260 | |||
| 024309f378 | |||
| ac4ded5b27 | |||
| c9873b4b82 | |||
| 07ebce9419 | |||
| f60b7687a2 | |||
| cb385a4822 | |||
| efc71bc00b | |||
| 1a3615df9c | |||
| cf7dd43f80 | |||
| 4ebb3bbc81 | |||
| d283aba52e | |||
| 5423c45660 | |||
| 98738d2ac1 | |||
| 371794047a | |||
| c43c931985 | |||
| 41dc622fb5 | |||
| 266ba7893a | |||
| 194dbe2033 | |||
| 97f1558daf | |||
| f00af363d1 | |||
| 2155d19be0 | |||
| 3e728f3edd | |||
| 6645131234 | |||
| 189d6a907a | |||
| 8a83ad41c8 | |||
| 5feb89aa99 | |||
| 8a532e5196 | |||
| b8089f8d58 | |||
| 85fb7389af | |||
| eb28030f87 | |||
| 5d2e011e90 | |||
| 64bde11887 | |||
| b928af7d9c | |||
| 9bc20e38b1 | |||
| 6a3aae52fd | |||
| 7dc81381b0 | |||
| c7cf2d1104 | |||
| 91eaff056f | |||
| cadaef3f07 | |||
| 17bd256ae6 | |||
| ecfd8cea41 | |||
| d2659de782 | |||
| d205a7ff9e | |||
| 296300ce46 | |||
| ea48a96945 | |||
| f2fec45099 | |||
| 4faa201c89 | |||
| 54603501ec | |||
| 45c66e7f1b | |||
| 441a235a0f | |||
| 2cd3de8b94 | |||
| 6830b3dee9 | |||
| f3edaf0e4d | |||
| e97a1e90cb | |||
| 2338b3bc81 | |||
| 9412648ee9 | |||
| 0a22dcc044 | |||
| 24402dda7c | |||
| 4f8697c407 | |||
| b2fa188029 | |||
| e0adb45d08 | |||
| ec4cc981fc | |||
| 06eb231fbd | |||
| fd1ddc9d08 | |||
| e852009876 | |||
| 99934ab98c | |||
| 633d486ac1 | |||
| 4d2472bfab | |||
| a851570b15 | |||
| f064e41bf1 | |||
| ef1bfc7508 | |||
| a38da63a34 | |||
| 9a49d0d6d0 | |||
| 74c3431a2b | |||
| 1b4e1e9c1a | |||
| 6f2c8cdf91 | |||
| e2f90dba44 | |||
| 5648597a61 | |||
| b3258e00df | |||
| 41d7cc33ca | |||
| a81fef9992 | |||
| 53c338d71f | |||
| d77daa89a2 | |||
| 78cde856b1 | |||
| a954944d69 | |||
| 27162c2729 | |||
| 1d1eefdb7b | |||
| c8746f6216 | |||
| c761d51dfb | |||
| ddee6aa8f3 | |||
| e3c77ef7f5 | |||
| c0592e276e | |||
| d41a5a9e24 | |||
| 726fede7cc | |||
| 9d3cb69935 | |||
| da9af8c1eb | |||
| dc120c547d | |||
| bbf3d0d430 | |||
| 4f4591bfdc | |||
| 28d6a4f518 | |||
| 42719624a8 | |||
| fef1b91feb | |||
| c5a311d3cc | |||
| ca2ef7ba82 | |||
| 65b65b32be | |||
| 3283a7b345 | |||
| 4cafb28951 | |||
| 79d5a5bdc5 | |||
| a9dc45ecb1 | |||
| 1a16003cca | |||
| 6a68ac8c27 | |||
| 09cdf9831d | |||
| 22a55361e0 | |||
| d7cc34e36f | |||
| 4f9a02400a | |||
| 30cb4adc61 | |||
| daf3046a35 | |||
| 151eaad043 | |||
| 4f4330167a | |||
| f31fb9bf68 | |||
| b22285ffe4 | |||
| db6fc44d05 | |||
| 90de7242c7 | |||
| 04bcd546b2 | |||
| 4b5e8941a7 | |||
| fe00860b87 | |||
| 0335cc362b | |||
| a06efe1418 | |||
| 1a96544b29 | |||
| ee1a9f567f | |||
| 17fbc2dc52 | |||
| 074de44caf | |||
| cb56add1d8 | |||
| 4287cddc00 | |||
| 0b7febc3b6 | |||
| d91100e354 | |||
| 0f5500adfc | |||
| 2685728695 | |||
| 789706c3ab | |||
| 4a0730a5fa | |||
| 3b42898a69 | |||
| d63c63be8c | |||
| 47974b6479 | |||
| 2bab4a2249 | |||
| 95eae00126 | |||
| ed024ceb54 | |||
| 6c9e743d43 | |||
| ea21df3f76 | |||
| 7e3c69c096 | |||
| 439548c25a | |||
| ed45d1c5af | |||
| e44f2f8540 | |||
| 2ea77e7cdc | |||
| a85f33cf98 | |||
| 4815ee416e | |||
| 53557ef471 | |||
| f9372b55d5 | |||
| 4590ae8535 | |||
| bce95274e3 | |||
| af9d8082a1 | |||
| 094516aec3 | |||
| 33eb857a81 | |||
| 6e96154d86 | |||
| 556c40e1f4 | |||
| b415d7234b | |||
| e0cba4b700 | |||
| 342d12db3f | |||
| c79d1638f5 | |||
| 9ef9bf7b80 | |||
| 809fd8fb78 | |||
| a3060f952e | |||
| 65096a653e | |||
| 9f738ad995 | |||
| 5c27ab1a06 | |||
| 2cfb1a2bb2 | |||
| 938dafda28 | |||
| e2a7d3446b | |||
| e42ddda5a6 | |||
| 289d6443a7 | |||
| 3066445e6b | |||
| 364555fd71 | |||
| 80b042ad47 | |||
| 4274075f6f | |||
| b9d0e72f70 | |||
| 33df48ebe4 | |||
| c9dc617711 | |||
| da5cd12b24 | |||
| 1714d416e2 | |||
| d43827fe57 | |||
| ee673c92d3 | |||
| decd7f2ea3 | |||
| f3e1ea150c | |||
| 12bd321a27 | |||
| 5fc341832d | |||
| dd07bc9ef1 | |||
| 2340bab32a | |||
| 4a237c591e | |||
| 44605d55dd | |||
| e5e14f31c4 | |||
| f69b59195a | |||
| 9c9bb12f23 | |||
| 70448b0006 | |||
| fc924d2e0a | |||
| cab4981b11 | |||
| 104600e642 | |||
| 3c6fab3439 | |||
| d05e7171d4 | |||
| 81dee25438 | |||
| bbf0f1cc1f | |||
| bfc8c42444 | |||
| 918dfc6ef0 | |||
| d6034119ae | |||
| 82e6562cfc | |||
| 8cea65c1b8 | |||
| 9d73402ef2 | |||
| 06434f229b | |||
| 0f2429cabc | |||
| 232e3937f2 | |||
| e23398e6d0 | |||
| a5fe138a25 | |||
| b23cfc383f | |||
| e46998278a | |||
| 7213f5a88e | |||
| fa7d49282b | |||
| 2f18903b29 | |||
| b63e43b40f | |||
| 56289041a6 | |||
| 8196f79fa4 | |||
| 320d7fca0a | |||
| f7d1938556 | |||
| 328c6640be | |||
| c001dd47a7 | |||
| a9c7696ecc | |||
| 9a34ba6a0c | |||
| cd2dc5714e | |||
| 2828d90f99 | |||
| ed6c184020 | |||
| 6f7d10870c | |||
| dea1fbe722 | |||
| 94993ca6c2 | |||
| 3e0325cc60 | |||
| e5ba75f3c8 | |||
| 8796de57b5 | |||
| 0f27485faf | |||
| 762f551ff4 | |||
| fb205f5057 | |||
| 32fdbb1eae | |||
| 19e5d6e36e | |||
| 0895027ed6 | |||
| c60cf9c1a8 | |||
| c14a004367 | |||
| a6ed0fc5ef | |||
| 266499800b | |||
| 9491670974 | |||
| d48aeda6b2 | |||
| 59c27baf0f | |||
| 33f385692d | |||
| b027b2c322 | |||
| 9350c8544d | |||
| f55033818d | |||
| 1ab4e87dda | |||
| 4e20803e7d | |||
| 5a154866e3 | |||
| ac3e0e18e4 | |||
| 9cc0c8ed57 | |||
| a991ed4488 | |||
| fe79b8ce0a | |||
| 25a728529d | |||
| 1056220421 | |||
| 08fcd7ce59 | |||
| 50fca2dca2 | |||
| 588888112a | |||
| eac20a8132 | |||
| d5da615c17 | |||
| 641594c2c6 | |||
| 2545aaabd2 | |||
| 3599a30ee2 | |||
| f14b854bc1 | |||
| 8f86a06c1a | |||
| 761c445263 | |||
| 588e344d5b | |||
| e3af9b0307 | |||
| 29bf9d731d | |||
| 3857fa8f62 | |||
| 24e108e1ad | |||
| 1d16cd855e | |||
| a0505bd4df | |||
| 630749f706 | |||
| 1daa454615 | |||
| 040c57e007 | |||
| 1ce2add943 | |||
| 418233193a | |||
| 0b04bd19df | |||
| ac2dae0427 | |||
| 53afcd8fc3 | |||
| d266fa3010 | |||
| e7d0a91986 | |||
| 801816ee6c | |||
| 5cbd8405ec | |||
| 35945c4a62 | |||
| 97908beb97 | |||
| f15540e501 | |||
| 074cfad3d4 | |||
| f3ec3d9cf0 | |||
| 46e865f1a2 |
@@ -0,0 +1 @@
|
||||
github: [sharkdp, tavianator]
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Bug Report
|
||||
description: Report a bug.
|
||||
title: "[BUG] "
|
||||
labels: bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please check out the [troubleshooting section](https://github.com/sharkdp/fd#troubleshooting) first.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
options:
|
||||
- label: I have read the troubleshooting section and still think this is a bug.
|
||||
required: true
|
||||
- type: textarea
|
||||
id: bug
|
||||
attributes:
|
||||
label: "Describe the bug you encountered:"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: "Describe what you expected to happen:"
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: "What version of `fd` are you using?"
|
||||
placeholder: "paste the output of `fd --version` here"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: os
|
||||
attributes:
|
||||
label: Which operating system / distribution are you on?
|
||||
placeholder: |
|
||||
Unix: paste the output of `uname -srm` and `lsb_release -a` here.
|
||||
Windows: please tell us your Windows version
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: true
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project.
|
||||
title: ''
|
||||
labels: feature-request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about 'fd'.
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
**What version of `fd` are you using?**
|
||||
[paste the output of `fd --version` here]
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
@@ -0,0 +1,364 @@
|
||||
name: CICD
|
||||
|
||||
env:
|
||||
MIN_SUPPORTED_RUST_VERSION: "1.54.0"
|
||||
CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
code_quality:
|
||||
name: Code quality
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
default: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
components: clippy, rustfmt
|
||||
- name: Ensure `cargo fmt` has been run
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: -- --check
|
||||
- name: Ensure MSRV is set in `clippy.toml`
|
||||
run: grep "^msrv = \"${{ env.MIN_SUPPORTED_RUST_VERSION }}\"\$" clippy.toml
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --locked --all-targets --all-features
|
||||
|
||||
min_version:
|
||||
name: Minimum supported rust version
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }}
|
||||
default: true
|
||||
components: clippy
|
||||
- name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --locked --all-targets --all-features
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
- name: Run tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --locked
|
||||
|
||||
build:
|
||||
name: ${{ matrix.job.os }} (${{ matrix.job.target }})
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { os: ubuntu-20.04, target: arm-unknown-linux-gnueabihf , use-cross: true }
|
||||
- { os: ubuntu-20.04, target: arm-unknown-linux-musleabihf, use-cross: true }
|
||||
- { os: ubuntu-20.04, target: aarch64-unknown-linux-gnu , use-cross: true }
|
||||
- { os: ubuntu-20.04, target: i686-unknown-linux-gnu , use-cross: true }
|
||||
- { os: ubuntu-20.04, target: i686-unknown-linux-musl , use-cross: true }
|
||||
- { os: ubuntu-20.04, target: x86_64-unknown-linux-gnu }
|
||||
- { os: ubuntu-20.04, target: x86_64-unknown-linux-musl , use-cross: true }
|
||||
- { os: macos-10.15 , target: x86_64-apple-darwin }
|
||||
# - { os: windows-2019, target: i686-pc-windows-gnu } ## disabled; error: linker `i686-w64-mingw32-gcc` not found
|
||||
- { os: windows-2019, target: i686-pc-windows-msvc }
|
||||
- { os: windows-2019, target: x86_64-pc-windows-gnu }
|
||||
- { os: windows-2019, target: x86_64-pc-windows-msvc }
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
case ${{ matrix.job.target }} in
|
||||
arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
|
||||
aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
|
||||
esac
|
||||
|
||||
- name: Extract crate information
|
||||
shell: bash
|
||||
run: |
|
||||
echo "PROJECT_NAME=fd" >> $GITHUB_ENV
|
||||
echo "PROJECT_VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV
|
||||
echo "PROJECT_MAINTAINER=$(sed -n 's/^authors = \["\(.*\)"\]/\1/p' Cargo.toml)" >> $GITHUB_ENV
|
||||
echo "PROJECT_HOMEPAGE=$(sed -n 's/^homepage = "\(.*\)"/\1/p' Cargo.toml)" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.job.target }}
|
||||
override: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
|
||||
- name: Show version information (Rust, cargo, GCC)
|
||||
shell: bash
|
||||
run: |
|
||||
gcc --version || true
|
||||
rustup -V
|
||||
rustup toolchain list
|
||||
rustup default
|
||||
cargo -V
|
||||
rustc -V
|
||||
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.job.use-cross }}
|
||||
command: build
|
||||
args: --locked --release --target=${{ matrix.job.target }}
|
||||
|
||||
- name: Strip debug information from executable
|
||||
id: strip
|
||||
shell: bash
|
||||
run: |
|
||||
# Figure out suffix of binary
|
||||
EXE_suffix=""
|
||||
case ${{ matrix.job.target }} in
|
||||
*-pc-windows-*) EXE_suffix=".exe" ;;
|
||||
esac;
|
||||
|
||||
# Figure out what strip tool to use if any
|
||||
STRIP="strip"
|
||||
case ${{ matrix.job.target }} in
|
||||
arm-unknown-linux-*) STRIP="arm-linux-gnueabihf-strip" ;;
|
||||
aarch64-unknown-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;;
|
||||
*-pc-windows-msvc) STRIP="" ;;
|
||||
esac;
|
||||
|
||||
# Setup paths
|
||||
BIN_DIR="${{ env.CICD_INTERMEDIATES_DIR }}/stripped-release-bin/"
|
||||
mkdir -p "${BIN_DIR}"
|
||||
BIN_NAME="${{ env.PROJECT_NAME }}${EXE_suffix}"
|
||||
BIN_PATH="${BIN_DIR}/${BIN_NAME}"
|
||||
|
||||
# Copy the release build binary to the result location
|
||||
cp "target/${{ matrix.job.target }}/release/${BIN_NAME}" "${BIN_DIR}"
|
||||
|
||||
# Also strip if possible
|
||||
if [ -n "${STRIP}" ]; then
|
||||
"${STRIP}" "${BIN_PATH}"
|
||||
fi
|
||||
|
||||
# Let subsequent steps know where to find the (stripped) bin
|
||||
echo ::set-output name=BIN_PATH::${BIN_PATH}
|
||||
echo ::set-output name=BIN_NAME::${BIN_NAME}
|
||||
|
||||
- name: Set testing options
|
||||
id: test-options
|
||||
shell: bash
|
||||
run: |
|
||||
# test only library unit tests and binary for arm-type targets
|
||||
unset CARGO_TEST_OPTIONS
|
||||
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac;
|
||||
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
|
||||
|
||||
- name: Run tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.job.use-cross }}
|
||||
command: test
|
||||
args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
|
||||
|
||||
- name: Create tarball
|
||||
id: package
|
||||
shell: bash
|
||||
run: |
|
||||
PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
|
||||
PKG_BASENAME=${PROJECT_NAME}-v${PROJECT_VERSION}-${{ matrix.job.target }}
|
||||
PKG_NAME=${PKG_BASENAME}${PKG_suffix}
|
||||
echo ::set-output name=PKG_NAME::${PKG_NAME}
|
||||
|
||||
PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package"
|
||||
ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/"
|
||||
mkdir -p "${ARCHIVE_DIR}"
|
||||
mkdir -p "${ARCHIVE_DIR}/autocomplete"
|
||||
|
||||
# Binary
|
||||
cp "${{ steps.strip.outputs.BIN_PATH }}" "$ARCHIVE_DIR"
|
||||
|
||||
# Man page
|
||||
cp 'doc/${{ env.PROJECT_NAME }}.1' "$ARCHIVE_DIR"
|
||||
|
||||
# README, LICENSE and CHANGELOG files
|
||||
cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR"
|
||||
|
||||
# Autocompletion files
|
||||
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "$ARCHIVE_DIR/autocomplete/"
|
||||
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "$ARCHIVE_DIR/autocomplete/"
|
||||
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'_${{ env.PROJECT_NAME }}.ps1' "$ARCHIVE_DIR/autocomplete/"
|
||||
cp 'contrib/completion/_fd' "$ARCHIVE_DIR/autocomplete/"
|
||||
|
||||
# base compressed package
|
||||
pushd "${PKG_STAGING}/" >/dev/null
|
||||
case ${{ matrix.job.target }} in
|
||||
*-pc-windows-*) 7z -y a "${PKG_NAME}" "${PKG_BASENAME}"/* | tail -2 ;;
|
||||
*) tar czf "${PKG_NAME}" "${PKG_BASENAME}"/* ;;
|
||||
esac;
|
||||
popd >/dev/null
|
||||
|
||||
# Let subsequent steps know where to find the compressed package
|
||||
echo ::set-output name=PKG_PATH::"${PKG_STAGING}/${PKG_NAME}"
|
||||
|
||||
- name: Create Debian package
|
||||
id: debian-package
|
||||
shell: bash
|
||||
if: startsWith(matrix.job.os, 'ubuntu')
|
||||
run: |
|
||||
COPYRIGHT_YEARS="2018 - "$(date "+%Y")
|
||||
DPKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/debian-package"
|
||||
DPKG_DIR="${DPKG_STAGING}/dpkg"
|
||||
mkdir -p "${DPKG_DIR}"
|
||||
|
||||
DPKG_BASENAME=${PROJECT_NAME}
|
||||
DPKG_CONFLICTS=${PROJECT_NAME}-musl
|
||||
case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac;
|
||||
DPKG_VERSION=${PROJECT_VERSION}
|
||||
|
||||
unset DPKG_ARCH
|
||||
case ${{ matrix.job.target }} in
|
||||
aarch64-*-linux-*) DPKG_ARCH=arm64 ;;
|
||||
arm-*-linux-*hf) DPKG_ARCH=armhf ;;
|
||||
i686-*-linux-*) DPKG_ARCH=i686 ;;
|
||||
x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
|
||||
*) DPKG_ARCH=notset ;;
|
||||
esac;
|
||||
|
||||
DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
|
||||
echo ::set-output name=DPKG_NAME::${DPKG_NAME}
|
||||
|
||||
# Binary
|
||||
install -Dm755 "${{ steps.strip.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.strip.outputs.BIN_NAME }}"
|
||||
|
||||
# Man page
|
||||
install -Dm644 'doc/${{ env.PROJECT_NAME }}.1' "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1"
|
||||
gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1"
|
||||
|
||||
# Autocompletion files
|
||||
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ env.PROJECT_NAME }}"
|
||||
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ env.PROJECT_NAME }}.fish"
|
||||
install -Dm644 'contrib/completion/_fd' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ env.PROJECT_NAME }}"
|
||||
|
||||
# README and LICENSE
|
||||
install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md"
|
||||
install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-MIT"
|
||||
install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-APACHE"
|
||||
install -Dm644 "CHANGELOG.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog"
|
||||
gzip -n --best "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog"
|
||||
|
||||
cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" <<EOF
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: ${{ env.PROJECT_NAME }}
|
||||
Source: ${{ env.PROJECT_HOMEPAGE }}
|
||||
|
||||
Files: *
|
||||
Copyright: ${{ env.PROJECT_MAINTAINER }}
|
||||
Copyright: $COPYRIGHT_YEARS ${{ env.PROJECT_MAINTAINER }}
|
||||
License: Apache-2.0 or MIT
|
||||
|
||||
License: Apache-2.0
|
||||
On Debian systems, the complete text of the Apache-2.0 can be found in the
|
||||
file /usr/share/common-licenses/Apache-2.0.
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
EOF
|
||||
chmod 644 "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright"
|
||||
|
||||
# control file
|
||||
mkdir -p "${DPKG_DIR}/DEBIAN"
|
||||
cat > "${DPKG_DIR}/DEBIAN/control" <<EOF
|
||||
Package: ${DPKG_BASENAME}
|
||||
Version: ${DPKG_VERSION}
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: ${{ env.PROJECT_MAINTAINER }}
|
||||
Homepage: ${{ env.PROJECT_HOMEPAGE }}
|
||||
Architecture: ${DPKG_ARCH}
|
||||
Provides: ${{ env.PROJECT_NAME }}
|
||||
Conflicts: ${DPKG_CONFLICTS}
|
||||
Description: simple, fast and user-friendly alternative to find
|
||||
fd is a program to find entries in your filesystem.
|
||||
It is a simple, fast and user-friendly alternative to find.
|
||||
While it does not aim to support all of finds powerful functionality, it provides
|
||||
sensible (opinionated) defaults for a majority of use cases.
|
||||
EOF
|
||||
|
||||
DPKG_PATH="${DPKG_STAGING}/${DPKG_NAME}"
|
||||
echo ::set-output name=DPKG_PATH::${DPKG_PATH}
|
||||
|
||||
# build dpkg
|
||||
fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}"
|
||||
|
||||
- name: "Artifact upload: tarball"
|
||||
uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: ${{ steps.package.outputs.PKG_NAME }}
|
||||
path: ${{ steps.package.outputs.PKG_PATH }}
|
||||
|
||||
- name: "Artifact upload: Debian package"
|
||||
uses: actions/upload-artifact@master
|
||||
if: steps.debian-package.outputs.DPKG_NAME
|
||||
with:
|
||||
name: ${{ steps.debian-package.outputs.DPKG_NAME }}
|
||||
path: ${{ steps.debian-package.outputs.DPKG_PATH }}
|
||||
|
||||
- name: Check for release
|
||||
id: is-release
|
||||
shell: bash
|
||||
run: |
|
||||
unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi
|
||||
echo ::set-output name=IS_RELEASE::${IS_RELEASE}
|
||||
|
||||
- name: Publish archives and packages
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: steps.is-release.outputs.IS_RELEASE
|
||||
with:
|
||||
files: |
|
||||
${{ steps.package.outputs.PKG_PATH }}
|
||||
${{ steps.debian-package.outputs.DPKG_PATH }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,29 +0,0 @@
|
||||
name: fd
|
||||
version: git
|
||||
summary: "A simple, fast and user-friendly alternative to 'find'"
|
||||
description: |
|
||||
fd is a simple, fast and user-friendly alternative to find.
|
||||
- While it does not seek to mirror all of find's powerful functionality, it provides sensible (opinionated) defaults for 80% of the use cases.
|
||||
Features
|
||||
Convenient syntax: fd PATTERN instead of find -iname '*PATTERN*'.
|
||||
Colorized terminal output (similar to ls).
|
||||
It's fast (see benchmarks below).
|
||||
Smart case: the search is case-insensitive by default. It switches to case-sensitive if the pattern contains an uppercase character*.
|
||||
Ignores hidden directories and files, by default.
|
||||
Ignores patterns from your .gitignore, by default.
|
||||
Regular expressions.
|
||||
Unicode-awareness.
|
||||
The command name is 50% shorter* than find :-).
|
||||
Parallel command execution with a syntax similar to GNU Parallel.
|
||||
|
||||
grade: stable
|
||||
confinement: classic
|
||||
|
||||
parts:
|
||||
fd:
|
||||
plugin: rust
|
||||
source: ./
|
||||
|
||||
apps:
|
||||
fd:
|
||||
command: bin/fd
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
language: rust
|
||||
cache: cargo
|
||||
matrix:
|
||||
# allow_failures:
|
||||
# - rust: nightly
|
||||
include:
|
||||
# Stable channel.
|
||||
- os: linux
|
||||
rust: stable
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
- os: linux
|
||||
rust: stable
|
||||
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
|
||||
rust: stable
|
||||
env: TARGET=x86_64-apple-darwin
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- TARGET=arm-unknown-linux-gnueabihf
|
||||
- CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- TARGET=arm-unknown-linux-musleabihf
|
||||
- CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
|
||||
|
||||
# Beta channel.
|
||||
- os: linux
|
||||
rust: beta
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
# Disabled to reduce total CI time
|
||||
# - os: linux
|
||||
# rust: beta
|
||||
# env: TARGET=x86_64-unknown-linux-musl
|
||||
# - os: linux
|
||||
# rust: beta
|
||||
# env: TARGET=i686-unknown-linux-gnu
|
||||
# - os: linux
|
||||
# rust: beta
|
||||
# env: TARGET=i686-unknown-linux-musl
|
||||
# - os: osx
|
||||
# rust: beta
|
||||
# env: TARGET=x86_64-apple-darwin
|
||||
|
||||
# Nightly channel.
|
||||
- os: linux
|
||||
rust: nightly
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
# Disabled to reduce total CI time
|
||||
# - os: linux
|
||||
# rust: nightly
|
||||
# env: TARGET=x86_64-unknown-linux-musl
|
||||
# - os: linux
|
||||
# rust: nightly
|
||||
# env: TARGET=i686-unknown-linux-gnu
|
||||
# - os: linux
|
||||
# rust: nightly
|
||||
# env: TARGET=i686-unknown-linux-musl
|
||||
# - os: osx
|
||||
# rust: nightly
|
||||
# env: TARGET=x86_64-apple-darwin
|
||||
|
||||
# Minimum Rust supported channel.
|
||||
- os: linux
|
||||
rust: 1.31.0
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
- os: linux
|
||||
rust: 1.31.0
|
||||
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
|
||||
|
||||
# Code formatting check
|
||||
- os: linux
|
||||
rust: nightly
|
||||
# skip the global install step
|
||||
install:
|
||||
- cargo install --debug --force rustfmt-nightly
|
||||
script: cargo fmt -- --check
|
||||
|
||||
sudo: required
|
||||
|
||||
before_install:
|
||||
- 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:
|
||||
email:
|
||||
on_success: never
|
||||
+588
@@ -0,0 +1,588 @@
|
||||
# Upcoming release
|
||||
|
||||
## Performance improvements
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
## Bugfixes
|
||||
|
||||
|
||||
## Changes
|
||||
|
||||
|
||||
## Other
|
||||
|
||||
|
||||
# v8.3.2
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Invalid absolute path on windows when searching from the drive root, see #931 and #936 (@gbarta)
|
||||
|
||||
|
||||
# v8.3.1
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Stop implying `--no-ignore-parent` when `--no-vcs-ignore` is supplied, see #907, #901, #908 (@tmccombs)
|
||||
- fd no longer waits for the whole traversal if the only matches arrive within max_buffer_time, see #868 and #895 (@tavianator)
|
||||
- `--max-results=1` now immediately quits after the first result, see #867
|
||||
- `fd -h` does not panic anymore when stdout is closed, see #897
|
||||
|
||||
## Changes
|
||||
|
||||
- Disable jemalloc on FreeBSD, see #896 (@xanderio)
|
||||
- Updated man page, see #912 (@rlue)
|
||||
- Updated zsh completions, see #932 (@tmccombs)
|
||||
|
||||
|
||||
# v8.3.0
|
||||
|
||||
## Performance improvements
|
||||
|
||||
- Colorized output is now significantly faster, see #720 and #853 (@tavianator)
|
||||
- Writing to stdout is now buffered if the output does not go to a TTY. This increases performance
|
||||
when the output of `fd` is piped to another program or to a file, see #885 (@tmccombs, original
|
||||
implementation by @sourlemon207)
|
||||
- File metadata is now cached between the different filters that require it (e.g. `--owner`,
|
||||
`--size`), reducing the number of `stat` syscalls when multiple filters are used; see #863
|
||||
(@tavianator, original implementation by @alexmaco)
|
||||
|
||||
## Features
|
||||
|
||||
- Don't buffer command output from `--exec` when using a single thread. See #522
|
||||
- Add new `-q, --quiet` flag, see #303 (@Asha20)
|
||||
- Add new `--no-ignore-parent` flag, see #787 (@will459)
|
||||
- Add new `--batch-size` flag, see #410 (@devonhollowood)
|
||||
- Add opposing command-line options, see #595 (@Asha20)
|
||||
- Add support for more filesystem indicators in `LS_COLORS`, see
|
||||
https://github.com/sharkdp/lscolors/pull/35 (@tavianator)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Always show the `./` prefix for search results unless the output is a TTY or `--strip-cwd-prefix` is set, see #760 and #861 (@jcaplan)
|
||||
- Set default path separator to `/` in MSYS, see #537 and #730 (@aswild)
|
||||
- fd cannot search files under a RAM disk, see #752
|
||||
- fd doesn't show substituted drive on Windows, see #365
|
||||
- Properly handle write errors to devices that are full, see #737
|
||||
- Use local time zone for time functions (`--change-newer-than`, `--change-older-than`), see #631 (@jacobmischka)
|
||||
- Support `--list-details` on more platforms (like BusyBox), see #783
|
||||
- The filters `--owner`, `--size`, and `--changed-{within,before}` now apply to symbolic links
|
||||
themselves, rather than the link target, except when `--follow` is specified; see #863
|
||||
- Change time comparisons to be exclusive, see #794 (@jacobmischka)
|
||||
|
||||
## Changes
|
||||
|
||||
- Apply custom `--path-separator` to commands run with `--exec(-batch)` and `--list-details`, see #697 (@aswild)
|
||||
|
||||
## Other
|
||||
|
||||
- Many documentation updates
|
||||
|
||||
|
||||
# v8.2.1
|
||||
|
||||
No functional changes with respect to v8.2.0. Bugfix in the release process.
|
||||
|
||||
# v8.2.0
|
||||
|
||||
## Features
|
||||
|
||||
- Add new `--prune` flag, see #535 (@reima)
|
||||
- Improved the usability of the time-based options, see #624 and #645 (@gorogoroumaru)
|
||||
- Add support for exact file sizes in the `--size` filter, see #669 and #696 (@Rogach)
|
||||
- `fd` now prints an error message if the search pattern requires a leading dot but
|
||||
`--hidden` is not enabled (Unix only), see #615
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Avoid panic when performing limited searches in directories with restricted permissions, see #678
|
||||
- Invalid numeric command-line arguments are silently ignored, see #675
|
||||
- Disable jemalloc on Android, see #662
|
||||
- The `--help` text will be colorless if `NO_COLOR` has been set, see #600 (@xanonid)
|
||||
|
||||
## Changes
|
||||
|
||||
- If `LS_COLORS` is not set (e.g. on Windows), we now provide a more comprehensive default which
|
||||
includes much more filetypes, see #604 and #682 (mjsir911).
|
||||
|
||||
## Other
|
||||
|
||||
- Added `zsh` completion files, see #654 and #189 (@smancill)
|
||||
|
||||
# v8.1.1
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Support colored output on older Windows versions if either (1) `--color=always` is set or (2) the `TERM` environment variable is set. See #469
|
||||
|
||||
# v8.1.0
|
||||
|
||||
## Features
|
||||
|
||||
- Add new `--owner [user][:group]` filter. See #307 (pull #581) (@alexmaco)
|
||||
- Add support for a global ignore file (`~/.config/fd/ignore` on Unix), see #575 (@soedirgo)
|
||||
- Do not exit immediately if one of the search paths is missing, see #587 (@DJRHails)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Reverted a change from fd 8.0 that enabled colors on all Windows terminals (see below) in order to support older Windows versions again, see #577. Unfortunately, this re-opens #469
|
||||
- Fix segfault caused by jemalloc on macOS Catalina, see #498
|
||||
- Fix `--glob` behavior with empty pattern, see #579 (@SeamusConnor)
|
||||
- Fix `--list-details` on FreeBSD, DragonFly BSD, OpenBSD and NetBSD. See #573 (@t6)
|
||||
|
||||
## Changes
|
||||
|
||||
- Updated documentation for `--size`, see #584
|
||||
|
||||
# v8.0.0
|
||||
|
||||
## Features
|
||||
|
||||
- Add a new `-l`/`--list-details` option to show more details about the search results. This is
|
||||
basically an alias for `--exec-batch ls -l` with some additional `ls` options.
|
||||
This can be used in order to:
|
||||
* see metadata like permissions, owner, file size, modification times (#491)
|
||||
* see symlink targets (#482)
|
||||
* achieve a deterministic output order (#324, #196, #159)
|
||||
- Add a new `--max-results=<count>` option to limit the number of search results, see #472, #476 and #555
|
||||
This can be useful to speed up searches in cases where you know that there are only N results.
|
||||
Using this option is also (slightly) faster than piping to `head -n <count>` where `fd` can only
|
||||
exit when it finds the search results `<count> + 1`.
|
||||
- Add the alias `-1` for `--max-results=1`, see #561. (@SimplyDanny).
|
||||
- Add new `--type socket` and `--type pipe` filters, see #511.
|
||||
- Add new `--min-depth <depth>` and `--exact-depth <depth>` options in addition to the existing option
|
||||
to limit the maximum depth. See #404.
|
||||
- Support additional ANSI font styles in `LS_COLORS`: faint, slow blink, rapid blink, dimmed, hidden and strikethrough.
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Preserve non-UTF8 filenames: invalid UTF-8 filenames are now properly passed to child-processes
|
||||
when using `--exec`, `--exec-batch` or `--list-details`. In `fd`'s output, we replace non-UTF-8
|
||||
sequences with the "�" character. However, if the output of `fd` goes to another process, we
|
||||
print the actual bytes of the filename. For more details, see #558 and #295.
|
||||
- `LS_COLORS` entries with unsupported font styles are not completely ignored, see #552
|
||||
|
||||
## Changes
|
||||
|
||||
- Colored output will now be enabled by default on older Windows versions.
|
||||
This allows the use of colored output if the terminal supports it (e.g.
|
||||
MinTTY, Git Bash). On the other hand, this will be a regression for users
|
||||
on older Windows versions with terminals that do not support ANSI escape
|
||||
sequences. Affected users can use an alias `fd="fd --color=never"` to
|
||||
continue using `fd` without colors. There is no change of behavior for
|
||||
Windows 10. See #469.
|
||||
- When using `--glob` in combination with `--full-path`, a `*` character does not match a path
|
||||
separation character (`/` or `\\`) anymore. You can use `**` for that. This allows things like
|
||||
`fd -p -g '/some/base/path/*/*/*.txt'` which would previously match to arbitrary depths (instead
|
||||
of exactly two folders below `/some/base/path`. See #404.
|
||||
- "Legacy" support to use `fd -exec` (with a single dash) has been removed. Use `fd -x` or
|
||||
`fd --exec` instead.
|
||||
- Overall improved error handling and error messages.
|
||||
|
||||
|
||||
## Other
|
||||
|
||||
- Korean translation of the README, see: [한국어](https://github.com/spearkkk/fd-kor) (@spearkkk)
|
||||
|
||||
|
||||
# v7.5.0
|
||||
|
||||
## Features
|
||||
|
||||
- Added `--one-file-system` (aliases: `--mount`, `--xdev`) to not cross file system boundaries on Unix and Windows, see #507 (@FallenWarrior2k).
|
||||
- Added `--base-directory` to change the working directory in which `fd` is run, see #509 and #475 (@hajdamak).
|
||||
- `fd` will not use colored output if the `NO_COLOR` environment variable is set, see #550 and #551 (@metadave).
|
||||
- `fd --exec` will return exit code 1 if one of the executed commands fails, see #526 and #531 (@fusillicode and @Giuffre)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Fixed 'command not found' error when using zsh completion, see #487 (@barskern).
|
||||
- `fd -L` should include broken symlinks, see #357 and #497 (@tommilligan, @neersighted and @sharkdp)
|
||||
- Display directories even if we don't have permission to enter, see #437 (@sharkdp)
|
||||
|
||||
## Changes
|
||||
|
||||
- A flag can now be passed multiple times without producing an error, see #488 and #496 (@rootbid).
|
||||
- Search results are sorted when using the `-X` option to match the behaviour of piping to `xargs`, see #441 and #524 (@Marcoleni @crash-g).
|
||||
|
||||
|
||||
# v7.4.0
|
||||
|
||||
## Performance improvements
|
||||
|
||||
- Reduce number of `stat` syscalls, improving the performance for searches where file metadata is
|
||||
required (`--type`, `--size`, `--changed-within`, …), see #434 (@tavianator)
|
||||
- Use jemalloc by default, improving the performance for almost all searches, see #481. Note that
|
||||
Windows and `*musl*` builds do not profit from this.
|
||||
|
||||
## Features
|
||||
|
||||
- Added a new `-g`/`--glob` option to switch to glob-based searches (instead of regular expression
|
||||
based searches). This is accompanied by a new `--regex` option that can be used to switch back,
|
||||
if users want to `alias fd="fd --glob"`. See #284
|
||||
- Added a new `--path-separator <sep>` option which can be useful for Windows users who
|
||||
want/need `fd` to use `/` instead of `\`, see #428 and #153 (@mookid)
|
||||
- Added support for hidden files on Windows, see #379
|
||||
- When `fd` is run with the `--exec-batch`/`-X` option, it now exposes the exit status of the
|
||||
command that was run, see #333.
|
||||
- Exit immediately when Ctrl-C has been pressed twice, see #423
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Make `--changed-within`/`--changed-before` work for directories, see #470
|
||||
|
||||
## Other
|
||||
|
||||
- Pre-built `fd` binaries should now be available for `armhf` targets, see #457 (@detly)
|
||||
- `fd` is now available on Alpine Linux, see #451 (@5paceToast)
|
||||
- `fd` is now in the officla FreeBSD repositories, see #412 (@t6)
|
||||
- Added OpenBSD install instructions, see #421 (@evitalis)
|
||||
- Added metadata to the Debian package, see #416 (@cathalgarvey)
|
||||
- `fd` can be installed via npm, see #438 (@pablopunk)
|
||||
|
||||
|
||||
# v7.3.0
|
||||
|
||||
## Features
|
||||
|
||||
- New `--exec-batch <cmd>`/`-X <cmd>` option for batch execution of commands, see #360 (@kimsnj).
|
||||
This allows you to do things like:
|
||||
``` bash
|
||||
fd … -X vim # open all search results in vim (or any other editor)
|
||||
fd … -X ls -l # view detailed stats about the search results with 'ls'
|
||||
fd -e svg -X inkscape # open all SVG files in Inkscape
|
||||
```
|
||||
- Support for 24-bit color codes (when specified via `LS_COLORS`) as well as
|
||||
different font styles (bold, italic, underline).
|
||||
|
||||
## Changes
|
||||
|
||||
- A few performance improvements, in particular when printing lots of colorized
|
||||
results to the console, see #370
|
||||
- The `LS_COLORS` handling has been "outsourced" to a separate crate (https://github.com/sharkdp/lscolors) that is now being used by other tools as well: [fselect](https://github.com/jhspetersson/fselect), [lsd](https://github.com/Peltoche/lsd/pull/84). For details, see #363.
|
||||
|
||||
## Other
|
||||
|
||||
- `fd` will be available in Ubuntu Disco DIngo (19.04), see #373 (@sylvestre)
|
||||
- This release should come with a static ARM binary (`arm-unknown-linux-musleabihf`), see #320 (@duncanfinney)
|
||||
- Various documentation improvements, see #389
|
||||
|
||||
## Thanks
|
||||
|
||||
Special thanks to @alexmaco for his awesome work on refactoring and code improvements! (see #401, #398, and #383)
|
||||
|
||||
# v7.2.0
|
||||
|
||||
## Features
|
||||
|
||||
* Added support for filtering by file modification time by adding two new options `--changed-before <date|duration>` and `--changed-within <..>`. For more details, see the `--help` text, the man page, the relevant issue #165 and the PR #339 (@kimsnj)
|
||||
* Added `--show-errors` option to enable the display of filesystem error messages such as "permission denied", see #311 (@psinghal20 and @majecty)
|
||||
* Added `--maxdepth` as a (hidden) alias for `--max-depth`, see #323 (@mqudsi)
|
||||
* Added `--search-path` option which can be supplied to replace the positional `path` argument at any position.
|
||||
|
||||
## Changes
|
||||
|
||||
* Loosen strict handling of missing `--ignore-file`, see #280 (@psinghal20)
|
||||
* Re-enabled `.ignore` files, see #156.
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* `fd` could previously get stuck when run from the root directory in the
|
||||
presence of zombie processes. This curious bug has been fixed in Rust 1.29 and higher. For more details, see #288, [rust-lang/rust#50619](https://github.com/rust-lang/rust/issues/50619) and [the fix](https://github.com/rust-lang/rust/pull/50630)
|
||||
|
||||
## Other
|
||||
|
||||
* `fd` has officially landed in Debian! See #345 for details. Thanks goes to @sylvestre, @paride and possibly others I don't know about.
|
||||
* Added Chinese translation of README (@chinanf-boy)
|
||||
|
||||
## Thanks
|
||||
|
||||
A special thanks goes to @joshleeb for his amazing improvements throughout
|
||||
the code base (new tests, refactoring work and various other things)!
|
||||
|
||||
|
||||
# v7.1.0
|
||||
|
||||
## Features
|
||||
|
||||
* Added `--size` filter option, see #276 (@stevepentland, @JonathanxD and @alexmaco)
|
||||
* Added `--type empty` (or `-t e`) to search for empty files and/or directories, see #273
|
||||
|
||||
## Changes
|
||||
|
||||
* With the new version, `.gitignore` files will only be respected in Git repositories, not outside.
|
||||
* A few performance improvements for `--type` searches, see 641976cf7ad311ba741571ca8b7f02b2654b6955 and 50a2bab5cd52d26d4a3bc786885a2c270ed3b227
|
||||
|
||||
## Other
|
||||
|
||||
* Starting with this release, we will offer pre-built ARM binaries, see #244
|
||||
* Added instructions on how to use `fd` with `emacs`, see #282 (@redguardtoo)
|
||||
* `fd` is now in the official openSUSE repositories, see #275 (@avindra)
|
||||
* `fd` is now available via MacPorts, see #291 (@raimue)
|
||||
|
||||
|
||||
# v7.0.0
|
||||
|
||||
## Features
|
||||
|
||||
* Added `--type executable` (or `-t x`) to search for executable files only, see #246 (@PramodBisht)
|
||||
* Added support for `.fdignore` files, see #156 and #241.
|
||||
* Added `--ignore-file` option to add custom ignore files, see #156.
|
||||
* Suggest `--fixed-strings` on invalid regular expressions, see #234 (@PramodBisht)
|
||||
* Detect when user supplied path instead of pattern, see #235.
|
||||
|
||||
## Changes
|
||||
|
||||
* `.ignore` and `.rgignore` files are not parsed anymore. Use `.fdignore` files
|
||||
or add custom files via `--ignore-file` instead.
|
||||
* Updated to `regex-syntax` 0.5 (@cuviper)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* Properly normalize absolute paths, see #268
|
||||
* Invalid utf8 filenames displayed when `-e` is used, see #250
|
||||
* If `--type` is used, fifos/sockets/etc. are always shown, see #260
|
||||
|
||||
## Other
|
||||
|
||||
* Packaging:
|
||||
* The Arch Linux package is now simply called `fd`.
|
||||
* There is now a `fd` ebuild for Gentoo Linux.
|
||||
* There is a `scoop` package for `fd` (Windows).
|
||||
* There is a `Chocolatey` package for `fd` (Windows).
|
||||
* There is a Fedora `copr` package for `fd`.
|
||||
|
||||
|
||||
# v6.3.0
|
||||
|
||||
## Features
|
||||
|
||||
* Files with multiple extensions can now be found via `--extension`/`-e`, see #214 (@althonos)
|
||||
``` bash
|
||||
> fd -e tar.gz
|
||||
```
|
||||
|
||||
* Added new `-F`/`--fixed-strings`/`--literal` option that treats the pattern as a literal string instead of a regular expression, see #157
|
||||
|
||||
``` bash
|
||||
> fd -F 'file(1).txt'
|
||||
```
|
||||
|
||||
* Allow `-exec` to work as `--exec`, see #226 (@stevepentland)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* Fixed `Ctrl-C` handling when using `--exec`, see #224 (@Doxterpepper)
|
||||
|
||||
* Fixed wrong file owner for files in deb package, see #213
|
||||
|
||||
## Other
|
||||
|
||||
* Replaced old gif by a fancy new SVG screencast (@marionebl)
|
||||
* Updated [benchmark results](https://github.com/sharkdp/fd#benchmark) (fd has become faster in the meantime!). There is a new repository that hosts several benchmarking scripts for fd: https://github.com/sharkdp/fd-benchmarks
|
||||
|
||||
|
||||
# v6.2.0
|
||||
|
||||
## Features
|
||||
|
||||
* Support for filtering by multiple file extensions and multiple file types, see #199 and #177
|
||||
(@tkadur).
|
||||
|
||||
For example, it's possible to search for C++ source or header files:
|
||||
``` bash
|
||||
> fd -e cpp -e c -e cxx -e h pattern
|
||||
```
|
||||
|
||||
## Changes
|
||||
|
||||
* The size of the output buffer (for sorting search results) is now limited to 1000 entries. This
|
||||
improves the search speed significantly if there are a lot of results, see #191 (@sharkdp).
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* Fix a bug where long-running searches could not be killed via Ctrl-C, see #210 (@Doxterpepper)
|
||||
* fd's exit codes are now in accordance with Unix standards, see #201 (@Doxterpepper)
|
||||
|
||||
## Other
|
||||
|
||||
* Bash, zsh and fish completion should now work with the Ubuntu `.deb` packages, see #195 and #209
|
||||
(@tmccombs and @sharkdp)
|
||||
* There is a new section on how to set up `fzf` to use `fd` in the
|
||||
[README](https://github.com/sharkdp/fd#using-fd-with-fzf), see #168.
|
||||
|
||||
|
||||
# v6.1.0
|
||||
|
||||
## Features
|
||||
|
||||
* Support for multiple search paths, see #166 (@Doxterpepper)
|
||||
* Added `--no-ignore-vcs` option to disable `.gitignore` and other VCS ignore files,
|
||||
without disabling `.ignore` files - see #156 (@ptzz).
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* Handle terminal signals, see #128 (@Doxterpepper)
|
||||
* Fixed hang on `--exec` when user input was required, see #178 and #193 (@reima)
|
||||
|
||||
## Other
|
||||
|
||||
* Debian packages are now created via Travis CI and should be available for this and all
|
||||
future releases (@tmccombs).
|
||||
* fd is now available on Void Linux (@maxice8)
|
||||
* The minimum required Rust version is now 1.20
|
||||
|
||||
## Thanks
|
||||
|
||||
@Doxterpepper deserves a special mention for his great work that is included in this release and
|
||||
for the support in ticket discussions and concerning Travis CI fixes. Thank you very much!
|
||||
|
||||
Thanks also go out to @tmccombs for the work on Debian packages and for reviewing a lot of pull requests!
|
||||
|
||||
# v6.0.0
|
||||
|
||||
## Changes
|
||||
|
||||
- The `--exec`/`-x` option does not spawn an intermediate shell anymore. This improves the
|
||||
performance of parallel command execution and fixes a whole class of (present and potentially
|
||||
future) problems with shell escaping. The drawback is that shell commands cannot directly be
|
||||
called with `--exec`. See #155 for the full discussion. These changes have been implemented by
|
||||
@reima (Thanks!).
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- `--exec` does not escape cmd.exe metacharacters on Windows (see #155, as above).
|
||||
|
||||
## Other
|
||||
|
||||
* *fd* is now available in the FreeBSD ports (@andoriyu)
|
||||
* The minimal `rustc` version is now checked when building with `cargo`, see #164 (@matematikaadit)
|
||||
* The output directory for the shell completion files is created if it does not exist (@andoriyu)
|
||||
|
||||
|
||||
# v5.0.0
|
||||
|
||||
## Features
|
||||
|
||||
* Added new `--exec`, `-x` option for parallel command execution (@mmstick, see #84 and #116). See the corresponding [README section](https://github.com/sharkdp/fd#parallel-command-execution) for an introduction.
|
||||
* Auto-disable color output on unsupported Windows shells like `cmd.exe` (@iology, see #129)
|
||||
* Added the `--exclude`, `-X` option to suppress certain files/directories in the search results
|
||||
(see #89).
|
||||
* Added ripgrep aliases `-u` and `-uu` for `--no-ignore` and `--no-ignore --hidden`, respectively
|
||||
(@unsignedint, see #92)
|
||||
* Added `-i`, `--ignore-case` (@iology, see #95)
|
||||
* Made smart case really smart (@reima, see #103)
|
||||
* Added RedoxOS support (@goyox86, see #131)
|
||||
|
||||
## Changes
|
||||
|
||||
* The dot `.` can now match newlines in file names (@iology, see #111)
|
||||
* The short `--type` argument for symlinks has been changed from `s` to `l` (@jcpetkovich, see #83)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
* Various improvements in root-path and symlink handling (@iology, see #82, #107, and #113)
|
||||
* Fixed absolute path handling on Windows (@reima, #93)
|
||||
* Fixed: current directory not included when using relative path (see #81)
|
||||
* Fixed `--type` behavior for unknown file types (@iology, see #150)
|
||||
* Some fixes around `--exec` (@iology, see #142)
|
||||
|
||||
## Other
|
||||
|
||||
* Major updates and bugfixes to our continuous integration and deployment tooling on Travis
|
||||
(@matematikaadit, see #149, #145, #133)
|
||||
* Code style improvements & automatic style checking via `rustfmt` on Travis (@Detegr, see #99)
|
||||
* Added a man page (@pickfire, see #77)
|
||||
* *fd* has been relicensed under the dual license MIT/Apache-2.0 (@Detegr, see #105)
|
||||
* Major refactorings and code improvements (Big thanks to @gsquire, @reima, @iology)
|
||||
* First version of [`CONTRIBUTING`](https://github.com/sharkdp/fd/blob/master/CONTRIBUTING.md) guidelines
|
||||
* There is now a Nix package (@mehandes)
|
||||
* *fd* is now in the official Arch Linux repos (@cassava)
|
||||
* Improved tooling around shell completion files (@ImbaKnugel, see #124)
|
||||
* Updated tutorial in the [`README`](https://github.com/sharkdp/fd/blob/master/README.md)
|
||||
* The minimum required version of Rust has been bumped to 1.19.
|
||||
|
||||
## Thanks
|
||||
|
||||
A *lot* of things have happened since the last release and I'd like to thank all contributors for their great support. I'd also like to thank those that have contributed by reporting bugs and by posting feature requests.
|
||||
|
||||
I'd also like to take this chance to say a special Thank You to a few people that have stood out in one way or another: To @iology, for contributing a multitude of bugfixes, improvements and new features. To @reima and @Detegr for their continuing great support. To @mmstick, for implementing the most advanced new feature of *fd*. And to @matematikaadit for the CI/tooling upgrades.
|
||||
|
||||
|
||||
# v4.0.0
|
||||
|
||||
## Features
|
||||
|
||||
* Added filtering by file extension, for example `fd -e txt`, see #56 (@reima)
|
||||
* Add option to force colored output: `--color always`, see #49 (@Detegr)
|
||||
* Generate Shell completions for Bash, ZSH, Fish and Powershell, see #64 (@ImbaKnugel)
|
||||
* Better & extended `--help` text (@abaez and @Detegr)
|
||||
* Proper Windows support, see #70
|
||||
|
||||
## Changes
|
||||
|
||||
* The integration tests have been re-written in Rust :sparkles:, making them platform-independent and easily callable via `cargo test` - see #65 (many thanks to @reima!)
|
||||
* New tutorial in the README (@deg4uss3r)
|
||||
* Reduced number of `stat` syscalls for each result from 3 to 1, see #36.
|
||||
* Enabled Appveyor CI
|
||||
|
||||
# v3.1.0
|
||||
|
||||
## Features
|
||||
- Added file type filtering, e.g. `find --type directory` or `find -t f` (@exitium)
|
||||
|
||||
# v3.0.0
|
||||
|
||||
## Features
|
||||
- Directories are now traversed in parallel, leading to significant performance improvements (see [benchmarks](https://github.com/sharkdp/fd#benchmark))
|
||||
- Added `--print0` option (@michaelmior)
|
||||
- Added AUR packages (@wezm)
|
||||
|
||||
## Changes
|
||||
- Changed short flag for `--follow` from `-f` to `-L` (consistency with `ripgrep`)
|
||||
|
||||
# v2.0.0
|
||||
|
||||
* Changed `--sensitive` to `--case-sensitive`
|
||||
* Changed `--absolute` to `--absolute-path`
|
||||
* Throw an error if root directory is not existent, see #39
|
||||
* Use absolute paths if the root dir is an absolute path, see #40
|
||||
* Handle invalid UTF-8, see #34 #38
|
||||
* Support `-V`, `--version` by switching from `getopts` to `clap`.
|
||||
|
||||
Misc:
|
||||
* It's now possible to install `fd` via homebrew on macOS: `brew install fd`.
|
||||
|
||||
# v1.1.0
|
||||
|
||||
- Windows compatibility (@sebasv), see #29 #35
|
||||
- Safely exit on broken output pipes (e.g.: usage with `head`, `tail`, ..), see #24
|
||||
- Backport for rust 1.16, see #23
|
||||
|
||||
# v1.0.0
|
||||
|
||||
* Respect `.(git)ignore` files
|
||||
* Use `LS_COLORS` environment variable directly, instead of `~/.dir_colors` file.
|
||||
* Added unit and integration tests
|
||||
* Added optional second argument (search path)
|
||||
|
||||
# v0.3.0
|
||||
|
||||
- Parse dircolors files, closes #20
|
||||
- Colorize each path component, closes #19
|
||||
- Add short command line option for --hidden, see #18
|
||||
|
||||
# v0.2.0
|
||||
|
||||
- Option to follow symlinks, disable colors, closes #16, closes #17
|
||||
- `--filename` instead of `--full-path`
|
||||
- Option to search hidden directories, closes #12
|
||||
- Configurable search depth, closes #13
|
||||
- Detect interactive terminal, closes #11
|
||||
|
||||
# v0.1.0
|
||||
|
||||
Initial release
|
||||
+18
-1
@@ -10,6 +10,24 @@ We welcome any form of contribution:
|
||||
**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.
|
||||
|
||||
## Add an entry to the changelog
|
||||
|
||||
If your contribution changes the behavior of `fd` (as opposed to a typo-fix
|
||||
in the documentation), please update the [`CHANGELOG.md`](CHANGELOG.md) file
|
||||
and describe your changes. This makes the release process much easier and
|
||||
therefore helps to get your changes into a new `fd` release faster.
|
||||
|
||||
The top of the `CHANGELOG` contains a *"unreleased"* section with a few
|
||||
subsections (Features, Bugfixes, …). Please add your entry to the subsection
|
||||
that best describes your change.
|
||||
|
||||
Entries follow this format:
|
||||
```
|
||||
- Short description of what has been changed, see #123 (@user)
|
||||
```
|
||||
Here, `#123` is the number of the original issue and/or your pull request.
|
||||
Please replace `@user` by your GitHub username.
|
||||
|
||||
## Important links
|
||||
|
||||
* [Open issues](https://github.com/sharkdp/fd/issues)
|
||||
@@ -17,4 +35,3 @@ give us the chance to discuss any potential changes first.
|
||||
* [Development section in the README](https://github.com/sharkdp/fd#development)
|
||||
* [fd on crates.io](https://crates.io/crates/fd-find)
|
||||
* [LICENSE-APACHE](https://github.com/sharkdp/fd/blob/master/LICENSE-APACHE) and [LICENSE-MIT](https://github.com/sharkdp/fd/blob/master/LICENSE-MIT)
|
||||
|
||||
|
||||
Generated
+428
-254
@@ -1,516 +1,690 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.6.9"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.11"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.4"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[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"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.32.0"
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.3.8"
|
||||
name = "cc"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||
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)",
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f34b09b9ee8c7c7b400fe2f8df39cafc9538b03d6ba7f4ae13e4cb90bfbb7d"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"terminal_size",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a394f7ec0715b42a4e52b294984c27c9a61f77c8d82f7774c5198350be143f19"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.6.5"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
||||
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)",
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.1.1"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf"
|
||||
dependencies = [
|
||||
"nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.11"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fd-find"
|
||||
version = "7.3.0"
|
||||
version = "8.3.2"
|
||||
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)",
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
"atty",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"ctrlc",
|
||||
"diff",
|
||||
"dirs-next",
|
||||
"filetime",
|
||||
"globset",
|
||||
"humantime",
|
||||
"ignore",
|
||||
"jemallocator",
|
||||
"libc",
|
||||
"lscolors",
|
||||
"nix",
|
||||
"normpath",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"tempdir",
|
||||
"test-case",
|
||||
"users",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.4"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.2"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.2.0"
|
||||
version = "2.1.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)",
|
||||
]
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.6"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
|
||||
dependencies = [
|
||||
"crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils",
|
||||
"globset",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex",
|
||||
"same-file",
|
||||
"thread_local",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
name = "indexmap"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
|
||||
dependencies = [
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jemalloc-sys"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"fs_extra",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jemallocator"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69"
|
||||
dependencies = [
|
||||
"jemalloc-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.2.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.48"
|
||||
version = "0.2.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.6"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lscolors"
|
||||
version = "0.5.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd58d8727f3035fa6d5272f16b519741fd4875936b99d8a7cde21291b7d9174"
|
||||
dependencies = [
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ansi_term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.2.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.11.0"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
|
||||
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)",
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "normpath"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04aaf5e9cb0fbf883cc0423159eacdf96a9878022084b35c462c428cab73bcaf"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.10.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.2"
|
||||
name = "once_cell"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
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)",
|
||||
"fuchsia-cprng",
|
||||
"libc",
|
||||
"rand_core 0.3.1",
|
||||
"rdrand",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
dependencies = [
|
||||
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.0"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.51"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.1"
|
||||
name = "redox_users"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
|
||||
dependencies = [
|
||||
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.1.0"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.5"
|
||||
version = "0.6.25"
|
||||
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)",
|
||||
]
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.1"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.4"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.7.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempdir"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||
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)",
|
||||
"rand",
|
||||
"remove_dir_all",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term_size"
|
||||
version = "0.3.1"
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
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)",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "1.5.1"
|
||||
name = "terminal_size"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
||||
dependencies = [
|
||||
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-case"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7cad0a06f9a61e94355aa3b3dc92d85ab9c83406722b1ca5e918d4297c12c23"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.10.0"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||
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)",
|
||||
"terminal_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.6"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
|
||||
dependencies = [
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ucd-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "1.0.0"
|
||||
name = "time"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||
dependencies = [
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "1.0.2"
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.1"
|
||||
name = "users"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.2.7"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||
dependencies = [
|
||||
"same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.6"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
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)",
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.2"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
||||
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
|
||||
"checksum cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)" = "4390a3b5f4f6bce9c1d0c00128379df433e53777fdd30e92f16a529332baec4e"
|
||||
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
|
||||
"checksum crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0ed1a4de2235cabda8558ff5840bffb97fcb64c97827f354a451307df5f72b"
|
||||
"checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c"
|
||||
"checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e"
|
||||
"checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
|
||||
"checksum filetime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a2df5c1a8c4be27e7707789dc42ae65976e60b394afd293d1419ab915833e646"
|
||||
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
"checksum globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4743617a7464bbda3c8aec8558ff2f9429047e025771037df561d383337ff865"
|
||||
"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
|
||||
"checksum ignore 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ad03ca67dc12474ecd91fdb94d758cbd20cb4e7a78ebe831df26a9b7511e1162"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
|
||||
"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047"
|
||||
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
|
||||
"checksum lscolors 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9938fd8c379393454f73ec4c9c5b40f3d8332d80b25a29da05e41ee0ecbb559"
|
||||
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
|
||||
"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
|
||||
"checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba"
|
||||
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
||||
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
|
||||
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
|
||||
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||
"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f"
|
||||
"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861"
|
||||
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
|
||||
"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
|
||||
"checksum smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "88aea073965ab29f6edb5493faf96ad662fb18aa9eeb186a3b7057951605ed15"
|
||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||
"checksum 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 walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
"checksum 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"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
+38
-16
@@ -16,46 +16,68 @@ license = "MIT/Apache-2.0"
|
||||
name = "fd-find"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/sharkdp/fd"
|
||||
version = "7.3.0"
|
||||
version = "8.3.2"
|
||||
edition= "2018"
|
||||
|
||||
[[bin]]
|
||||
name = "fd"
|
||||
path = "src/main.rs"
|
||||
[badges.appveyor]
|
||||
repository = "sharkdp/fd"
|
||||
|
||||
[badges.travis-ci]
|
||||
repository = "sharkdp/fd"
|
||||
|
||||
[[bin]]
|
||||
name = "fd"
|
||||
path = "src/main.rs"
|
||||
|
||||
[build-dependencies]
|
||||
clap = "2.31.2"
|
||||
version_check = "0.1.3"
|
||||
clap = "3.0"
|
||||
clap_complete = "3.0"
|
||||
version_check = "0.9"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.11"
|
||||
ansi_term = "0.12"
|
||||
atty = "0.2"
|
||||
ignore = "0.4.3"
|
||||
lazy_static = "1.1.0"
|
||||
num_cpus = "1.8"
|
||||
regex = "1.0.0"
|
||||
num_cpus = "1.13"
|
||||
regex = "1.5.4"
|
||||
regex-syntax = "0.6"
|
||||
ctrlc = "3.1"
|
||||
humantime = "1.1.1"
|
||||
lscolors = "0.5"
|
||||
ctrlc = "3.2"
|
||||
humantime = "2.1"
|
||||
lscolors = "0.8"
|
||||
globset = "0.4"
|
||||
anyhow = "1.0"
|
||||
dirs-next = "2.0"
|
||||
normpath = "0.3.2"
|
||||
chrono = "0.4"
|
||||
once_cell = "1.9.0"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "2.31.2"
|
||||
features = ["suggestions", "color", "wrap_help"]
|
||||
version = "3.0"
|
||||
features = ["suggestions", "color", "wrap_help", "cargo"]
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
users = "0.11.0"
|
||||
nix = "0.23.1"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
# FIXME: Re-enable jemalloc on macOS
|
||||
# jemalloc is currently disabled on macOS due to a bug in jemalloc in combination with macOS
|
||||
# Catalina. See https://github.com/sharkdp/fd/issues/498 for details.
|
||||
[target.'cfg(all(not(windows), not(target_os = "android"), not(target_os = "macos"), not(target_os = "freebsd"), not(target_env = "musl"), not(target_arch = "riscv64")))'.dependencies]
|
||||
jemallocator = {version = "0.3.0", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
diff = "0.1"
|
||||
tempdir = "0.3"
|
||||
filetime = "0.2.1"
|
||||
filetime = "0.2"
|
||||
test-case = "1.2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
||||
[features]
|
||||
use-jemalloc = ["jemallocator"]
|
||||
default = ["use-jemalloc"]
|
||||
|
||||
+1
-1
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017 fd developers
|
||||
Copyright 2017-2020 fd developers
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+19
-21
@@ -1,23 +1,21 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
MIT License
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
Copyright (c) 2017-2021 The fd developers
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -1,302 +1,43 @@
|
||||
# fd
|
||||
[](https://travis-ci.org/sharkdp/fd)
|
||||
[](https://ci.appveyor.com/project/sharkdp/fd)
|
||||
|
||||
[](https://github.com/sharkdp/fd/actions/workflows/CICD.yml)
|
||||
[](https://crates.io/crates/fd-find)
|
||||
[中文](https://github.com/chinanf-boy/fd-zh)
|
||||
[[中文](https://github.com/chinanf-boy/fd-zh)]
|
||||
[[한국어](https://github.com/spearkkk/fd-kor)]
|
||||
|
||||
*fd* is a simple, fast and user-friendly alternative to
|
||||
[*find*](https://www.gnu.org/software/findutils/).
|
||||
`fd` is a program to find entries in your filesystem.
|
||||
It is a simple, fast and user-friendly alternative to [`find`](https://www.gnu.org/software/findutils/).
|
||||
While it does not aim to support all of `find`'s powerful functionality, it provides sensible
|
||||
(opinionated) defaults for a majority of use cases.
|
||||
|
||||
While it does not seek to mirror all of *find*'s powerful functionality, it provides sensible
|
||||
(opinionated) defaults for [80%](https://en.wikipedia.org/wiki/Pareto_principle) of the use cases.
|
||||
Quick links:
|
||||
* [How to use](#how-to-use)
|
||||
* [Installation](#installation)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Features
|
||||
* Convenient syntax: `fd PATTERN` instead of `find -iname '*PATTERN*'`.
|
||||
* Colorized terminal output (similar to *ls*).
|
||||
* It's *fast* (see [benchmarks](#benchmark) below).
|
||||
|
||||
* Intuitive syntax: `fd PATTERN` instead of `find -iname '*PATTERN*'`.
|
||||
* Regular expression (default) and glob-based patterns.
|
||||
* [Very fast](#benchmark) due to parallelized directory traversal.
|
||||
* Uses colors to highlight different file types (same as *ls*).
|
||||
* Supports [parallel command execution](#command-execution)
|
||||
* Smart case: the search is case-insensitive by default. It switches to
|
||||
case-sensitive if the pattern contains an uppercase
|
||||
character[\*](http://vimdoc.sourceforge.net/htmldoc/options.html#'smartcase').
|
||||
* Ignores hidden directories and files, by default.
|
||||
* Ignores patterns from your `.gitignore`, by default.
|
||||
* Regular expressions.
|
||||
* Unicode-awareness.
|
||||
* The command name is *50%* shorter[\*](https://github.com/ggreer/the_silver_searcher) than
|
||||
`find` :-).
|
||||
* Parallel command execution with a syntax similar to GNU Parallel.
|
||||
|
||||
## 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
|
||||
`fd` can colorize files by extension, just like `ls`. In order for this to work, the environment
|
||||
variable [`LS_COLORS`](https://linux.die.net/man/5/dir_colors) has to be set. Typically, the value
|
||||
of this variable is set by the `dircolors` command which provides a convenient configuration format
|
||||
to define colors for different file formats.
|
||||
On most distributions, `LS_COLORS` should be set already. If you are looking for alternative, more
|
||||
complete (and more colorful) variants, see
|
||||
[here](https://github.com/seebi/dircolors-solarized) or
|
||||
[here](https://github.com/trapd00r/LS_COLORS).
|
||||
|
||||
## Parallel command execution
|
||||
If the `-x`/`--exec` option is specified alongside a command template, a job pool will be created
|
||||
for executing commands in parallel for each discovered path as the input. The syntax for generating
|
||||
commands is similar to that of GNU Parallel:
|
||||
|
||||
- `{}`: A placeholder token that will be replaced with the path of the search result
|
||||
(`documents/images/party.jpg`).
|
||||
- `{.}`: Like `{}`, but without the file extension (`documents/images/party`).
|
||||
- `{/}`: A placeholder that will be replaced by the basename of the search result (`party.jpg`).
|
||||
- `{//}`: Uses the parent of the discovered path (`documents/images`).
|
||||
- `{/.}`: Uses the basename, with the extension removed (`party`).
|
||||
|
||||
``` bash
|
||||
# Convert all jpg files to png files:
|
||||
fd -e jpg -x convert {} {.}.png
|
||||
|
||||
# Unpack all zip files (if no placeholder is given, the path is appended):
|
||||
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
|
||||
```
|
||||
|
||||
## 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: `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
|
||||
```bash
|
||||
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
|
||||
## How to use
|
||||
|
||||
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
|
||||
[`fd -h`](#command-line-options) for a concise help message or `fd --help` for a more detailed
|
||||
version.
|
||||
|
||||
### Simple search
|
||||
@@ -322,6 +63,8 @@ X11/xinit/xinitrc
|
||||
X11/xinit/xserverrc
|
||||
```
|
||||
|
||||
The regular expression syntax used by `fd` is [documented here](https://docs.rs/regex/1.0.0/regex/#syntax).
|
||||
|
||||
### Specifying the root directory
|
||||
|
||||
If we want to search a specific directory, it can be given as a second argument to *fd*:
|
||||
@@ -332,7 +75,7 @@ If we want to search a specific directory, it can be given as a second argument
|
||||
/etc/passwd
|
||||
```
|
||||
|
||||
### Running *fd* without any arguments
|
||||
### List all files, recursively
|
||||
|
||||
*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`):
|
||||
@@ -372,6 +115,15 @@ src/lscolors/mod.rs
|
||||
tests/testenv/mod.rs
|
||||
```
|
||||
|
||||
### Searching for a particular file name
|
||||
|
||||
To find files with exactly the provided search pattern, use the `-g` (or `--glob`) option:
|
||||
``` bash
|
||||
> fd -g libc.so /usr
|
||||
/usr/lib32/libc.so
|
||||
/usr/lib/libc.so
|
||||
```
|
||||
|
||||
### 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:
|
||||
@@ -393,6 +145,88 @@ 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`).
|
||||
|
||||
### Matching the full path
|
||||
By default, *fd* only matches the filename of each file. However, using the `--full-path` or `-p` option,
|
||||
you can match against the full path.
|
||||
|
||||
```bash
|
||||
> fd -p -g '**/.git/config'
|
||||
> fd -p '.*/lesson-\d+/[a-z]+.(jpg|png)'
|
||||
```
|
||||
|
||||
### Command execution
|
||||
|
||||
Instead of just showing the search results, you often want to *do something* with them. `fd`
|
||||
provides two ways to execute external commands for each of your search results:
|
||||
|
||||
* The `-x`/`--exec` option runs an external command *for each of the search results* (in parallel).
|
||||
* The `-X`/`--exec-batch` option launches the external command once, with *all search results as arguments*.
|
||||
|
||||
#### Examples
|
||||
|
||||
Recursively find all zip archives and unpack them:
|
||||
``` bash
|
||||
fd -e zip -x unzip
|
||||
```
|
||||
If there are two such files, `file1.zip` and `backup/file2.zip`, this would execute
|
||||
`unzip file1.zip` and `unzip backup/file2.zip`. The two `unzip` processes run in parallel
|
||||
(if the files are found fast enough).
|
||||
|
||||
Find all `*.h` and `*.cpp` files and auto-format them inplace with `clang-format -i`:
|
||||
``` bash
|
||||
fd -e h -e cpp -x clang-format -i
|
||||
```
|
||||
Note how the `-i` option to `clang-format` can be passed as a separate argument. This is why
|
||||
we put the `-x` option last.
|
||||
|
||||
Find all `test_*.py` files and open them in your favorite editor:
|
||||
``` bash
|
||||
fd -g 'test_*.py' -X vim
|
||||
```
|
||||
Note that we use capital `-X` here to open a single `vim` instance. If there are two such files,
|
||||
`test_basic.py` and `lib/test_advanced.py`, this will run `vim test_basic.py lib/test_advanced.py`.
|
||||
|
||||
To see details like file permissions, owners, file sizes etc., you can tell `fd` to show them
|
||||
by running `ls` for each result:
|
||||
``` bash
|
||||
fd … -X ls -lhd --color=always
|
||||
```
|
||||
This pattern is so useful that `fd` provides a shortcut. You can use the `-l`/`--list-details`
|
||||
option to execute `ls` in this way: `fd … -l`.
|
||||
|
||||
The `-X` option is also useful when combining `fd` with [ripgrep](https://github.com/BurntSushi/ripgrep/) (`rg`) in order to search within a certain class of files, like all C++ source files:
|
||||
```bash
|
||||
fd -e cpp -e cxx -e h -e hpp -X rg 'std::cout'
|
||||
```
|
||||
|
||||
Convert all `*.jpg` files to `*.png` files:
|
||||
``` bash
|
||||
fd -e jpg -x convert {} {.}.png
|
||||
```
|
||||
Here, `{}` is a placeholder for the search result. `{.}` is the same, without the file extension.
|
||||
See below for more details on the placeholder syntax.
|
||||
|
||||
#### Placeholder syntax
|
||||
|
||||
The `-x` and `-X` options take a *command template* as a series of arguments (instead of a single string).
|
||||
If you want to add additional options to `fd` after the command template, you can terminate it with a `\;`.
|
||||
|
||||
The syntax for generating commands is similar to that of [GNU Parallel](https://www.gnu.org/software/parallel/):
|
||||
|
||||
- `{}`: A placeholder token that will be replaced with the path of the search result
|
||||
(`documents/images/party.jpg`).
|
||||
- `{.}`: Like `{}`, but without the file extension (`documents/images/party`).
|
||||
- `{/}`: A placeholder that will be replaced by the basename of the search result (`party.jpg`).
|
||||
- `{//}`: The parent of the discovered path (`documents/images`).
|
||||
- `{/.}`: The basename, with the extension removed (`party`).
|
||||
|
||||
If you do not include a placeholder, *fd* automatically adds a `{}` at the end.
|
||||
|
||||
#### Parallel vs. serial execution
|
||||
|
||||
For `-x`/`--exec`, you can control the number of parallel jobs by using the `-j`/`--threads` option.
|
||||
Use `--threads=1` for serial execution.
|
||||
|
||||
### Excluding specific files or directories
|
||||
|
||||
Sometimes we want to ignore search results from a specific subdirectory. For example, we might
|
||||
@@ -422,18 +256,147 @@ To make exclude-patterns like these permanent, you can create a `.fdignore` file
|
||||
```
|
||||
Note: `fd` also supports `.ignore` files that are used by other programs such as `rg` or `ag`.
|
||||
|
||||
### Using fd with `xargs` or `parallel`
|
||||
If you want `fd` to ignore these patterns globally, you can put them in `fd`'s global ignore file.
|
||||
This is usually located in `~/.config/fd/ignore` in macOS or Linux, and `%APPDATA%\fd\ignore` in
|
||||
Windows.
|
||||
|
||||
If we want to run a command on all search results, we can pipe the output to `xargs`:
|
||||
### Deleting files
|
||||
|
||||
You can use `fd` to remove all files and directories that are matched by your search pattern.
|
||||
If you only want to remove files, you can use the `--exec-batch`/`-X` option to call `rm`. For
|
||||
example, to recursively remove all `.DS_Store` files, run:
|
||||
``` bash
|
||||
> fd -0 -e rs | xargs -0 wc -l
|
||||
> fd -H '^\.DS_Store$' -tf -X rm
|
||||
```
|
||||
If you are unsure, always call `fd` without `-X rm` first. Alternatively, use `rm`s "interactive"
|
||||
option:
|
||||
``` bash
|
||||
> fd -H '^\.DS_Store$' -tf -X rm -i
|
||||
```
|
||||
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
|
||||
If you also want to remove a certain class of directories, you can use the same technique. You will
|
||||
have to use `rm`s `--recursive`/`-r` flag to remove directories.
|
||||
|
||||
#### `fd` does not find my file!
|
||||
Note: there are scenarios where using `fd … -X rm -r` can cause race conditions: if you have a
|
||||
path like `…/foo/bar/foo/…` and want to remove all directories named `foo`, you can end up in a
|
||||
situation where the outer `foo` directory is removed first, leading to (harmless) *"'foo/bar/foo':
|
||||
No such file or directory"* errors in the `rm` call.
|
||||
|
||||
### Command-line options
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
USAGE:
|
||||
fd [FLAGS/OPTIONS] [<pattern>] [<path>...]
|
||||
|
||||
FLAGS:
|
||||
-H, --hidden Search hidden files and directories
|
||||
-I, --no-ignore Do not respect .(git|fd)ignore files
|
||||
-s, --case-sensitive Case-sensitive search (default: smart case)
|
||||
-i, --ignore-case Case-insensitive search (default: smart case)
|
||||
-g, --glob Glob-based search (default: regular expression)
|
||||
-a, --absolute-path Show absolute instead of relative paths
|
||||
-l, --list-details Use a long listing format with file metadata
|
||||
-L, --follow Follow symbolic links
|
||||
-p, --full-path Search full abs. path (default: filename only)
|
||||
-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), socket (s), pipe (p)
|
||||
-e, --extension <ext>... Filter by file extension
|
||||
-x, --exec <cmd> Execute a command for each search result
|
||||
-X, --exec-batch <cmd> Execute a command with all search results at once
|
||||
-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)
|
||||
-o, --owner <user:group> Filter by owning user and/or group
|
||||
|
||||
ARGS:
|
||||
<pattern> the search pattern (a regular expression, unless '--glob' is used; optional)
|
||||
<path>... the root directory for the filesystem search (optional)
|
||||
```
|
||||
|
||||
## 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!).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Colorized output
|
||||
|
||||
`fd` can colorize files by extension, just like `ls`. In order for this to work, the environment
|
||||
variable [`LS_COLORS`](https://linux.die.net/man/5/dir_colors) has to be set. Typically, the value
|
||||
of this variable is set by the `dircolors` command which provides a convenient configuration format
|
||||
to define colors for different file formats.
|
||||
On most distributions, `LS_COLORS` should be set already. If you are on Windows or if you are looking
|
||||
for alternative, more complete (or more colorful) variants, see [here](https://github.com/sharkdp/vivid),
|
||||
[here](https://github.com/seebi/dircolors-solarized) or
|
||||
[here](https://github.com/trapd00r/LS_COLORS).
|
||||
|
||||
`fd` also honors the [`NO_COLOR`](https://no-color.org/) environment variable.
|
||||
|
||||
### `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
|
||||
@@ -442,7 +405,7 @@ use the options `-H` and `-I` to disable these two features:
|
||||
> fd -HI …
|
||||
```
|
||||
|
||||
#### `fd` doesn't seem to interpret my regex pattern correctly
|
||||
### `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:
|
||||
@@ -460,9 +423,17 @@ use a character class with a single hyphen character:
|
||||
> fd '[-]pattern'
|
||||
```
|
||||
|
||||
### Integration with other programs
|
||||
### "Command not found" for `alias`es or shell functions
|
||||
|
||||
#### Using fd with `fzf`
|
||||
Shell `alias`es and shell functions can not be used for command execution via `fd -x` or
|
||||
`fd -X`. In `zsh`, you can make the alias global via `alias -g myalias="…"`. In `bash`,
|
||||
you can use `export -f my_function` to make available to child processes. You would still
|
||||
need to call `fd -x bash -c 'my_function "$1"' bash`. For other use cases or shells, use
|
||||
a (temporary) shell script.
|
||||
|
||||
## 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
|
||||
@@ -485,7 +456,21 @@ 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`
|
||||
### Using fd with `rofi`
|
||||
|
||||
[*rofi*](https://github.com/davatorium/rofi) is a graphical launch menu application that is able to create menus by reading from *stdin*. Piping `fd` output into `rofi`s `-dmenu` mode creates fuzzy-searchable lists of files and directories.
|
||||
|
||||
#### Example
|
||||
|
||||
Create a case-insensitive searchable multi-select list of *PDF* files under your `$HOME` directory and open the selection with your configured PDF viewer. To list all file types, drop the `-e pdf` argument.
|
||||
|
||||
``` bash
|
||||
fd --type f -e pdf . $HOME | rofi -keep-right -dmenu -i -p FILES -multi-select | xargs -I {} xdg-open {}
|
||||
```
|
||||
|
||||
To modify the list that is presented by rofi, add arguments to the `fd` command. To modify the search behaviour of rofi, add arguments to the `rofi` command.
|
||||
|
||||
### 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.
|
||||
@@ -495,3 +480,211 @@ After installing `find-file-in-project`, add the line `(setq ffip-use-rust-fd t)
|
||||
|
||||
In emacs, run `M-x find-file-in-project-by-selected` to find matching files. Alternatively, run
|
||||
`M-x find-file-in-project` to list all available files in the project.
|
||||
|
||||
### Printing the output as a tree
|
||||
|
||||
To format the output of `fd` similar to the `tree` command, install [`as-tree`] and pipe the output
|
||||
of `fd` to `as-tree`:
|
||||
```bash
|
||||
fd | as-tree
|
||||
```
|
||||
|
||||
This can be more useful than running `tree` by itself because `tree` does not ignore any files by
|
||||
default, nor does it support as rich a set of options as `fd` does to control what to print:
|
||||
```bash
|
||||
❯ fd --extension rs | as-tree
|
||||
.
|
||||
├── build.rs
|
||||
└── src
|
||||
├── app.rs
|
||||
└── error.rs
|
||||
```
|
||||
|
||||
For more information about `as-tree`, see [the `as-tree` README][`as-tree`].
|
||||
|
||||
[`as-tree`]: https://github.com/jez/as-tree
|
||||
|
||||
### Using fd with `xargs` or `parallel`
|
||||
|
||||
Note that `fd` has a builtin feature for [command execution](#command-execution) with
|
||||
its `-x`/`--exec` and `-X`/`--exec-batch` options. If you prefer, you can still use
|
||||
it in combination with `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.
|
||||
|
||||
## Installation
|
||||
|
||||
[](https://repology.org/project/fd-find/versions)
|
||||
|
||||
### On Ubuntu
|
||||
*... and other Debian-based Linux distributions.*
|
||||
|
||||
If you run Ubuntu 19.04 (Disco Dingo) or newer, you can install the
|
||||
[officially maintained package](https://packages.ubuntu.com/fd-find):
|
||||
```
|
||||
sudo apt install fd-find
|
||||
```
|
||||
Note that the binary is called `fdfind` as the binary name `fd` is already used by another package.
|
||||
It is recommended that after installation, you add a link to `fd` by executing command
|
||||
`ln -s $(which fdfind) ~/.local/bin/fd`, in order to use `fd` in the same way as in this documentation.
|
||||
Make sure that `$HOME/.local/bin` is in your `$PATH`.
|
||||
|
||||
If you use an older version of Ubuntu, you can download the latest `.deb` package from the
|
||||
[release page](https://github.com/sharkdp/fd/releases) and install it via:
|
||||
``` bash
|
||||
sudo dpkg -i fd_8.3.2_amd64.deb # adapt version number and architecture
|
||||
```
|
||||
|
||||
### On Debian
|
||||
|
||||
If you run Debian Buster or newer, you can install the
|
||||
[officially maintained Debian package](https://tracker.debian.org/pkg/rust-fd-find):
|
||||
```
|
||||
sudo apt-get install fd-find
|
||||
```
|
||||
Note that the binary is called `fdfind` as the binary name `fd` is already used by another package.
|
||||
It is recommended that after installation, you add a link to `fd` by executing command
|
||||
`ln -s $(which fdfind) ~/.local/bin/fd`, in order to use `fd` in the same way as in this documentation.
|
||||
Make sure that `$HOME/.local/bin` is in your `$PATH`.
|
||||
|
||||
### On Fedora
|
||||
|
||||
Starting with Fedora 28, you can install `fd` from the official package sources:
|
||||
``` bash
|
||||
dnf install fd-find
|
||||
```
|
||||
|
||||
For older versions, you can use this [Fedora copr](https://copr.fedorainfracloud.org/coprs/keefle/fd/) to install `fd`:
|
||||
``` bash
|
||||
dnf copr enable keefle/fd
|
||||
dnf install fd
|
||||
```
|
||||
|
||||
### On Alpine Linux
|
||||
|
||||
You can install [the fd package](https://pkgs.alpinelinux.org/packages?name=fd)
|
||||
from the official sources, provided you have the appropriate repository enabled:
|
||||
```
|
||||
apk add fd
|
||||
```
|
||||
|
||||
### On Arch Linux
|
||||
|
||||
You can install [the fd 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](https://formulae.brew.sh/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 [the fd-find package](https://www.freshports.org/sysutils/fd) from the official repo:
|
||||
```
|
||||
pkg install fd-find
|
||||
```
|
||||
|
||||
### From npm
|
||||
|
||||
On linux and macOS, you can install the [fd-find](https://npm.im/fd-find) package:
|
||||
|
||||
```
|
||||
npm install -g fd-find
|
||||
```
|
||||
|
||||
### From source
|
||||
|
||||
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.54.0* or later is required.
|
||||
|
||||
`make` is also needed for the build.
|
||||
|
||||
### From binaries
|
||||
|
||||
The [release page](https://github.com/sharkdp/fd/releases) includes precompiled binaries for Linux, macOS and Windows. Statically-linked binaries are also available: look for archives with `musl` in the file name.
|
||||
|
||||
## Development
|
||||
```bash
|
||||
git clone https://github.com/sharkdp/fd
|
||||
|
||||
# Build
|
||||
cd fd
|
||||
cargo build
|
||||
|
||||
# Run unit tests and integration tests
|
||||
cargo test
|
||||
|
||||
# Install
|
||||
cargo install --path .
|
||||
```
|
||||
|
||||
## Maintainers
|
||||
|
||||
- [sharkdp](https://github.com/sharkdp)
|
||||
- [tmccombs](https://github.com/tmccombs)
|
||||
- [tavianator](https://github.com/tavianator)
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2017-2021 The fd developers
|
||||
|
||||
`fd` is distributed under the terms of both the MIT License and the Apache License 2.0.
|
||||
|
||||
See the [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) files for license details.
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
# 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'
|
||||
@@ -1,29 +1,24 @@
|
||||
// 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;
|
||||
|
||||
use clap_complete::{generate_to, Shell};
|
||||
use Shell::*;
|
||||
//use clap_complete::shells::Shel{Bash, Fish, PowerShell, Elvish};
|
||||
|
||||
include!("src/app.rs");
|
||||
|
||||
fn main() {
|
||||
match version_check::is_min_version("1.31") {
|
||||
Some((true, _)) => {}
|
||||
let min_version = "1.54";
|
||||
|
||||
match version_check::is_min_version(min_version) {
|
||||
Some(true) => {}
|
||||
// rustc version too small or can't figure it out
|
||||
_ => {
|
||||
writeln!(&mut io::stderr(), "'fd' requires rustc >= 1.31").unwrap();
|
||||
exit(1);
|
||||
eprintln!("'fd' requires rustc >= {}", min_version);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let var = std::env::var_os("SHELL_COMPLETIONS_DIR").or(std::env::var_os("OUT_DIR"));
|
||||
let var = std::env::var_os("SHELL_COMPLETIONS_DIR").or_else(|| std::env::var_os("OUT_DIR"));
|
||||
let outdir = match var {
|
||||
None => return,
|
||||
Some(outdir) => outdir,
|
||||
@@ -31,8 +26,8 @@ fn main() {
|
||||
fs::create_dir_all(&outdir).unwrap();
|
||||
|
||||
let mut app = build_app();
|
||||
app.gen_completions("fd", Shell::Bash, &outdir);
|
||||
app.gen_completions("fd", Shell::Fish, &outdir);
|
||||
app.gen_completions("fd", Shell::Zsh, &outdir);
|
||||
app.gen_completions("fd", Shell::PowerShell, &outdir);
|
||||
// NOTE: zsh completions are hand written in contrib/completion/_fd
|
||||
for shell in [Bash, PowerShell, Fish, Elvish] {
|
||||
generate_to(shell, &mut app, "fd", &outdir).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
* linguist-vendored
|
||||
@@ -1,128 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/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
|
||||
@@ -0,0 +1 @@
|
||||
msrv = "1.54.0"
|
||||
@@ -0,0 +1,273 @@
|
||||
#compdef fd
|
||||
|
||||
##
|
||||
# zsh completion function for fd
|
||||
#
|
||||
# Based on ripgrep completion function.
|
||||
# Originally based on code from the zsh-users project — see copyright notice
|
||||
# below.
|
||||
|
||||
autoload -U is-at-least
|
||||
|
||||
_fd() {
|
||||
local curcontext="$curcontext" no='!' ret=1
|
||||
local -a context line state state_descr _arguments_options fd_types fd_args
|
||||
local -A opt_args
|
||||
|
||||
if is-at-least 5.2; then
|
||||
_arguments_options=( -s -S )
|
||||
else
|
||||
_arguments_options=( -s )
|
||||
fi
|
||||
|
||||
fd_types=(
|
||||
{f,file}'\:"regular files"'
|
||||
{d,directory}'\:"directories"'
|
||||
{l,symlink}'\:"symbolic links"'
|
||||
{e,empty}'\:"empty files or directories"'
|
||||
{x,executable}'\:"executable (files)"'
|
||||
{s,socket}'\:"sockets"'
|
||||
{p,pipe}'\:"named pipes (FIFOs)"'
|
||||
)
|
||||
|
||||
# Do not complete rare options unless either the current prefix
|
||||
# matches one of those options or the user has the `complete-all`
|
||||
# style set. Note that this prefix check has to be updated manually to account
|
||||
# for all of the potential negation options listed below!
|
||||
if
|
||||
# (--[bpsu]* => match all options marked with '$no')
|
||||
[[ $PREFIX$SUFFIX == --[bopsu]* ]] ||
|
||||
zstyle -t ":complete:$curcontext:*" complete-all
|
||||
then
|
||||
no=
|
||||
fi
|
||||
|
||||
# We make heavy use of argument groups here to prevent the option specs from
|
||||
# growing unwieldy. These aren't supported in zsh <5.4, though, so we'll strip
|
||||
# them out below if necessary. This makes the exclusions inaccurate on those
|
||||
# older versions, but oh well — it's not that big a deal
|
||||
fd_args=(
|
||||
+ '(hidden)' # hidden files
|
||||
{-H,--hidden}'[search hidden files/directories]'
|
||||
|
||||
+ '(no-ignore-full)' # all ignore files
|
||||
'(no-ignore-partial)'{-I,--no-ignore}"[don't respect .(git|fd)ignore and global ignore files]"
|
||||
$no'(no-ignore-partial)*'{-u,--unrestricted}'[alias for --no-ignore, when repeated also alias for --hidden]'
|
||||
|
||||
+ no-ignore-partial # some ignore files
|
||||
"(no-ignore-full --no-ignore-vcs)--no-ignore-vcs[don't respect .gitignore files]"
|
||||
"!(no-ignore-full --no-global-ignore-file)--no-global-ignore-file[don't respect the global ignore file]"
|
||||
$no'(no-ignore-full --no-ignore-parent)--no-ignore-parent[]'
|
||||
|
||||
+ '(case)' # case-sensitivity
|
||||
{-s,--case-sensitive}'[perform a case-sensitive search]'
|
||||
{-i,--ignore-case}'[perform a case-insensitive search]'
|
||||
|
||||
+ '(regex-pattern)' # regex-based search pattern
|
||||
'(no-regex-pattern)--regex[perform a regex-based search (default)]'
|
||||
|
||||
+ '(no-regex-pattern)' # non-regex-based search pattern
|
||||
{-g,--glob}'[perform a glob-based search]'
|
||||
{-F,--fixed-strings}'[treat pattern as literal string instead of a regex]'
|
||||
|
||||
+ '(match-full)' # match against full path
|
||||
{-p,--full-path}'[match the pattern against the full path instead of the basename]'
|
||||
|
||||
+ '(follow)' # follow symlinks
|
||||
{-L,--follow}'[follow symbolic links to directories]'
|
||||
|
||||
+ '(abs-path)' # show absolute paths
|
||||
'(long-listing)'{-a,--absolute-path}'[show absolute paths instead of relative paths]'
|
||||
|
||||
+ '(null-sep)' # use null separator for output
|
||||
'(long-listing)'{-0,--print0}'[separate search results by the null character]'
|
||||
|
||||
+ '(long-listing)' # long-listing output
|
||||
'(abs-path null-sep max-results exec-cmds)'{-l,--list-details}'[use a long listing format with file metadata]'
|
||||
|
||||
+ '(max-results)' # max number of results
|
||||
'(long-listing exec-cmds)--max-results=[limit number of search results to given count and quit]:count'
|
||||
'(long-listing exec-cmds)-1[limit to a single search result and quit]'
|
||||
|
||||
+ '(fs-errors)' # file-system errors
|
||||
$no'--show-errors[enable the display of filesystem errors]'
|
||||
|
||||
+ '(fs-traversal)' # file-system traversal
|
||||
$no"--one-file-system[don't descend into directories on other file systems]"
|
||||
'!--mount'
|
||||
'!--xdev'
|
||||
|
||||
+ dir-depth # directory depth
|
||||
'(--exact-depth -d --max-depth)'{-d+,--max-depth=}'[set max directory depth to descend when searching]:depth'
|
||||
'!(--exact-depth -d --max-depth)--maxdepth:depth'
|
||||
'(--exact-depth --min-depth)--min-depth=[set directory depth to descend before start searching]:depth'
|
||||
'(--exact-depth -d --max-depth --maxdepth --min-depth)--exact-depth=[only search at the exact given directory depth]:depth'
|
||||
|
||||
+ prune # pruning
|
||||
"--prune[don't traverse into matching directories]"
|
||||
|
||||
+ filter-misc # filter search
|
||||
'*'{-t+,--type=}"[filter search by type]:type:(($fd_types))"
|
||||
'*'{-e+,--extension=}'[filter search by file extension]:extension'
|
||||
'*'{-E+,--exclude=}'[exclude files/directories that match the given glob pattern]:glob pattern'
|
||||
'*'{-S+,--size=}'[limit search by file size]:size limit:->size'
|
||||
'(-o --owner)'{-o+,--owner=}'[filter by owning user and/or group]:owner and/or group:->owner'
|
||||
|
||||
+ ignore-file # extra ignore files
|
||||
'*--ignore-file=[add a custom, low-precedence ignore-file with .gitignore format]: :_files'
|
||||
|
||||
+ '(filter-mtime-newer)' # filter by files modified after than
|
||||
'--changed-within=[limit search to files/directories modified within the given date/duration]:date or duration'
|
||||
'!--change-newer-than=:date/duration'
|
||||
'!--newer=:date/duration'
|
||||
|
||||
+ '(filter-mtime-older)' # filter by files modified before than
|
||||
'--changed-before=[limit search to files/directories modified before the given date/duration]:date or duration'
|
||||
'!--change-older-than=:date/duration'
|
||||
'!--older=:date/duration'
|
||||
|
||||
+ '(color)' # colorize output
|
||||
{-c+,--color=}'[declare when to colorize search results]:when to colorize:((
|
||||
auto\:"show colors if the output goes to an interactive console (default)"
|
||||
never\:"do not use colorized output"
|
||||
always\:"always use colorized output"
|
||||
))'
|
||||
|
||||
+ '(threads)'
|
||||
{-j+,--threads=}'[set the number of threads for searching and executing]:number of threads'
|
||||
|
||||
+ '(exec-cmds)' # execute command
|
||||
'(long-listing max-results)'{-x+,--exec=}'[execute command for each search result]:command: _command_names -e:*\;::program arguments: _normal'
|
||||
'(long-listing max-results)'{-X+,--exec-batch=}'[execute command for all search results at once]:command: _command_names -e:*\;::program arguments: _normal'
|
||||
'(long-listing max-results)--batch-size=[max number of args for each -X call]:size'
|
||||
|
||||
+ other
|
||||
'!(--max-buffer-time)--max-buffer-time=[set amount of time to buffer before showing output]:time (ms)'
|
||||
|
||||
+ '(about)' # about flags
|
||||
'(: * -)'{-h,--help}'[display help message]'
|
||||
'(: * -)'{-v,--version}'[display version information]'
|
||||
|
||||
+ path-sep # set path separator for output
|
||||
$no'(--path-separator)--path-separator=[set the path separator to use when printing file paths]:path separator'
|
||||
|
||||
+ search-path
|
||||
$no'(--base-directory)--base-directory=[change the current working directory to the given path]:directory:_files -/'
|
||||
$no'(*)*--search-path=[set search path (instead of positional <path> arguments)]:directory:_files -/'
|
||||
|
||||
+ strip-cwd-prefix
|
||||
$no'(strip-cwd-prefix exec-cmds)--strip-cwd-prefix[Strip ./ prefix when output is redirected]'
|
||||
|
||||
+ args # positional arguments
|
||||
'1: :_guard "^-*" pattern'
|
||||
'(--search-path)*:directory:_files -/'
|
||||
)
|
||||
|
||||
# Strip out argument groups where unsupported (see above)
|
||||
is-at-least 5.4 ||
|
||||
fd_args=( ${(@)args:#(#i)(+|[a-z0-9][a-z0-9_-]#|\([a-z0-9][a-z0-9_-]#\))} )
|
||||
|
||||
_arguments $_arguments_options : $fd_args && ret=0
|
||||
|
||||
case ${state} in
|
||||
owner)
|
||||
compset -P '(\\|)\!'
|
||||
if compset -P '*:'; then
|
||||
_groups && ret=0
|
||||
else
|
||||
if
|
||||
compset -S ':*' ||
|
||||
# Do not add the colon suffix when completing "!user<TAB>
|
||||
# (with a starting double-quote) otherwise pressing tab again
|
||||
# after the inserted colon "!user:<TAB> will complete history modifiers
|
||||
[[ $IPREFIX == (\\|\!)* && ($QIPREFIX == \"* && -z $QISUFFIX) ]]
|
||||
then
|
||||
_users && ret=0
|
||||
else
|
||||
local q
|
||||
# Since quotes are needed when using the negation prefix !,
|
||||
# automatically remove the colon suffix also when closing the quote
|
||||
if [[ $QIPREFIX == [\'\"]* ]]; then
|
||||
q=${QIPREFIX:0:1}
|
||||
fi
|
||||
_users -r ": \t\n\-$q" -S : && ret=0
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
size)
|
||||
if compset -P '[-+][0-9]##'; then
|
||||
local -a suff=(
|
||||
'B:bytes'
|
||||
'K:kilobytes (10^3 = 1000 bytes)'
|
||||
'M:megabytes (10^6 = 1000^2 bytes)'
|
||||
'G:gigabytes (10^9 = 1000^3 bytes)'
|
||||
'T:terabytes (10^12 = 1000^4 bytes)'
|
||||
'Ki:kibibytes ( 2^10 = 1024 bytes)'
|
||||
'Mi:mebibytes ( 2^20 = 1024^2 bytes)'
|
||||
'Gi:gigibytes ( 2^30 = 1024^3 bytes)'
|
||||
'Ti:tebibytes ( 2^40 = 1024^4 bytes)'
|
||||
)
|
||||
_describe -t units 'size limit units' suff -V 'units'
|
||||
elif compset -P '[-+]'; then
|
||||
_message -e 'size limit number (full format: <+-><number><unit>)'
|
||||
else
|
||||
_values 'size limit prefix (full format: <prefix><number><unit>)' \
|
||||
'\+[file size must be greater or equal to]'\
|
||||
'-[file size must be less than or equal to]' && ret=0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
_fd "$@"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright (c) 2011 GitHub zsh-users - http://github.com/zsh-users
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the zsh-users nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL ZSH-USERS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
# ------------------------------------------------------------------------------
|
||||
# Description
|
||||
# -----------
|
||||
#
|
||||
# Completion script for fd
|
||||
#
|
||||
# ------------------------------------------------------------------------------
|
||||
# Authors
|
||||
# -------
|
||||
#
|
||||
# * smancill (https://github.com/smancill)
|
||||
#
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Local Variables:
|
||||
# mode: shell-script
|
||||
# coding: utf-8-unix
|
||||
# indent-tabs-mode: nil
|
||||
# sh-indentation: 2
|
||||
# sh-basic-offset: 2
|
||||
# End:
|
||||
# vim: ft=zsh sw=2 ts=2 et
|
||||
@@ -28,20 +28,47 @@ is a simple, fast and user-friendly alternative to
|
||||
.TP
|
||||
.B \-H, \-\-hidden
|
||||
Include hidden files and directories in the search results
|
||||
(default: hidden files and directories are skipped).
|
||||
(default: hidden files and directories are skipped). The flag can be overridden with '--no-hidden'.
|
||||
.TP
|
||||
.B \-I, \-\-no\-ignore
|
||||
Show search results from files and directories that would otherwise be ignored by
|
||||
.IR .gitignore ,
|
||||
.RS
|
||||
.IP \[bu] 2
|
||||
.I .gitignore
|
||||
.IP \[bu]
|
||||
.I .git/info/exclude
|
||||
.IP \[bu]
|
||||
The global gitignore configuration (by default
|
||||
.IR $HOME/.config/git/ignore )
|
||||
.IP \[bu]
|
||||
.I .ignore
|
||||
or
|
||||
.IP \[bu]
|
||||
.I .fdignore
|
||||
files.
|
||||
.IP \[bu]
|
||||
The global fd ignore file (usually
|
||||
.I $HOME/.config/fd/ignore
|
||||
)
|
||||
.RE
|
||||
.IP
|
||||
The flag can be overridden with '--ignore'.
|
||||
.TP
|
||||
.B \-u, \-\-unrestricted
|
||||
Alias for '--no-ignore'. Can be repeated; '-uu' is an alias for '--no-ignore --hidden'.
|
||||
.TP
|
||||
.B \-\-no\-ignore\-vcs
|
||||
Show search results from files and directories that would otherwise be ignored by
|
||||
.I .gitignore
|
||||
files.
|
||||
Show search results from files and directories that would otherwise be ignored by gitignore files
|
||||
including
|
||||
.IR .gitignore ,
|
||||
.IR .git/info/exclude ,
|
||||
and the global gitignore configuration
|
||||
.RI ( core.excludesFile
|
||||
git setting, which defaults to
|
||||
.IR $HOME/.config/git/ignore ).
|
||||
The flag can be overridden with '--ignore-vcs'.
|
||||
.TP
|
||||
.B \-\-no\-ignore\-parent
|
||||
Show search results from files and directories that would otherwise be ignored by gitignore files in
|
||||
parent directories.
|
||||
.TP
|
||||
.B \-s, \-\-case\-sensitive
|
||||
Perform a case-sensitive search. By default, fd uses case-insensitive searches, unless the
|
||||
@@ -51,15 +78,30 @@ pattern contains an uppercase character (smart case).
|
||||
Perform a case-insensitive search. By default, fd uses case-insensitive searches, unless the
|
||||
pattern contains an uppercase character (smart case).
|
||||
.TP
|
||||
.B \-g, \-\-glob
|
||||
Perform a glob-based search instead of a regular expression search.
|
||||
If combined with the '\-\-full-path' option, '**' can be used to match multiple path components.
|
||||
.TP
|
||||
.B \-\-regex
|
||||
Perform a regular-expression based search (default). This can be used to override --glob.
|
||||
.TP
|
||||
.B \-F, \-\-fixed\-strings
|
||||
Treat the pattern as a literal string instead of a regular expression.
|
||||
Treat the pattern as a literal string instead of a regular expression. Note that this also
|
||||
performs substring comparison. If you want to match on an exact filename, consider using '\-\-glob'.
|
||||
.TP
|
||||
.B \-a, \-\-absolute\-path
|
||||
Shows the full path starting from the root as opposed to relative paths.
|
||||
The flag can be overridden with '--relative-path'.
|
||||
.TP
|
||||
.B \-l, \-\-list\-details
|
||||
Use a detailed listing format like 'ls -l'. This is basically an alias
|
||||
for '--exec-batch ls -l' with some additional 'ls' options. This can be used
|
||||
to see more metadata, to show symlink targets and to achieve a deterministic
|
||||
sort order.
|
||||
.TP
|
||||
.B \-L, \-\-follow
|
||||
By default, fd does not descend into symlinked directories. Using this flag, symbolic links are
|
||||
also traversed.
|
||||
also traversed. The flag can be overridden with '--no-follow'.
|
||||
.TP
|
||||
.B \-p, \-\-full\-path
|
||||
By default, the search pattern is only matched against the filename (or directory name). Using
|
||||
@@ -71,10 +113,30 @@ is matched against the full path.
|
||||
Separate search results by the null character (instead of newlines). Useful for piping results to
|
||||
.IR xargs .
|
||||
.TP
|
||||
.B \-\-max\-results count
|
||||
Limit the number of search results to 'count' and quit immediately.
|
||||
.TP
|
||||
.B \-1
|
||||
Limit the search to a single result and quit immediately. This is an alias for '--max-results=1'.
|
||||
.TP
|
||||
.B \-q, \-\-quiet
|
||||
When the flag is present, the program does not print anything and will instead exit with a code of 0 if there is at least one search result.
|
||||
Otherwise, the exit code will be 1.
|
||||
This is mainly for usage in scripts and can be faster than checking for output because the search can be stopped early after the first match.
|
||||
.B \-\-has\-results
|
||||
can be used as an alias.
|
||||
.TP
|
||||
.B \-\-show-errors
|
||||
Enable the display of filesystem errors for situations such as insufficient
|
||||
permissions or dead symlinks.
|
||||
.TP
|
||||
.B \-\-strip-cwd-prefix
|
||||
By default, relative paths are prefixed with './' when the output goes to a non interactive terminal
|
||||
(TTY). Use this flag to disable this behaviour.
|
||||
.TP
|
||||
.B \-\-one\-file\-system, \-\-mount, \-\-xdev
|
||||
By default, fd will traverse the file system tree as far as other options dictate. With this flag, fd ensures that it does not descend into a different file system than the one it started in. Comparable to the -mount or -xdev filters of find(1).
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Print help information.
|
||||
.TP
|
||||
@@ -86,15 +148,28 @@ Limit directory traversal to at most
|
||||
.I d
|
||||
levels of depth. By default, there is no limit on the search depth.
|
||||
.TP
|
||||
.BI "\-\-min\-depth " d
|
||||
Only show search results starting at the given depth. See also: '--max-depth' and '--exact-depth'.
|
||||
.TP
|
||||
.BI "\-\-exact\-depth " d
|
||||
Only show search results at the exact given depth. This is an alias for '--min-depth <depth> --max-depth <depth>'.
|
||||
.TP
|
||||
.B \-\-prune
|
||||
Do not traverse into matching directories.
|
||||
.TP
|
||||
.BI "\-t, \-\-type " filetype
|
||||
Filter search by type:
|
||||
.RS
|
||||
.IP "f, file"
|
||||
regular files
|
||||
.IP "d, directories"
|
||||
.IP "d, directory"
|
||||
directories
|
||||
.IP "l, symlink"
|
||||
symbolic links
|
||||
.IP "s, socket"
|
||||
sockets
|
||||
.IP "p, pipe"
|
||||
named pipes (FIFOs)
|
||||
.IP "x, executable"
|
||||
executable (files)
|
||||
.IP "e, empty"
|
||||
@@ -102,18 +177,46 @@ empty files or directories
|
||||
.RE
|
||||
|
||||
.RS
|
||||
This option can be used repeatedly to allow for multiple file types.
|
||||
This option can be specified more than once to include multiple file types.
|
||||
Searching for '--type file --type symlink' will show both regular files as well as
|
||||
symlinks. Note that the 'executable' and 'empty' filters work differently: '--type
|
||||
executable' implies '--type file' by default. And '--type empty' searches for
|
||||
empty files and directories, unless either '--type file' or '--type directory' is
|
||||
specified in addition.
|
||||
|
||||
Examples:
|
||||
- Only search for files:
|
||||
fd --type file …
|
||||
fd -tf …
|
||||
- Find both files and symlinks
|
||||
fd --type file --type symlink …
|
||||
fd -tf -tl …
|
||||
- Find executable files:
|
||||
fd --type executable
|
||||
fd -tx
|
||||
- Find empty files:
|
||||
fd --type empty --type file
|
||||
fd -te -tf
|
||||
- Find empty directories:
|
||||
fd --type empty --type directory
|
||||
fd -te -td
|
||||
.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.
|
||||
|
||||
If you want to search for files without extension, you can use the regex '^[^.]+$'
|
||||
as a normal search pattern.
|
||||
.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.
|
||||
Examples:
|
||||
\-\-exclude '*.pyc'
|
||||
\-\-exclude node_modules
|
||||
.TP
|
||||
.BI "\-\-ignore-file " path
|
||||
Add a custom ignore-file in '.gitignore' format.
|
||||
@@ -133,7 +236,7 @@ Always colorize output.
|
||||
.RE
|
||||
.TP
|
||||
.BI "\-j, \-\-threads " num
|
||||
Number of threads to use for searching (default: number of available CPUs).
|
||||
Set number of threads to use for searching & executing (default: number of available CPU cores).
|
||||
.TP
|
||||
.BI "\-S, \-\-size " size
|
||||
Limit results based on the size of files using the format
|
||||
@@ -143,6 +246,8 @@ Limit results based on the size of files using the format
|
||||
file size must be greater than or equal to this
|
||||
.IP '-'
|
||||
file size must be less than or equal to this
|
||||
.P
|
||||
If neither '+' nor '-' is specified, file size must be exactly equal to this.
|
||||
.IP 'NUM'
|
||||
The numeric size (e.g. 500)
|
||||
.IP 'UNIT'
|
||||
@@ -152,7 +257,7 @@ Allowed unit values:
|
||||
.IP 'b'
|
||||
bytes
|
||||
.IP 'k'
|
||||
kilobytes
|
||||
kilobytes (base ten, 10^3 = 1000 bytes)
|
||||
.IP 'm'
|
||||
megabytes
|
||||
.IP 'g'
|
||||
@@ -160,7 +265,7 @@ gigabytes
|
||||
.IP 't'
|
||||
terabytes
|
||||
.IP 'ki'
|
||||
kibibytes
|
||||
kibibytes (base two, 2^10 = 1024 bytes)
|
||||
.IP 'mi'
|
||||
mebibytes
|
||||
.IP 'gi'
|
||||
@@ -171,8 +276,11 @@ tebibytes
|
||||
.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).
|
||||
Filter results based on the file modification time.
|
||||
Files with modification times greater than the argument will be returned.
|
||||
The argument can be provided as a duration (\fI10h, 1d, 35min\fR) or as a specific point
|
||||
in time in either full RFC3339 format with time zone, or as a date or datetime in the
|
||||
local time zone (\fIYYYY-MM-DD\fR or \fIYYYY-MM-DD HH:MM:SS\fR).
|
||||
.B --change-newer-than
|
||||
can be used as an alias.
|
||||
|
||||
@@ -181,8 +289,11 @@ Examples:
|
||||
\-\-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).
|
||||
Filter results based on the file modification time.
|
||||
Files with modification times less than the argument will be returned.
|
||||
The argument can be provided as a duration (\fI10h, 1d, 35min\fR) or as a specific point
|
||||
in time in either full RFC3339 format with time zone, or as a date or datetime in the
|
||||
local time zone (\fIYYYY-MM-DD\fR or \fIYYYY-MM-DD HH:MM:SS\fR).
|
||||
.B --change-older-than
|
||||
can be used as an alias.
|
||||
|
||||
@@ -190,13 +301,46 @@ Examples:
|
||||
\-\-changed-before "2018-10-27 10:00:00"
|
||||
\-\-change-older-than 2weeks
|
||||
.TP
|
||||
.BI "-o, \-\-owner " [user][:group]
|
||||
Filter files by their user and/or group. Format: [(user|uid)][:(group|gid)]. Either side
|
||||
is optional. Precede either side with a '!' to exclude files instead.
|
||||
|
||||
Examples:
|
||||
\-\-owner john
|
||||
\-\-owner :students
|
||||
\-\-owner "!john:students"
|
||||
.TP
|
||||
.BI "\-\-base\-directory " path
|
||||
Change the current working directory of fd to the provided path. This means that search results will
|
||||
be shown with respect to the given base path. Note that relative paths which are passed to fd via the
|
||||
positional \fIpath\fR argument or the \fB\-\-search\-path\fR option will also be resolved relative to
|
||||
this directory.
|
||||
.TP
|
||||
.BI "\-\-path\-separator " separator
|
||||
Set the path separator to use when printing file paths. The default is the OS-specific separator
|
||||
('/' on Unix, '\\' on Windows).
|
||||
.TP
|
||||
.BI "\-\-search\-path " search\-path
|
||||
Provide paths to search as an alternative to the positional \fIpath\fR argument. Changes the usage to
|
||||
\'fd [FLAGS/OPTIONS] \-\-search\-path PATH \-\-search\-path PATH2 [PATTERN]\'
|
||||
.TP
|
||||
.BI "\-x, \-\-exec " command
|
||||
.RS
|
||||
Execute
|
||||
.I command
|
||||
for each search result. The following placeholders are substituted by a path derived from the current search result:
|
||||
for each search result in parallel (use --threads=1 for sequential command execution).
|
||||
|
||||
Note that all subsequent positional arguments are considered to be arguments to the
|
||||
.I command
|
||||
- not to fd.
|
||||
It is therefore recommended to place the \-x/\-\-exec option last. Alternatively, you can supply
|
||||
a ';' argument to end the argument list and continue with more fd options.
|
||||
Most shells require ';' to be escaped: '\\;'.
|
||||
|
||||
The following placeholders are substituted before the command is executed:
|
||||
.RS
|
||||
.IP {}
|
||||
path
|
||||
path (of the current search result)
|
||||
.IP {/}
|
||||
basename
|
||||
.IP {//}
|
||||
@@ -206,15 +350,33 @@ path without file extension
|
||||
.IP {/.}
|
||||
basename without file extension
|
||||
.RE
|
||||
|
||||
If no placeholder is present, an implicit "{}" at the end is assumed.
|
||||
|
||||
Examples:
|
||||
|
||||
- find all *.zip files and unzip them:
|
||||
|
||||
fd -e zip -x unzip
|
||||
|
||||
- find *.h and *.cpp files and run "clang-format -i .." for each of them:
|
||||
|
||||
fd -e h -e cpp -x clang-format -i
|
||||
|
||||
- Convert all *.jpg files to *.png files:
|
||||
|
||||
fd -e jpg -x convert {} {.}.png
|
||||
.RE
|
||||
.TP
|
||||
.BI "\-X, \-\-exec-batch " command
|
||||
.RS
|
||||
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:
|
||||
once, with all search results as arguments.
|
||||
One of the following placeholders is substituted before the command is executed:
|
||||
.RS
|
||||
.IP {}
|
||||
path
|
||||
path (of all search results)
|
||||
.IP {/}
|
||||
basename
|
||||
.IP {//}
|
||||
@@ -224,11 +386,50 @@ path without file extension
|
||||
.IP {/.}
|
||||
basename without file extension
|
||||
.RE
|
||||
|
||||
If no placeholder is present, an implicit "{}" at the end is assumed.
|
||||
|
||||
Examples:
|
||||
|
||||
- Find all test_*.py files and open them in your favorite editor:
|
||||
|
||||
fd -g 'test_*.py' -X vim
|
||||
|
||||
Note that this executes a single "vim" process with all search results as arguments.
|
||||
|
||||
- Find all *.rs files and count the lines with "wc -l ...":
|
||||
|
||||
fd -e rs -X wc -l
|
||||
.RE
|
||||
.TP
|
||||
.BI "\-\-batch-size " size
|
||||
Maximum number of arguments to pass to the command given with -X. If the number of results is
|
||||
greater than the given size, the command given with -X is run again with remaining arguments. A
|
||||
batch size of zero means there is no limit.
|
||||
.SH PATTERN SYNTAX
|
||||
The regular expression syntax used by fd is documented here:
|
||||
|
||||
https://docs.rs/regex/1.0.0/regex/#syntax
|
||||
|
||||
The glob syntax is documented here:
|
||||
|
||||
https://docs.rs/globset/#syntax
|
||||
.SH ENVIRONMENT
|
||||
.TP
|
||||
.B LS_COLORS
|
||||
Determines how to colorize search results, see
|
||||
.BR dircolors (1) .
|
||||
.TP
|
||||
.B NO_COLOR
|
||||
Disables colorized output.
|
||||
.TP
|
||||
.B XDG_CONFIG_HOME, HOME
|
||||
Used to locate the global ignore file. If
|
||||
.B XDG_CONFIG_HOME
|
||||
is set, use
|
||||
.IR $XDG_CONFIG_HOME/fd/ignore .
|
||||
Otherwise, use
|
||||
.IR $HOME/.config/fd/ignore .
|
||||
.SH EXAMPLES
|
||||
.TP
|
||||
.RI "Find files and directories that match the pattern '" needle "':"
|
||||
@@ -237,10 +438,16 @@ $ fd needle
|
||||
.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:"
|
||||
.RI "Find all Python files (all files with the extension " .py ") in the current directory:"
|
||||
$ fd -e py
|
||||
.TP
|
||||
.RI "Open all search results with vim:"
|
||||
$ fd pattern -X vim
|
||||
.TP
|
||||
.BI "\-\-batch\-size " size
|
||||
Pass at most
|
||||
.I size
|
||||
arguments to each call to the command given with -X.
|
||||
.TP
|
||||
.SH SEE ALSO
|
||||
.BR find (1)
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
Vendored
+161
@@ -0,0 +1,161 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="66mm"
|
||||
height="66mm"
|
||||
viewBox="0 0 66 66.000001"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:export-filename="/home/shark/Informatik/rust/fd/doc/logo.png"
|
||||
inkscape:export-xdpi="192.42"
|
||||
inkscape:export-ydpi="192.42"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#999999"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.1795515"
|
||||
inkscape:cx="114.47309"
|
||||
inkscape:cy="176.18304"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1175"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
showguides="false"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:snap-global="false"
|
||||
fit-margin-top="2"
|
||||
fit-margin-left="2"
|
||||
fit-margin-right="2"
|
||||
fit-margin-bottom="2"
|
||||
lock-margins="true">
|
||||
<sodipodi:guide
|
||||
position="26.228232,26.126763"
|
||||
orientation="0,-1"
|
||||
id="guide47826" />
|
||||
<sodipodi:guide
|
||||
position="25.799494,2.3628924"
|
||||
orientation="0,-1"
|
||||
id="guide47828" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-21.358009,-148.28012)">
|
||||
<g
|
||||
id="g66267"
|
||||
transform="matrix(0.84959471,0,0,0.84959471,7.9920783,43.351816)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.7126px;line-height:0;font-family:'Fira Sans Condensed';-inkscape-font-specification:'Fira Sans Condensed, ';white-space:pre;inline-size:37.3715;fill:#e5e5e5;fill-opacity:1;stroke-width:0.742816"
|
||||
x="50.647034"
|
||||
y="173.19841"
|
||||
id="text50653"
|
||||
transform="matrix(1.0604862,0,0,1.0604862,-3.3101428,-10.150043)"><tspan
|
||||
x="50.647034"
|
||||
y="173.19841"
|
||||
id="tspan66635"><tspan
|
||||
style="font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro'"
|
||||
id="tspan66633">fd</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.7126px;line-height:0;font-family:'Fira Sans Condensed';-inkscape-font-specification:'Fira Sans Condensed, ';white-space:pre;inline-size:37.3715;fill:#00ccff;fill-opacity:0.996078;stroke-width:0.742816"
|
||||
x="50.647034"
|
||||
y="173.19841"
|
||||
id="text1244"
|
||||
transform="matrix(1.0604862,0,0,1.0604862,-2.8008599,-9.6407599)"><tspan
|
||||
x="50.647034"
|
||||
y="173.19841"
|
||||
id="tspan66639"><tspan
|
||||
style="font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro'"
|
||||
id="tspan66637">fd</tspan></tspan></text>
|
||||
<g
|
||||
id="g47824"
|
||||
transform="translate(0.1724878,-0.35338542)">
|
||||
<g
|
||||
id="g42041">
|
||||
<path
|
||||
style="fill:none;stroke:#939dac;stroke-width:1.065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 28.209616,155.51329 v 15.68758 H 40.83001"
|
||||
id="path39763"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="fill:#b7bec8;stroke:#939dac;stroke-width:1.065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 27.869464,161.83767 H 40.261291"
|
||||
id="path39765" />
|
||||
</g>
|
||||
<g
|
||||
id="g41945"
|
||||
transform="translate(-1.0583333)">
|
||||
<path
|
||||
style="fill:#0088aa;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
|
||||
id="path40006"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<rect
|
||||
style="fill:#01ccff;fill-opacity:1;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
|
||||
id="rect39949"
|
||||
width="9.4925022"
|
||||
height="6.2080379"
|
||||
x="37.492516"
|
||||
y="158.82776"
|
||||
ry="0.90871465" />
|
||||
</g>
|
||||
<g
|
||||
id="g41951"
|
||||
transform="translate(-1.0583334,9.3665773)">
|
||||
<path
|
||||
style="fill:#373e48;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
|
||||
id="path41947"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<rect
|
||||
style="fill:#535d6c;fill-opacity:0.993797;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
|
||||
id="rect41949"
|
||||
width="9.4925022"
|
||||
height="6.2080379"
|
||||
x="37.492516"
|
||||
y="158.82776"
|
||||
ry="0.90871465" />
|
||||
</g>
|
||||
<g
|
||||
id="g41957"
|
||||
transform="translate(-14.306994,-6.8962642)">
|
||||
<path
|
||||
style="fill:#373e48;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
|
||||
id="path41953"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<rect
|
||||
style="fill:#535d6c;fill-opacity:0.993797;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
|
||||
id="rect41955"
|
||||
width="9.4925022"
|
||||
height="6.2080379"
|
||||
x="37.492516"
|
||||
y="158.82776"
|
||||
ry="0.90871465" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g65006"
|
||||
transform="matrix(0.55302761,0,0,0.55302761,66.463548,117.45819)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
Vendored
+1
-1
@@ -20,7 +20,7 @@ enter() {
|
||||
}
|
||||
|
||||
prompt() {
|
||||
printf '%b ' $PROMPT | pv -q
|
||||
printf '%b ' "$PROMPT" | pv -q
|
||||
}
|
||||
|
||||
type() {
|
||||
|
||||
+665
-274
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,16 @@
|
||||
use crate::exec::CommandTemplate;
|
||||
use crate::internal::{
|
||||
filter::{SizeFilter, TimeFilter},
|
||||
FileTypes,
|
||||
};
|
||||
use lscolors::LsColors;
|
||||
use regex::RegexSet;
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use lscolors::LsColors;
|
||||
use regex::bytes::RegexSet;
|
||||
|
||||
use crate::exec::CommandTemplate;
|
||||
use crate::filetypes::FileTypes;
|
||||
#[cfg(unix)]
|
||||
use crate::filter::OwnerFilter;
|
||||
use crate::filter::{SizeFilter, TimeFilter};
|
||||
|
||||
/// Configuration options for *fd*.
|
||||
pub struct FdOptions {
|
||||
pub struct Config {
|
||||
/// Whether the search is case-sensitive or case-insensitive.
|
||||
pub case_sensitive: bool,
|
||||
|
||||
@@ -22,12 +24,21 @@ pub struct FdOptions {
|
||||
/// Whether to respect `.fdignore` files or not.
|
||||
pub read_fdignore: bool,
|
||||
|
||||
/// Whether to respect ignore files in parent directories or not.
|
||||
pub read_parent_ignore: bool,
|
||||
|
||||
/// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
|
||||
pub read_vcsignore: bool,
|
||||
|
||||
/// Whether to respect the global ignore file or not.
|
||||
pub read_global_ignore: bool,
|
||||
|
||||
/// Whether to follow symlinks or not.
|
||||
pub follow_links: bool,
|
||||
|
||||
/// Whether to limit the search to starting file system or not.
|
||||
pub one_file_system: bool,
|
||||
|
||||
/// Whether elements of output should be separated by a null character
|
||||
pub null_separator: bool,
|
||||
|
||||
@@ -37,9 +48,19 @@ pub struct FdOptions {
|
||||
/// all files under subdirectories of the current directory, etc.
|
||||
pub max_depth: Option<usize>,
|
||||
|
||||
/// The minimum depth for reported entries, or `None`.
|
||||
pub min_depth: Option<usize>,
|
||||
|
||||
/// Whether to stop traversing into matching directories.
|
||||
pub prune: bool,
|
||||
|
||||
/// The number of threads to use.
|
||||
pub threads: usize,
|
||||
|
||||
/// If true, the program doesn't print anything and will instead return an exit code of 0
|
||||
/// if there's at least one match. Otherwise, the exit code will be 1.
|
||||
pub quiet: bool,
|
||||
|
||||
/// 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`.
|
||||
@@ -49,6 +70,9 @@ pub struct FdOptions {
|
||||
/// how to style different filetypes.
|
||||
pub ls_colors: Option<LsColors>,
|
||||
|
||||
/// Whether or not we are writing to an interactive terminal
|
||||
pub interactive_terminal: bool,
|
||||
|
||||
/// The type of file to search for. If set to `None`, all file types are displayed. If
|
||||
/// set to `Some(..)`, only the types that are specified are shown.
|
||||
pub file_types: Option<FileTypes>,
|
||||
@@ -61,6 +85,10 @@ pub struct FdOptions {
|
||||
/// If a value is supplied, each item found will be used to generate and execute commands.
|
||||
pub command: Option<Arc<CommandTemplate>>,
|
||||
|
||||
/// Maximum number of search results to pass to each `command`. If zero, the number is
|
||||
/// unlimited.
|
||||
pub batch_size: usize,
|
||||
|
||||
/// A list of glob patterns that should be excluded from the search.
|
||||
pub exclude_patterns: Vec<String>,
|
||||
|
||||
@@ -73,6 +101,19 @@ pub struct FdOptions {
|
||||
/// Constraints on last modification time of files
|
||||
pub time_constraints: Vec<TimeFilter>,
|
||||
|
||||
#[cfg(unix)]
|
||||
/// User/group ownership constraint
|
||||
pub owner_constraint: Option<OwnerFilter>,
|
||||
|
||||
/// Whether or not to display filesystem errors
|
||||
pub show_filesystem_errors: bool,
|
||||
|
||||
/// The separator used to print file paths.
|
||||
pub path_separator: Option<String>,
|
||||
|
||||
/// The maximum number of search results
|
||||
pub max_results: Option<usize>,
|
||||
|
||||
/// Whether or not to strip the './' prefix for search results
|
||||
pub strip_cwd_prefix: bool,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn print_error(msg: impl Into<String>) {
|
||||
eprintln!("[fd error]: {}", msg.into());
|
||||
}
|
||||
+25
-12
@@ -1,20 +1,25 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::error::print_error;
|
||||
use crate::exit_codes::ExitCode;
|
||||
|
||||
/// Executes a command.
|
||||
pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) {
|
||||
pub fn execute_command(
|
||||
mut cmd: Command,
|
||||
out_perm: &Mutex<()>,
|
||||
enable_output_buffering: bool,
|
||||
) -> ExitCode {
|
||||
// Spawn the supplied command.
|
||||
let output = cmd.output();
|
||||
let output = if enable_output_buffering {
|
||||
cmd.output()
|
||||
} else {
|
||||
// If running on only one thread, don't buffer output
|
||||
// Allows for viewing and interacting with intermediate command output
|
||||
cmd.spawn().and_then(|c| c.wait_with_output())
|
||||
};
|
||||
|
||||
// Then wait for the command to exit, if it was spawned.
|
||||
match output {
|
||||
@@ -28,12 +33,20 @@ pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) {
|
||||
|
||||
let _ = stdout.lock().write_all(&output.stdout);
|
||||
let _ = stderr.lock().write_all(&output.stderr);
|
||||
|
||||
if output.status.code() == Some(0) {
|
||||
ExitCode::Success
|
||||
} else {
|
||||
ExitCode::GeneralError
|
||||
}
|
||||
}
|
||||
Err(ref why) if why.kind() == io::ErrorKind::NotFound => {
|
||||
print_error!("Command not found: {:?}", cmd);
|
||||
print_error(format!("Command not found: {:?}", cmd));
|
||||
ExitCode::GeneralError
|
||||
}
|
||||
Err(why) => {
|
||||
print_error!("Problem while executing command: {}", why);
|
||||
print_error(format!("Problem while executing command: {}", why));
|
||||
ExitCode::GeneralError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-67
@@ -1,78 +1,40 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
use crate::filesystem::strip_current_dir;
|
||||
|
||||
/// Removes the parent component of the path
|
||||
pub fn basename(path: &str) -> &str {
|
||||
let mut index = 0;
|
||||
for (id, character) in path.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
index = id;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: On Windows, should return what for C:file.txt D:file.txt and \\server\share ?
|
||||
if index != 0 {
|
||||
return &path[index + 1..];
|
||||
}
|
||||
|
||||
path
|
||||
pub fn basename(path: &Path) -> &OsStr {
|
||||
path.file_name().unwrap_or_else(|| path.as_os_str())
|
||||
}
|
||||
|
||||
/// Removes the extension from the path
|
||||
pub fn remove_extension(path: &str) -> &str {
|
||||
let mut has_dir = false;
|
||||
let mut dir_index = 0;
|
||||
let mut ext_index = 0;
|
||||
pub fn remove_extension(path: &Path) -> OsString {
|
||||
let dirname = dirname(path);
|
||||
let stem = path.file_stem().unwrap_or_else(|| path.as_os_str());
|
||||
|
||||
for (id, character) in path.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
has_dir = true;
|
||||
dir_index = id;
|
||||
}
|
||||
if character == '.' {
|
||||
ext_index = id;
|
||||
}
|
||||
}
|
||||
let path = PathBuf::from(dirname).join(stem);
|
||||
|
||||
// Account for hidden files and directories
|
||||
if ext_index != 0 && (!has_dir || dir_index + 2 <= ext_index) {
|
||||
return &path[0..ext_index];
|
||||
}
|
||||
|
||||
path
|
||||
strip_current_dir(&path).to_owned().into_os_string()
|
||||
}
|
||||
|
||||
/// Removes the basename from the path.
|
||||
pub fn dirname(path: &str) -> &str {
|
||||
let mut has_dir = false;
|
||||
let mut index = 0;
|
||||
for (id, character) in path.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
has_dir = true;
|
||||
index = id;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: On Windows, return what for C:file.txt D:file.txt and \\server\share ?
|
||||
if !has_dir {
|
||||
"."
|
||||
} else if index == 0 {
|
||||
&path[..1]
|
||||
} else {
|
||||
&path[0..index]
|
||||
}
|
||||
pub fn dirname(path: &Path) -> OsString {
|
||||
path.parent()
|
||||
.map(|p| {
|
||||
if p == OsStr::new("") {
|
||||
OsString::from(".")
|
||||
} else {
|
||||
p.as_os_str().to_owned()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| path.as_os_str().to_owned())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod path_tests {
|
||||
use super::*;
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
|
||||
fn correct(input: &str) -> String {
|
||||
input.replace('/', &MAIN_SEPARATOR.to_string())
|
||||
@@ -83,7 +45,9 @@ mod path_tests {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_eq!($func(&correct($input)), correct($output));
|
||||
let input_path = PathBuf::from(&correct($input));
|
||||
let output_string = OsString::from(correct($output));
|
||||
assert_eq!($func(&input_path), output_string);
|
||||
}
|
||||
)+
|
||||
}
|
||||
@@ -106,16 +70,18 @@ mod path_tests {
|
||||
dirname_dir: dirname for "dir/foo.txt" => "dir"
|
||||
dirname_utf8_0: dirname for "💖/foo.txt" => "💖"
|
||||
dirname_utf8_1: dirname for "dir/💖.txt" => "dir"
|
||||
dirname_empty: dirname for "" => "."
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn dirname_root() {
|
||||
#[cfg(windows)]
|
||||
assert_eq!(dirname("C:\\"), "C:");
|
||||
#[cfg(windows)]
|
||||
assert_eq!(dirname("\\"), "\\");
|
||||
#[cfg(not(windows))]
|
||||
assert_eq!(dirname("/"), "/");
|
||||
assert_eq!(dirname(&PathBuf::from("C:")), OsString::from("C:"));
|
||||
assert_eq!(dirname(&PathBuf::from("\\")), OsString::from("\\"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn dirname_root() {
|
||||
assert_eq!(dirname(&PathBuf::from("/")), OsString::from("/"));
|
||||
}
|
||||
}
|
||||
|
||||
+38
-20
@@ -1,17 +1,13 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use super::CommandTemplate;
|
||||
use crate::walk::WorkerResult;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::error::print_error;
|
||||
use crate::exit_codes::{merge_exitcodes, ExitCode};
|
||||
use crate::walk::WorkerResult;
|
||||
|
||||
use super::CommandTemplate;
|
||||
|
||||
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
|
||||
/// generate a command with the supplied command template. The generated command will then
|
||||
/// be executed, and this process will continue until the receiver's sender has closed.
|
||||
@@ -20,7 +16,9 @@ pub fn job(
|
||||
cmd: Arc<CommandTemplate>,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
show_filesystem_errors: bool,
|
||||
) {
|
||||
buffer_output: bool,
|
||||
) -> ExitCode {
|
||||
let mut results: Vec<ExitCode> = Vec::new();
|
||||
loop {
|
||||
// Create a lock on the shared receiver for this thread.
|
||||
let lock = rx.lock().unwrap();
|
||||
@@ -28,32 +26,52 @@ pub fn job(
|
||||
// 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::Entry(path)) => path,
|
||||
Ok(WorkerResult::Error(err)) => {
|
||||
if show_filesystem_errors {
|
||||
print_error!("{}", err);
|
||||
print_error(err.to_string());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Err(_) => break,
|
||||
};
|
||||
|
||||
// Drop the lock so that other threads can read from the the receiver.
|
||||
// Drop the lock so that other threads can read from the receiver.
|
||||
drop(lock);
|
||||
// Generate a command and execute it.
|
||||
cmd.generate_and_execute(&value, Arc::clone(&out_perm));
|
||||
// Generate a command, execute it and store its exit code.
|
||||
results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm), buffer_output))
|
||||
}
|
||||
// Returns error in case of any error.
|
||||
merge_exitcodes(results)
|
||||
}
|
||||
|
||||
pub fn batch(rx: Receiver<WorkerResult>, cmd: &CommandTemplate, show_filesystem_errors: bool) {
|
||||
pub fn batch(
|
||||
rx: Receiver<WorkerResult>,
|
||||
cmd: &CommandTemplate,
|
||||
show_filesystem_errors: bool,
|
||||
buffer_output: bool,
|
||||
limit: usize,
|
||||
) -> ExitCode {
|
||||
let paths = rx.iter().filter_map(|value| match value {
|
||||
WorkerResult::Entry(val) => Some(val),
|
||||
WorkerResult::Entry(path) => Some(path),
|
||||
WorkerResult::Error(err) => {
|
||||
if show_filesystem_errors {
|
||||
print_error!("{}", err);
|
||||
print_error(err.to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
});
|
||||
cmd.generate_and_execute_batch(paths);
|
||||
if limit == 0 {
|
||||
// no limit
|
||||
return cmd.generate_and_execute_batch(paths, buffer_output);
|
||||
}
|
||||
|
||||
let mut exit_codes = Vec::new();
|
||||
let mut peekable = paths.peekable();
|
||||
while peekable.peek().is_some() {
|
||||
let limited = peekable.by_ref().take(limit);
|
||||
let exit_code = cmd.generate_and_execute_batch(limited, buffer_output);
|
||||
exit_codes.push(exit_code);
|
||||
}
|
||||
merge_exitcodes(exit_codes)
|
||||
}
|
||||
|
||||
+179
-66
@@ -1,25 +1,20 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
// TODO: Possible optimization could avoid pushing characters on a buffer.
|
||||
mod command;
|
||||
mod input;
|
||||
mod job;
|
||||
mod token;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::{Component, Path, PathBuf, Prefix};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use anyhow::{anyhow, Result};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::exit_codes::ExitCode;
|
||||
|
||||
use self::command::execute_command;
|
||||
use self::input::{basename, dirname, remove_extension};
|
||||
pub use self::job::{batch, job};
|
||||
@@ -42,40 +37,42 @@ pub enum ExecutionMode {
|
||||
pub struct CommandTemplate {
|
||||
args: Vec<ArgumentTemplate>,
|
||||
mode: ExecutionMode,
|
||||
path_separator: Option<String>,
|
||||
}
|
||||
|
||||
impl CommandTemplate {
|
||||
pub fn new<I, S>(input: I) -> CommandTemplate
|
||||
pub fn new<I, S>(input: I, path_separator: Option<String>) -> CommandTemplate
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
Self::build(input, ExecutionMode::OneByOne)
|
||||
Self::build(input, ExecutionMode::OneByOne, path_separator)
|
||||
}
|
||||
|
||||
pub fn new_batch<I, S>(input: I) -> Result<CommandTemplate, &'static str>
|
||||
pub fn new_batch<I, S>(input: I, path_separator: Option<String>) -> Result<CommandTemplate>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let cmd = Self::build(input, ExecutionMode::Batch);
|
||||
let cmd = Self::build(input, ExecutionMode::Batch, path_separator);
|
||||
if cmd.number_of_tokens() > 1 {
|
||||
return Err("Only one placeholder allowed for batch commands");
|
||||
return Err(anyhow!("Only one placeholder allowed for batch commands"));
|
||||
}
|
||||
if cmd.args[0].has_tokens() {
|
||||
return Err("First argument of exec-batch is expected to be a fixed executable");
|
||||
return Err(anyhow!(
|
||||
"First argument of exec-batch is expected to be a fixed executable"
|
||||
));
|
||||
}
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
fn build<I, S>(input: I, mode: ExecutionMode) -> CommandTemplate
|
||||
fn build<I, S>(input: I, mode: ExecutionMode, path_separator: Option<String>) -> CommandTemplate
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
lazy_static! {
|
||||
static ref PLACEHOLDER_PATTERN: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap();
|
||||
}
|
||||
static PLACEHOLDER_PATTERN: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"\{(/?\.?|//)\}").unwrap());
|
||||
|
||||
let mut args = Vec::new();
|
||||
let mut has_placeholder = false;
|
||||
@@ -100,7 +97,7 @@ impl CommandTemplate {
|
||||
"{/}" => tokens.push(Token::Basename),
|
||||
"{//}" => tokens.push(Token::Parent),
|
||||
"{/.}" => tokens.push(Token::BasenameNoExt),
|
||||
_ => panic!("Unhandled placeholder"),
|
||||
_ => unreachable!("Unhandled placeholder"),
|
||||
}
|
||||
|
||||
has_placeholder = true;
|
||||
@@ -125,67 +122,70 @@ impl CommandTemplate {
|
||||
args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder]));
|
||||
}
|
||||
|
||||
CommandTemplate { args, mode }
|
||||
CommandTemplate {
|
||||
args,
|
||||
mode,
|
||||
path_separator,
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
pub fn generate_and_execute(
|
||||
&self,
|
||||
input: &Path,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
buffer_output: bool,
|
||||
) -> ExitCode {
|
||||
let mut cmd = Command::new(self.args[0].generate(&input, self.path_separator.as_deref()));
|
||||
for arg in &self.args[1..] {
|
||||
cmd.arg(arg.generate(&input).as_ref());
|
||||
cmd.arg(arg.generate(&input, self.path_separator.as_deref()));
|
||||
}
|
||||
|
||||
execute_command(cmd, &out_perm)
|
||||
execute_command(cmd, &out_perm, buffer_output)
|
||||
}
|
||||
|
||||
pub fn in_batch_mode(&self) -> bool {
|
||||
self.mode == ExecutionMode::Batch
|
||||
}
|
||||
|
||||
pub fn generate_and_execute_batch<I>(&self, paths: I)
|
||||
pub fn generate_and_execute_batch<I>(&self, paths: I, buffer_output: bool) -> ExitCode
|
||||
where
|
||||
I: Iterator<Item = PathBuf>,
|
||||
{
|
||||
let mut cmd = Command::new(self.args[0].generate("").as_ref());
|
||||
let mut cmd = Command::new(self.args[0].generate("", None));
|
||||
cmd.stdin(Stdio::inherit());
|
||||
cmd.stdout(Stdio::inherit());
|
||||
cmd.stderr(Stdio::inherit());
|
||||
|
||||
let mut paths = paths.map(|p| Self::prepare_path(&p));
|
||||
let mut paths: Vec<_> = paths.collect();
|
||||
let mut has_path = false;
|
||||
|
||||
for arg in &self.args[1..] {
|
||||
if arg.has_tokens() {
|
||||
paths.sort();
|
||||
|
||||
// A single `Tokens` is expected
|
||||
// So we can directy consume the iterator once and for all
|
||||
// So we can directly consume the iterator once and for all
|
||||
for path in &mut paths {
|
||||
cmd.arg(arg.generate(&path).as_ref());
|
||||
cmd.arg(arg.generate(path, self.path_separator.as_deref()));
|
||||
has_path = true;
|
||||
}
|
||||
} else {
|
||||
cmd.arg(arg.generate("").as_ref());
|
||||
cmd.arg(arg.generate("", None));
|
||||
}
|
||||
}
|
||||
|
||||
if has_path {
|
||||
execute_command(cmd, &Mutex::new(()));
|
||||
execute_command(cmd, &Mutex::new(()), buffer_output)
|
||||
} else {
|
||||
ExitCode::Success
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,33 +202,97 @@ enum ArgumentTemplate {
|
||||
|
||||
impl ArgumentTemplate {
|
||||
pub fn has_tokens(&self) -> bool {
|
||||
match self {
|
||||
ArgumentTemplate::Tokens(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, ArgumentTemplate::Tokens(_))
|
||||
}
|
||||
|
||||
pub fn generate<'a>(&'a self, path: &str) -> Cow<'a, str> {
|
||||
/// Generate an argument from this template. If path_separator is Some, then it will replace
|
||||
/// the path separator in all placeholder tokens. Text arguments and tokens are not affected by
|
||||
/// path separator substitution.
|
||||
pub fn generate(&self, path: impl AsRef<Path>, path_separator: Option<&str>) -> OsString {
|
||||
use self::Token::*;
|
||||
let path = path.as_ref();
|
||||
|
||||
match *self {
|
||||
ArgumentTemplate::Tokens(ref tokens) => {
|
||||
let mut s = String::new();
|
||||
let mut s = OsString::new();
|
||||
for token in tokens {
|
||||
match *token {
|
||||
Basename => s += basename(path),
|
||||
BasenameNoExt => s += remove_extension(basename(path)),
|
||||
NoExt => s += remove_extension(path),
|
||||
Parent => s += dirname(path),
|
||||
Placeholder => s += path,
|
||||
Text(ref string) => s += string,
|
||||
Basename => s.push(Self::replace_separator(basename(path), path_separator)),
|
||||
BasenameNoExt => s.push(Self::replace_separator(
|
||||
&remove_extension(basename(path).as_ref()),
|
||||
path_separator,
|
||||
)),
|
||||
NoExt => s.push(Self::replace_separator(
|
||||
&remove_extension(path),
|
||||
path_separator,
|
||||
)),
|
||||
Parent => s.push(Self::replace_separator(&dirname(path), path_separator)),
|
||||
Placeholder => {
|
||||
s.push(Self::replace_separator(path.as_ref(), path_separator))
|
||||
}
|
||||
Text(ref string) => s.push(string),
|
||||
}
|
||||
}
|
||||
Cow::Owned(s)
|
||||
s
|
||||
}
|
||||
ArgumentTemplate::Text(ref text) => Cow::Borrowed(text),
|
||||
ArgumentTemplate::Text(ref text) => OsString::from(text),
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace the path separator in the input with the custom separator string. If path_separator
|
||||
/// is None, simply return a borrowed Cow<OsStr> of the input. Otherwise, the input is
|
||||
/// interpreted as a Path and its components are iterated through and re-joined into a new
|
||||
/// OsString.
|
||||
fn replace_separator<'a>(path: &'a OsStr, path_separator: Option<&str>) -> Cow<'a, OsStr> {
|
||||
// fast-path - no replacement necessary
|
||||
if path_separator.is_none() {
|
||||
return Cow::Borrowed(path);
|
||||
}
|
||||
|
||||
let path_separator = path_separator.unwrap();
|
||||
let mut out = OsString::with_capacity(path.len());
|
||||
let mut components = Path::new(path).components().peekable();
|
||||
|
||||
while let Some(comp) = components.next() {
|
||||
match comp {
|
||||
// Absolute paths on Windows are tricky. A Prefix component is usually a drive
|
||||
// letter or UNC path, and is usually followed by RootDir. There are also
|
||||
// "verbatim" prefixes beginning with "\\?\" that skip normalization. We choose to
|
||||
// ignore verbatim path prefixes here because they're very rare, might be
|
||||
// impossible to reach here, and there's no good way to deal with them. If users
|
||||
// are doing something advanced involving verbatim windows paths, they can do their
|
||||
// own output filtering with a tool like sed.
|
||||
Component::Prefix(prefix) => {
|
||||
if let Prefix::UNC(server, share) = prefix.kind() {
|
||||
// Prefix::UNC is a parsed version of '\\server\share'
|
||||
out.push(path_separator);
|
||||
out.push(path_separator);
|
||||
out.push(server);
|
||||
out.push(path_separator);
|
||||
out.push(share);
|
||||
} else {
|
||||
// All other Windows prefix types are rendered as-is. This results in e.g. "C:" for
|
||||
// drive letters. DeviceNS and Verbatim* prefixes won't have backslashes converted,
|
||||
// but they're not returned by directories fd can search anyway so we don't worry
|
||||
// about them.
|
||||
out.push(comp.as_os_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Root directory is always replaced with the custom separator.
|
||||
Component::RootDir => out.push(path_separator),
|
||||
|
||||
// Everything else is joined normally, with a trailing separator if we're not last
|
||||
_ => {
|
||||
out.push(comp.as_os_str());
|
||||
if components.peek().is_some() {
|
||||
out.push(path_separator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Cow::Owned(out)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -238,7 +302,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_with_placeholder() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&[&"echo", &"${SHELL}:"]),
|
||||
CommandTemplate::new(&[&"echo", &"${SHELL}:"], None),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
@@ -246,6 +310,7 @@ mod tests {
|
||||
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -253,13 +318,14 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_with_no_extension() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{.}"]),
|
||||
CommandTemplate::new(&["echo", "{.}"], None),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::NoExt]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -267,13 +333,14 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_with_basename() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{/}"]),
|
||||
CommandTemplate::new(&["echo", "{/}"], None),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::Basename]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -281,13 +348,14 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_with_parent() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{//}"]),
|
||||
CommandTemplate::new(&["echo", "{//}"], None),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::Parent]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -295,13 +363,14 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_with_basename_no_extension() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["echo", "{/.}"]),
|
||||
CommandTemplate::new(&["echo", "{/.}"], None),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -309,7 +378,7 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_multiple() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new(&["cp", "{}", "{/.}.ext"]),
|
||||
CommandTemplate::new(&["cp", "{}", "{/.}.ext"], None),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("cp".into()),
|
||||
@@ -320,6 +389,7 @@ mod tests {
|
||||
]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -327,19 +397,62 @@ mod tests {
|
||||
#[test]
|
||||
fn tokens_single_batch() {
|
||||
assert_eq!(
|
||||
CommandTemplate::new_batch(&["echo", "{.}"]).unwrap(),
|
||||
CommandTemplate::new_batch(&["echo", "{.}"], None).unwrap(),
|
||||
CommandTemplate {
|
||||
args: vec![
|
||||
ArgumentTemplate::Text("echo".into()),
|
||||
ArgumentTemplate::Tokens(vec![Token::NoExt]),
|
||||
],
|
||||
mode: ExecutionMode::Batch,
|
||||
path_separator: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokens_multiple_batch() {
|
||||
assert!(CommandTemplate::new_batch(&["echo", "{.}", "{}"]).is_err());
|
||||
assert!(CommandTemplate::new_batch(&["echo", "{.}", "{}"], None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_custom_path_separator() {
|
||||
let arg = ArgumentTemplate::Tokens(vec![Token::Placeholder]);
|
||||
macro_rules! check {
|
||||
($input:expr, $expected:expr) => {
|
||||
assert_eq!(arg.generate($input, Some("#")), OsString::from($expected));
|
||||
};
|
||||
}
|
||||
|
||||
check!("foo", "foo");
|
||||
check!("foo/bar", "foo#bar");
|
||||
check!("/foo/bar/baz", "#foo#bar#baz");
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn generate_custom_path_separator_windows() {
|
||||
let arg = ArgumentTemplate::Tokens(vec![Token::Placeholder]);
|
||||
macro_rules! check {
|
||||
($input:expr, $expected:expr) => {
|
||||
assert_eq!(arg.generate($input, Some("#")), OsString::from($expected));
|
||||
};
|
||||
}
|
||||
|
||||
// path starting with a drive letter
|
||||
check!(r"C:\foo\bar", "C:#foo#bar");
|
||||
// UNC path
|
||||
check!(r"\\server\share\path", "##server#share#path");
|
||||
// Drive Relative path - no separator after the colon omits the RootDir path component.
|
||||
// This is uncommon, but valid
|
||||
check!(r"C:foo\bar", "C:foo#bar");
|
||||
|
||||
// forward slashes should get normalized and interpreted as separators
|
||||
check!("C:/foo/bar", "C:#foo#bar");
|
||||
check!("C:foo/bar", "C:foo#bar");
|
||||
|
||||
// Rust does not interpret "//server/share" as a UNC path, but rather as a normal
|
||||
// absolute path that begins with RootDir, and the two slashes get combined together as
|
||||
// a single path separator during normalization.
|
||||
//check!("//server/share/path", "##server#share#path");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
/// Designates what should be written to a buffer
|
||||
|
||||
+84
-3
@@ -1,13 +1,94 @@
|
||||
use std::process;
|
||||
|
||||
#[cfg(unix)]
|
||||
use nix::sys::signal::{raise, signal, SigHandler, Signal};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ExitCode {
|
||||
Success,
|
||||
HasResults(bool),
|
||||
GeneralError,
|
||||
KilledBySigint,
|
||||
}
|
||||
|
||||
impl Into<i32> for ExitCode {
|
||||
fn into(self) -> i32 {
|
||||
match self {
|
||||
impl From<ExitCode> for i32 {
|
||||
fn from(code: ExitCode) -> Self {
|
||||
match code {
|
||||
ExitCode::Success => 0,
|
||||
ExitCode::HasResults(has_results) => !has_results as i32,
|
||||
ExitCode::GeneralError => 1,
|
||||
ExitCode::KilledBySigint => 130,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExitCode {
|
||||
fn is_error(self) -> bool {
|
||||
i32::from(self) != 0
|
||||
}
|
||||
|
||||
/// Exit the process with the appropriate code.
|
||||
pub fn exit(self) -> ! {
|
||||
#[cfg(unix)]
|
||||
if self == ExitCode::KilledBySigint {
|
||||
// Get rid of the SIGINT handler, if present, and raise SIGINT
|
||||
unsafe {
|
||||
if signal(Signal::SIGINT, SigHandler::SigDfl).is_ok() {
|
||||
let _ = raise(Signal::SIGINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process::exit(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge_exitcodes(results: impl IntoIterator<Item = ExitCode>) -> ExitCode {
|
||||
if results.into_iter().any(ExitCode::is_error) {
|
||||
return ExitCode::GeneralError;
|
||||
}
|
||||
ExitCode::Success
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn success_when_no_results() {
|
||||
assert_eq!(merge_exitcodes([]), ExitCode::Success);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn general_error_if_at_least_one_error() {
|
||||
assert_eq!(
|
||||
merge_exitcodes([ExitCode::GeneralError]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes([ExitCode::KilledBySigint]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes([ExitCode::KilledBySigint, ExitCode::Success]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes([ExitCode::Success, ExitCode::GeneralError]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
assert_eq!(
|
||||
merge_exitcodes([ExitCode::GeneralError, ExitCode::KilledBySigint]),
|
||||
ExitCode::GeneralError
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn success_if_no_error() {
|
||||
assert_eq!(merge_exitcodes([ExitCode::Success]), ExitCode::Success);
|
||||
assert_eq!(
|
||||
merge_exitcodes([ExitCode::Success, ExitCode::Success]),
|
||||
ExitCode::Success
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
use std::os::unix::fs::{FileTypeExt, PermissionsExt};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use normpath::PathExt;
|
||||
|
||||
use crate::walk;
|
||||
|
||||
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
|
||||
if path.is_absolute() {
|
||||
return Ok(path.to_path_buf());
|
||||
}
|
||||
|
||||
let path = path.strip_prefix(".").unwrap_or(path);
|
||||
env::current_dir().map(|path_buf| path_buf.join(path))
|
||||
}
|
||||
|
||||
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
|
||||
let path_buf = path_absolute_form(path)?;
|
||||
|
||||
#[cfg(windows)]
|
||||
let path_buf = Path::new(
|
||||
path_buf
|
||||
.as_path()
|
||||
.to_string_lossy()
|
||||
.trim_start_matches(r"\\?\"),
|
||||
)
|
||||
.to_path_buf();
|
||||
|
||||
Ok(path_buf)
|
||||
}
|
||||
|
||||
pub fn is_existing_directory(path: &Path) -> bool {
|
||||
// Note: we do not use `.exists()` here, as `.` always exists, even if
|
||||
// the CWD has been deleted.
|
||||
path.is_dir() && (path.file_name().is_some() || path.normalize().is_ok())
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn is_executable(md: &fs::Metadata) -> bool {
|
||||
md.permissions().mode() & 0o111 != 0
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn is_executable(_: &fs::Metadata) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_empty(entry: &walk::DirEntry) -> bool {
|
||||
if let Some(file_type) = entry.file_type() {
|
||||
if file_type.is_dir() {
|
||||
if let Ok(mut entries) = fs::read_dir(entry.path()) {
|
||||
entries.next().is_none()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else if file_type.is_file() {
|
||||
entry.metadata().map(|m| m.len() == 0).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn is_socket(ft: fs::FileType) -> bool {
|
||||
ft.is_socket()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn is_socket(_: fs::FileType) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn is_pipe(ft: fs::FileType) -> bool {
|
||||
ft.is_fifo()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn is_pipe(_: fs::FileType) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn osstr_to_bytes(input: &OsStr) -> Cow<[u8]> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
Cow::Borrowed(input.as_bytes())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn osstr_to_bytes(input: &OsStr) -> Cow<[u8]> {
|
||||
let string = input.to_string_lossy();
|
||||
|
||||
match string {
|
||||
Cow::Owned(string) => Cow::Owned(string.into_bytes()),
|
||||
Cow::Borrowed(string) => Cow::Borrowed(string.as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the `./` prefix from a path.
|
||||
pub fn strip_current_dir(path: &Path) -> &Path {
|
||||
path.strip_prefix(".").unwrap_or(path)
|
||||
}
|
||||
|
||||
/// Default value for the path_separator, mainly for MSYS/MSYS2, which set the MSYSTEM
|
||||
/// environment variable, and we set fd's path separator to '/' rather than Rust's default of '\'.
|
||||
///
|
||||
/// Returns Some to use a nonstandard path separator, or None to use rust's default on the target
|
||||
/// platform.
|
||||
pub fn default_path_separator() -> Option<String> {
|
||||
if cfg!(windows) {
|
||||
let msystem = env::var("MSYSTEM").ok()?;
|
||||
match msystem.as_str() {
|
||||
"MINGW64" | "MINGW32" | "MSYS" => Some("/".to_owned()),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::strip_current_dir;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn strip_current_dir_basic() {
|
||||
assert_eq!(strip_current_dir(Path::new("./foo")), Path::new("foo"));
|
||||
assert_eq!(strip_current_dir(Path::new("foo")), Path::new("foo"));
|
||||
assert_eq!(
|
||||
strip_current_dir(Path::new("./foo/bar/baz")),
|
||||
Path::new("foo/bar/baz")
|
||||
);
|
||||
assert_eq!(
|
||||
strip_current_dir(Path::new("foo/bar/baz")),
|
||||
Path::new("foo/bar/baz")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use crate::filesystem;
|
||||
use crate::walk;
|
||||
|
||||
/// Whether or not to show
|
||||
#[derive(Default)]
|
||||
pub struct FileTypes {
|
||||
pub files: bool,
|
||||
pub directories: bool,
|
||||
pub symlinks: bool,
|
||||
pub sockets: bool,
|
||||
pub pipes: bool,
|
||||
pub executables_only: bool,
|
||||
pub empty_only: bool,
|
||||
}
|
||||
|
||||
impl FileTypes {
|
||||
pub fn should_ignore(&self, entry: &walk::DirEntry) -> bool {
|
||||
if let Some(ref entry_type) = entry.file_type() {
|
||||
(!self.files && entry_type.is_file())
|
||||
|| (!self.directories && entry_type.is_dir())
|
||||
|| (!self.symlinks && entry_type.is_symlink())
|
||||
|| (!self.sockets && filesystem::is_socket(*entry_type))
|
||||
|| (!self.pipes && filesystem::is_pipe(*entry_type))
|
||||
|| (self.executables_only
|
||||
&& !entry
|
||||
.metadata()
|
||||
.map(filesystem::is_executable)
|
||||
.unwrap_or(false))
|
||||
|| (self.empty_only && !filesystem::is_empty(entry))
|
||||
|| !(entry_type.is_file()
|
||||
|| entry_type.is_dir()
|
||||
|| entry_type.is_symlink()
|
||||
|| filesystem::is_socket(*entry_type)
|
||||
|| filesystem::is_pipe(*entry_type))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
pub use self::size::SizeFilter;
|
||||
pub use self::time::TimeFilter;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use self::owner::OwnerFilter;
|
||||
|
||||
mod size;
|
||||
mod time;
|
||||
|
||||
#[cfg(unix)]
|
||||
mod owner;
|
||||
@@ -0,0 +1,123 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::fs;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct OwnerFilter {
|
||||
uid: Check<u32>,
|
||||
gid: Check<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum Check<T> {
|
||||
Equal(T),
|
||||
NotEq(T),
|
||||
Ignore,
|
||||
}
|
||||
|
||||
impl OwnerFilter {
|
||||
/// Parses an owner constraint
|
||||
/// Returns an error if the string is invalid
|
||||
/// Returns Ok(None) when string is acceptable but a noop (such as "" or ":")
|
||||
pub fn from_string(input: &str) -> Result<Option<Self>> {
|
||||
let mut it = input.split(':');
|
||||
let (fst, snd) = (it.next(), it.next());
|
||||
|
||||
if it.next().is_some() {
|
||||
return Err(anyhow!(
|
||||
"more than one ':' present in owner string '{}'. See 'fd --help'.",
|
||||
input
|
||||
));
|
||||
}
|
||||
|
||||
let uid = Check::parse(fst, |s| {
|
||||
s.parse()
|
||||
.ok()
|
||||
.or_else(|| users::get_user_by_name(s).map(|user| user.uid()))
|
||||
.ok_or_else(|| anyhow!("'{}' is not a recognized user name", s))
|
||||
})?;
|
||||
let gid = Check::parse(snd, |s| {
|
||||
s.parse()
|
||||
.ok()
|
||||
.or_else(|| users::get_group_by_name(s).map(|group| group.gid()))
|
||||
.ok_or_else(|| anyhow!("'{}' is not a recognized group name", s))
|
||||
})?;
|
||||
|
||||
if let (Check::Ignore, Check::Ignore) = (uid, gid) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(OwnerFilter { uid, gid }))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches(&self, md: &fs::Metadata) -> bool {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
self.uid.check(md.uid()) && self.gid.check(md.gid())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> Check<T> {
|
||||
fn check(&self, v: T) -> bool {
|
||||
match self {
|
||||
Check::Equal(x) => v == *x,
|
||||
Check::NotEq(x) => v != *x,
|
||||
Check::Ignore => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse<F>(s: Option<&str>, f: F) -> Result<Self>
|
||||
where
|
||||
F: Fn(&str) -> Result<T>,
|
||||
{
|
||||
let (s, equality) = match s {
|
||||
Some("") | None => return Ok(Check::Ignore),
|
||||
Some(s) if s.starts_with('!') => (&s[1..], false),
|
||||
Some(s) => (s, true),
|
||||
};
|
||||
|
||||
f(s).map(|x| {
|
||||
if equality {
|
||||
Check::Equal(x)
|
||||
} else {
|
||||
Check::NotEq(x)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod owner_parsing {
|
||||
use super::OwnerFilter;
|
||||
|
||||
macro_rules! owner_tests {
|
||||
($($name:ident: $value:expr => $result:pat,)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
let o = OwnerFilter::from_string($value);
|
||||
match o {
|
||||
$result => {},
|
||||
_ => panic!("{:?} does not match {}", o, stringify!($result)),
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
use super::Check::*;
|
||||
owner_tests! {
|
||||
empty: "" => Ok(None),
|
||||
uid_only: "5" => Ok(Some(OwnerFilter { uid: Equal(5), gid: Ignore })),
|
||||
uid_gid: "9:3" => Ok(Some(OwnerFilter { uid: Equal(9), gid: Equal(3) })),
|
||||
gid_only: ":8" => Ok(Some(OwnerFilter { uid: Ignore, gid: Equal(8) })),
|
||||
colon_only: ":" => Ok(None),
|
||||
trailing: "5:" => Ok(Some(OwnerFilter { uid: Equal(5), gid: Ignore })),
|
||||
|
||||
uid_negate: "!5" => Ok(Some(OwnerFilter { uid: NotEq(5), gid: Ignore })),
|
||||
both_negate:"!4:!3" => Ok(Some(OwnerFilter { uid: NotEq(4), gid: NotEq(3) })),
|
||||
uid_not_gid:"6:!8" => Ok(Some(OwnerFilter { uid: Equal(6), gid: NotEq(8) })),
|
||||
|
||||
more_colons:"3:5:" => Err(_),
|
||||
only_colons:"::" => Err(_),
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
use lazy_static::lazy_static;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static! {
|
||||
static ref SIZE_CAPTURES: Regex = { Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap() };
|
||||
}
|
||||
static SIZE_CAPTURES: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"(?i)^([+-]?)(\d+)(b|[kmgt]i?b?)$").unwrap());
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum SizeFilter {
|
||||
Max(u64),
|
||||
Min(u64),
|
||||
Equals(u64),
|
||||
}
|
||||
|
||||
// SI prefixes (powers of 10)
|
||||
@@ -37,28 +37,31 @@ impl SizeFilter {
|
||||
|
||||
let multiplier = match &captures.get(3).map_or("b", |m| m.as_str()).to_lowercase()[..] {
|
||||
v if v.starts_with("ki") => KIBI,
|
||||
v if v.starts_with("k") => KILO,
|
||||
v if v.starts_with('k') => KILO,
|
||||
v if v.starts_with("mi") => MEBI,
|
||||
v if v.starts_with("m") => MEGA,
|
||||
v if v.starts_with('m') => MEGA,
|
||||
v if v.starts_with("gi") => GIBI,
|
||||
v if v.starts_with("g") => GIGA,
|
||||
v if v.starts_with('g') => GIGA,
|
||||
v if v.starts_with("ti") => TEBI,
|
||||
v if v.starts_with("t") => TERA,
|
||||
v if v.starts_with('t') => TERA,
|
||||
"b" => 1,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let size = quantity * multiplier;
|
||||
Some(match limit_kind {
|
||||
"+" => SizeFilter::Min(size),
|
||||
_ => SizeFilter::Max(size),
|
||||
})
|
||||
match limit_kind {
|
||||
"+" => Some(SizeFilter::Min(size)),
|
||||
"-" => Some(SizeFilter::Max(size)),
|
||||
"" => Some(SizeFilter::Equals(size)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_within(&self, size: u64) -> bool {
|
||||
match *self {
|
||||
SizeFilter::Max(limit) => size <= limit,
|
||||
SizeFilter::Min(limit) => size >= limit,
|
||||
SizeFilter::Equals(limit) => size == limit,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,8 +83,8 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
/// Parsing and size conversion tests data. Ensure that each type gets properly interpreted.
|
||||
/// Call with higher base values to ensure expected multiplication (only need a couple)
|
||||
// Parsing and size conversion tests data. Ensure that each type gets properly interpreted.
|
||||
// Call with higher base values to ensure expected multiplication (only need a couple)
|
||||
gen_size_filter_parse_test! {
|
||||
byte_plus: ("+1b", SizeFilter::Min(1)),
|
||||
byte_plus_multiplier: ("+10b", SizeFilter::Min(10)),
|
||||
@@ -89,70 +92,70 @@ mod tests {
|
||||
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_multiplier: ("-100k", SizeFilter::Max(100_000)),
|
||||
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_multiplier: ("+10ki", SizeFilter::Min(10_240)),
|
||||
kibi_plus_suffix: ("+1kib", SizeFilter::Min(1024)),
|
||||
kibi_minus: ("-1ki", SizeFilter::Max(1024)),
|
||||
kibi_minus_multiplier: ("-100ki", SizeFilter::Max(102400)),
|
||||
kibi_minus_multiplier: ("-100ki", SizeFilter::Max(102_400)),
|
||||
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)),
|
||||
mega_plus: ("+1m", SizeFilter::Min(1_000_000)),
|
||||
mega_plus_suffix: ("+1mb", SizeFilter::Min(1_000_000)),
|
||||
mega_minus: ("-1m", SizeFilter::Max(1_000_000)),
|
||||
mega_minus_suffix: ("-1mb", SizeFilter::Max(1_000_000)),
|
||||
mega_plus_upper: ("+1M", SizeFilter::Min(1_000_000)),
|
||||
mega_plus_suffix_upper: ("+1MB", SizeFilter::Min(1_000_000)),
|
||||
mega_minus_upper: ("-1M", SizeFilter::Max(1_000_000)),
|
||||
mega_minus_suffix_upper: ("-1Mb", SizeFilter::Max(1_000_000)),
|
||||
mebi_plus: ("+1mi", SizeFilter::Min(1_048_576)),
|
||||
mebi_plus_suffix: ("+1mib", SizeFilter::Min(1_048_576)),
|
||||
mebi_minus: ("-1mi", SizeFilter::Max(1_048_576)),
|
||||
mebi_minus_suffix: ("-1mib", SizeFilter::Max(1_048_576)),
|
||||
mebi_plus_upper: ("+1MI", SizeFilter::Min(1_048_576)),
|
||||
mebi_plus_suffix_upper: ("+1MiB", SizeFilter::Min(1_048_576)),
|
||||
mebi_minus_upper: ("-1Mi", SizeFilter::Max(1_048_576)),
|
||||
mebi_minus_suffix_upper: ("-1MIB", SizeFilter::Max(1_048_576)),
|
||||
giga_plus: ("+1g", SizeFilter::Min(1_000_000_000)),
|
||||
giga_plus_suffix: ("+1gb", SizeFilter::Min(1_000_000_000)),
|
||||
giga_minus: ("-1g", SizeFilter::Max(1_000_000_000)),
|
||||
giga_minus_suffix: ("-1gb", SizeFilter::Max(1_000_000_000)),
|
||||
giga_plus_upper: ("+1G", SizeFilter::Min(1_000_000_000)),
|
||||
giga_plus_suffix_upper: ("+1GB", SizeFilter::Min(1_000_000_000)),
|
||||
giga_minus_upper: ("-1G", SizeFilter::Max(1_000_000_000)),
|
||||
giga_minus_suffix_upper: ("-1Gb", SizeFilter::Max(1_000_000_000)),
|
||||
gibi_plus: ("+1gi", SizeFilter::Min(1_073_741_824)),
|
||||
gibi_plus_suffix: ("+1gib", SizeFilter::Min(1_073_741_824)),
|
||||
gibi_minus: ("-1gi", SizeFilter::Max(1_073_741_824)),
|
||||
gibi_minus_suffix: ("-1gib", SizeFilter::Max(1_073_741_824)),
|
||||
gibi_plus_upper: ("+1GI", SizeFilter::Min(1_073_741_824)),
|
||||
gibi_plus_suffix_upper: ("+1GiB", SizeFilter::Min(1_073_741_824)),
|
||||
gibi_minus_upper: ("-1Gi", SizeFilter::Max(1_073_741_824)),
|
||||
gibi_minus_suffix_upper: ("-1GIB", SizeFilter::Max(1_073_741_824)),
|
||||
tera_plus: ("+1t", SizeFilter::Min(1_000_000_000_000)),
|
||||
tera_plus_suffix: ("+1tb", SizeFilter::Min(1_000_000_000_000)),
|
||||
tera_minus: ("-1t", SizeFilter::Max(1_000_000_000_000)),
|
||||
tera_minus_suffix: ("-1tb", SizeFilter::Max(1_000_000_000_000)),
|
||||
tera_plus_upper: ("+1T", SizeFilter::Min(1_000_000_000_000)),
|
||||
tera_plus_suffix_upper: ("+1TB", SizeFilter::Min(1_000_000_000_000)),
|
||||
tera_minus_upper: ("-1T", SizeFilter::Max(1_000_000_000_000)),
|
||||
tera_minus_suffix_upper: ("-1Tb", SizeFilter::Max(1_000_000_000_000)),
|
||||
tebi_plus: ("+1ti", SizeFilter::Min(1_099_511_627_776)),
|
||||
tebi_plus_suffix: ("+1tib", SizeFilter::Min(1_099_511_627_776)),
|
||||
tebi_minus: ("-1ti", SizeFilter::Max(1_099_511_627_776)),
|
||||
tebi_minus_suffix: ("-1tib", SizeFilter::Max(1_099_511_627_776)),
|
||||
tebi_plus_upper: ("+1TI", SizeFilter::Min(1_099_511_627_776)),
|
||||
tebi_plus_suffix_upper: ("+1TiB", SizeFilter::Min(1_099_511_627_776)),
|
||||
tebi_minus_upper: ("-1Ti", SizeFilter::Max(1_099_511_627_776)),
|
||||
tebi_minus_suffix_upper: ("-1TIB", SizeFilter::Max(1_099_511_627_776)),
|
||||
}
|
||||
|
||||
/// Invalid parse testing
|
||||
@@ -168,9 +171,8 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
/// Invalid parse data
|
||||
// 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",
|
||||
@@ -1,3 +1,5 @@
|
||||
use chrono::{offset::TimeZone, DateTime, Local, NaiveDate};
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
/// Filter based on time ranges.
|
||||
@@ -9,11 +11,22 @@ pub enum TimeFilter {
|
||||
|
||||
impl TimeFilter {
|
||||
fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
|
||||
use humantime;
|
||||
humantime::parse_duration(s)
|
||||
.map(|duration| *ref_time - duration)
|
||||
.or_else(|_| humantime::parse_rfc3339_weak(s))
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
DateTime::parse_from_rfc3339(s)
|
||||
.map(|dt| dt.into())
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
NaiveDate::parse_from_str(s, "%F")
|
||||
.map(|nd| nd.and_hms(0, 0, 0))
|
||||
.ok()
|
||||
.and_then(|ndt| Local.from_local_datetime(&ndt).single())
|
||||
})
|
||||
.or_else(|| Local.datetime_from_str(s, "%F %T").ok())
|
||||
.map(|dt| dt.into())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn before(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
|
||||
@@ -26,8 +39,8 @@ impl TimeFilter {
|
||||
|
||||
pub fn applies_to(&self, t: &SystemTime) -> bool {
|
||||
match self {
|
||||
TimeFilter::Before(limit) => t <= limit,
|
||||
TimeFilter::After(limit) => t >= limit,
|
||||
TimeFilter::Before(limit) => t < limit,
|
||||
TimeFilter::After(limit) => t > limit,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,9 +52,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn is_time_filter_applicable() {
|
||||
use humantime;
|
||||
let ref_time = Local
|
||||
.datetime_from_str("2010-10-10 10:10:10", "%F %T")
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let ref_time = humantime::parse_rfc3339("2010-10-10T10:10:10Z").unwrap();
|
||||
assert!(TimeFilter::after(&ref_time, "1min")
|
||||
.unwrap()
|
||||
.applies_to(&ref_time));
|
||||
@@ -78,5 +93,39 @@ mod tests {
|
||||
assert!(!TimeFilter::after(&ref_time, t10s_before)
|
||||
.unwrap()
|
||||
.applies_to(&t1m_ago));
|
||||
|
||||
let same_day = "2010-10-10";
|
||||
assert!(!TimeFilter::before(&ref_time, same_day)
|
||||
.unwrap()
|
||||
.applies_to(&ref_time));
|
||||
assert!(!TimeFilter::before(&ref_time, same_day)
|
||||
.unwrap()
|
||||
.applies_to(&t1m_ago));
|
||||
|
||||
assert!(TimeFilter::after(&ref_time, same_day)
|
||||
.unwrap()
|
||||
.applies_to(&ref_time));
|
||||
assert!(TimeFilter::after(&ref_time, same_day)
|
||||
.unwrap()
|
||||
.applies_to(&t1m_ago));
|
||||
|
||||
let ref_time = DateTime::parse_from_rfc3339("2010-10-10T10:10:10+00:00")
|
||||
.unwrap()
|
||||
.into();
|
||||
let t1m_ago = ref_time - Duration::from_secs(60);
|
||||
let t10s_before = "2010-10-10T10:10:00+00: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));
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::env::current_dir;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use ignore::DirEntry;
|
||||
|
||||
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
|
||||
if path.is_absolute() {
|
||||
return Ok(path.to_path_buf());
|
||||
}
|
||||
|
||||
let path = path.strip_prefix(".").unwrap_or(path);
|
||||
current_dir().map(|path_buf| path_buf.join(path))
|
||||
}
|
||||
|
||||
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
|
||||
let path_buf = path_absolute_form(path)?;
|
||||
|
||||
#[cfg(windows)]
|
||||
let path_buf = Path::new(
|
||||
path_buf
|
||||
.as_path()
|
||||
.to_string_lossy()
|
||||
.trim_left_matches(r"\\?\"),
|
||||
)
|
||||
.to_path_buf();
|
||||
|
||||
Ok(path_buf)
|
||||
}
|
||||
|
||||
// Path::is_dir() is not guaranteed to be intuitively correct for "." and ".."
|
||||
// See: https://github.com/rust-lang/rust/issues/45302
|
||||
pub fn is_dir(path: &Path) -> bool {
|
||||
path.is_dir() && (path.file_name().is_some() || path.canonicalize().is_ok())
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn is_executable(md: &fs::Metadata) -> bool {
|
||||
md.permissions().mode() & 0o111 != 0
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn is_executable(_: &fs::Metadata) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_empty(entry: &DirEntry) -> bool {
|
||||
if let Some(file_type) = entry.file_type() {
|
||||
if file_type.is_dir() {
|
||||
if let Ok(mut entries) = fs::read_dir(entry.path()) {
|
||||
entries.next().is_none()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else if file_type.is_file() {
|
||||
entry.metadata().map(|m| m.len() == 0).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use regex_syntax::hir::Hir;
|
||||
use regex_syntax::Parser;
|
||||
use std::ffi::OsString;
|
||||
|
||||
pub use self::file_types::FileTypes;
|
||||
|
||||
mod file_types;
|
||||
pub mod filter;
|
||||
pub mod opts;
|
||||
|
||||
macro_rules! print_error {
|
||||
($($arg:tt)*) => (eprintln!("[fd error]: {}", format!($($arg)*)))
|
||||
}
|
||||
|
||||
macro_rules! print_error_and_exit {
|
||||
($($arg:tt)*) => {
|
||||
print_error!($($arg)*);
|
||||
::std::process::exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
/// Determine if a regex pattern contains a literal uppercase character.
|
||||
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
|
||||
Parser::new()
|
||||
.parse(pattern)
|
||||
.map(|hir| hir_has_uppercase_char(&hir))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Determine if a regex expression contains a literal uppercase character.
|
||||
fn hir_has_uppercase_char(hir: &Hir) -> bool {
|
||||
use regex_syntax::hir::*;
|
||||
|
||||
match *hir.kind() {
|
||||
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
|
||||
HirKind::Class(Class::Unicode(ref ranges)) => ranges
|
||||
.iter()
|
||||
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
|
||||
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
|
||||
hir_has_uppercase_char(hir)
|
||||
}
|
||||
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
|
||||
hirs.iter().any(hir_has_uppercase_char)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum size of the output buffer before flushing results to the console
|
||||
pub const MAX_BUFFER_LENGTH: usize = 1000;
|
||||
|
||||
/// Traverse args_os, looking for -exec and replacing it with --exec.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * The args, with substitution if required
|
||||
pub fn transform_args_with_exec<I>(original: I) -> Vec<OsString>
|
||||
where
|
||||
I: Iterator<Item = OsString>,
|
||||
{
|
||||
let mut in_exec_opt = false;
|
||||
let target = OsString::from("-exec");
|
||||
let long_start = OsString::from("--exec");
|
||||
let short_start = OsString::from("-x");
|
||||
let exec_end = OsString::from(";");
|
||||
|
||||
original.fold(vec![], |mut args, curr| {
|
||||
if in_exec_opt {
|
||||
if curr == exec_end {
|
||||
in_exec_opt = false;
|
||||
}
|
||||
args.push(curr);
|
||||
return args;
|
||||
}
|
||||
|
||||
if curr == target || curr == long_start || curr == short_start {
|
||||
args.push(if curr == target {
|
||||
OsString::from("--exec")
|
||||
} else {
|
||||
curr
|
||||
});
|
||||
in_exec_opt = true;
|
||||
} else {
|
||||
args.push(curr);
|
||||
}
|
||||
args
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn oss_vec(strs: &[&str]) -> Vec<OsString> {
|
||||
strs.into_iter().map(OsString::from).collect()
|
||||
}
|
||||
|
||||
/// Ensure that -exec gets transformed into --exec
|
||||
#[test]
|
||||
fn normal_exec_substitution() {
|
||||
let original = oss_vec(&["fd", "foo", "-exec", "cmd"]);
|
||||
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||
let actual = transform_args_with_exec(original.into_iter());
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
/// Ensure that --exec is not touched
|
||||
#[test]
|
||||
fn passthru_of_original_exec() {
|
||||
let original = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||
let expected = oss_vec(&["fd", "foo", "--exec", "cmd"]);
|
||||
let actual = transform_args_with_exec(original.into_iter());
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
#[test]
|
||||
fn temp_check_that_exec_context_observed() {
|
||||
let original = oss_vec(&[
|
||||
"fd",
|
||||
"foo",
|
||||
"-exec",
|
||||
"cmd",
|
||||
"-exec",
|
||||
"ls",
|
||||
";",
|
||||
"-exec",
|
||||
"rm",
|
||||
";",
|
||||
"--exec",
|
||||
"find",
|
||||
"-exec",
|
||||
"rm",
|
||||
";",
|
||||
"-x",
|
||||
"foo",
|
||||
"-exec",
|
||||
"something",
|
||||
";",
|
||||
"-exec",
|
||||
]);
|
||||
let expected = oss_vec(&[
|
||||
"fd",
|
||||
"foo",
|
||||
"--exec",
|
||||
"cmd",
|
||||
"-exec",
|
||||
"ls",
|
||||
";",
|
||||
"--exec",
|
||||
"rm",
|
||||
";",
|
||||
"--exec",
|
||||
"find",
|
||||
"-exec",
|
||||
"rm",
|
||||
";",
|
||||
"-x",
|
||||
"foo",
|
||||
"-exec",
|
||||
"something",
|
||||
";",
|
||||
"--exec",
|
||||
]);
|
||||
let actual = transform_args_with_exec(original.into_iter());
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
}
|
||||
+454
-148
@@ -1,169 +1,258 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
#[macro_use]
|
||||
mod internal;
|
||||
|
||||
mod app;
|
||||
mod config;
|
||||
mod error;
|
||||
mod exec;
|
||||
mod exit_codes;
|
||||
pub mod fshelper;
|
||||
mod filesystem;
|
||||
mod filetypes;
|
||||
mod filter;
|
||||
mod output;
|
||||
mod regex_helper;
|
||||
mod walk;
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use atty::Stream;
|
||||
use globset::GlobBuilder;
|
||||
use lscolors::LsColors;
|
||||
use regex::{RegexBuilder, RegexSetBuilder};
|
||||
use normpath::PathExt;
|
||||
use regex::bytes::{RegexBuilder, RegexSetBuilder};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::print_error;
|
||||
use crate::exec::CommandTemplate;
|
||||
use crate::internal::{
|
||||
filter::{SizeFilter, TimeFilter},
|
||||
opts::FdOptions,
|
||||
pattern_has_uppercase_char, transform_args_with_exec, FileTypes,
|
||||
};
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::filetypes::FileTypes;
|
||||
#[cfg(unix)]
|
||||
use crate::filter::OwnerFilter;
|
||||
use crate::filter::{SizeFilter, TimeFilter};
|
||||
use crate::regex_helper::{pattern_has_uppercase_char, pattern_matches_strings_with_leading_dot};
|
||||
|
||||
// We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481
|
||||
// FIXME: re-enable jemalloc on macOS, see comment in Cargo.toml file for more infos
|
||||
#[cfg(all(
|
||||
not(windows),
|
||||
not(target_os = "android"),
|
||||
not(target_os = "macos"),
|
||||
not(target_os = "freebsd"),
|
||||
not(target_env = "musl"),
|
||||
not(target_arch = "riscv64"),
|
||||
feature = "use-jemalloc"
|
||||
))]
|
||||
#[global_allocator]
|
||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||
|
||||
// vivid --color-mode 8-bit generate molokai
|
||||
const DEFAULT_LS_COLORS: &str = "
|
||||
ow=0:or=0;38;5;16;48;5;203:no=0:ex=1;38;5;203:cd=0;38;5;203;48;5;236:mi=0;38;5;16;48;5;203:*~=0;38;5;243:st=0:pi=0;38;5;16;48;5;81:fi=0:di=0;38;5;81:so=0;38;5;16;48;5;203:bd=0;38;5;81;48;5;236:tw=0:ln=0;38;5;203:*.m=0;38;5;48:*.o=0;38;5;243:*.z=4;38;5;203:*.a=1;38;5;203:*.r=0;38;5;48:*.c=0;38;5;48:*.d=0;38;5;48:*.t=0;38;5;48:*.h=0;38;5;48:*.p=0;38;5;48:*.cc=0;38;5;48:*.ll=0;38;5;48:*.jl=0;38;5;48:*css=0;38;5;48:*.md=0;38;5;185:*.gz=4;38;5;203:*.nb=0;38;5;48:*.mn=0;38;5;48:*.go=0;38;5;48:*.xz=4;38;5;203:*.so=1;38;5;203:*.rb=0;38;5;48:*.pm=0;38;5;48:*.bc=0;38;5;243:*.py=0;38;5;48:*.as=0;38;5;48:*.pl=0;38;5;48:*.rs=0;38;5;48:*.sh=0;38;5;48:*.7z=4;38;5;203:*.ps=0;38;5;186:*.cs=0;38;5;48:*.el=0;38;5;48:*.rm=0;38;5;208:*.hs=0;38;5;48:*.td=0;38;5;48:*.ui=0;38;5;149:*.ex=0;38;5;48:*.js=0;38;5;48:*.cp=0;38;5;48:*.cr=0;38;5;48:*.la=0;38;5;243:*.kt=0;38;5;48:*.ml=0;38;5;48:*.vb=0;38;5;48:*.gv=0;38;5;48:*.lo=0;38;5;243:*.hi=0;38;5;243:*.ts=0;38;5;48:*.ko=1;38;5;203:*.hh=0;38;5;48:*.pp=0;38;5;48:*.di=0;38;5;48:*.bz=4;38;5;203:*.fs=0;38;5;48:*.png=0;38;5;208:*.zsh=0;38;5;48:*.mpg=0;38;5;208:*.pid=0;38;5;243:*.xmp=0;38;5;149:*.iso=4;38;5;203:*.m4v=0;38;5;208:*.dot=0;38;5;48:*.ods=0;38;5;186:*.inc=0;38;5;48:*.sxw=0;38;5;186:*.aif=0;38;5;208:*.git=0;38;5;243:*.gvy=0;38;5;48:*.tbz=4;38;5;203:*.log=0;38;5;243:*.txt=0;38;5;185:*.ico=0;38;5;208:*.csx=0;38;5;48:*.vob=0;38;5;208:*.pgm=0;38;5;208:*.pps=0;38;5;186:*.ics=0;38;5;186:*.img=4;38;5;203:*.fon=0;38;5;208:*.hpp=0;38;5;48:*.bsh=0;38;5;48:*.sql=0;38;5;48:*TODO=1:*.php=0;38;5;48:*.pkg=4;38;5;203:*.ps1=0;38;5;48:*.csv=0;38;5;185:*.ilg=0;38;5;243:*.ini=0;38;5;149:*.pyc=0;38;5;243:*.psd=0;38;5;208:*.htc=0;38;5;48:*.swp=0;38;5;243:*.mli=0;38;5;48:*hgrc=0;38;5;149:*.bst=0;38;5;149:*.ipp=0;38;5;48:*.fsi=0;38;5;48:*.tcl=0;38;5;48:*.exs=0;38;5;48:*.out=0;38;5;243:*.jar=4;38;5;203:*.xls=0;38;5;186:*.ppm=0;38;5;208:*.apk=4;38;5;203:*.aux=0;38;5;243:*.rpm=4;38;5;203:*.dll=1;38;5;203:*.eps=0;38;5;208:*.exe=1;38;5;203:*.doc=0;38;5;186:*.wma=0;38;5;208:*.deb=4;38;5;203:*.pod=0;38;5;48:*.ind=0;38;5;243:*.nix=0;38;5;149:*.lua=0;38;5;48:*.epp=0;38;5;48:*.dpr=0;38;5;48:*.htm=0;38;5;185:*.ogg=0;38;5;208:*.bin=4;38;5;203:*.otf=0;38;5;208:*.yml=0;38;5;149:*.pro=0;38;5;149:*.cxx=0;38;5;48:*.tex=0;38;5;48:*.fnt=0;38;5;208:*.erl=0;38;5;48:*.sty=0;38;5;243:*.bag=4;38;5;203:*.rst=0;38;5;185:*.pdf=0;38;5;186:*.pbm=0;38;5;208:*.xcf=0;38;5;208:*.clj=0;38;5;48:*.gif=0;38;5;208:*.rar=4;38;5;203:*.elm=0;38;5;48:*.bib=0;38;5;149:*.tsx=0;38;5;48:*.dmg=4;38;5;203:*.tmp=0;38;5;243:*.bcf=0;38;5;243:*.mkv=0;38;5;208:*.svg=0;38;5;208:*.cpp=0;38;5;48:*.vim=0;38;5;48:*.bmp=0;38;5;208:*.ltx=0;38;5;48:*.fls=0;38;5;243:*.flv=0;38;5;208:*.wav=0;38;5;208:*.m4a=0;38;5;208:*.mid=0;38;5;208:*.hxx=0;38;5;48:*.pas=0;38;5;48:*.wmv=0;38;5;208:*.tif=0;38;5;208:*.kex=0;38;5;186:*.mp4=0;38;5;208:*.bak=0;38;5;243:*.xlr=0;38;5;186:*.dox=0;38;5;149:*.swf=0;38;5;208:*.tar=4;38;5;203:*.tgz=4;38;5;203:*.cfg=0;38;5;149:*.xml=0;
|
||||
38;5;185:*.jpg=0;38;5;208:*.mir=0;38;5;48:*.sxi=0;38;5;186:*.bz2=4;38;5;203:*.odt=0;38;5;186:*.mov=0;38;5;208:*.toc=0;38;5;243:*.bat=1;38;5;203:*.asa=0;38;5;48:*.awk=0;38;5;48:*.sbt=0;38;5;48:*.vcd=4;38;5;203:*.kts=0;38;5;48:*.arj=4;38;5;203:*.blg=0;38;5;243:*.c++=0;38;5;48:*.odp=0;38;5;186:*.bbl=0;38;5;243:*.idx=0;38;5;243:*.com=1;38;5;203:*.mp3=0;38;5;208:*.avi=0;38;5;208:*.def=0;38;5;48:*.cgi=0;38;5;48:*.zip=4;38;5;203:*.ttf=0;38;5;208:*.ppt=0;38;5;186:*.tml=0;38;5;149:*.fsx=0;38;5;48:*.h++=0;38;5;48:*.rtf=0;38;5;186:*.inl=0;38;5;48:*.yaml=0;38;5;149:*.html=0;38;5;185:*.mpeg=0;38;5;208:*.java=0;38;5;48:*.hgrc=0;38;5;149:*.orig=0;38;5;243:*.conf=0;38;5;149:*.dart=0;38;5;48:*.psm1=0;38;5;48:*.rlib=0;38;5;243:*.fish=0;38;5;48:*.bash=0;38;5;48:*.make=0;38;5;149:*.docx=0;38;5;186:*.json=0;38;5;149:*.psd1=0;38;5;48:*.lisp=0;38;5;48:*.tbz2=4;38;5;203:*.diff=0;38;5;48:*.epub=0;38;5;186:*.xlsx=0;38;5;186:*.pptx=0;38;5;186:*.toml=0;38;5;149:*.h264=0;38;5;208:*.purs=0;38;5;48:*.flac=0;38;5;208:*.tiff=0;38;5;208:*.jpeg=0;38;5;208:*.lock=0;38;5;243:*.less=0;38;5;48:*.dyn_o=0;38;5;243:*.scala=0;38;5;48:*.mdown=0;38;5;185:*.shtml=0;38;5;185:*.class=0;38;5;243:*.cache=0;38;5;243:*.cmake=0;38;5;149:*passwd=0;38;5;149:*.swift=0;38;5;48:*shadow=0;38;5;149:*.xhtml=0;38;5;185:*.patch=0;38;5;48:*.cabal=0;38;5;48:*README=0;38;5;16;48;5;186:*.toast=4;38;5;203:*.ipynb=0;38;5;48:*COPYING=0;38;5;249:*.gradle=0;38;5;48:*.matlab=0;38;5;48:*.config=0;38;5;149:*LICENSE=0;38;5;249:*.dyn_hi=0;38;5;243:*.flake8=0;38;5;149:*.groovy=0;38;5;48:*INSTALL=0;38;5;16;48;5;186:*TODO.md=1:*.ignore=0;38;5;149:*Doxyfile=0;38;5;149:*TODO.txt=1:*setup.py=0;38;5;149:*Makefile=0;38;5;149:*.gemspec=0;38;5;149:*.desktop=0;38;5;149:*.rgignore=0;38;5;149:*.markdown=0;38;5;185:*COPYRIGHT=0;38;5;249:*configure=0;38;5;149:*.DS_Store=0;38;5;243:*.kdevelop=0;38;5;149:*.fdignore=0;38;5;149:*README.md=0;38;5;16;48;5;186:*.cmake.in=0;38;5;149:*SConscript=0;38;5;149:*CODEOWNERS=0;38;5;149:*.localized=0;38;5;243:*.gitignore=0;38;5;149:*Dockerfile=0;38;5;149:*.gitconfig=0;38;5;149:*INSTALL.md=0;38;5;16;48;5;186:*README.txt=0;38;5;16;48;5;186:*SConstruct=0;38;5;149:*.scons_opt=0;38;5;243:*.travis.yml=0;38;5;186:*.gitmodules=0;38;5;149:*.synctex.gz=0;38;5;243:*LICENSE-MIT=0;38;5;249:*MANIFEST.in=0;38;5;149:*Makefile.in=0;38;5;243:*Makefile.am=0;38;5;149:*INSTALL.txt=0;38;5;16;48;5;186:*configure.ac=0;38;5;149:*.applescript=0;38;5;48:*appveyor.yml=0;38;5;186:*.fdb_latexmk=0;38;5;243:*CONTRIBUTORS=0;38;5;16;48;5;186:*.clang-format=0;38;5;149:*LICENSE-APACHE=0;38;5;249:*CMakeLists.txt=0;38;5;149:*CMakeCache.txt=0;38;5;243:*.gitattributes=0;38;5;149:*CONTRIBUTORS.md=0;38;5;16;48;5;186:*.sconsign.dblite=0;38;5;243:*requirements.txt=0;38;5;149:*CONTRIBUTORS.txt=0;38;5;16;48;5;186:*package-lock.json=0;38;5;243:*.CFUserTextEncoding=0;38;5;243
|
||||
";
|
||||
|
||||
fn main() {
|
||||
let checked_args = transform_args_with_exec(env::args_os());
|
||||
let matches = app::build_app().get_matches_from(checked_args);
|
||||
|
||||
// Get the search pattern
|
||||
let pattern = matches.value_of("pattern").unwrap_or("");
|
||||
|
||||
// Get the current working directory
|
||||
let current_dir = Path::new(".");
|
||||
if !fshelper::is_dir(current_dir) {
|
||||
print_error_and_exit!("Could not get current directory.");
|
||||
let result = run();
|
||||
match result {
|
||||
Ok(exit_code) => {
|
||||
exit_code.exit();
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("[fd error]: {:#}", err);
|
||||
ExitCode::GeneralError.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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()],
|
||||
};
|
||||
fn run() -> Result<ExitCode> {
|
||||
let matches = app::build_app().get_matches_from(env::args_os());
|
||||
|
||||
set_working_dir(&matches)?;
|
||||
let current_directory = Path::new(".");
|
||||
ensure_current_directory_exists(current_directory)?;
|
||||
let search_paths = extract_search_paths(&matches, current_directory)?;
|
||||
|
||||
let pattern = extract_search_pattern(&matches)?;
|
||||
ensure_search_pattern_is_not_a_path(&matches, pattern)?;
|
||||
let pattern_regex = build_pattern_regex(&matches, pattern)?;
|
||||
|
||||
let config = construct_config(matches, &pattern_regex)?;
|
||||
ensure_use_hidden_option_for_leading_dot_pattern(&config, &pattern_regex)?;
|
||||
let re = build_regex(pattern_regex, &config)?;
|
||||
walk::scan(&search_paths, Arc::new(re), Arc::new(config))
|
||||
}
|
||||
|
||||
fn set_working_dir(matches: &clap::ArgMatches) -> Result<()> {
|
||||
if let Some(base_directory) = matches.value_of_os("base-directory") {
|
||||
let base_directory = Path::new(base_directory);
|
||||
if !filesystem::is_existing_directory(base_directory) {
|
||||
return Err(anyhow!(
|
||||
"The '--base-directory' path '{}' is not a directory.",
|
||||
base_directory.to_string_lossy()
|
||||
));
|
||||
}
|
||||
env::set_current_dir(base_directory).with_context(|| {
|
||||
format!(
|
||||
"Could not set '{}' as the current working directory",
|
||||
base_directory.to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_current_directory_exists(current_directory: &Path) -> Result<()> {
|
||||
if filesystem::is_existing_directory(current_directory) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Could not retrieve current directory (has it been deleted?)."
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_search_pattern<'a>(matches: &'a clap::ArgMatches) -> Result<&'a str> {
|
||||
let pattern = matches
|
||||
.value_of_os("pattern")
|
||||
.map(|p| {
|
||||
p.to_str()
|
||||
.ok_or_else(|| anyhow!("The search pattern includes invalid UTF-8 sequences."))
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or("");
|
||||
Ok(pattern)
|
||||
}
|
||||
|
||||
fn extract_search_paths(
|
||||
matches: &clap::ArgMatches,
|
||||
current_directory: &Path,
|
||||
) -> Result<Vec<PathBuf>> {
|
||||
let mut search_paths = matches
|
||||
.values_of_os("path")
|
||||
.or_else(|| matches.values_of_os("search-path"))
|
||||
.map_or_else(
|
||||
|| vec![current_directory.to_path_buf()],
|
||||
|paths| {
|
||||
paths
|
||||
.filter_map(|path| {
|
||||
let path_buffer = PathBuf::from(path);
|
||||
if filesystem::is_existing_directory(&path_buffer) {
|
||||
Some(path_buffer)
|
||||
} else {
|
||||
print_error(format!(
|
||||
"Search path '{}' is not a directory.",
|
||||
path_buffer.to_string_lossy()
|
||||
));
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
);
|
||||
if search_paths.is_empty() {
|
||||
return Err(anyhow!("No valid search paths given."));
|
||||
}
|
||||
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();
|
||||
update_to_absolute_paths(&mut search_paths);
|
||||
}
|
||||
Ok(search_paths)
|
||||
}
|
||||
|
||||
// Detect if the user accidentally supplied a path instead of a search pattern
|
||||
fn update_to_absolute_paths(search_paths: &mut [PathBuf]) {
|
||||
for buffer in search_paths.iter_mut() {
|
||||
*buffer = filesystem::absolute_path(buffer.normalize().unwrap().as_path()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect if the user accidentally supplied a path instead of a search pattern
|
||||
fn ensure_search_pattern_is_not_a_path(matches: &clap::ArgMatches, pattern: &str) -> Result<()> {
|
||||
if !matches.is_present("full-path")
|
||||
&& pattern.contains(std::path::MAIN_SEPARATOR)
|
||||
&& fshelper::is_dir(Path::new(pattern))
|
||||
&& Path::new(pattern).is_dir()
|
||||
{
|
||||
print_error_and_exit!(
|
||||
Err(anyhow!(
|
||||
"The search pattern '{pattern}' contains a path-separation character ('{sep}') \
|
||||
and will not lead to any search results.\n\n\
|
||||
If you want to search for all files inside the '{pattern}' directory, use a match-all pattern:\n\n \
|
||||
fd . '{pattern}'\n\n\
|
||||
Instead, if you want to search for the pattern in the full path, use:\n\n \
|
||||
Instead, if you want your pattern to match the full file path, use:\n\n \
|
||||
fd --full-path '{pattern}'",
|
||||
pattern = pattern,
|
||||
sep = std::path::MAIN_SEPARATOR,
|
||||
);
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Treat pattern as literal string if '--fixed-strings' is used
|
||||
let pattern_regex = if matches.is_present("fixed-strings") {
|
||||
fn build_pattern_regex(matches: &clap::ArgMatches, pattern: &str) -> Result<String> {
|
||||
Ok(if matches.is_present("glob") && !pattern.is_empty() {
|
||||
let glob = GlobBuilder::new(pattern).literal_separator(true).build()?;
|
||||
glob.regex().to_owned()
|
||||
} else if matches.is_present("fixed-strings") {
|
||||
// Treat pattern as literal string if '--fixed-strings' is used
|
||||
regex::escape(pattern)
|
||||
} else {
|
||||
String::from(pattern)
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
fn check_path_separator_length(path_separator: Option<&str>) -> Result<()> {
|
||||
match (cfg!(windows), path_separator) {
|
||||
(true, Some(sep)) if sep.len() > 1 => Err(anyhow!(
|
||||
"A path separator must be exactly one byte, but \
|
||||
the given separator is {} bytes: '{}'.\n\
|
||||
In some shells on Windows, '/' is automatically \
|
||||
expanded. Try to use '//' instead.",
|
||||
sep.len(),
|
||||
sep
|
||||
)),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result<Config> {
|
||||
// The search will be case-sensitive if the command line flag is set or
|
||||
// if the pattern has an uppercase character (smart case).
|
||||
let case_sensitive = !matches.is_present("ignore-case")
|
||||
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
|
||||
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(pattern_regex));
|
||||
|
||||
let path_separator = matches
|
||||
.value_of("path-separator")
|
||||
.map_or_else(filesystem::default_path_separator, |s| Some(s.to_owned()));
|
||||
check_path_separator_length(path_separator.as_deref())?;
|
||||
|
||||
let size_limits = extract_size_limits(&matches)?;
|
||||
let time_constraints = extract_time_constraints(&matches)?;
|
||||
#[cfg(unix)]
|
||||
let owner_constraint = matches
|
||||
.value_of("owner")
|
||||
.map(OwnerFilter::from_string)
|
||||
.transpose()?
|
||||
.flatten();
|
||||
|
||||
#[cfg(windows)]
|
||||
let ansi_colors_support =
|
||||
ansi_term::enable_ansi_support().is_ok() || std::env::var_os("TERM").is_some();
|
||||
#[cfg(not(windows))]
|
||||
let ansi_colors_support = true;
|
||||
|
||||
let interactive_terminal = atty::is(Stream::Stdout);
|
||||
let colored_output = match matches.value_of("color") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
_ => atty::is(Stream::Stdout),
|
||||
_ => ansi_colors_support && env::var_os("NO_COLOR").is_none() && interactive_terminal,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok();
|
||||
|
||||
let ls_colors = if colored_output {
|
||||
Some(LsColors::from_env().unwrap_or_default())
|
||||
Some(LsColors::from_env().unwrap_or_else(|| LsColors::from_string(DEFAULT_LS_COLORS)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let command = extract_command(&matches, path_separator.as_deref(), colored_output)?;
|
||||
|
||||
let command = matches
|
||||
.values_of("exec")
|
||||
.map(CommandTemplate::new)
|
||||
.or_else(|| {
|
||||
matches.values_of("exec-batch").map(|m| {
|
||||
CommandTemplate::new_batch(m).unwrap_or_else(|e| {
|
||||
print_error_and_exit!("{}", e);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let size_limits: Vec<SizeFilter> = matches
|
||||
.values_of("size")
|
||||
.map(|v| {
|
||||
v.map(|sf| {
|
||||
if let Some(f) = SizeFilter::from_string(sf) {
|
||||
return f;
|
||||
}
|
||||
print_error_and_exit!("'{}' is not a valid size constraint. See 'fd --help'.", sf);
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| vec![]);
|
||||
|
||||
let now = time::SystemTime::now();
|
||||
let mut time_constraints: Vec<TimeFilter> = Vec::new();
|
||||
if let Some(t) = matches.value_of("changed-within") {
|
||||
if let Some(f) = TimeFilter::after(&now, t) {
|
||||
time_constraints.push(f);
|
||||
} else {
|
||||
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
|
||||
}
|
||||
}
|
||||
if let Some(t) = matches.value_of("changed-before") {
|
||||
if let Some(f) = TimeFilter::before(&now, t) {
|
||||
time_constraints.push(f);
|
||||
} else {
|
||||
print_error_and_exit!("'{}' is not a valid date or duration. See 'fd --help'.", t);
|
||||
}
|
||||
}
|
||||
|
||||
let config = FdOptions {
|
||||
Ok(Config {
|
||||
case_sensitive,
|
||||
search_full_path: matches.is_present("full-path"),
|
||||
ignore_hidden: !(matches.is_present("hidden")
|
||||
@@ -173,24 +262,53 @@ fn main() {
|
||||
read_vcsignore: !(matches.is_present("no-ignore")
|
||||
|| matches.is_present("rg-alias-hidden-ignore")
|
||||
|| matches.is_present("no-ignore-vcs")),
|
||||
read_parent_ignore: !matches.is_present("no-ignore-parent"),
|
||||
read_global_ignore: !(matches.is_present("no-ignore")
|
||||
|| matches.is_present("rg-alias-hidden-ignore")
|
||||
|| matches.is_present("no-global-ignore-file")),
|
||||
follow_links: matches.is_present("follow"),
|
||||
one_file_system: matches.is_present("one-file-system"),
|
||||
null_separator: matches.is_present("null_separator"),
|
||||
quiet: matches.is_present("quiet"),
|
||||
max_depth: matches
|
||||
.value_of("depth")
|
||||
.value_of("max-depth")
|
||||
.or_else(|| matches.value_of("rg-depth"))
|
||||
.and_then(|n| usize::from_str_radix(n, 10).ok()),
|
||||
.or_else(|| matches.value_of("exact-depth"))
|
||||
.map(|n| n.parse::<usize>())
|
||||
.transpose()
|
||||
.context("Failed to parse argument to --max-depth/--exact-depth")?,
|
||||
min_depth: matches
|
||||
.value_of("min-depth")
|
||||
.or_else(|| matches.value_of("exact-depth"))
|
||||
.map(|n| n.parse::<usize>())
|
||||
.transpose()
|
||||
.context("Failed to parse argument to --min-depth/--exact-depth")?,
|
||||
prune: matches.is_present("prune"),
|
||||
threads: std::cmp::max(
|
||||
matches
|
||||
.value_of("threads")
|
||||
.and_then(|n| usize::from_str_radix(n, 10).ok())
|
||||
.map(|n| n.parse::<usize>())
|
||||
.transpose()
|
||||
.context("Failed to parse number of threads")?
|
||||
.map(|n| {
|
||||
if n > 0 {
|
||||
Ok(n)
|
||||
} else {
|
||||
Err(anyhow!("Number of threads must be positive."))
|
||||
}
|
||||
})
|
||||
.transpose()?
|
||||
.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(|n| n.parse::<u64>())
|
||||
.transpose()
|
||||
.context("Failed to parse max. buffer time argument")?
|
||||
.map(time::Duration::from_millis),
|
||||
ls_colors,
|
||||
interactive_terminal,
|
||||
file_types: matches.values_of("file-type").map(|values| {
|
||||
let mut file_types = FileTypes::default();
|
||||
for value in values {
|
||||
@@ -202,9 +320,9 @@ fn main() {
|
||||
file_types.executables_only = true;
|
||||
file_types.files = true;
|
||||
}
|
||||
"e" | "empty" => {
|
||||
file_types.empty_only = true;
|
||||
}
|
||||
"e" | "empty" => file_types.empty_only = true,
|
||||
"s" | "socket" => file_types.sockets = true,
|
||||
"p" | "pipe" => file_types.pipes = true,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -217,46 +335,234 @@ fn main() {
|
||||
|
||||
file_types
|
||||
}),
|
||||
extensions: matches.values_of("extension").map(|exts| {
|
||||
let patterns = exts
|
||||
.map(|e| e.trim_left_matches('.'))
|
||||
.map(|e| format!(r".\.{}$", regex::escape(e)));
|
||||
match RegexSetBuilder::new(patterns)
|
||||
.case_insensitive(true)
|
||||
.build()
|
||||
{
|
||||
Ok(re) => re,
|
||||
Err(err) => {
|
||||
print_error_and_exit!("{}", err.description());
|
||||
}
|
||||
}
|
||||
}),
|
||||
extensions: matches
|
||||
.values_of("extension")
|
||||
.map(|exts| {
|
||||
let patterns = exts
|
||||
.map(|e| e.trim_start_matches('.'))
|
||||
.map(|e| format!(r".\.{}$", regex::escape(e)));
|
||||
RegexSetBuilder::new(patterns)
|
||||
.case_insensitive(true)
|
||||
.build()
|
||||
})
|
||||
.transpose()?,
|
||||
command: command.map(Arc::new),
|
||||
batch_size: matches
|
||||
.value_of("batch-size")
|
||||
.map(|n| n.parse::<usize>())
|
||||
.transpose()
|
||||
.context("Failed to parse --batch-size argument")?
|
||||
.unwrap_or_default(),
|
||||
exclude_patterns: matches
|
||||
.values_of("exclude")
|
||||
.map(|v| v.map(|p| String::from("!") + p).collect())
|
||||
.unwrap_or_else(|| vec![]),
|
||||
.unwrap_or_else(Vec::new),
|
||||
ignore_files: matches
|
||||
.values_of("ignore-file")
|
||||
.map(|vs| vs.map(PathBuf::from).collect())
|
||||
.unwrap_or_else(|| vec![]),
|
||||
.unwrap_or_else(Vec::new),
|
||||
size_constraints: size_limits,
|
||||
time_constraints,
|
||||
#[cfg(unix)]
|
||||
owner_constraint,
|
||||
show_filesystem_errors: matches.is_present("show-errors"),
|
||||
};
|
||||
path_separator,
|
||||
max_results: matches
|
||||
.value_of("max-results")
|
||||
.map(|n| n.parse::<usize>())
|
||||
.transpose()
|
||||
.context("Failed to parse --max-results argument")?
|
||||
.filter(|&n| n > 0)
|
||||
.or_else(|| {
|
||||
if matches.is_present("max-one-result") {
|
||||
Some(1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
strip_cwd_prefix: (!matches.is_present("path")
|
||||
&& !matches.is_present("search-path")
|
||||
&& (interactive_terminal || matches.is_present("strip-cwd-prefix"))),
|
||||
})
|
||||
}
|
||||
|
||||
match RegexBuilder::new(&pattern_regex)
|
||||
fn extract_command(
|
||||
matches: &clap::ArgMatches,
|
||||
path_separator: Option<&str>,
|
||||
colored_output: bool,
|
||||
) -> Result<Option<CommandTemplate>> {
|
||||
None.or_else(|| {
|
||||
matches.values_of("exec").map(|args| {
|
||||
Ok(CommandTemplate::new(
|
||||
args,
|
||||
path_separator.map(str::to_string),
|
||||
))
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
matches
|
||||
.values_of("exec-batch")
|
||||
.map(|args| CommandTemplate::new_batch(args, path_separator.map(str::to_string)))
|
||||
})
|
||||
.or_else(|| {
|
||||
if !matches.is_present("list-details") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let color = matches.value_of("color").unwrap_or("auto");
|
||||
let color_arg = format!("--color={}", color);
|
||||
|
||||
let res = determine_ls_command(&color_arg, colored_output).map(|cmd| {
|
||||
CommandTemplate::new_batch(cmd, path_separator.map(str::to_string)).unwrap()
|
||||
});
|
||||
|
||||
Some(res)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
fn determine_ls_command(color_arg: &str, colored_output: bool) -> Result<Vec<&str>> {
|
||||
#[allow(unused)]
|
||||
let gnu_ls = |command_name| {
|
||||
// Note: we use short options here (instead of --long-options) to support more
|
||||
// platforms (like BusyBox).
|
||||
vec![
|
||||
command_name,
|
||||
"-l", // long listing format
|
||||
"-h", // human readable file sizes
|
||||
"-d", // list directories themselves, not their contents
|
||||
color_arg,
|
||||
]
|
||||
};
|
||||
let cmd: Vec<&str> = if cfg!(unix) {
|
||||
if !cfg!(any(
|
||||
target_os = "macos",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
)) {
|
||||
// Assume ls is GNU ls
|
||||
gnu_ls("ls")
|
||||
} else {
|
||||
// MacOS, DragonFlyBSD, FreeBSD
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// Use GNU ls, if available (support for --color=auto, better LS_COLORS support)
|
||||
let gnu_ls_exists = Command::new("gls")
|
||||
.arg("--version")
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.is_ok();
|
||||
|
||||
if gnu_ls_exists {
|
||||
gnu_ls("gls")
|
||||
} else {
|
||||
let mut cmd = vec![
|
||||
"ls", // BSD version of ls
|
||||
"-l", // long listing format
|
||||
"-h", // '--human-readable' is not available, '-h' is
|
||||
"-d", // '--directory' is not available, but '-d' is
|
||||
];
|
||||
|
||||
if !cfg!(any(target_os = "netbsd", target_os = "openbsd")) && colored_output {
|
||||
// -G is not available in NetBSD's and OpenBSD's ls
|
||||
cmd.push("-G");
|
||||
}
|
||||
|
||||
cmd
|
||||
}
|
||||
}
|
||||
} else if cfg!(windows) {
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
// Use GNU ls, if available
|
||||
let gnu_ls_exists = Command::new("ls")
|
||||
.arg("--version")
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.is_ok();
|
||||
|
||||
if gnu_ls_exists {
|
||||
gnu_ls("ls")
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"'fd --list-details' is not supported on Windows unless GNU 'ls' is installed."
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"'fd --list-details' is not supported on this platform."
|
||||
));
|
||||
};
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
fn extract_size_limits(matches: &clap::ArgMatches) -> Result<Vec<SizeFilter>> {
|
||||
matches.values_of("size").map_or(Ok(Vec::new()), |vs| {
|
||||
vs.map(|sf| {
|
||||
SizeFilter::from_string(sf)
|
||||
.ok_or_else(|| anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", sf))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_time_constraints(matches: &clap::ArgMatches) -> Result<Vec<TimeFilter>> {
|
||||
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 {
|
||||
return Err(anyhow!(
|
||||
"'{}' is not a valid date or duration. See 'fd --help'.",
|
||||
t
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(t) = matches.value_of("changed-before") {
|
||||
if let Some(f) = TimeFilter::before(&now, t) {
|
||||
time_constraints.push(f);
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"'{}' is not a valid date or duration. See 'fd --help'.",
|
||||
t
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(time_constraints)
|
||||
}
|
||||
|
||||
fn ensure_use_hidden_option_for_leading_dot_pattern(
|
||||
config: &Config,
|
||||
pattern_regex: &str,
|
||||
) -> Result<()> {
|
||||
if cfg!(unix) && config.ignore_hidden && pattern_matches_strings_with_leading_dot(pattern_regex)
|
||||
{
|
||||
Err(anyhow!(
|
||||
"The pattern seems to only match files with a leading dot, but hidden files are \
|
||||
filtered by default. Consider adding -H/--hidden to search hidden files as well \
|
||||
or adjust your search pattern."
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn build_regex(pattern_regex: String, config: &Config) -> Result<regex::bytes::Regex> {
|
||||
RegexBuilder::new(&pattern_regex)
|
||||
.case_insensitive(!config.case_sensitive)
|
||||
.dot_matches_new_line(true)
|
||||
.build()
|
||||
{
|
||||
Ok(re) => walk::scan(&dir_vec, Arc::new(re), Arc::new(config)),
|
||||
Err(err) => {
|
||||
print_error_and_exit!(
|
||||
"{}\nHint: You can use the '--fixed-strings' option to search for a \
|
||||
literal string instead of a regular expression",
|
||||
err.description()
|
||||
);
|
||||
}
|
||||
}
|
||||
.map_err(|e| {
|
||||
anyhow!(
|
||||
"{}\n\nNote: You can use the '--fixed-strings' option to search for a \
|
||||
literal string instead of a regular expression. Alternatively, you can \
|
||||
also use the '--glob' option to match on a glob pattern.",
|
||||
e.to_string()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
+101
-63
@@ -1,94 +1,132 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
use std::borrow::Cow;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use lscolors::{Indicator, LsColors, Style};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::print_error;
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::internal::opts::FdOptions;
|
||||
use lscolors::{LsColors, Style};
|
||||
use crate::filesystem::strip_current_dir;
|
||||
|
||||
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()
|
||||
fn replace_path_separator(path: &str, new_path_separator: &str) -> String {
|
||||
path.replace(std::path::MAIN_SEPARATOR, new_path_separator)
|
||||
}
|
||||
|
||||
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 {
|
||||
// TODO: this function is performance critical and can probably be optimized
|
||||
pub fn print_entry<W: Write>(stdout: &mut W, entry: &Path, config: &Config) {
|
||||
let path = if config.strip_cwd_prefix {
|
||||
strip_current_dir(entry)
|
||||
} else {
|
||||
entry
|
||||
};
|
||||
|
||||
let r = if let Some(ref ls_colors) = config.ls_colors {
|
||||
print_entry_colorized(stdout, path, config, ls_colors, &wants_to_quit)
|
||||
print_entry_colorized(stdout, path, config, ls_colors)
|
||||
} else {
|
||||
print_entry_uncolorized(stdout, path, config)
|
||||
};
|
||||
|
||||
if r.is_err() {
|
||||
// Probably a broken pipe. Exit gracefully.
|
||||
process::exit(ExitCode::GeneralError.into());
|
||||
if let Err(e) = r {
|
||||
if e.kind() == ::std::io::ErrorKind::BrokenPipe {
|
||||
// Exit gracefully in case of a broken pipe (e.g. 'fd ... | head -n 3').
|
||||
ExitCode::Success.exit();
|
||||
} else {
|
||||
print_error(format!("Could not write to output: {}", e));
|
||||
ExitCode::GeneralError.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_entry_colorized(
|
||||
stdout: &mut StdoutLock,
|
||||
// TODO: this function is performance critical and can probably be optimized
|
||||
fn print_entry_colorized<W: Write>(
|
||||
stdout: &mut W,
|
||||
path: &Path,
|
||||
config: &FdOptions,
|
||||
config: &Config,
|
||||
ls_colors: &LsColors,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
) -> io::Result<()> {
|
||||
let default_style = ansi_term::Style::default();
|
||||
// Split the path between the parent and the last component
|
||||
let mut offset = 0;
|
||||
let path_str = path.to_string_lossy();
|
||||
|
||||
// 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 let Some(parent) = path.parent() {
|
||||
offset = parent.to_string_lossy().len();
|
||||
for c in path_str[offset..].chars() {
|
||||
if std::path::is_separator(c) {
|
||||
offset += c.len_utf8();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.null_separator {
|
||||
write!(stdout, "\0")
|
||||
} else {
|
||||
writeln!(stdout)
|
||||
if offset > 0 {
|
||||
let mut parent_str = Cow::from(&path_str[..offset]);
|
||||
if let Some(ref separator) = config.path_separator {
|
||||
*parent_str.to_mut() = replace_path_separator(&parent_str, separator);
|
||||
}
|
||||
|
||||
let style = ls_colors
|
||||
.style_for_indicator(Indicator::Directory)
|
||||
.map(Style::to_ansi_term_style)
|
||||
.unwrap_or_default();
|
||||
write!(stdout, "{}", style.paint(parent_str))?;
|
||||
}
|
||||
|
||||
let style = ls_colors
|
||||
.style_for_path(path)
|
||||
.map(Style::to_ansi_term_style)
|
||||
.unwrap_or_default();
|
||||
write!(stdout, "{}", style.paint(&path_str[offset..]))?;
|
||||
|
||||
if config.null_separator {
|
||||
write!(stdout, "\0")?;
|
||||
} else {
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_entry_uncolorized(
|
||||
stdout: &mut StdoutLock,
|
||||
// TODO: this function is performance critical and can probably be optimized
|
||||
fn print_entry_uncolorized_base<W: Write>(
|
||||
stdout: &mut W,
|
||||
path: &Path,
|
||||
config: &FdOptions,
|
||||
config: &Config,
|
||||
) -> io::Result<()> {
|
||||
let separator = if config.null_separator { "\0" } else { "\n" };
|
||||
|
||||
let path_str = path.to_string_lossy();
|
||||
write!(stdout, "{}{}", path_str, separator)
|
||||
let mut path_string = path.to_string_lossy();
|
||||
if let Some(ref separator) = config.path_separator {
|
||||
*path_string.to_mut() = replace_path_separator(&path_string, separator);
|
||||
}
|
||||
write!(stdout, "{}{}", path_string, separator)
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn print_entry_uncolorized<W: Write>(
|
||||
stdout: &mut W,
|
||||
path: &Path,
|
||||
config: &Config,
|
||||
) -> io::Result<()> {
|
||||
print_entry_uncolorized_base(stdout, path, config)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn print_entry_uncolorized<W: Write>(
|
||||
stdout: &mut W,
|
||||
path: &Path,
|
||||
config: &Config,
|
||||
) -> io::Result<()> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
if config.interactive_terminal || config.path_separator.is_some() {
|
||||
// Fall back to the base implementation
|
||||
print_entry_uncolorized_base(stdout, path, config)
|
||||
} else {
|
||||
// Print path as raw bytes, allowing invalid UTF-8 filenames to be passed to other processes
|
||||
let separator = if config.null_separator { b"\0" } else { b"\n" };
|
||||
stdout.write_all(path.as_os_str().as_bytes())?;
|
||||
stdout.write_all(separator)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use regex_syntax::hir::Hir;
|
||||
use regex_syntax::ParserBuilder;
|
||||
|
||||
/// Determine if a regex pattern contains a literal uppercase character.
|
||||
pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
|
||||
let mut parser = ParserBuilder::new().allow_invalid_utf8(true).build();
|
||||
|
||||
parser
|
||||
.parse(pattern)
|
||||
.map(|hir| hir_has_uppercase_char(&hir))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Determine if a regex expression contains a literal uppercase character.
|
||||
fn hir_has_uppercase_char(hir: &Hir) -> bool {
|
||||
use regex_syntax::hir::*;
|
||||
|
||||
match *hir.kind() {
|
||||
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
|
||||
HirKind::Literal(Literal::Byte(b)) => char::from(b).is_uppercase(),
|
||||
HirKind::Class(Class::Unicode(ref ranges)) => ranges
|
||||
.iter()
|
||||
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
|
||||
HirKind::Class(Class::Bytes(ref ranges)) => ranges
|
||||
.iter()
|
||||
.any(|r| char::from(r.start()).is_uppercase() || char::from(r.end()).is_uppercase()),
|
||||
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
|
||||
hir_has_uppercase_char(hir)
|
||||
}
|
||||
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
|
||||
hirs.iter().any(hir_has_uppercase_char)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if a regex pattern only matches strings starting with a literal dot (hidden files)
|
||||
pub fn pattern_matches_strings_with_leading_dot(pattern: &str) -> bool {
|
||||
let mut parser = ParserBuilder::new().allow_invalid_utf8(true).build();
|
||||
|
||||
parser
|
||||
.parse(pattern)
|
||||
.map(|hir| hir_matches_strings_with_leading_dot(&hir))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// See above.
|
||||
fn hir_matches_strings_with_leading_dot(hir: &Hir) -> bool {
|
||||
use regex_syntax::hir::*;
|
||||
|
||||
// Note: this only really detects the simplest case where a regex starts with
|
||||
// "^\\.", i.e. a start text anchor and a literal dot character. There are a lot
|
||||
// of other patterns that ONLY match hidden files, e.g. ^(\\.foo|\\.bar) which are
|
||||
// not (yet) detected by this algorithm.
|
||||
match *hir.kind() {
|
||||
HirKind::Concat(ref hirs) => {
|
||||
let mut hirs = hirs.iter();
|
||||
if let Some(hir) = hirs.next() {
|
||||
if *hir.kind() != HirKind::Anchor(Anchor::StartText) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(hir) = hirs.next() {
|
||||
*hir.kind() == HirKind::Literal(Literal::Unicode('.'))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_has_uppercase_char_simple() {
|
||||
assert!(pattern_has_uppercase_char("A"));
|
||||
assert!(pattern_has_uppercase_char("foo.EXE"));
|
||||
|
||||
assert!(!pattern_has_uppercase_char("a"));
|
||||
assert!(!pattern_has_uppercase_char("foo.exe123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_has_uppercase_char_advanced() {
|
||||
assert!(pattern_has_uppercase_char("foo.[a-zA-Z]"));
|
||||
|
||||
assert!(!pattern_has_uppercase_char(r"\Acargo"));
|
||||
assert!(!pattern_has_uppercase_char(r"carg\x6F"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_strings_with_leading_dot_simple() {
|
||||
assert!(pattern_matches_strings_with_leading_dot("^\\.gitignore"));
|
||||
|
||||
assert!(!pattern_matches_strings_with_leading_dot("^.gitignore"));
|
||||
assert!(!pattern_matches_strings_with_leading_dot("\\.gitignore"));
|
||||
assert!(!pattern_matches_strings_with_leading_dot("^gitignore"));
|
||||
}
|
||||
+437
-177
@@ -1,32 +1,30 @@
|
||||
// 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::ffi::OsStr;
|
||||
use std::fs::{FileType, Metadata};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{borrow::Cow, io::Write};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use ignore::overrides::OverrideBuilder;
|
||||
use ignore::{self, WalkBuilder};
|
||||
use regex::Regex;
|
||||
use once_cell::unsync::OnceCell;
|
||||
use regex::bytes::Regex;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::print_error;
|
||||
use crate::exec;
|
||||
use crate::exit_codes::{merge_exitcodes, ExitCode};
|
||||
use crate::filesystem;
|
||||
use crate::output;
|
||||
|
||||
/// The receiver thread can either be buffering results or directly streaming to the console.
|
||||
#[derive(PartialEq)]
|
||||
enum ReceiverMode {
|
||||
/// Receiver is still buffering in order to sort the results, if the search finishes fast
|
||||
/// enough.
|
||||
@@ -42,12 +40,17 @@ pub enum WorkerResult {
|
||||
Error(ignore::Error),
|
||||
}
|
||||
|
||||
/// Maximum size of the output buffer before flushing results to the console
|
||||
pub const MAX_BUFFER_LENGTH: usize = 1000;
|
||||
/// Default duration until output buffering switches to streaming.
|
||||
pub const DEFAULT_MAX_BUFFER_TIME: Duration = Duration::from_millis(100);
|
||||
|
||||
/// Recursively scan the given search path for files / pathnames matching the pattern.
|
||||
///
|
||||
/// If the `--exec` argument was supplied, this will create a thread pool for executing
|
||||
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
|
||||
/// path will simply be written to standard output.
|
||||
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> Result<ExitCode> {
|
||||
let mut path_iter = path_vec.iter();
|
||||
let first_path_buf = path_iter
|
||||
.next()
|
||||
@@ -57,44 +60,69 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
|
||||
|
||||
for pattern in &config.exclude_patterns {
|
||||
let res = override_builder.add(pattern);
|
||||
if res.is_err() {
|
||||
print_error_and_exit!("Malformed exclude pattern '{}'", pattern);
|
||||
}
|
||||
override_builder
|
||||
.add(pattern)
|
||||
.map_err(|e| anyhow!("Malformed exclude pattern: {}", e))?;
|
||||
}
|
||||
let overrides = override_builder.build().unwrap_or_else(|_| {
|
||||
print_error_and_exit!("Mismatch in exclude patterns");
|
||||
});
|
||||
let overrides = override_builder
|
||||
.build()
|
||||
.map_err(|_| anyhow!("Mismatch in exclude patterns"))?;
|
||||
|
||||
let mut walker = WalkBuilder::new(first_path_buf.as_path());
|
||||
walker
|
||||
.hidden(config.ignore_hidden)
|
||||
.ignore(config.read_fdignore)
|
||||
.parents(config.read_fdignore || config.read_vcsignore)
|
||||
.parents(config.read_parent_ignore && (config.read_fdignore || config.read_vcsignore))
|
||||
.git_ignore(config.read_vcsignore)
|
||||
.git_global(config.read_vcsignore)
|
||||
.git_exclude(config.read_vcsignore)
|
||||
.overrides(overrides)
|
||||
.follow_links(config.follow_links)
|
||||
// No need to check for supported platforms, option is unavailable on unsupported ones
|
||||
.same_file_system(config.one_file_system)
|
||||
.max_depth(config.max_depth);
|
||||
|
||||
if config.read_fdignore {
|
||||
walker.add_custom_ignore_filename(".fdignore");
|
||||
}
|
||||
|
||||
if config.read_global_ignore {
|
||||
#[cfg(target_os = "macos")]
|
||||
let config_dir_op = std::env::var_os("XDG_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|p| p.is_absolute())
|
||||
.or_else(|| dirs_next::home_dir().map(|d| d.join(".config")));
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let config_dir_op = dirs_next::config_dir();
|
||||
|
||||
if let Some(global_ignore_file) = config_dir_op
|
||||
.map(|p| p.join("fd").join("ignore"))
|
||||
.filter(|p| p.is_file())
|
||||
{
|
||||
let result = walker.add_ignore(global_ignore_file);
|
||||
match result {
|
||||
Some(ignore::Error::Partial(_)) => (),
|
||||
Some(err) => {
|
||||
print_error(format!(
|
||||
"Malformed pattern in global ignore file. {}.",
|
||||
err.to_string()
|
||||
));
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ignore_file in &config.ignore_files {
|
||||
let result = walker.add_ignore(ignore_file);
|
||||
match result {
|
||||
Some(ignore::Error::Partial(_)) => (),
|
||||
Some(err) => {
|
||||
print_error!(
|
||||
"{}",
|
||||
format!(
|
||||
"Malformed pattern in custom ignore file '{}': {}.",
|
||||
ignore_file.to_string_lossy(),
|
||||
err.description()
|
||||
)
|
||||
);
|
||||
print_error(format!(
|
||||
"Malformed pattern in custom ignore file. {}.",
|
||||
err.to_string()
|
||||
));
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
@@ -106,45 +134,235 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||
|
||||
let parallel_walker = walker.threads(config.threads).build_parallel();
|
||||
|
||||
let wants_to_quit = Arc::new(AtomicBool::new(false));
|
||||
// Flag for cleanly shutting down the parallel walk
|
||||
let quit_flag = Arc::new(AtomicBool::new(false));
|
||||
// Flag specifically for quitting due to ^C
|
||||
let interrupt_flag = Arc::new(AtomicBool::new(false));
|
||||
|
||||
if config.ls_colors.is_some() && config.command.is_none() {
|
||||
let wq = Arc::clone(&wants_to_quit);
|
||||
let quit_flag = Arc::clone(&quit_flag);
|
||||
let interrupt_flag = Arc::clone(&interrupt_flag);
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
wq.store(true, Ordering::Relaxed);
|
||||
quit_flag.store(true, Ordering::Relaxed);
|
||||
|
||||
if interrupt_flag.fetch_or(true, Ordering::Relaxed) {
|
||||
// Ctrl-C has been pressed twice, exit NOW
|
||||
ExitCode::KilledBySigint.exit();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Spawn the thread that receives all results through the channel.
|
||||
let receiver_thread = spawn_receiver(&config, &wants_to_quit, rx);
|
||||
let receiver_thread = spawn_receiver(&config, &quit_flag, &interrupt_flag, rx);
|
||||
|
||||
// Spawn the sender threads.
|
||||
spawn_senders(&config, &wants_to_quit, pattern, parallel_walker, tx);
|
||||
spawn_senders(&config, &quit_flag, pattern, parallel_walker, tx);
|
||||
|
||||
// Wait for the receiver thread to print out all results.
|
||||
receiver_thread.join().unwrap();
|
||||
let exit_code = receiver_thread.join().unwrap();
|
||||
|
||||
if wants_to_quit.load(Ordering::Relaxed) {
|
||||
process::exit(ExitCode::KilledBySigint.into());
|
||||
if interrupt_flag.load(Ordering::Relaxed) {
|
||||
Ok(ExitCode::KilledBySigint)
|
||||
} else {
|
||||
Ok(exit_code)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for the receiver thread's buffering behavior.
|
||||
struct ReceiverBuffer<W> {
|
||||
/// The configuration.
|
||||
config: Arc<Config>,
|
||||
/// For shutting down the senders.
|
||||
quit_flag: Arc<AtomicBool>,
|
||||
/// The ^C notifier.
|
||||
interrupt_flag: Arc<AtomicBool>,
|
||||
/// Receiver for worker results.
|
||||
rx: Receiver<WorkerResult>,
|
||||
/// Standard output.
|
||||
stdout: W,
|
||||
/// The current buffer mode.
|
||||
mode: ReceiverMode,
|
||||
/// The deadline to switch to streaming mode.
|
||||
deadline: Instant,
|
||||
/// The buffer of quickly received paths.
|
||||
buffer: Vec<PathBuf>,
|
||||
/// Result count.
|
||||
num_results: usize,
|
||||
}
|
||||
|
||||
impl<W: Write> ReceiverBuffer<W> {
|
||||
/// Create a new receiver buffer.
|
||||
fn new(
|
||||
config: Arc<Config>,
|
||||
quit_flag: Arc<AtomicBool>,
|
||||
interrupt_flag: Arc<AtomicBool>,
|
||||
rx: Receiver<WorkerResult>,
|
||||
stdout: W,
|
||||
) -> Self {
|
||||
let max_buffer_time = config.max_buffer_time.unwrap_or(DEFAULT_MAX_BUFFER_TIME);
|
||||
let deadline = Instant::now() + max_buffer_time;
|
||||
|
||||
Self {
|
||||
config,
|
||||
quit_flag,
|
||||
interrupt_flag,
|
||||
rx,
|
||||
stdout,
|
||||
mode: ReceiverMode::Buffering,
|
||||
deadline,
|
||||
buffer: Vec::with_capacity(MAX_BUFFER_LENGTH),
|
||||
num_results: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Process results until finished.
|
||||
fn process(&mut self) -> ExitCode {
|
||||
loop {
|
||||
if let Err(ec) = self.poll() {
|
||||
self.quit_flag.store(true, Ordering::Relaxed);
|
||||
return ec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive the next worker result.
|
||||
fn recv(&self) -> Result<WorkerResult, RecvTimeoutError> {
|
||||
match self.mode {
|
||||
ReceiverMode::Buffering => {
|
||||
// Wait at most until we should switch to streaming
|
||||
let now = Instant::now();
|
||||
self.deadline
|
||||
.checked_duration_since(now)
|
||||
.ok_or(RecvTimeoutError::Timeout)
|
||||
.and_then(|t| self.rx.recv_timeout(t))
|
||||
}
|
||||
ReceiverMode::Streaming => {
|
||||
// Wait however long it takes for a result
|
||||
Ok(self.rx.recv()?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for a result or state change.
|
||||
fn poll(&mut self) -> Result<(), ExitCode> {
|
||||
match self.recv() {
|
||||
Ok(WorkerResult::Entry(path)) => {
|
||||
if self.config.quiet {
|
||||
return Err(ExitCode::HasResults(true));
|
||||
}
|
||||
|
||||
match self.mode {
|
||||
ReceiverMode::Buffering => {
|
||||
self.buffer.push(path);
|
||||
if self.buffer.len() > MAX_BUFFER_LENGTH {
|
||||
self.stream()?;
|
||||
}
|
||||
}
|
||||
ReceiverMode::Streaming => {
|
||||
self.print(&path)?;
|
||||
self.flush()?;
|
||||
}
|
||||
}
|
||||
|
||||
self.num_results += 1;
|
||||
if let Some(max_results) = self.config.max_results {
|
||||
if self.num_results >= max_results {
|
||||
return self.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(WorkerResult::Error(err)) => {
|
||||
if self.config.show_filesystem_errors {
|
||||
print_error(err.to_string());
|
||||
}
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {
|
||||
self.stream()?;
|
||||
}
|
||||
Err(RecvTimeoutError::Disconnected) => {
|
||||
return self.stop();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Output a path.
|
||||
fn print(&mut self, path: &Path) -> Result<(), ExitCode> {
|
||||
output::print_entry(&mut self.stdout, path, &self.config);
|
||||
|
||||
if self.interrupt_flag.load(Ordering::Relaxed) {
|
||||
// Ignore any errors on flush, because we're about to exit anyway
|
||||
let _ = self.flush();
|
||||
return Err(ExitCode::KilledBySigint);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Switch ourselves into streaming mode.
|
||||
fn stream(&mut self) -> Result<(), ExitCode> {
|
||||
self.mode = ReceiverMode::Streaming;
|
||||
|
||||
let buffer = mem::take(&mut self.buffer);
|
||||
for path in buffer {
|
||||
self.print(&path)?;
|
||||
}
|
||||
|
||||
self.flush()
|
||||
}
|
||||
|
||||
/// Stop looping.
|
||||
fn stop(&mut self) -> Result<(), ExitCode> {
|
||||
if self.mode == ReceiverMode::Buffering {
|
||||
self.buffer.sort();
|
||||
self.stream()?;
|
||||
}
|
||||
|
||||
if self.config.quiet {
|
||||
Err(ExitCode::HasResults(self.num_results > 0))
|
||||
} else {
|
||||
Err(ExitCode::Success)
|
||||
}
|
||||
}
|
||||
|
||||
/// Flush stdout if necessary.
|
||||
fn flush(&mut self) -> Result<(), ExitCode> {
|
||||
if self.config.interactive_terminal && self.stdout.flush().is_err() {
|
||||
// Probably a broken pipe. Exit gracefully.
|
||||
return Err(ExitCode::GeneralError);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_receiver(
|
||||
config: &Arc<FdOptions>,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
config: &Arc<Config>,
|
||||
quit_flag: &Arc<AtomicBool>,
|
||||
interrupt_flag: &Arc<AtomicBool>,
|
||||
rx: Receiver<WorkerResult>,
|
||||
) -> thread::JoinHandle<()> {
|
||||
) -> thread::JoinHandle<ExitCode> {
|
||||
let config = Arc::clone(config);
|
||||
let wants_to_quit = Arc::clone(wants_to_quit);
|
||||
let quit_flag = Arc::clone(quit_flag);
|
||||
let interrupt_flag = Arc::clone(interrupt_flag);
|
||||
|
||||
let show_filesystem_errors = config.show_filesystem_errors;
|
||||
let threads = config.threads;
|
||||
|
||||
// This will be used to check if output should be buffered when only running a single thread
|
||||
let enable_output_buffering: bool = threads > 1;
|
||||
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);
|
||||
exec::batch(
|
||||
rx,
|
||||
cmd,
|
||||
show_filesystem_errors,
|
||||
enable_output_buffering,
|
||||
config.batch_size,
|
||||
)
|
||||
} else {
|
||||
let shared_rx = Arc::new(Mutex::new(rx));
|
||||
|
||||
@@ -158,88 +376,97 @@ fn spawn_receiver(
|
||||
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));
|
||||
let handle = thread::spawn(move || {
|
||||
exec::job(
|
||||
rx,
|
||||
cmd,
|
||||
out_perm,
|
||||
show_filesystem_errors,
|
||||
enable_output_buffering,
|
||||
)
|
||||
});
|
||||
|
||||
// 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();
|
||||
}
|
||||
let exit_codes = handles
|
||||
.into_iter()
|
||||
.map(|handle| handle.join().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
merge_exitcodes(exit_codes)
|
||||
}
|
||||
} 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();
|
||||
let stdout = stdout.lock();
|
||||
let stdout = io::BufWriter::new(stdout);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
let mut rxbuffer = ReceiverBuffer::new(config, quit_flag, interrupt_flag, rx, stdout);
|
||||
rxbuffer.process()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
enum DirEntryInner {
|
||||
Normal(ignore::DirEntry),
|
||||
BrokenSymlink(PathBuf),
|
||||
}
|
||||
|
||||
pub struct DirEntry {
|
||||
inner: DirEntryInner,
|
||||
metadata: OnceCell<Option<Metadata>>,
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
fn normal(e: ignore::DirEntry) -> Self {
|
||||
Self {
|
||||
inner: DirEntryInner::Normal(e),
|
||||
metadata: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn broken_symlink(path: PathBuf) -> Self {
|
||||
Self {
|
||||
inner: DirEntryInner::BrokenSymlink(path),
|
||||
metadata: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &Path {
|
||||
match &self.inner {
|
||||
DirEntryInner::Normal(e) => e.path(),
|
||||
DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_type(&self) -> Option<FileType> {
|
||||
match &self.inner {
|
||||
DirEntryInner::Normal(e) => e.file_type(),
|
||||
DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> Option<&Metadata> {
|
||||
self.metadata
|
||||
.get_or_init(|| match &self.inner {
|
||||
DirEntryInner::Normal(e) => e.metadata().ok(),
|
||||
DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(),
|
||||
})
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
pub fn depth(&self) -> Option<usize> {
|
||||
match &self.inner {
|
||||
DirEntryInner::Normal(e) => Some(e.depth()),
|
||||
DirEntryInner::BrokenSymlink(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_senders(
|
||||
config: &Arc<FdOptions>,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
config: &Arc<Config>,
|
||||
quit_flag: &Arc<AtomicBool>,
|
||||
pattern: Arc<Regex>,
|
||||
parallel_walker: ignore::WalkParallel,
|
||||
tx: Sender<WorkerResult>,
|
||||
@@ -248,45 +475,82 @@ fn spawn_senders(
|
||||
let config = Arc::clone(config);
|
||||
let pattern = Arc::clone(&pattern);
|
||||
let tx_thread = tx.clone();
|
||||
let wants_to_quit = Arc::clone(wants_to_quit);
|
||||
let quit_flag = Arc::clone(quit_flag);
|
||||
|
||||
Box::new(move |entry_o| {
|
||||
if wants_to_quit.load(Ordering::Relaxed) {
|
||||
if quit_flag.load(Ordering::Relaxed) {
|
||||
return ignore::WalkState::Quit;
|
||||
}
|
||||
|
||||
let entry = match entry_o {
|
||||
Ok(e) => e,
|
||||
Err(err) => {
|
||||
tx_thread.send(WorkerResult::Error(err)).unwrap();
|
||||
Ok(ref e) if e.depth() == 0 => {
|
||||
// Skip the root directory entry.
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
Ok(e) => DirEntry::normal(e),
|
||||
Err(ignore::Error::WithPath {
|
||||
path,
|
||||
err: inner_err,
|
||||
}) => match inner_err.as_ref() {
|
||||
ignore::Error::Io(io_error)
|
||||
if io_error.kind() == io::ErrorKind::NotFound
|
||||
&& path
|
||||
.symlink_metadata()
|
||||
.ok()
|
||||
.map_or(false, |m| m.file_type().is_symlink()) =>
|
||||
{
|
||||
DirEntry::broken_symlink(path)
|
||||
}
|
||||
_ => {
|
||||
return match tx_thread.send(WorkerResult::Error(ignore::Error::WithPath {
|
||||
path,
|
||||
err: inner_err,
|
||||
})) {
|
||||
Ok(_) => ignore::WalkState::Continue,
|
||||
Err(_) => ignore::WalkState::Quit,
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
return match tx_thread.send(WorkerResult::Error(err)) {
|
||||
Ok(_) => ignore::WalkState::Continue,
|
||||
Err(_) => ignore::WalkState::Quit,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if entry.depth() == 0 {
|
||||
if let Some(min_depth) = config.min_depth {
|
||||
if entry.depth().map_or(true, |d| d < min_depth) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the name first, since it doesn't require metadata
|
||||
let entry_path = entry.path();
|
||||
|
||||
let search_str: Cow<OsStr> = if config.search_full_path {
|
||||
let path_abs_buf = filesystem::path_absolute_form(entry_path)
|
||||
.expect("Retrieving absolute path succeeds");
|
||||
Cow::Owned(path_abs_buf.as_os_str().to_os_string())
|
||||
} else {
|
||||
match entry_path.file_name() {
|
||||
Some(filename) => Cow::Borrowed(filename),
|
||||
None => unreachable!(
|
||||
"Encountered file system entry without a file name. This should only \
|
||||
happen for paths like 'foo/bar/..' or '/' which are not supposed to \
|
||||
appear in a file system traversal."
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
if !pattern.is_match(&filesystem::osstr_to_bytes(search_str.as_ref())) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
|
||||
// Filter out unwanted file types.
|
||||
|
||||
if let Some(ref file_types) = config.file_types {
|
||||
if let Some(ref entry_type) = entry.file_type() {
|
||||
if (!file_types.files && entry_type.is_file())
|
||||
|| (!file_types.directories && entry_type.is_dir())
|
||||
|| (!file_types.symlinks && entry_type.is_symlink())
|
||||
|| (file_types.executables_only
|
||||
&& !entry
|
||||
.metadata()
|
||||
.map(|m| fshelper::is_executable(&m))
|
||||
.unwrap_or(false))
|
||||
|| (file_types.empty_only && !fshelper::is_empty(&entry))
|
||||
{
|
||||
return ignore::WalkState::Continue;
|
||||
} else if !(entry_type.is_file()
|
||||
|| entry_type.is_dir()
|
||||
|| entry_type.is_symlink())
|
||||
{
|
||||
// This is probably a block device, char device, fifo or socket. Skip it.
|
||||
// Filter out unwanted extensions.
|
||||
if let Some(ref exts_regex) = config.extensions {
|
||||
if let Some(path_str) = entry_path.file_name() {
|
||||
if !exts_regex.is_match(&filesystem::osstr_to_bytes(path_str)) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
@@ -294,23 +558,30 @@ fn spawn_senders(
|
||||
}
|
||||
}
|
||||
|
||||
let entry_path = entry.path();
|
||||
// Filter out unwanted file types.
|
||||
if let Some(ref file_types) = config.file_types {
|
||||
if file_types.should_ignore(&entry) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if let Some(ref owner_constraint) = config.owner_constraint {
|
||||
if let Some(metadata) = entry.metadata() {
|
||||
if !owner_constraint.matches(metadata) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
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() {
|
||||
if let Some(metadata) = entry.metadata() {
|
||||
let file_size = metadata.len();
|
||||
if config
|
||||
.size_constraints
|
||||
@@ -330,14 +601,12 @@ fn spawn_senders(
|
||||
// Filter out unwanted modification times
|
||||
if !config.time_constraints.is_empty() {
|
||||
let mut matched = false;
|
||||
if entry_path.is_file() {
|
||||
if let Ok(metadata) = entry_path.metadata() {
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
matched = config
|
||||
.time_constraints
|
||||
.iter()
|
||||
.all(|tf| tf.applies_to(&modified));
|
||||
}
|
||||
if let Some(metadata) = entry.metadata() {
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
matched = config
|
||||
.time_constraints
|
||||
.iter()
|
||||
.all(|tf| tf.applies_to(&modified));
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
@@ -345,24 +614,15 @@ fn spawn_senders(
|
||||
}
|
||||
}
|
||||
|
||||
let search_str_o = if config.search_full_path {
|
||||
match fshelper::path_absolute_form(entry_path) {
|
||||
Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),
|
||||
Err(_) => {
|
||||
print_error_and_exit!("Unable to retrieve absolute path.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
entry_path.file_name().map(|f| f.to_string_lossy())
|
||||
};
|
||||
let send_result = tx_thread.send(WorkerResult::Entry(entry_path.to_owned()));
|
||||
|
||||
if let Some(search_str) = search_str_o {
|
||||
if pattern.is_match(&*search_str) {
|
||||
// TODO: take care of the unwrap call
|
||||
tx_thread
|
||||
.send(WorkerResult::Entry(entry_path.to_owned()))
|
||||
.unwrap()
|
||||
}
|
||||
if send_result.is_err() {
|
||||
return ignore::WalkState::Quit;
|
||||
}
|
||||
|
||||
// Apply pruning.
|
||||
if config.prune {
|
||||
return ignore::WalkState::Skip;
|
||||
}
|
||||
|
||||
ignore::WalkState::Continue
|
||||
|
||||
+121
-49
@@ -1,24 +1,12 @@
|
||||
// Copyright (c) 2017 fd developers
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||
// at your option. All files in the project carrying such
|
||||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
use std::io::{self, Write};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix;
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
use tempdir::TempDir;
|
||||
|
||||
@@ -121,17 +109,17 @@ fn format_output_error(args: &[&str], expected: &str, actual: &str) -> String {
|
||||
}
|
||||
|
||||
/// Normalize the output for comparison.
|
||||
fn normalize_output(s: &str, trim_left: bool, normalize_line: bool) -> String {
|
||||
fn normalize_output(s: &str, trim_start: bool, normalize_line: bool) -> String {
|
||||
// Split into lines and normalize separators.
|
||||
let mut lines = s
|
||||
.replace('\0', "NULL\n")
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let line = if trim_left { line.trim_left() } else { line };
|
||||
let line = if trim_start { line.trim_start() } else { line };
|
||||
let line = line.replace('/', &std::path::MAIN_SEPARATOR.to_string());
|
||||
if normalize_line {
|
||||
let mut words: Vec<_> = line.split_whitespace().collect();
|
||||
words.sort();
|
||||
words.sort_unstable();
|
||||
return words.join(" ");
|
||||
}
|
||||
line
|
||||
@@ -142,6 +130,17 @@ fn normalize_output(s: &str, trim_left: bool, normalize_line: bool) -> String {
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
/// Trim whitespace from the beginning of each line.
|
||||
fn trim_lines(s: &str) -> String {
|
||||
s.lines()
|
||||
.map(|line| line.trim_start())
|
||||
.fold(String::new(), |mut str, line| {
|
||||
str.push_str(line);
|
||||
str.push('\n');
|
||||
str
|
||||
})
|
||||
}
|
||||
|
||||
impl TestEnv {
|
||||
pub fn new(directories: &[&'static str], files: &[&'static str]) -> TestEnv {
|
||||
let temp_dir = create_working_directory(directories, files).expect("working directory");
|
||||
@@ -162,6 +161,25 @@ impl TestEnv {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a broken symlink at the given path in the temp_dir.
|
||||
pub fn create_broken_symlink<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
link_path: P,
|
||||
) -> Result<PathBuf, io::Error> {
|
||||
let root = self.test_root();
|
||||
let broken_symlink_link = root.join(link_path);
|
||||
{
|
||||
let temp_target_dir = TempDir::new("fd-tests-broken-symlink")?;
|
||||
let broken_symlink_target = temp_target_dir.path().join("broken_symlink_target");
|
||||
fs::File::create(&broken_symlink_target)?;
|
||||
#[cfg(unix)]
|
||||
unix::fs::symlink(&broken_symlink_target, &broken_symlink_link)?;
|
||||
#[cfg(windows)]
|
||||
windows::fs::symlink_file(&broken_symlink_target, &broken_symlink_link)?;
|
||||
}
|
||||
Ok(broken_symlink_link)
|
||||
}
|
||||
|
||||
/// Get the root directory for the tests.
|
||||
pub fn test_root(&self) -> PathBuf {
|
||||
self.temp_dir.path().to_path_buf()
|
||||
@@ -173,11 +191,55 @@ impl TestEnv {
|
||||
PathBuf::from(components.next().expect("root directory").as_os_str())
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* in the specified path under the root working directory,
|
||||
/// and with the specified arguments produces the expected output.
|
||||
pub fn assert_success_and_get_output<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
args: &[&str],
|
||||
) -> process::Output {
|
||||
// Setup *fd* command.
|
||||
let mut cmd = process::Command::new(&self.fd_exe);
|
||||
cmd.current_dir(self.temp_dir.path().join(path));
|
||||
cmd.arg("--no-global-ignore-file").args(args);
|
||||
|
||||
// Run *fd*.
|
||||
let output = cmd.output().expect("fd output");
|
||||
|
||||
// Check for exit status.
|
||||
if !output.status.success() {
|
||||
panic!("{}", format_exit_error(args, &output));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
pub fn assert_success_and_get_normalized_output<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
args: &[&str],
|
||||
) -> String {
|
||||
let output = self.assert_success_and_get_output(path, args);
|
||||
normalize_output(
|
||||
&String::from_utf8_lossy(&output.stdout),
|
||||
false,
|
||||
self.normalize_line,
|
||||
)
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* with the specified arguments produces the expected output.
|
||||
pub fn assert_output(&self, args: &[&str], expected: &str) {
|
||||
self.assert_output_subdirectory(".", args, expected)
|
||||
}
|
||||
|
||||
/// Similar to assert_output, but able to handle non-utf8 output
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub fn assert_output_raw(&self, args: &[&str], expected: &[u8]) {
|
||||
let output = self.assert_success_and_get_output(".", args);
|
||||
|
||||
assert_eq!(expected, &output.stdout[..]);
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* in the specified path under the root working directory,
|
||||
/// and with the specified arguments produces the expected output.
|
||||
pub fn assert_output_subdirectory<P: AsRef<Path>>(
|
||||
@@ -186,58 +248,68 @@ impl TestEnv {
|
||||
args: &[&str],
|
||||
expected: &str,
|
||||
) {
|
||||
// Setup *fd* command.
|
||||
let mut cmd = process::Command::new(&self.fd_exe);
|
||||
cmd.current_dir(self.temp_dir.path().join(path));
|
||||
cmd.args(args);
|
||||
|
||||
// Run *fd*.
|
||||
let output = cmd.output().expect("fd output");
|
||||
|
||||
// Check for exit status.
|
||||
if !output.status.success() {
|
||||
panic!(format_exit_error(args, &output));
|
||||
}
|
||||
|
||||
// Normalize both expected and actual output.
|
||||
let expected = normalize_output(expected, true, self.normalize_line);
|
||||
let actual = normalize_output(
|
||||
&String::from_utf8_lossy(&output.stdout),
|
||||
false,
|
||||
self.normalize_line,
|
||||
);
|
||||
let actual = self.assert_success_and_get_normalized_output(path, args);
|
||||
|
||||
// Compare actual output to expected output.
|
||||
if expected != actual {
|
||||
panic!(format_output_error(args, &expected, &actual));
|
||||
panic!("{}", format_output_error(args, &expected, &actual));
|
||||
}
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* with the specified arguments produces the expected error,
|
||||
/// and does not succeed.
|
||||
pub fn assert_failure_with_error(&self, args: &[&str], expected: &str) {
|
||||
let status = self.assert_error_subdirectory(".", args, Some(expected));
|
||||
if status.success() {
|
||||
panic!("error '{}' did not occur.", expected);
|
||||
}
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* with the specified arguments does not succeed.
|
||||
pub fn assert_failure(&self, args: &[&str]) {
|
||||
let status = self.assert_error_subdirectory(".", args, None);
|
||||
if status.success() {
|
||||
panic!("Failure did not occur as expected.");
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
pub fn assert_error(&self, args: &[&str], expected: &str) -> process::ExitStatus {
|
||||
self.assert_error_subdirectory(".", args, Some(expected))
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* in the specified path under the root working directory,
|
||||
/// and with the specified arguments produces an error with the expected message.
|
||||
fn assert_error_subdirectory<P: AsRef<Path>>(&self, path: P, args: &[&str], expected: &str) {
|
||||
fn assert_error_subdirectory<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
args: &[&str],
|
||||
expected: Option<&str>,
|
||||
) -> process::ExitStatus {
|
||||
// Setup *fd* command.
|
||||
let mut cmd = process::Command::new(&self.fd_exe);
|
||||
cmd.current_dir(self.temp_dir.path().join(path));
|
||||
cmd.args(args);
|
||||
cmd.arg("--no-global-ignore-file").args(args);
|
||||
|
||||
// Run *fd*.
|
||||
let output = cmd.output().expect("fd output");
|
||||
|
||||
// Check for exit status.
|
||||
if output.status.success() {
|
||||
panic!("error '{}' did not occur.", expected);
|
||||
if let Some(expected) = expected {
|
||||
// Normalize both expected and actual output.
|
||||
let expected_error = trim_lines(expected);
|
||||
let actual_err = trim_lines(&String::from_utf8_lossy(&output.stderr));
|
||||
|
||||
// Compare actual output to expected output.
|
||||
if !actual_err.trim_start().starts_with(&expected_error) {
|
||||
panic!(
|
||||
"{}",
|
||||
format_output_error(args, &expected_error, &actual_err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
output.status
|
||||
}
|
||||
}
|
||||
|
||||
+1123
-338
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user