mirror of
https://github.com/sharkdp/fd.git
synced 2026-08-31 20:03:34 +08:00
Compare commits
120 Commits
v8.1.0
...
pull/620/head
| Author | SHA1 | Date | |
|---|---|---|---|
| bf37c27a38 | |||
| fcaebd2295 | |||
| c37592b0b7 | |||
| 224b7f2354 | |||
| b5344dac30 | |||
| 018556a367 | |||
| aeff525c30 | |||
| 2d398dc4a7 | |||
| 6e44828cc8 | |||
| b507449146 | |||
| d9697d1486 | |||
| 515e0ee469 | |||
| a0ca460901 | |||
| 693325bcf2 | |||
| ab81cad3b5 | |||
| 70850629e9 | |||
| 8b0db189a1 | |||
| ad1b267601 | |||
| 04829c287d | |||
| ca92bea0d7 | |||
| 252beb8df7 | |||
| b80764b19a | |||
| 148fa64a73 | |||
| 6ce55624f7 | |||
| a8951f0641 | |||
| 5860dedb1e | |||
| 7656358f44 | |||
| 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 |
@@ -9,6 +9,11 @@ assignees: ''
|
||||
|
||||
**Describe the bug you encountered:**
|
||||
|
||||
<!--
|
||||
Please check out the troubleshooting section first:
|
||||
https://github.com/sharkdp/fd#troubleshooting
|
||||
-->
|
||||
|
||||
|
||||
**Describe what you expected to happen:**
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
@@ -0,0 +1,333 @@
|
||||
name: CICD
|
||||
|
||||
env:
|
||||
MIN_SUPPORTED_RUST_VERSION: "1.42.0"
|
||||
CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
min_version:
|
||||
name: Minimum supported rust version
|
||||
runs-on: ubuntu-18.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
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
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: --all-targets --all-features
|
||||
- name: Run tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
|
||||
build:
|
||||
name: ${{ matrix.job.os }} (${{ matrix.job.target }})
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { os: ubuntu-18.04, target: arm-unknown-linux-gnueabihf , use-cross: true }
|
||||
- { os: ubuntu-18.04, target: arm-unknown-linux-musleabihf, use-cross: true }
|
||||
- { os: ubuntu-18.04, target: aarch64-unknown-linux-gnu , use-cross: true }
|
||||
- { os: ubuntu-18.04, target: i686-unknown-linux-gnu , use-cross: true }
|
||||
- { os: ubuntu-18.04, target: i686-unknown-linux-musl , use-cross: true }
|
||||
- { os: ubuntu-18.04, target: x86_64-unknown-linux-gnu }
|
||||
- { os: ubuntu-18.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: --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: |
|
||||
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: --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: cat(1) clone with wings.
|
||||
A cat(1) clone with syntax highlighting and Git integration.
|
||||
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 }}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
language: rust
|
||||
matrix:
|
||||
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
|
||||
osx_image: xcode11.4 # Catalina
|
||||
rust: stable
|
||||
env: TARGET=x86_64-apple-darwin
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- TARGET=arm-unknown-linux-gnueabihf
|
||||
- CC_arm_unknown_linux_gnueabihf=/usr/bin/arm-linux-gnueabihf-gcc-4.8
|
||||
- CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
|
||||
- os: linux
|
||||
rust: stable
|
||||
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
|
||||
|
||||
# Minimum Rust supported channel.
|
||||
- os: linux
|
||||
rust: 1.36.0
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
- os: linux
|
||||
rust: 1.36.0
|
||||
env: TARGET=x86_64-unknown-linux-musl
|
||||
- os: linux
|
||||
rust: 1.36.0
|
||||
env: TARGET=i686-unknown-linux-gnu
|
||||
- os: linux
|
||||
rust: 1.36.0
|
||||
env: TARGET=i686-unknown-linux-musl
|
||||
- os: osx
|
||||
rust: 1.36.0
|
||||
env: TARGET=x86_64-apple-darwin
|
||||
|
||||
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
|
||||
@@ -1,10 +1,58 @@
|
||||
# Upcoming release
|
||||
|
||||
## Features
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- 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
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Generated
+231
-263
@@ -1,603 +1,571 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.10"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.1"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.11.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf"
|
||||
dependencies = [
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.53"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.1"
|
||||
version = "2.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||
dependencies = [
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ansi_term 0.11.0",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"term_size",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.7.2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"autocfg",
|
||||
"cfg-if 1.0.0",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.1.4"
|
||||
version = "3.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "232295399409a8b7ae41276757b5a1cc21032848d42bff2352261f958b3ca29a"
|
||||
dependencies = [
|
||||
"nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nix",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "2.0.2"
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 1.0.0",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.4"
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fd-find"
|
||||
version = "8.1.0"
|
||||
version = "8.2.1"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ctrlc 3.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"diff 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"filetime 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"humantime 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ignore 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jemallocator 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lscolors 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ansi_term 0.12.1",
|
||||
"anyhow",
|
||||
"atty",
|
||||
"clap",
|
||||
"ctrlc",
|
||||
"diff",
|
||||
"dirs-next",
|
||||
"filetime",
|
||||
"globset",
|
||||
"humantime",
|
||||
"ignore",
|
||||
"jemallocator",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"lscolors",
|
||||
"normpath",
|
||||
"num_cpus",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"tempdir",
|
||||
"users",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.10"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall 0.2.10",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.1.0"
|
||||
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.1.14"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.5"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
|
||||
dependencies = [
|
||||
"aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.13"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
|
||||
dependencies = [
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a"
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.15"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b287fb45c60bb826a0dc68ff08742b9d88a2fea13d6e0c286b3172065aaf878c"
|
||||
dependencies = [
|
||||
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils",
|
||||
"globset",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex",
|
||||
"same-file",
|
||||
"thread_local",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jemalloc-sys"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45"
|
||||
dependencies = [
|
||||
"cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"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 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jemalloc-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.70"
|
||||
version = "0.2.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.8"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lscolors"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ansi_term 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.3"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.17.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a"
|
||||
dependencies = [
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "normpath"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27e6e8f70e9fbbe3752d330d769e3424f24b9458ce266df93a3b456902fd696a"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[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.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (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.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
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.56"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
|
||||
dependencies = [
|
||||
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getrandom",
|
||||
"redox_syscall 0.1.57",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.3.7"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.17"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-argon2"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[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.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand",
|
||||
"remove_dir_all",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term_size"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
|
||||
dependencies = [
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term_size",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||
dependencies = [
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "users"
|
||||
version = "0.10.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
|
||||
dependencies = [
|
||||
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
|
||||
dependencies = [
|
||||
"same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.8"
|
||||
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-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.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi 0.3.8 (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.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
"checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
|
||||
"checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||
"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
||||
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
||||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
"checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
|
||||
"checksum bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931"
|
||||
"checksum cc 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)" = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
|
||||
"checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
|
||||
"checksum ctrlc 3.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7a4ba686dff9fa4c1c9636ce1010b0cf98ceb421361b0bb3d6faeec43bd217a7"
|
||||
"checksum diff 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
|
||||
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
|
||||
"checksum filetime 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695"
|
||||
"checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
"checksum fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674"
|
||||
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
||||
"checksum globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120"
|
||||
"checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71"
|
||||
"checksum humantime 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9b6c53306532d3c8e8087b44e6580e10db51a023cf9b433cea2ac38066b92da"
|
||||
"checksum ignore 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)" = "128b9e89d15a3faa642ee164c998fd4fae3d89d054463cddb2c25a7baad3a352"
|
||||
"checksum jemalloc-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45"
|
||||
"checksum jemallocator 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)" = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
"checksum lscolors 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f77452267149eac960ded529fe5f5460ddf792845a1d71b5d0cfcee5642e47e"
|
||||
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||
"checksum nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
|
||||
"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
"checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431"
|
||||
"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
|
||||
"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
|
||||
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
||||
"checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
|
||||
"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||
"checksum term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
|
||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
|
||||
"checksum users 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486"
|
||||
"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
|
||||
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
+7
-6
@@ -16,7 +16,7 @@ license = "MIT/Apache-2.0"
|
||||
name = "fd-find"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/sharkdp/fd"
|
||||
version = "8.1.0"
|
||||
version = "8.2.1"
|
||||
edition= "2018"
|
||||
|
||||
[badges.appveyor]
|
||||
@@ -39,21 +39,22 @@ atty = "0.2"
|
||||
ignore = "0.4.3"
|
||||
lazy_static = "1.1.0"
|
||||
num_cpus = "1.8"
|
||||
regex = "1.0.0"
|
||||
regex = "1.5.4"
|
||||
regex-syntax = "0.6"
|
||||
ctrlc = "3.1"
|
||||
humantime = "2.0"
|
||||
lscolors = "0.7"
|
||||
globset = "0.4"
|
||||
anyhow = "1.0"
|
||||
dirs = "2.0"
|
||||
dirs-next = "2.0"
|
||||
normpath = "0.3"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "2.31.2"
|
||||
features = ["suggestions", "color", "wrap_help"]
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
users = "0.10.0"
|
||||
users = "0.11.0"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
|
||||
libc = "0.2"
|
||||
@@ -61,13 +62,13 @@ libc = "0.2"
|
||||
# FIXME: Re-enable jemalloc on macOS
|
||||
# jemalloc is currently disabled on macOS due to a bug in jemalloc in combination with macOS
|
||||
# Catalina. See https://github.com/sharkdp/fd/issues/498 for details.
|
||||
[target.'cfg(all(not(windows), not(target_os = "macos"), not(target_env = "musl")))'.dependencies]
|
||||
[target.'cfg(all(not(windows), not(target_os = "android"), not(target_os = "macos"), not(target_env = "musl")))'.dependencies]
|
||||
jemallocator = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
diff = "0.1"
|
||||
tempdir = "0.3"
|
||||
filetime = "0.2.1"
|
||||
filetime = "0.2.14"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2020 The fd developers
|
||||
Copyright (c) 2017-2021 The fd developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,35 +1,305 @@
|
||||
# fd
|
||||
[](https://travis-ci.org/sharkdp/fd)
|
||||
[](https://ci.appveyor.com/project/sharkdp/fd/branch/master)
|
||||
|
||||
[](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/spearkkk/fd-kor)
|
||||
[[中文](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
|
||||
|
||||

|
||||
|
||||
## How to use
|
||||
|
||||
First, to get an overview of all available command line options, you can either run
|
||||
[`fd -h`](#command-line-options) for a concise help message or `fd --help` for a more detailed
|
||||
version.
|
||||
|
||||
### Simple search
|
||||
|
||||
*fd* is designed to find entries in your filesystem. The most basic search you can perform is to
|
||||
run *fd* with a single argument: the search pattern. For example, assume that you want to find an
|
||||
old script of yours (the name included `netflix`):
|
||||
``` bash
|
||||
> fd netfl
|
||||
Software/python/imdb-ratings/netflix-details.py
|
||||
```
|
||||
If called with just a single argument like this, *fd* searches the current directory recursively
|
||||
for any entries that *contain* the pattern `netfl`.
|
||||
|
||||
### Regular expression search
|
||||
|
||||
The search pattern is treated as a regular expression. Here, we search for entries that start
|
||||
with `x` and end with `rc`:
|
||||
``` bash
|
||||
> cd /etc
|
||||
> fd '^x.*rc$'
|
||||
X11/xinit/xinitrc
|
||||
X11/xinit/xserverrc
|
||||
```
|
||||
|
||||
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*:
|
||||
``` bash
|
||||
> fd passwd /etc
|
||||
/etc/default/passwd
|
||||
/etc/pam.d/passwd
|
||||
/etc/passwd
|
||||
```
|
||||
|
||||
### 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`):
|
||||
``` bash
|
||||
> cd fd/tests
|
||||
> fd
|
||||
testenv
|
||||
testenv/mod.rs
|
||||
tests.rs
|
||||
```
|
||||
|
||||
If you want to use this functionality to list all files in a given directory, you have to use
|
||||
a catch-all pattern such as `.` or `^`:
|
||||
``` bash
|
||||
> fd . fd/tests/
|
||||
testenv
|
||||
testenv/mod.rs
|
||||
tests.rs
|
||||
```
|
||||
|
||||
### Searching for a particular file extension
|
||||
|
||||
Often, we are interested in all files of a particular type. This can be done with the `-e` (or
|
||||
`--extension`) option. Here, we search for all Markdown files in the fd repository:
|
||||
``` bash
|
||||
> cd fd
|
||||
> fd -e md
|
||||
CONTRIBUTING.md
|
||||
README.md
|
||||
```
|
||||
|
||||
The `-e` option can be used in combination with a search pattern:
|
||||
``` bash
|
||||
> fd -e rs mod
|
||||
src/fshelper/mod.rs
|
||||
src/lscolors/mod.rs
|
||||
tests/testenv/mod.rs
|
||||
```
|
||||
|
||||
### Hidden and ignored files
|
||||
By default, *fd* does not search hidden directories and does not show hidden files in the
|
||||
search results. To disable this behavior, we can use the `-H` (or `--hidden`) option:
|
||||
``` bash
|
||||
> fd pre-commit
|
||||
> fd -H pre-commit
|
||||
.git/hooks/pre-commit.sample
|
||||
```
|
||||
|
||||
If we work in a directory that is a Git repository (or includes Git repositories), *fd* does not
|
||||
search folders (and does not show files) that match one of the `.gitignore` patterns. To disable
|
||||
this behavior, we can use the `-I` (or `--no-ignore`) option:
|
||||
``` bash
|
||||
> fd num_cpu
|
||||
> fd -I num_cpu
|
||||
target/debug/deps/libnum_cpus-f5ce7ef99006aa05.rlib
|
||||
```
|
||||
|
||||
To really search *all* files and directories, simply combine the hidden and ignore features to show
|
||||
everything (`-HI`).
|
||||
|
||||
### 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`.
|
||||
|
||||
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
|
||||
want to search all hidden files and directories (`-H`) but exclude all matches from `.git`
|
||||
directories. We can use the `-E` (or `--exclude`) option for this. It takes an arbitrary glob
|
||||
pattern as an argument:
|
||||
``` bash
|
||||
> fd -H -E .git …
|
||||
```
|
||||
|
||||
We can also use this to skip mounted directories:
|
||||
``` bash
|
||||
> fd -E /mnt/external-drive …
|
||||
```
|
||||
|
||||
.. or to skip certain file types:
|
||||
``` bash
|
||||
> fd -E '*.bak' …
|
||||
```
|
||||
|
||||
To make exclude-patterns like these permanent, you can create a `.fdignore` file. They work like
|
||||
`.gitignore` files, but are specific to `fd`. For example:
|
||||
``` bash
|
||||
> cat ~/.fdignore
|
||||
/mnt/external-drive
|
||||
*.bak
|
||||
```
|
||||
Note: `fd` also supports `.ignore` files that are used by other programs such as `rg` or `ag`.
|
||||
|
||||
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.
|
||||
|
||||
### Deleting files
|
||||
|
||||
You can use `fd` to remove all files and directories that are matched by your search pattern.
|
||||
If you only want to remove files, you can use the `--exec-batch`/`-X` option to call `rm`. For
|
||||
example, to recursively remove all `.DS_Store` files, run:
|
||||
``` bash
|
||||
> fd -H '^\.DS_Store$' -tf -X rm
|
||||
```
|
||||
If you are unsure, always call `fd` without `-X rm` first. Alternatively, use `rm`s "interactive"
|
||||
option:
|
||||
``` bash
|
||||
> fd -H '^\.DS_Store$' -tf -X rm -i
|
||||
```
|
||||
|
||||
If you also want to remove a certain class of directories, you can use the same technique. You will
|
||||
have to use `rm`s `--recursive`/`-r` flag to remove directories.
|
||||
|
||||
Note: there are scenarios where using `fd … -X rm -r` can cause race conditions: if you have a
|
||||
path like `…/foo/bar/foo/…` and want to remove all directories named `foo`, you can end up in a
|
||||
situation where the outer `foo` directory is removed first, leading to (harmless) *"'foo/bar/foo':
|
||||
No such file or directory"* errors in the `rm` call.
|
||||
|
||||
### 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 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), 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)
|
||||
|
||||
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
|
||||
@@ -88,71 +358,153 @@ be different for you! I encourage everyone to try it out on their own. See
|
||||
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
|
||||
## 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 looking for alternative, more
|
||||
complete (and more colorful) variants, see
|
||||
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.
|
||||
|
||||
## 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`).
|
||||
### `fd` does not find my file!
|
||||
|
||||
Remember that `fd` ignores hidden directories and files by default. It also ignores patterns
|
||||
from `.gitignore` files. If you want to make sure to find absolutely every possible file, always
|
||||
use the options `-H` and `-I` to disable these two features:
|
||||
``` bash
|
||||
# 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
|
||||
> fd -HI …
|
||||
```
|
||||
|
||||
The number of threads used for command execution can be set with the `--threads`/`-j` option.
|
||||
### `fd` doesn't seem to interpret my regex pattern correctly
|
||||
|
||||
A lot of special regex characters (like `[]`, `^`, `$`, ..) are also special characters in your
|
||||
shell. If in doubt, always make sure to put single quotes around the regex pattern:
|
||||
|
||||
``` bash
|
||||
> fd '^[A-Z][0-9]+$'
|
||||
```
|
||||
|
||||
If your pattern starts with a dash, you have to add `--` to signal the end of command line
|
||||
options. Otherwise, the pattern will be interpreted as a command-line option. Alternatively,
|
||||
use a character class with a single hyphen character:
|
||||
|
||||
``` bash
|
||||
> fd -- '-pattern'
|
||||
> fd '[-]pattern'
|
||||
```
|
||||
|
||||
## Integration with other programs
|
||||
|
||||
### Using fd with `fzf`
|
||||
|
||||
You can use *fd* to generate input for the command-line fuzzy finder [fzf](https://github.com/junegunn/fzf):
|
||||
``` bash
|
||||
export FZF_DEFAULT_COMMAND='fd --type file'
|
||||
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
||||
```
|
||||
|
||||
Then, you can type `vim <Ctrl-T>` on your terminal to open fzf and search through the fd-results.
|
||||
|
||||
Alternatively, you might like to follow symbolic links and include hidden files (but exclude `.git` folders):
|
||||
``` bash
|
||||
export FZF_DEFAULT_COMMAND='fd --type file --follow --hidden --exclude .git'
|
||||
```
|
||||
|
||||
You can even use fd's colored output inside fzf by setting:
|
||||
``` bash
|
||||
export FZF_DEFAULT_COMMAND="fd --type file --color=always"
|
||||
export FZF_DEFAULT_OPTS="--ansi"
|
||||
```
|
||||
|
||||
For more details, see the [Tips section](https://github.com/junegunn/fzf#tips) of the fzf README.
|
||||
|
||||
### Using fd with `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.
|
||||
|
||||
After installing `find-file-in-project`, add the line `(setq ffip-use-rust-fd t)` to your
|
||||
`~/.emacs` or `~/.emacs.d/init.el` file.
|
||||
|
||||
In emacs, run `M-x find-file-in-project-by-selected` to find matching files. Alternatively, run
|
||||
`M-x find-file-in-project` to list all available files in the project.
|
||||
|
||||
### 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/versions)
|
||||
|
||||
[](https://repology.org/project/rust:fd-find/versions)
|
||||
|
||||
|
||||
[](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/disco/fd-find):
|
||||
[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 you add an `alias fd=fdfind` to your shells initialization file, in order to
|
||||
use `fd` in the same way as in this documentation.
|
||||
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.1.0_amd64.deb # adapt version number and architecture
|
||||
sudo dpkg -i fd_8.2.1_amd64.deb # adapt version number and architecture
|
||||
```
|
||||
|
||||
### On Debian
|
||||
@@ -163,8 +515,9 @@ If you run Debian Buster or newer, you can install the
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -268,11 +621,11 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can
|
||||
```
|
||||
cargo install fd-find
|
||||
```
|
||||
Note that rust version *1.36.0* or later is required.
|
||||
Note that rust version *1.42.0* or later is required.
|
||||
|
||||
### From binaries
|
||||
|
||||
The [release page](https://github.com/sharkdp/fd/releases) includes precompiled binaries for Linux, macOS and Windows.
|
||||
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
|
||||
@@ -286,303 +639,18 @@ cargo build
|
||||
cargo test
|
||||
|
||||
# Install
|
||||
cargo install
|
||||
cargo install --path .
|
||||
```
|
||||
|
||||
## Command-line options
|
||||
```
|
||||
USAGE:
|
||||
fd [FLAGS/OPTIONS] [<pattern>] [<path>...]
|
||||
## Maintainers
|
||||
|
||||
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 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), 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)
|
||||
|
||||
ARGS:
|
||||
<pattern> the search pattern - a regular expression unless '--glob' is used (optional)
|
||||
<path>... the root directory for the filesystem search (optional)
|
||||
```
|
||||
|
||||
This is the output of `fd -h`. To see the full set of command-line options, use `fd --help` which
|
||||
also includes a much more detailed help text.
|
||||
|
||||
## Tutorial
|
||||
|
||||
First, to get an overview of all available command line options, you can either run
|
||||
`fd -h` for a concise help message (see above) or `fd --help` for a more detailed
|
||||
version.
|
||||
|
||||
### Simple search
|
||||
|
||||
*fd* is designed to find entries in your filesystem. The most basic search you can perform is to
|
||||
run *fd* with a single argument: the search pattern. For example, assume that you want to find an
|
||||
old script of yours (the name included `netflix`):
|
||||
``` bash
|
||||
> fd netfl
|
||||
Software/python/imdb-ratings/netflix-details.py
|
||||
```
|
||||
If called with just a single argument like this, *fd* searches the current directory recursively
|
||||
for any entries that *contain* the pattern `netfl`.
|
||||
|
||||
### Regular expression search
|
||||
|
||||
The search pattern is treated as a regular expression. Here, we search for entries that start
|
||||
with `x` and end with `rc`:
|
||||
``` bash
|
||||
> cd /etc
|
||||
> fd '^x.*rc$'
|
||||
X11/xinit/xinitrc
|
||||
X11/xinit/xserverrc
|
||||
```
|
||||
|
||||
### Specifying the root directory
|
||||
|
||||
If we want to search a specific directory, it can be given as a second argument to *fd*:
|
||||
``` bash
|
||||
> fd passwd /etc
|
||||
/etc/default/passwd
|
||||
/etc/pam.d/passwd
|
||||
/etc/passwd
|
||||
```
|
||||
|
||||
### Running *fd* without any arguments
|
||||
|
||||
*fd* can be called with no arguments. This is very useful to get a quick overview of all entries
|
||||
in the current directory, recursively (similar to `ls -R`):
|
||||
``` bash
|
||||
> cd fd/tests
|
||||
> fd
|
||||
testenv
|
||||
testenv/mod.rs
|
||||
tests.rs
|
||||
```
|
||||
|
||||
If you want to use this functionality to list all files in a given directory, you have to use
|
||||
a catch-all pattern such as `.` or `^`:
|
||||
``` bash
|
||||
> fd . fd/tests/
|
||||
testenv
|
||||
testenv/mod.rs
|
||||
tests.rs
|
||||
```
|
||||
|
||||
### Searching for a particular file extension
|
||||
|
||||
Often, we are interested in all files of a particular type. This can be done with the `-e` (or
|
||||
`--extension`) option. Here, we search for all Markdown files in the fd repository:
|
||||
``` bash
|
||||
> cd fd
|
||||
> fd -e md
|
||||
CONTRIBUTING.md
|
||||
README.md
|
||||
```
|
||||
|
||||
The `-e` option can be used in combination with a search pattern:
|
||||
``` bash
|
||||
> fd -e rs mod
|
||||
src/fshelper/mod.rs
|
||||
src/lscolors/mod.rs
|
||||
tests/testenv/mod.rs
|
||||
```
|
||||
|
||||
### Hidden and ignored files
|
||||
By default, *fd* does not search hidden directories and does not show hidden files in the
|
||||
search results. To disable this behavior, we can use the `-H` (or `--hidden`) option:
|
||||
``` bash
|
||||
> fd pre-commit
|
||||
> fd -H pre-commit
|
||||
.git/hooks/pre-commit.sample
|
||||
```
|
||||
|
||||
If we work in a directory that is a Git repository (or includes Git repositories), *fd* does not
|
||||
search folders (and does not show files) that match one of the `.gitignore` patterns. To disable
|
||||
this behavior, we can use the `-I` (or `--no-ignore`) option:
|
||||
``` bash
|
||||
> fd num_cpu
|
||||
> fd -I num_cpu
|
||||
target/debug/deps/libnum_cpus-f5ce7ef99006aa05.rlib
|
||||
```
|
||||
|
||||
To really search *all* files and directories, simply combine the hidden and ignore features to show
|
||||
everything (`-HI`).
|
||||
|
||||
### Excluding specific files or directories
|
||||
|
||||
Sometimes we want to ignore search results from a specific subdirectory. For example, we might
|
||||
want to search all hidden files and directories (`-H`) but exclude all matches from `.git`
|
||||
directories. We can use the `-E` (or `--exclude`) option for this. It takes an arbitrary glob
|
||||
pattern as an argument:
|
||||
``` bash
|
||||
> fd -H -E .git …
|
||||
```
|
||||
|
||||
We can also use this to skip mounted directories:
|
||||
``` bash
|
||||
> fd -E /mnt/external-drive …
|
||||
```
|
||||
|
||||
.. or to skip certain file types:
|
||||
``` bash
|
||||
> fd -E '*.bak' …
|
||||
```
|
||||
|
||||
To make exclude-patterns like these permanent, you can create a `.fdignore` file. They work like
|
||||
`.gitignore` files, but are specific to `fd`. For example:
|
||||
``` bash
|
||||
> cat ~/.fdignore
|
||||
/mnt/external-drive
|
||||
*.bak
|
||||
```
|
||||
Note: `fd` also supports `.ignore` files that are used by other programs such as `rg` or `ag`.
|
||||
|
||||
If you want `fd` to ignore these patterns globally, you can put them in `fd`'s global ignore file.
|
||||
This is usually located in `~/.config/fd/ignore` in macOS or Linux, and `%APPDATA%\fd\ignore` in
|
||||
Windows.
|
||||
|
||||
### Using fd with `xargs` or `parallel`
|
||||
|
||||
If we want to run a command on all search results, we can pipe the output to `xargs`:
|
||||
``` 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.
|
||||
|
||||
### Deleting files
|
||||
|
||||
You can use `fd` to remove all files and directories that are matched by your search pattern.
|
||||
If you only want to remove files, you can use the `--exec-batch`/`-X` option to call `rm`. For
|
||||
example, to recursively remove all `.DS_Store` files, run:
|
||||
``` bash
|
||||
> fd -H '^\.DS_Store$' -tf -X rm
|
||||
```
|
||||
If you are unsure, always call `fd` without `-X rm` first. Alternatively, use `rm`s "interactive"
|
||||
option:
|
||||
``` bash
|
||||
> fd -H '^\.DS_Store$' -tf -X rm -i
|
||||
```
|
||||
|
||||
If you also want to remove a certain class of directories, you can use the same technique. You will
|
||||
have to use `rm`s `--recursive`/`-r` flag to remove directories.
|
||||
|
||||
Note: there are scenarios where using `fd … -X rm -r` can cause race conditions: if you have a
|
||||
path like `…/foo/bar/foo/…` and want to remove all directories named `foo`, you can end up in a
|
||||
situation where the outer `foo` directory is removed first, leading to (harmless) *"'foo/bar/foo':
|
||||
No such file or directory"* errors in the `rm` call.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### `fd` does not find my file!
|
||||
|
||||
Remember that `fd` ignores hidden directories and files by default. It also ignores patterns
|
||||
from `.gitignore` files. If you want to make sure to find absolutely every possible file, always
|
||||
use the options `-H` and `-I` to disable these two features:
|
||||
``` bash
|
||||
> fd -HI …
|
||||
```
|
||||
|
||||
#### `fd` doesn't seem to interpret my regex pattern correctly
|
||||
|
||||
A lot of special regex characters (like `[]`, `^`, `$`, ..) are also special characters in your
|
||||
shell. If in doubt, always make sure to put single quotes around the regex pattern:
|
||||
|
||||
``` bash
|
||||
> fd '^[A-Z][0-9]+$'
|
||||
```
|
||||
|
||||
If your pattern starts with a dash, you have to add `--` to signal the end of command line
|
||||
options. Otherwise, the pattern will be interpreted as a command-line option. Alternatively,
|
||||
use a character class with a single hyphen character:
|
||||
|
||||
``` bash
|
||||
> fd -- '-pattern'
|
||||
> fd '[-]pattern'
|
||||
```
|
||||
|
||||
### Integration with other programs
|
||||
|
||||
#### Using fd with `fzf`
|
||||
|
||||
You can use *fd* to generate input for the command-line fuzzy finder [fzf](https://github.com/junegunn/fzf):
|
||||
``` bash
|
||||
export FZF_DEFAULT_COMMAND='fd --type file'
|
||||
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
||||
```
|
||||
|
||||
Then, you can type `vim <Ctrl-T>` on your terminal to open fzf and search through the fd-results.
|
||||
|
||||
Alternatively, you might like to follow symbolic links and include hidden files (but exclude `.git` folders):
|
||||
``` bash
|
||||
export FZF_DEFAULT_COMMAND='fd --type file --follow --hidden --exclude .git'
|
||||
```
|
||||
|
||||
You can even use fd's colored output inside fzf by setting:
|
||||
``` bash
|
||||
export FZF_DEFAULT_COMMAND="fd --type file --color=always"
|
||||
export FZF_DEFAULT_OPTS="--ansi"
|
||||
```
|
||||
|
||||
For more details, see the [Tips section](https://github.com/junegunn/fzf#tips) of the fzf README.
|
||||
|
||||
#### Using fd with `emacs`
|
||||
|
||||
The emacs package [find-file-in-project](https://github.com/technomancy/find-file-in-project) can
|
||||
use *fd* to find files.
|
||||
|
||||
After installing `find-file-in-project`, add the line `(setq ffip-use-rust-fd t)` to your
|
||||
`~/.emacs` or `~/.emacs.d/init.el` file.
|
||||
|
||||
In emacs, run `M-x find-file-in-project-by-selected` to find matching files. Alternatively, run
|
||||
`M-x find-file-in-project` to list all available files in the project.
|
||||
|
||||
#### Printing fd's output as a tree
|
||||
|
||||
To format the output of `fd` similar to the `tree` command, install [`as-tree`] and pipe the output
|
||||
of `fd` to `as-tree`:
|
||||
```bash
|
||||
fd | as-tree
|
||||
```
|
||||
|
||||
This can be more useful than running `tree` by itself because `tree` does not ignore any files by
|
||||
default, nor does it support as rich a set of options as `fd` does to control what to print:
|
||||
```bash
|
||||
❯ fd --extension rs | as-tree
|
||||
.
|
||||
├── build.rs
|
||||
└── src
|
||||
├── app.rs
|
||||
└── error.rs
|
||||
```
|
||||
|
||||
For more information about `as-tree`, see [the `as-tree` README][`as-tree`].
|
||||
|
||||
[`as-tree`]: https://github.com/jez/as-tree
|
||||
- [sharkdp](https://github.com/sharkdp)
|
||||
- [tmccombs](https://github.com/tmccombs)
|
||||
- [tavianator](https://github.com/tavianator)
|
||||
|
||||
## License
|
||||
Copyright (c) 2017-2020 The fd developers
|
||||
|
||||
Copyright (c) 2017-2021 The fd developers
|
||||
|
||||
`fd` is distributed under the terms of both the MIT License and the Apache License 2.0.
|
||||
|
||||
|
||||
@@ -1,71 +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
|
||||
|
||||
# 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+.*$/
|
||||
@@ -5,7 +5,7 @@ use clap::Shell;
|
||||
include!("src/app.rs");
|
||||
|
||||
fn main() {
|
||||
let min_version = "1.36";
|
||||
let min_version = "1.42";
|
||||
|
||||
match version_check::is_min_version(min_version) {
|
||||
Some(true) => {}
|
||||
@@ -16,7 +16,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -26,6 +26,5 @@ fn main() {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
* linguist-vendored
|
||||
@@ -1,179 +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
|
||||
local homepage
|
||||
local maintainer
|
||||
local gcc_prefix
|
||||
|
||||
homepage="https://github.com/sharkdp/fd"
|
||||
maintainer="David Peter <mail@david-peter.de>"
|
||||
|
||||
case $TARGET in
|
||||
x86_64*)
|
||||
architecture=amd64
|
||||
gcc_prefix=""
|
||||
;;
|
||||
i686*)
|
||||
architecture=i386
|
||||
gcc_prefix=""
|
||||
;;
|
||||
arm*hf)
|
||||
architecture=armhf
|
||||
gcc_prefix="arm-linux-gnueabihf-"
|
||||
;;
|
||||
*)
|
||||
echo "make_deb: skipping target '${TARGET}'" >&2
|
||||
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"
|
||||
"${gcc_prefix}"strip "$tempdir/usr/bin/$PROJECT_NAME"
|
||||
|
||||
# manpage
|
||||
install -Dm644 "doc/$PROJECT_NAME.1" "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
|
||||
gzip --best "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
|
||||
|
||||
# readme and license
|
||||
install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md"
|
||||
install -Dm644 LICENSE-MIT "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-MIT"
|
||||
install -Dm644 LICENSE-APACHE "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-APACHE"
|
||||
cat > "$tempdir/usr/share/doc/$PROJECT_NAME/copyright" <<EOF
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: $PROJECT_NAME
|
||||
Source: $homepage
|
||||
|
||||
Files: *
|
||||
Copyright: $maintainer
|
||||
License: Apache-2.0 or MIT
|
||||
|
||||
License: Apache-2.0
|
||||
On Debian systems, the complete text of the Apache-2.0 can be found in the
|
||||
file /usr/share/common-licenses/Apache-2.0.
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
EOF
|
||||
|
||||
# completions
|
||||
install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.bash "$tempdir/usr/share/bash-completion/completions/${PROJECT_NAME}"
|
||||
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: $maintainer
|
||||
Architecture: $architecture
|
||||
Provides: $PROJECT_NAME
|
||||
Conflicts: $conflictname
|
||||
Homepage: $homepage
|
||||
Description: Simple, fast and user-friendly alternative to find
|
||||
While fd does not seek to mirror all of find's powerful functionality, it
|
||||
provides sensible (opinionated) defaults for 80% of the use cases.
|
||||
EOF
|
||||
|
||||
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,268 @@
|
||||
#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]"
|
||||
|
||||
+ '(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'
|
||||
|
||||
+ 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 -/'
|
||||
|
||||
+ 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
|
||||
@@ -57,10 +57,11 @@ pattern contains an uppercase character (smart case).
|
||||
Perform a glob-based search instead of a regular expression search.
|
||||
.TP
|
||||
.B \-\-regex
|
||||
Perform a regular-expression based seach (default). This can be used to override --glob.
|
||||
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.
|
||||
@@ -115,6 +116,9 @@ Only show search results starting at the given depth. See also: '--max-depth' an
|
||||
.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
|
||||
@@ -142,11 +146,17 @@ This option can be used repeatedly to allow for multiple file types.
|
||||
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.
|
||||
@@ -176,6 +186,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'
|
||||
@@ -232,13 +244,37 @@ Examples:
|
||||
\-\-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 {//}
|
||||
@@ -248,15 +284,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 {//}
|
||||
@@ -266,22 +320,39 @@ 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
|
||||
|
||||
.SH PATTERN SYNTAX
|
||||
The regular expression syntax used by fd is documented here:
|
||||
|
||||
.UR https://docs.rs/regex/1.0.0/regex/#syntax
|
||||
.UE
|
||||
https://docs.rs/regex/1.0.0/regex/#syntax
|
||||
|
||||
The glob syntax is documented here:
|
||||
|
||||
.UR https://docs.rs/globset/#syntax
|
||||
.UE
|
||||
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
|
||||
@@ -297,7 +368,7 @@ $ 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:"
|
||||
|
||||
+69
-26
@@ -1,10 +1,16 @@
|
||||
use clap::{crate_version, App, AppSettings, Arg};
|
||||
|
||||
pub fn build_app() -> App<'static, 'static> {
|
||||
let clap_color_setting = if std::env::var_os("NO_COLOR").is_none() {
|
||||
AppSettings::ColoredHelp
|
||||
} else {
|
||||
AppSettings::ColorNever
|
||||
};
|
||||
|
||||
let mut app = App::new("fd")
|
||||
.version(crate_version!())
|
||||
.usage("fd [FLAGS/OPTIONS] [<pattern>] [<path>...]")
|
||||
.setting(AppSettings::ColoredHelp)
|
||||
.setting(clap_color_setting)
|
||||
.setting(AppSettings::DeriveDisplayOrder)
|
||||
.after_help(
|
||||
"Note: `fd -h` prints a short and concise overview while `fd --help` gives all \
|
||||
@@ -111,7 +117,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.overrides_with("fixed-strings")
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"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'.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@@ -167,6 +175,7 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.short("0")
|
||||
.overrides_with("print0")
|
||||
.conflicts_with("list-details")
|
||||
.hidden_short_help(true)
|
||||
.help("Separate results by the null character")
|
||||
.long_help(
|
||||
"Separate search results by the null character (instead of newlines). \
|
||||
@@ -215,6 +224,13 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
'--min-depth <depth> --max-depth <depth>'.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("prune")
|
||||
.long("prune")
|
||||
.conflicts_with_all(&["size", "exact-depth"])
|
||||
.hidden_short_help(true)
|
||||
.long_help("Do not traverse into matching directories.")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("file-type")
|
||||
.long("type")
|
||||
@@ -266,7 +282,9 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.help("Filter by file extension")
|
||||
.long_help(
|
||||
"(Additionally) filter search results by their file extension. Multiple \
|
||||
allowable file extensions can be specified.",
|
||||
allowable file extensions can be specified.\n\
|
||||
If you want to search for files without extension, \
|
||||
you can use the regex '^[^.]+$' as a normal search pattern.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@@ -280,16 +298,24 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.conflicts_with("list-details")
|
||||
.help("Execute a command for each search result")
|
||||
.long_help(
|
||||
"Execute a command for each search result.\n\
|
||||
All arguments following --exec are taken to be arguments to the command until the \
|
||||
argument ';' is encountered.\n\
|
||||
Each occurrence of the following placeholders is substituted by a path derived from the \
|
||||
current search result before the command is executed:\n \
|
||||
'{}': path\n \
|
||||
"Execute a command for each search result in parallel (use --threads=1 for sequential command execution). \
|
||||
All positional arguments following --exec are considered to be arguments to the command - not to fd. \
|
||||
It is therefore recommended to place the '-x'/'--exec' option last.\n\
|
||||
The following placeholders are substituted before the command is executed:\n \
|
||||
'{}': path (of the current search result)\n \
|
||||
'{/}': basename\n \
|
||||
'{//}': parent directory\n \
|
||||
'{.}': path without file extension\n \
|
||||
'{/.}': basename without file extension",
|
||||
'{/.}': basename without file extension\n\n\
|
||||
If no placeholder is present, an implicit \"{}\" at the end is assumed.\n\n\
|
||||
Examples:\n\n \
|
||||
- find all *.zip files and unzip them:\n\n \
|
||||
fd -e zip -x unzip\n\n \
|
||||
- find *.h and *.cpp files and run \"clang-format -i ..\" for each of them:\n\n \
|
||||
fd -e h -e cpp -x clang-format -i\n\n \
|
||||
- Convert all *.jpg files to *.png files:\n\n \
|
||||
fd -e jpg -x convert {} {.}.png\
|
||||
",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@@ -303,16 +329,20 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.conflicts_with_all(&["exec", "list-details"])
|
||||
.help("Execute a command with all search results at once")
|
||||
.long_help(
|
||||
"Execute a command with all search results at once.\n\
|
||||
All arguments following --exec-batch are taken to be arguments to the command until the \
|
||||
argument ';' is encountered.\n\
|
||||
A single occurrence of the following placeholders is authorized and substituted by the paths derived from the \
|
||||
search results before the command is executed:\n \
|
||||
'{}': path\n \
|
||||
"Execute the given command once, with all search results as arguments.\n\
|
||||
One of the following placeholders is substituted before the command is executed:\n \
|
||||
'{}': path (of all search results)\n \
|
||||
'{/}': basename\n \
|
||||
'{//}': parent directory\n \
|
||||
'{.}': path without file extension\n \
|
||||
'{/.}': basename without file extension",
|
||||
'{/.}': basename without file extension\n\n\
|
||||
If no placeholder is present, an implicit \"{}\" at the end is assumed.\n\n\
|
||||
Examples:\n\n \
|
||||
- Find all test_*.py files and open them in your favorite editor:\n\n \
|
||||
fd -g 'test_*.py' -X vim\n\n \
|
||||
- Find all *.rs files and count the lines with \"wc -l ...\":\n\n \
|
||||
fd -e rs -X wc -l\
|
||||
"
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@@ -327,7 +357,10 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.long_help(
|
||||
"Exclude files/directories that match the given glob pattern. This \
|
||||
overrides any other ignore logic. Multiple exclude patterns can be \
|
||||
specified.",
|
||||
specified.\n\n\
|
||||
Examples:\n \
|
||||
--exclude '*.pyc'\n \
|
||||
--exclude node_modules",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@@ -383,7 +416,8 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.long_help(
|
||||
"Limit results based on the size of files using the format <+-><NUM><UNIT>.\n \
|
||||
'+': file size must be greater than or equal to this\n \
|
||||
'-': file size must be less than or equal to this\n \
|
||||
'-': file size must be less than or equal to this\n\
|
||||
If neither '+' nor '-' is specified, file size must be exactly equal to this.\n \
|
||||
'NUM': The numeric size (e.g. 500)\n \
|
||||
'UNIT': The units for NUM. They are not case-sensitive.\n\
|
||||
Allowed unit values:\n \
|
||||
@@ -412,6 +446,7 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
Arg::with_name("changed-within")
|
||||
.long("changed-within")
|
||||
.alias("change-newer-than")
|
||||
.alias("newer")
|
||||
.takes_value(true)
|
||||
.value_name("date|dur")
|
||||
.number_of_values(1)
|
||||
@@ -419,16 +454,19 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.long_help(
|
||||
"Filter results based on the file modification time. The argument can be provided \
|
||||
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
|
||||
'--change-newer-than' can be used as an alias.\n\
|
||||
If the time is not specified, it defaults to 00:00:00. \
|
||||
'--change-newer-than' or '--newer' can be used as aliases.\n\
|
||||
Examples:\n \
|
||||
--changed-within 2weeks\n \
|
||||
--change-newer-than '2018-10-27 10:00:00'",
|
||||
--change-newer-than '2018-10-27 10:00:00'\n \
|
||||
--newer 2018-10-27",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("changed-before")
|
||||
.long("changed-before")
|
||||
.alias("change-older-than")
|
||||
.alias("older")
|
||||
.takes_value(true)
|
||||
.value_name("date|dur")
|
||||
.number_of_values(1)
|
||||
@@ -436,10 +474,11 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.long_help(
|
||||
"Filter results based on the file modification time. The argument can be provided \
|
||||
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
|
||||
'--change-older-than' can be used as an alias.\n\
|
||||
'--change-older-than' or '--older' can be used as aliases.\n\
|
||||
Examples:\n \
|
||||
--changed-before '2018-10-27 10:00:00'\n \
|
||||
--change-older-than 2weeks",
|
||||
--change-older-than 2weeks\n \
|
||||
--older 2018-10-27",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@@ -483,7 +522,7 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.number_of_values(1)
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"Change the current working directory of fd to the provided path. The \
|
||||
"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 \
|
||||
<path> argument or the '--search-path' option will also be resolved \
|
||||
@@ -492,8 +531,12 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("pattern").help(
|
||||
"the search pattern - a regular expression unless '--glob' is used (optional)",
|
||||
),
|
||||
"the search pattern (a regular expression, unless '--glob' is used; optional)",
|
||||
).long_help(
|
||||
"the search pattern which is either a regular expression (default) or a glob \
|
||||
pattern (if --glob is used). If no pattern has been specified, every entry \
|
||||
is considered a match. If your pattern starts with a dash (-), make sure to \
|
||||
pass '--' first, or it will be considered as a flag (fd -- '-foo').")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("path-separator")
|
||||
|
||||
+12
-2
@@ -7,9 +7,19 @@ use crate::error::print_error;
|
||||
use crate::exit_codes::ExitCode;
|
||||
|
||||
/// Executes a command.
|
||||
pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) -> ExitCode {
|
||||
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 {
|
||||
|
||||
+4
-2
@@ -16,6 +16,7 @@ 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 {
|
||||
@@ -38,7 +39,7 @@ pub fn job(
|
||||
// Drop the lock so that other threads can read from the receiver.
|
||||
drop(lock);
|
||||
// Generate a command, execute it and store its exit code.
|
||||
results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm)))
|
||||
results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm), buffer_output))
|
||||
}
|
||||
// Returns error in case of any error.
|
||||
merge_exitcodes(&results)
|
||||
@@ -48,6 +49,7 @@ pub fn batch(
|
||||
rx: Receiver<WorkerResult>,
|
||||
cmd: &CommandTemplate,
|
||||
show_filesystem_errors: bool,
|
||||
buffer_output: bool,
|
||||
) -> ExitCode {
|
||||
let paths = rx.iter().filter_map(|value| match value {
|
||||
WorkerResult::Entry(val) => Some(val),
|
||||
@@ -58,5 +60,5 @@ pub fn batch(
|
||||
None
|
||||
}
|
||||
});
|
||||
cmd.generate_and_execute_batch(paths)
|
||||
cmd.generate_and_execute_batch(paths, buffer_output)
|
||||
}
|
||||
|
||||
+158
-36
@@ -3,8 +3,9 @@ mod input;
|
||||
mod job;
|
||||
mod token;
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::{Component, Path, PathBuf, Prefix};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -37,23 +38,24 @@ 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>
|
||||
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(anyhow!("Only one placeholder allowed for batch commands"));
|
||||
}
|
||||
@@ -65,7 +67,7 @@ impl CommandTemplate {
|
||||
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>,
|
||||
@@ -122,7 +124,11 @@ impl CommandTemplate {
|
||||
args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder]));
|
||||
}
|
||||
|
||||
CommandTemplate { args, mode }
|
||||
CommandTemplate {
|
||||
args,
|
||||
mode,
|
||||
path_separator,
|
||||
}
|
||||
}
|
||||
|
||||
fn number_of_tokens(&self) -> usize {
|
||||
@@ -133,26 +139,31 @@ impl CommandTemplate {
|
||||
///
|
||||
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
|
||||
/// build. Once all arguments have been processed, the command is executed.
|
||||
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) -> ExitCode {
|
||||
pub fn generate_and_execute(
|
||||
&self,
|
||||
input: &Path,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
buffer_output: bool,
|
||||
) -> ExitCode {
|
||||
let input = strip_current_dir(input);
|
||||
|
||||
let mut cmd = Command::new(self.args[0].generate(&input));
|
||||
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));
|
||||
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) -> ExitCode
|
||||
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(""));
|
||||
let mut cmd = Command::new(self.args[0].generate("", None));
|
||||
cmd.stdin(Stdio::inherit());
|
||||
cmd.stdout(Stdio::inherit());
|
||||
cmd.stderr(Stdio::inherit());
|
||||
@@ -167,16 +178,16 @@ impl CommandTemplate {
|
||||
// A single `Tokens` is expected
|
||||
// So we can directly consume the iterator once and for all
|
||||
for path in &mut paths {
|
||||
cmd.arg(arg.generate(strip_current_dir(path)));
|
||||
cmd.arg(arg.generate(strip_current_dir(path), self.path_separator.as_deref()));
|
||||
has_path = true;
|
||||
}
|
||||
} else {
|
||||
cmd.arg(arg.generate(""));
|
||||
cmd.arg(arg.generate("", None));
|
||||
}
|
||||
}
|
||||
|
||||
if has_path {
|
||||
execute_command(cmd, &Mutex::new(()))
|
||||
execute_command(cmd, &Mutex::new(()), buffer_output)
|
||||
} else {
|
||||
ExitCode::Success
|
||||
}
|
||||
@@ -195,27 +206,34 @@ enum ArgumentTemplate {
|
||||
|
||||
impl ArgumentTemplate {
|
||||
pub fn has_tokens(&self) -> bool {
|
||||
match self {
|
||||
ArgumentTemplate::Tokens(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, ArgumentTemplate::Tokens(_))
|
||||
}
|
||||
|
||||
pub fn generate(&self, path: impl AsRef<Path>) -> OsString {
|
||||
/// 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 = OsString::new();
|
||||
for token in tokens {
|
||||
match *token {
|
||||
Basename => s.push(basename(path.as_ref())),
|
||||
BasenameNoExt => {
|
||||
s.push(remove_extension(&PathBuf::from(basename(path.as_ref()))))
|
||||
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))
|
||||
}
|
||||
NoExt => s.push(remove_extension(path.as_ref())),
|
||||
Parent => s.push(dirname(path.as_ref())),
|
||||
Placeholder => s.push(path.as_ref()),
|
||||
Text(ref string) => s.push(string),
|
||||
}
|
||||
}
|
||||
@@ -224,6 +242,61 @@ impl ArgumentTemplate {
|
||||
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)]
|
||||
@@ -233,7 +306,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()),
|
||||
@@ -241,6 +314,7 @@ mod tests {
|
||||
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -248,13 +322,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,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -262,13 +337,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,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -276,13 +352,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,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -290,13 +367,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,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -304,7 +382,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()),
|
||||
@@ -315,6 +393,7 @@ mod tests {
|
||||
]),
|
||||
],
|
||||
mode: ExecutionMode::OneByOne,
|
||||
path_separator: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -322,19 +401,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 slashses should get normalized and interpreted as separators
|
||||
check!("C:/foo/bar", "C:#foo#bar");
|
||||
check!("C:foo/bar", "C:foo#bar");
|
||||
|
||||
// Rust does not intrepret "//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");
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -5,9 +5,9 @@ pub enum ExitCode {
|
||||
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::GeneralError => 1,
|
||||
ExitCode::KilledBySigint => 130,
|
||||
@@ -16,13 +16,13 @@ impl Into<i32> for ExitCode {
|
||||
}
|
||||
|
||||
impl ExitCode {
|
||||
fn is_error(&self) -> bool {
|
||||
*self != ExitCode::Success
|
||||
fn is_error(self) -> bool {
|
||||
self != ExitCode::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge_exitcodes(results: &[ExitCode]) -> ExitCode {
|
||||
if results.iter().any(ExitCode::is_error) {
|
||||
if results.iter().any(|&c| ExitCode::is_error(c)) {
|
||||
return ExitCode::GeneralError;
|
||||
}
|
||||
ExitCode::Success
|
||||
|
||||
+29
-10
@@ -1,5 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
use std::env::current_dir;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
@@ -7,6 +7,8 @@ use std::io;
|
||||
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> {
|
||||
@@ -15,7 +17,7 @@ pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
|
||||
}
|
||||
|
||||
let path = path.strip_prefix(".").unwrap_or(path);
|
||||
current_dir().map(|path_buf| path_buf.join(path))
|
||||
env::current_dir().map(|path_buf| path_buf.join(path))
|
||||
}
|
||||
|
||||
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
|
||||
@@ -33,10 +35,10 @@ pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
|
||||
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())
|
||||
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"))]
|
||||
@@ -68,22 +70,22 @@ pub fn is_empty(entry: &walk::DirEntry) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn is_socket(ft: &fs::FileType) -> bool {
|
||||
pub fn is_socket(ft: fs::FileType) -> bool {
|
||||
ft.is_socket()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn is_socket(_: &fs::FileType) -> bool {
|
||||
pub fn is_socket(_: fs::FileType) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
pub fn is_pipe(ft: &fs::FileType) -> bool {
|
||||
pub fn is_pipe(ft: fs::FileType) -> bool {
|
||||
ft.is_fifo()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn is_pipe(_: &fs::FileType) -> bool {
|
||||
pub fn is_pipe(_: fs::FileType) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -108,6 +110,23 @@ 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;
|
||||
|
||||
+60
-57
@@ -2,13 +2,14 @@ use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static! {
|
||||
static ref SIZE_CAPTURES: Regex = Regex::new(r"(?i)^([+-])(\d+)(b|[kmgt]i?b?)$").unwrap();
|
||||
static ref SIZE_CAPTURES: Regex = Regex::new(r"(?i)^([+-]?)(\d+)(b|[kmgt]i?b?)$").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum SizeFilter {
|
||||
Max(u64),
|
||||
Min(u64),
|
||||
Equals(u64),
|
||||
}
|
||||
|
||||
// SI prefixes (powers of 10)
|
||||
@@ -49,16 +50,19 @@ impl SizeFilter {
|
||||
};
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,70 +93,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
|
||||
@@ -170,7 +174,6 @@ mod tests {
|
||||
|
||||
// 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",
|
||||
|
||||
@@ -12,6 +12,7 @@ impl TimeFilter {
|
||||
humantime::parse_duration(s)
|
||||
.map(|duration| *ref_time - duration)
|
||||
.or_else(|_| humantime::parse_rfc3339_weak(s))
|
||||
.or_else(|_| humantime::parse_rfc3339_weak(&(s.to_owned() + " 00:00:00")))
|
||||
.ok()
|
||||
}
|
||||
|
||||
|
||||
+88
-24
@@ -21,6 +21,7 @@ use atty::Stream;
|
||||
use globset::GlobBuilder;
|
||||
use lscolors::LsColors;
|
||||
use regex::bytes::{RegexBuilder, RegexSetBuilder};
|
||||
use normpath::PathExt;
|
||||
|
||||
use crate::error::print_error;
|
||||
use crate::exec::CommandTemplate;
|
||||
@@ -30,21 +31,32 @@ use crate::filetypes::FileTypes;
|
||||
use crate::filter::OwnerFilter;
|
||||
use crate::filter::{SizeFilter, TimeFilter};
|
||||
use crate::options::Options;
|
||||
use crate::regex_helper::pattern_has_uppercase_char;
|
||||
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 = "macos"), not(target_env = "musl")))]
|
||||
#[cfg(all(
|
||||
not(windows),
|
||||
not(target_os = "android"),
|
||||
not(target_os = "macos"),
|
||||
not(target_env = "musl")
|
||||
))]
|
||||
#[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 run() -> Result<ExitCode> {
|
||||
let matches = app::build_app().get_matches_from(env::args_os());
|
||||
|
||||
// Set the current working directory of the process
|
||||
if let Some(base_directory) = matches.value_of_os("base-directory") {
|
||||
let base_directory = Path::new(base_directory);
|
||||
if !filesystem::is_dir(base_directory) {
|
||||
if !filesystem::is_existing_directory(base_directory) {
|
||||
return Err(anyhow!(
|
||||
"The '--base-directory' path '{}' is not a directory.",
|
||||
base_directory.to_string_lossy()
|
||||
@@ -52,14 +64,14 @@ fn run() -> Result<ExitCode> {
|
||||
}
|
||||
env::set_current_dir(base_directory).with_context(|| {
|
||||
format!(
|
||||
"Could not set '{}' as the current working directory.",
|
||||
"Could not set '{}' as the current working directory",
|
||||
base_directory.to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
let current_directory = Path::new(".");
|
||||
if !filesystem::is_dir(current_directory) {
|
||||
if !filesystem::is_existing_directory(current_directory) {
|
||||
return Err(anyhow!(
|
||||
"Could not retrieve current directory (has it been deleted?)."
|
||||
));
|
||||
@@ -84,7 +96,7 @@ fn run() -> Result<ExitCode> {
|
||||
let mut directories = vec![];
|
||||
for path in paths {
|
||||
let path_buffer = PathBuf::from(path);
|
||||
if filesystem::is_dir(&path_buffer) {
|
||||
if filesystem::is_existing_directory(&path_buffer) {
|
||||
directories.push(path_buffer);
|
||||
} else {
|
||||
print_error(format!(
|
||||
@@ -109,7 +121,7 @@ fn run() -> Result<ExitCode> {
|
||||
.iter()
|
||||
.map(|path_buffer| {
|
||||
path_buffer
|
||||
.canonicalize()
|
||||
.normalize()
|
||||
.and_then(|pb| filesystem::absolute_path(pb.as_path()))
|
||||
.unwrap()
|
||||
})
|
||||
@@ -119,7 +131,7 @@ fn run() -> Result<ExitCode> {
|
||||
// Detect if the user accidentally supplied a path instead of a search pattern
|
||||
if !matches.is_present("full-path")
|
||||
&& pattern.contains(std::path::MAIN_SEPARATOR)
|
||||
&& filesystem::is_dir(Path::new(pattern))
|
||||
&& Path::new(pattern).is_dir()
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"The search pattern '{pattern}' contains a path-separation character ('{sep}') \
|
||||
@@ -148,28 +160,50 @@ fn run() -> Result<ExitCode> {
|
||||
let case_sensitive = !matches.is_present("ignore-case")
|
||||
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
|
||||
|
||||
#[cfg(windows)]
|
||||
let ansi_colors_support =
|
||||
ansi_term::enable_ansi_support().is_ok() || std::env::var_os("TERM").is_some();
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let ansi_colors_support = true;
|
||||
|
||||
let interactive_terminal = atty::is(Stream::Stdout);
|
||||
let colored_output = match matches.value_of("color") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
_ => env::var_os("NO_COLOR").is_none() && interactive_terminal,
|
||||
_ => ansi_colors_support && env::var_os("NO_COLOR").is_none() && interactive_terminal,
|
||||
};
|
||||
|
||||
let path_separator = matches.value_of("path-separator").map(|str| str.to_owned());
|
||||
let path_separator = matches
|
||||
.value_of("path-separator")
|
||||
.map_or_else(filesystem::default_path_separator, |s| Some(s.to_owned()));
|
||||
|
||||
#[cfg(windows)]
|
||||
let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok();
|
||||
{
|
||||
if let Some(ref sep) = path_separator {
|
||||
if sep.len() > 1 {
|
||||
return 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
|
||||
));
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
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 = if let Some(args) = matches.values_of("exec") {
|
||||
Some(CommandTemplate::new(args))
|
||||
Some(CommandTemplate::new(args, path_separator.clone()))
|
||||
} else if let Some(args) = matches.values_of("exec-batch") {
|
||||
Some(CommandTemplate::new_batch(args)?)
|
||||
Some(CommandTemplate::new_batch(args, path_separator.clone())?)
|
||||
} else if matches.is_present("list-details") {
|
||||
let color = matches.value_of("color").unwrap_or("auto");
|
||||
let color_arg = ["--color=", color].concat();
|
||||
@@ -249,7 +283,7 @@ fn run() -> Result<ExitCode> {
|
||||
));
|
||||
};
|
||||
|
||||
Some(CommandTemplate::new_batch(&cmd).unwrap())
|
||||
Some(CommandTemplate::new_batch(&cmd, path_separator.clone()).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -314,21 +348,38 @@ fn run() -> Result<ExitCode> {
|
||||
.value_of("max-depth")
|
||||
.or_else(|| matches.value_of("rg-depth"))
|
||||
.or_else(|| matches.value_of("exact-depth"))
|
||||
.and_then(|n| usize::from_str_radix(n, 10).ok()),
|
||||
.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"))
|
||||
.and_then(|n| usize::from_str_radix(n, 10).ok()),
|
||||
.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,
|
||||
@@ -373,11 +424,11 @@ fn run() -> Result<ExitCode> {
|
||||
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)]
|
||||
@@ -386,8 +437,10 @@ fn run() -> Result<ExitCode> {
|
||||
path_separator,
|
||||
max_results: matches
|
||||
.value_of("max-results")
|
||||
.and_then(|n| usize::from_str_radix(n, 10).ok())
|
||||
.filter(|&n| n != 0)
|
||||
.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)
|
||||
@@ -397,6 +450,17 @@ fn run() -> Result<ExitCode> {
|
||||
}),
|
||||
};
|
||||
|
||||
if cfg!(unix)
|
||||
&& config.ignore_hidden
|
||||
&& pattern_matches_strings_with_leading_dot(&pattern_regex)
|
||||
{
|
||||
return 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."
|
||||
));
|
||||
}
|
||||
|
||||
let re = RegexBuilder::new(&pattern_regex)
|
||||
.case_insensitive(!config.case_sensitive)
|
||||
.dot_matches_new_line(true)
|
||||
@@ -420,7 +484,7 @@ fn main() {
|
||||
process::exit(exit_code.into());
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("[fd error]: {}", err);
|
||||
eprintln!("[fd error]: {:#}", err);
|
||||
process::exit(ExitCode::GeneralError.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ pub struct Options {
|
||||
/// 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,
|
||||
|
||||
|
||||
+17
-12
@@ -1,42 +1,47 @@
|
||||
use std::io::{self, StdoutLock, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use ansi_term;
|
||||
use lscolors::{LsColors, Style};
|
||||
|
||||
use crate::error::print_error;
|
||||
use crate::exit_codes::ExitCode;
|
||||
use crate::filesystem::strip_current_dir;
|
||||
use crate::options::Options;
|
||||
|
||||
pub fn replace_path_separator(path: &str, new_path_separator: &str) -> String {
|
||||
path.replace(std::path::MAIN_SEPARATOR, &new_path_separator)
|
||||
fn replace_path_separator(path: &str, new_path_separator: &str) -> String {
|
||||
path.replace(std::path::MAIN_SEPARATOR, new_path_separator)
|
||||
}
|
||||
|
||||
// TODO: this function is performance critical and can probably be optimized
|
||||
pub fn print_entry(
|
||||
stdout: &mut StdoutLock,
|
||||
entry: &PathBuf,
|
||||
entry: &Path,
|
||||
config: &Options,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
) {
|
||||
let path = if entry.is_absolute() {
|
||||
entry.as_path()
|
||||
entry
|
||||
} else {
|
||||
strip_current_dir(entry)
|
||||
};
|
||||
|
||||
let r = if let Some(ref ls_colors) = config.ls_colors {
|
||||
print_entry_colorized(stdout, path, config, ls_colors, &wants_to_quit)
|
||||
print_entry_colorized(stdout, path, config, ls_colors, wants_to_quit)
|
||||
} else {
|
||||
print_entry_uncolorized(stdout, path, config)
|
||||
};
|
||||
|
||||
if r.is_err() {
|
||||
// Probably a broken pipe. Exit gracefully.
|
||||
process::exit(ExitCode::GeneralError.into());
|
||||
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').
|
||||
process::exit(0);
|
||||
} else {
|
||||
print_error(format!("Could not write to output: {}", e));
|
||||
process::exit(ExitCode::GeneralError.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +63,7 @@ fn print_entry_colorized(
|
||||
|
||||
let mut path_string = component.to_string_lossy();
|
||||
if let Some(ref separator) = config.path_separator {
|
||||
*path_string.to_mut() = replace_path_separator(&path_string, &separator);
|
||||
*path_string.to_mut() = replace_path_separator(&path_string, separator);
|
||||
}
|
||||
write!(stdout, "{}", style.paint(path_string))?;
|
||||
|
||||
@@ -86,7 +91,7 @@ fn print_entry_uncolorized_base(
|
||||
|
||||
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);
|
||||
*path_string.to_mut() = replace_path_separator(&path_string, separator);
|
||||
}
|
||||
write!(stdout, "{}{}", path_string, separator)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,45 @@ fn hir_has_uppercase_char(hir: &Hir) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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"));
|
||||
@@ -50,3 +89,12 @@ fn pattern_has_uppercase_char_advanced() {
|
||||
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"));
|
||||
}
|
||||
|
||||
+43
-22
@@ -87,10 +87,10 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Options>) ->
|
||||
let config_dir_op = std::env::var_os("XDG_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.filter(|p| p.is_absolute())
|
||||
.or_else(|| dirs::home_dir().map(|d| d.join(".config")));
|
||||
.or_else(|| dirs_next::home_dir().map(|d| d.join(".config")));
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let config_dir_op = dirs::config_dir();
|
||||
let config_dir_op = dirs_next::config_dir();
|
||||
|
||||
if let Some(global_ignore_file) = config_dir_op
|
||||
.map(|p| p.join("fd").join("ignore"))
|
||||
@@ -170,12 +170,13 @@ fn spawn_receiver(
|
||||
|
||||
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)
|
||||
} else {
|
||||
let shared_rx = Arc::new(Mutex::new(rx));
|
||||
|
||||
@@ -189,8 +190,15 @@ 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);
|
||||
@@ -358,19 +366,27 @@ fn spawn_senders(
|
||||
DirEntry::BrokenSymlink(path)
|
||||
}
|
||||
_ => {
|
||||
tx_thread
|
||||
.send(WorkerResult::Error(ignore::Error::WithPath {
|
||||
path,
|
||||
err: inner_err,
|
||||
}))
|
||||
.unwrap();
|
||||
return ignore::WalkState::Continue;
|
||||
match tx_thread.send(WorkerResult::Error(ignore::Error::WithPath {
|
||||
path,
|
||||
err: inner_err,
|
||||
})) {
|
||||
Ok(_) => {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
Err(_) => {
|
||||
return ignore::WalkState::Quit;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => match tx_thread.send(WorkerResult::Error(err)) {
|
||||
Ok(_) => {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
Err(_) => {
|
||||
return ignore::WalkState::Quit;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
tx_thread.send(WorkerResult::Error(err)).unwrap();
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(min_depth) = config.min_depth {
|
||||
@@ -418,8 +434,8 @@ fn spawn_senders(
|
||||
if (!file_types.files && entry_type.is_file())
|
||||
|| (!file_types.directories && entry_type.is_dir())
|
||||
|| (!file_types.symlinks && entry_type.is_symlink())
|
||||
|| (!file_types.sockets && filesystem::is_socket(entry_type))
|
||||
|| (!file_types.pipes && filesystem::is_pipe(entry_type))
|
||||
|| (!file_types.sockets && filesystem::is_socket(*entry_type))
|
||||
|| (!file_types.pipes && filesystem::is_pipe(*entry_type))
|
||||
|| (file_types.executables_only
|
||||
&& !entry
|
||||
.metadata()
|
||||
@@ -429,8 +445,8 @@ fn spawn_senders(
|
||||
|| !(entry_type.is_file()
|
||||
|| entry_type.is_dir()
|
||||
|| entry_type.is_symlink()
|
||||
|| filesystem::is_socket(entry_type)
|
||||
|| filesystem::is_pipe(entry_type))
|
||||
|| filesystem::is_socket(*entry_type)
|
||||
|| filesystem::is_pipe(*entry_type))
|
||||
{
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
@@ -443,7 +459,7 @@ fn spawn_senders(
|
||||
{
|
||||
if let Some(ref owner_constraint) = config.owner_constraint {
|
||||
if let Ok(ref metadata) = entry_path.metadata() {
|
||||
if !owner_constraint.matches(&metadata) {
|
||||
if !owner_constraint.matches(metadata) {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
@@ -494,6 +510,11 @@ fn spawn_senders(
|
||||
return ignore::WalkState::Quit;
|
||||
}
|
||||
|
||||
// Apply pruning.
|
||||
if config.prune {
|
||||
return ignore::WalkState::Skip;
|
||||
}
|
||||
|
||||
ignore::WalkState::Continue
|
||||
})
|
||||
});
|
||||
|
||||
+30
-17
@@ -119,7 +119,7 @@ fn normalize_output(s: &str, trim_start: bool, normalize_line: bool) -> String {
|
||||
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
|
||||
@@ -197,7 +197,7 @@ impl TestEnv {
|
||||
|
||||
// Check for exit status.
|
||||
if !output.status.success() {
|
||||
panic!(format_exit_error(args, &output));
|
||||
panic!("{}", format_exit_error(args, &output));
|
||||
}
|
||||
|
||||
output
|
||||
@@ -236,22 +236,30 @@ impl TestEnv {
|
||||
|
||||
// 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, expected);
|
||||
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) -> process::ExitStatus {
|
||||
self.assert_error_subdirectory(".", args, expected)
|
||||
self.assert_error_subdirectory(".", args, Some(expected))
|
||||
}
|
||||
|
||||
/// Assert that calling *fd* in the specified path under the root working directory,
|
||||
@@ -260,7 +268,7 @@ impl TestEnv {
|
||||
&self,
|
||||
path: P,
|
||||
args: &[&str],
|
||||
expected: &str,
|
||||
expected: Option<&str>,
|
||||
) -> process::ExitStatus {
|
||||
// Setup *fd* command.
|
||||
let mut cmd = process::Command::new(&self.fd_exe);
|
||||
@@ -270,19 +278,24 @@ impl TestEnv {
|
||||
// Run *fd*.
|
||||
let output = cmd.output().expect("fd output");
|
||||
|
||||
// Normalize both expected and actual output.
|
||||
let expected_error = normalize_output(expected, true, self.normalize_line);
|
||||
let actual_err = normalize_output(
|
||||
&String::from_utf8_lossy(&output.stderr),
|
||||
false,
|
||||
self.normalize_line,
|
||||
);
|
||||
if let Some(expected) = expected {
|
||||
// Normalize both expected and actual output.
|
||||
let expected_error = normalize_output(expected, true, self.normalize_line);
|
||||
let actual_err = normalize_output(
|
||||
&String::from_utf8_lossy(&output.stderr),
|
||||
false,
|
||||
self.normalize_line,
|
||||
);
|
||||
|
||||
// Compare actual output to expected output.
|
||||
if !actual_err.trim_start().starts_with(&expected_error) {
|
||||
panic!(format_output_error(args, &expected_error, &actual_err));
|
||||
// Compare actual output to expected output.
|
||||
if !actual_err.trim_start().starts_with(&expected_error) {
|
||||
panic!(
|
||||
"{}",
|
||||
format_output_error(args, &expected_error, &actual_err)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return output.status;
|
||||
output.status
|
||||
}
|
||||
}
|
||||
|
||||
+203
-12
@@ -5,6 +5,7 @@ use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use normpath::PathExt;
|
||||
use regex::escape;
|
||||
|
||||
use crate::testenv::TestEnv;
|
||||
@@ -23,11 +24,13 @@ static DEFAULT_FILES: &[&str] = &[
|
||||
"e1 e2",
|
||||
];
|
||||
|
||||
#[allow(clippy::let_and_return)]
|
||||
fn get_absolute_root_path(env: &TestEnv) -> String {
|
||||
let path = env
|
||||
.test_root()
|
||||
.canonicalize()
|
||||
.normalize()
|
||||
.expect("absolute path")
|
||||
.as_path()
|
||||
.to_str()
|
||||
.expect("string")
|
||||
.to_string();
|
||||
@@ -758,6 +761,40 @@ fn test_exact_depth() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Pruning (--prune)
|
||||
#[test]
|
||||
fn test_prune() {
|
||||
let dirs = &["foo/bar", "bar/foo", "baz"];
|
||||
let files = &[
|
||||
"foo/foo.file",
|
||||
"foo/bar/foo.file",
|
||||
"bar/foo.file",
|
||||
"bar/foo/foo.file",
|
||||
"baz/foo.file",
|
||||
];
|
||||
|
||||
let te = TestEnv::new(dirs, files);
|
||||
|
||||
te.assert_output(
|
||||
&["foo"],
|
||||
"foo
|
||||
foo/foo.file
|
||||
foo/bar/foo.file
|
||||
bar/foo.file
|
||||
bar/foo
|
||||
bar/foo/foo.file
|
||||
baz/foo.file",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--prune", "foo"],
|
||||
"foo
|
||||
bar/foo
|
||||
bar/foo.file
|
||||
baz/foo.file",
|
||||
);
|
||||
}
|
||||
|
||||
/// Absolute paths (--absolute-path)
|
||||
#[test]
|
||||
fn test_absolute_path() {
|
||||
@@ -982,6 +1019,34 @@ fn test_extension() {
|
||||
te4.assert_output(&["--hidden", "--extension", ".hidden"], "test.hidden");
|
||||
}
|
||||
|
||||
/// No file extension (test for the pattern provided in the --help text)
|
||||
#[test]
|
||||
fn test_no_extension() {
|
||||
let te = TestEnv::new(
|
||||
DEFAULT_DIRS,
|
||||
&["a.foo", "aa", "one/b.foo", "one/bb", "one/two/three/d"],
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["^[^.]+$"],
|
||||
"aa
|
||||
one
|
||||
one/bb
|
||||
one/two
|
||||
one/two/three
|
||||
one/two/three/d
|
||||
one/two/three/directory_foo
|
||||
symlink",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["^[^.]+$", "--type", "file"],
|
||||
"aa
|
||||
one/bb
|
||||
one/two/three/d",
|
||||
);
|
||||
}
|
||||
|
||||
/// Symlink as search directory
|
||||
#[test]
|
||||
fn test_symlink_as_root() {
|
||||
@@ -1028,16 +1093,19 @@ fn test_symlink_as_root() {
|
||||
fn test_symlink_and_absolute_path() {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
let expected_path = if cfg!(windows) { "symlink" } else { "one/two" };
|
||||
|
||||
te.assert_output_subdirectory(
|
||||
"symlink",
|
||||
&["--absolute-path"],
|
||||
&format!(
|
||||
"{abs_path}/one/two/c.foo
|
||||
{abs_path}/one/two/C.Foo2
|
||||
{abs_path}/one/two/three
|
||||
{abs_path}/one/two/three/d.foo
|
||||
{abs_path}/one/two/three/directory_foo",
|
||||
abs_path = &abs_path
|
||||
"{abs_path}/{expected_path}/c.foo
|
||||
{abs_path}/{expected_path}/C.Foo2
|
||||
{abs_path}/{expected_path}/three
|
||||
{abs_path}/{expected_path}/three/d.foo
|
||||
{abs_path}/{expected_path}/three/directory_foo",
|
||||
abs_path = &abs_path,
|
||||
expected_path = expected_path
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1065,6 +1133,8 @@ fn test_symlink_and_full_path() {
|
||||
let root = te.system_root();
|
||||
let prefix = escape(&root.to_string_lossy());
|
||||
|
||||
let expected_path = if cfg!(windows) { "symlink" } else { "one/two" };
|
||||
|
||||
te.assert_output_subdirectory(
|
||||
"symlink",
|
||||
&[
|
||||
@@ -1073,10 +1143,11 @@ fn test_symlink_and_full_path() {
|
||||
&format!("^{prefix}.*three", prefix = prefix),
|
||||
],
|
||||
&format!(
|
||||
"{abs_path}/one/two/three
|
||||
{abs_path}/one/two/three/d.foo
|
||||
{abs_path}/one/two/three/directory_foo",
|
||||
abs_path = &abs_path
|
||||
"{abs_path}/{expected_path}/three
|
||||
{abs_path}/{expected_path}/three/d.foo
|
||||
{abs_path}/{expected_path}/three/directory_foo",
|
||||
abs_path = &abs_path,
|
||||
expected_path = expected_path
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1275,6 +1346,85 @@ fn test_exec_batch() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shell script execution (--exec) with a custom --path-separator
|
||||
#[test]
|
||||
fn test_exec_with_separator() {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
te.assert_output(
|
||||
&[
|
||||
"--path-separator=#",
|
||||
"--absolute-path",
|
||||
"foo",
|
||||
"--exec",
|
||||
"echo",
|
||||
],
|
||||
&format!(
|
||||
"{abs_path}#a.foo
|
||||
{abs_path}#one#b.foo
|
||||
{abs_path}#one#two#C.Foo2
|
||||
{abs_path}#one#two#c.foo
|
||||
{abs_path}#one#two#three#d.foo
|
||||
{abs_path}#one#two#three#directory_foo",
|
||||
abs_path = abs_path.replace(std::path::MAIN_SEPARATOR, "#"),
|
||||
),
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--path-separator=#", "foo", "--exec", "echo", "{}"],
|
||||
"a.foo
|
||||
one#b.foo
|
||||
one#two#C.Foo2
|
||||
one#two#c.foo
|
||||
one#two#three#d.foo
|
||||
one#two#three#directory_foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--path-separator=#", "foo", "--exec", "echo", "{.}"],
|
||||
"a
|
||||
one#b
|
||||
one#two#C
|
||||
one#two#c
|
||||
one#two#three#d
|
||||
one#two#three#directory_foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--path-separator=#", "foo", "--exec", "echo", "{/}"],
|
||||
"a.foo
|
||||
b.foo
|
||||
C.Foo2
|
||||
c.foo
|
||||
d.foo
|
||||
directory_foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--path-separator=#", "foo", "--exec", "echo", "{/.}"],
|
||||
"a
|
||||
b
|
||||
C
|
||||
c
|
||||
d
|
||||
directory_foo",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--path-separator=#", "foo", "--exec", "echo", "{//}"],
|
||||
".
|
||||
one
|
||||
one#two
|
||||
one#two
|
||||
one#two#three
|
||||
one#two#three",
|
||||
);
|
||||
|
||||
te.assert_output(
|
||||
&["--path-separator=#", "e1", "--exec", "printf", "%s.%s\n"],
|
||||
"e1 e2.",
|
||||
);
|
||||
}
|
||||
|
||||
/// Literal search (--fixed-strings)
|
||||
#[test]
|
||||
fn test_fixed_strings() {
|
||||
@@ -1353,6 +1503,9 @@ fn test_size() {
|
||||
|
||||
// Zero sized files.
|
||||
te.assert_output(&["", "--size", "-0B"], "0_bytes.foo");
|
||||
te.assert_output(&["", "--size", "0B"], "0_bytes.foo");
|
||||
te.assert_output(&["", "--size=0B"], "0_bytes.foo");
|
||||
te.assert_output(&["", "-S", "0B"], "0_bytes.foo");
|
||||
|
||||
// Files with 2 bytes or more.
|
||||
te.assert_output(
|
||||
@@ -1369,6 +1522,9 @@ fn test_size() {
|
||||
// Files with size between 1 byte and 11 bytes.
|
||||
te.assert_output(&["", "--size", "+1B", "--size", "-11B"], "11_bytes.foo");
|
||||
|
||||
// Files with size equal 11 bytes.
|
||||
te.assert_output(&["", "--size", "11B"], "11_bytes.foo");
|
||||
|
||||
// Files with size between 1 byte and 30 bytes.
|
||||
te.assert_output(
|
||||
&["", "--size", "+1B", "--size", "-30B"],
|
||||
@@ -1400,6 +1556,7 @@ fn test_size() {
|
||||
|
||||
// Files with size equal 4 kibibytes.
|
||||
te.assert_output(&["", "--size", "+4ki", "--size", "-4ki"], "4_kibibytes.foo");
|
||||
te.assert_output(&["", "--size", "4ki"], "4_kibibytes.foo");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1515,7 +1672,7 @@ fn test_base_directory() {
|
||||
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
let abs_base_dir = &format!("{abs_path}/one/two", abs_path = &abs_path);
|
||||
te.assert_output(
|
||||
&["--base-directory", &abs_base_dir, "foo", &abs_path],
|
||||
&["--base-directory", abs_base_dir, "foo", &abs_path],
|
||||
&format!(
|
||||
"{abs_path}/a.foo
|
||||
{abs_path}/one/b.foo
|
||||
@@ -1611,3 +1768,37 @@ fn test_list_details() {
|
||||
// Make sure we can execute 'fd --list-details' without any errors.
|
||||
te.assert_success_and_get_output(".", &["--list-details"]);
|
||||
}
|
||||
|
||||
/// Make sure that fd fails if numeric arguments can not be parsed
|
||||
#[test]
|
||||
fn test_number_parsing_errors() {
|
||||
let te = TestEnv::new(&[], &[]);
|
||||
|
||||
te.assert_failure(&["--threads=a"]);
|
||||
te.assert_failure(&["-j", ""]);
|
||||
te.assert_failure(&["--threads=0"]);
|
||||
|
||||
te.assert_failure(&["--min-depth=a"]);
|
||||
te.assert_failure(&["--max-depth=a"]);
|
||||
te.assert_failure(&["--maxdepth=a"]);
|
||||
te.assert_failure(&["--exact-depth=a"]);
|
||||
|
||||
te.assert_failure(&["--max-buffer-time=a"]);
|
||||
|
||||
te.assert_failure(&["--max-results=a"]);
|
||||
}
|
||||
|
||||
/// Print error if search pattern starts with a dot and --hidden is not set
|
||||
/// (Unix only, hidden files on Windows work differently)
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_error_if_hidden_not_set_and_pattern_starts_with_dot() {
|
||||
let te = TestEnv::new(&[], &[".gitignore", ".whatever", "non-hidden"]);
|
||||
|
||||
te.assert_failure(&["^\\.gitignore"]);
|
||||
te.assert_failure(&["--glob", ".gitignore"]);
|
||||
|
||||
te.assert_output(&["--hidden", "^\\.gitignore"], ".gitignore");
|
||||
te.assert_output(&["--hidden", "--glob", ".gitignore"], ".gitignore");
|
||||
te.assert_output(&[".gitignore"], "");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user