mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
Compare commits
208 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6a8a799d3 | |||
| 7e98dbcf03 | |||
| a53099b8f1 | |||
| b5f2d3a8cf | |||
| a5f720874a | |||
| 329a4fd622 | |||
| c4f7872cab | |||
| 67f56fea8c | |||
| 27bf2d2d26 | |||
| c55e2caade | |||
| 55dad3745e | |||
| 249b6eedcf | |||
| c464e8ff1f | |||
| fa976740e9 | |||
| 13b4ae4d45 | |||
| 1cbc975b33 | |||
| 6a034e28e8 | |||
| abdd86c63d | |||
| eb6322df5f | |||
| cd44d01d57 | |||
| bda32f0482 | |||
| eed9b64aaf | |||
| ed3a62e9d7 | |||
| 1488d55ceb | |||
| 61f2ffe803 | |||
| efdf1f17c7 | |||
| 6be2cd4c2c | |||
| a1ae326e72 | |||
| 61002aec2b | |||
| 225f77f7ff | |||
| 0ebefda3a8 | |||
| 91a6e64c9f | |||
| f36f576fd9 | |||
| a5db23c712 | |||
| 04dad0ba5f | |||
| 2011a7adfa | |||
| 1753e032b2 | |||
| 6be211a336 | |||
| dbc2f2729b | |||
| 828a72a1ba | |||
| bcc4e0b099 | |||
| 0e4517b8c9 | |||
| 671916f718 | |||
| e4121cc988 | |||
| d7bb67e53d | |||
| 3e96a14d1f | |||
| 3f6ac572e1 | |||
| c6e025f19a | |||
| 8f2409742d | |||
| 7469db2027 | |||
| 5659812641 | |||
| 30a22453d6 | |||
| a3287a978f | |||
| 238100bc67 | |||
| 59301c788a | |||
| 5b907e2ea8 | |||
| 31d3ae2cd1 | |||
| f5f1aac007 | |||
| f044f44cf4 | |||
| cbd2849546 | |||
| 0cbeae5a21 | |||
| bd0ab84fd5 | |||
| 78865ea683 | |||
| 65c29e0d71 | |||
| e523ea125a | |||
| 7af32c58e9 | |||
| 1fbf4775cd | |||
| 624e7b6c3a | |||
| 147a62d2d6 | |||
| 2933d89c28 | |||
| 327898dcd9 | |||
| a83ce989c5 | |||
| b028814190 | |||
| 6e0b4da8ce | |||
| 233ae30477 | |||
| cb1ea61de5 | |||
| 28628f0e86 | |||
| e6c3e5e7cf | |||
| 992bad1f40 | |||
| 7869e09f4a | |||
| 620ae272ad | |||
| b06e8d36cf | |||
| b74e6b403f | |||
| 85014b0218 | |||
| 232a301525 | |||
| fa552493ba | |||
| f0b8792cfd | |||
| c561643aa8 | |||
| 7e1fb57ede | |||
| be420a911b | |||
| 47274c6784 | |||
| 46f280e838 | |||
| 931b6e6d54 | |||
| bfc246b564 | |||
| 366c8b019f | |||
| 7581c68813 | |||
| 0b02baf61b | |||
| 2a13f16446 | |||
| b95879684a | |||
| f61a57ce6f | |||
| 5d7966f654 | |||
| e785397880 | |||
| 3d10d81356 | |||
| fa8e118a30 | |||
| 25c1b5084d | |||
| 4563289689 | |||
| c7f5ed7760 | |||
| cb9771e694 | |||
| e3f9fc4e90 | |||
| c21a0110cd | |||
| 497d655661 | |||
| f9d0fc2e36 | |||
| 5c5d4d9543 | |||
| 32318b77aa | |||
| 0d20a3d5cd | |||
| 07916e1c23 | |||
| 43d725275b | |||
| 96de758f19 | |||
| 60f554c699 | |||
| a66f38bf1c | |||
| 8b05a3f5e4 | |||
| 1c1f6fc6e6 | |||
| ddbe013d05 | |||
| 2418d11397 | |||
| 67c3193446 | |||
| a41537a803 | |||
| 8f673c5899 | |||
| 4aab22af08 | |||
| da45c3b424 | |||
| 1a4cb25c5c | |||
| 2a8636162f | |||
| 6bad054a5e | |||
| 4a47054ffc | |||
| a6b1827ee0 | |||
| f05c2f1b61 | |||
| f967b29735 | |||
| dbde7abdc2 | |||
| a9241678e5 | |||
| 37547b00be | |||
| 7c3268d9ef | |||
| 972ac0bbff | |||
| 1bfbe7623e | |||
| 08c22e6e33 | |||
| 7855d08379 | |||
| 80bfec17eb | |||
| 75de125d45 | |||
| e04cd34384 | |||
| 36e3229537 | |||
| eff4c5d38a | |||
| 2defcbeacc | |||
| 301b495097 | |||
| f3ecd5b424 | |||
| a44b981812 | |||
| 3466eabfc7 | |||
| 283082a36d | |||
| 5741479e2f | |||
| 9e434052e5 | |||
| 92a24ecad0 | |||
| 0ec8d472d1 | |||
| 6050706bfd | |||
| 946544eec9 | |||
| 5e32ed0381 | |||
| b111e50fa9 | |||
| d58fb523b1 | |||
| fcb89d2156 | |||
| 0b23d9b202 | |||
| 13bc54f7ae | |||
| 4cd4e4fb79 | |||
| 27bfbdc906 | |||
| 1faeef0ab2 | |||
| ce375a71b0 | |||
| 016995fa4a | |||
| 36cc88e159 | |||
| 22cbd8e467 | |||
| a9ad63838f | |||
| e18c369b33 | |||
| cead712118 | |||
| 193aaf2aa0 | |||
| 7be2715f07 | |||
| a3501a267a | |||
| a3928291f8 | |||
| 493a1e9998 | |||
| d26cdf3010 | |||
| eddfddf217 | |||
| f2c808c3cf | |||
| d16305d5fd | |||
| 1f3b2094a5 | |||
| b16a05f260 | |||
| f04d297372 | |||
| 7ffd5f8424 | |||
| 48d6f63fc8 | |||
| 979a442443 | |||
| 43027d0808 | |||
| 5c45b30427 | |||
| a7af22724b | |||
| 4eb943fea2 | |||
| f399291533 | |||
| 59be8d5ce7 | |||
| 384aa23e74 | |||
| 58070d6a00 | |||
| 5f054351d4 | |||
| 78eba7efbc | |||
| d6433f9f54 | |||
| 1bf7ff7e13 | |||
| 162d5f3822 | |||
| efa2a4b553 | |||
| 0a8b72fdbc | |||
| 1517cc0e84 |
@@ -1,23 +0,0 @@
|
||||
os: Visual Studio 2015
|
||||
|
||||
version: "{build}"
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- channel: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
- channel: nightly
|
||||
target: x86_64-pc-windows-msvc
|
||||
|
||||
install:
|
||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init -yv --default-toolchain %channel% --default-host %target%
|
||||
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
- rustc -vV && cargo -vV
|
||||
|
||||
build_script:
|
||||
- cargo build --verbose
|
||||
|
||||
test_script:
|
||||
- cargo test --verbose
|
||||
|
||||
+10
-6
@@ -1,8 +1,12 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "07:00"
|
||||
open-pull-requests-limit: 10
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 5
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 5
|
||||
|
||||
+50
-95
@@ -10,12 +10,12 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
|
||||
# For bindgen: https://github.com/rust-lang/rust-bindgen/issues/1797
|
||||
- uses: KyleMayes/install-llvm-action@v1
|
||||
@@ -26,9 +26,9 @@ jobs:
|
||||
- run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
- name: Check
|
||||
run: |
|
||||
cargo check --all --all-features
|
||||
|
||||
test:
|
||||
name: cargo test
|
||||
@@ -37,12 +37,12 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
|
||||
# For bindgen: https://github.com/rust-lang/rust-bindgen/issues/1797
|
||||
- uses: KyleMayes/install-llvm-action@v1
|
||||
@@ -53,41 +53,40 @@ jobs:
|
||||
- run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
- name: Check
|
||||
run: |
|
||||
cargo check
|
||||
|
||||
fmt:
|
||||
name: cargo fmt --all -- --check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- name: cargo fmt
|
||||
run: |
|
||||
cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
name: cargo clippy -- -D warnings
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
- run: rustup component add clippy
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
- name: cargo clippy
|
||||
run: |
|
||||
cargo clippy -- -D warnings
|
||||
|
||||
grcov:
|
||||
name: Code coverage
|
||||
@@ -102,27 +101,20 @@ jobs:
|
||||
- "--all-features"
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
override: true
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install nightly --no-self-update -c rustfmt --profile minimal
|
||||
rustup default nightly
|
||||
|
||||
- name: Install grcov
|
||||
uses: actions-rs/install@v0.1
|
||||
with:
|
||||
crate: grcov
|
||||
version: latest
|
||||
use-tool-cache: true
|
||||
- name: "`grcov` ~ install"
|
||||
run: cargo install grcov
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all --no-fail-fast ${{ matrix.cargo_flags }}
|
||||
- name: cargo test
|
||||
run: |
|
||||
cargo test --all --no-fail-fast ${{ matrix.cargo_flags }}
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off'
|
||||
@@ -130,7 +122,6 @@ jobs:
|
||||
|
||||
- name: Generate coverage data
|
||||
id: grcov
|
||||
# uses: actions-rs/grcov@v0.1
|
||||
run: |
|
||||
grcov target/debug/ \
|
||||
--branch \
|
||||
@@ -147,51 +138,15 @@ jobs:
|
||||
--service-name "GitHub Actions" \
|
||||
--service-number ${{ github.run_id }}
|
||||
- name: Upload coverage as artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: lcov.info
|
||||
# path: ${{ steps.grcov.outputs.report }}
|
||||
path: lcov.info
|
||||
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@v1
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
# file: ${{ steps.grcov.outputs.report }}
|
||||
file: lcov.info
|
||||
fail_ci_if_error: true
|
||||
|
||||
external:
|
||||
name: External testsuites
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
toolchain:
|
||||
- nightly
|
||||
cargo_flags:
|
||||
- "--all-features"
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
override: true
|
||||
|
||||
- name: Run the gnu testsuite
|
||||
id: gnu
|
||||
run: |
|
||||
sudo apt install autopoint
|
||||
git clone https://git.savannah.gnu.org/git/findutils.git ../findutils.gnu
|
||||
bash util/build-gnu.sh || true
|
||||
|
||||
- name: Run the bfs testsuite
|
||||
id: bfs
|
||||
run: |
|
||||
sudo apt install acl libacl1-dev attr libattr1-dev libcap2-bin libcap-dev
|
||||
git clone https://github.com/tavianator/bfs.git ../bfs
|
||||
bash util/build-bfs.sh || true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
on: [push, pull_request]
|
||||
|
||||
name: GNU findutils compatibility tests
|
||||
name: External testsuites
|
||||
|
||||
jobs:
|
||||
gnu-tests:
|
||||
@@ -8,21 +8,21 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout GNU findutils
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: gnu-mirror-unofficial/findutils
|
||||
path: findutils.gnu
|
||||
ref: 5768a03ddfb5e18b1682e339d6cdd24ff721c510
|
||||
submodules: true
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -38,39 +38,14 @@ jobs:
|
||||
- name: Extract testing info
|
||||
shell: bash
|
||||
run: |
|
||||
LOG_FILE=findutils.gnu/tests/test-suite.log
|
||||
if test -f "$LOG_FILE"; then
|
||||
TOTAL=$(sed -n "s/.*# TOTAL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
|
||||
PASS=$(sed -n "s/.*# PASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
|
||||
SKIP=$(sed -n "s/.*# SKIP: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
|
||||
FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
|
||||
XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
|
||||
ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
|
||||
if [[ "$TOTAL" -eq 0 || "$TOTAL" -eq 1 ]]; then
|
||||
echo "Error in the execution, failing early"
|
||||
exit 1
|
||||
fi
|
||||
output="GNU tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR"
|
||||
echo "${output}"
|
||||
if [[ "$FAIL" -gt 0 || "$ERROR" -gt 0 ]]; then echo "::warning ::${output}" ; fi
|
||||
jq -n \
|
||||
--arg date "$(date --rfc-email)" \
|
||||
--arg sha "$GITHUB_SHA" \
|
||||
--arg total "$TOTAL" \
|
||||
--arg pass "$PASS" \
|
||||
--arg skip "$SKIP" \
|
||||
--arg fail "$FAIL" \
|
||||
--arg xpass "$XPASS" \
|
||||
--arg error "$ERROR" \
|
||||
'{($date): { sha: $sha, total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }}' > gnu-result.json
|
||||
else
|
||||
echo "::error ::Failed to get summary of test results"
|
||||
fi
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: gnu-test-report
|
||||
path: findutils.gnu/tests/**/*.log
|
||||
- uses: actions/upload-artifact@v2
|
||||
path: |
|
||||
findutils.gnu/find/testsuite/*.log
|
||||
findutils.gnu/xargs/testsuite/*.log
|
||||
findutils.gnu/tests/**/*.log
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: gnu-result
|
||||
path: gnu-result.json
|
||||
@@ -95,18 +70,7 @@ jobs:
|
||||
- name: Compare failing tests against master
|
||||
shell: bash
|
||||
run: |
|
||||
OLD_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" dl/test-suite.log | sort)
|
||||
NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" findutils.gnu/tests/test-suite.log | sort)
|
||||
for LINE in $OLD_FAILING; do
|
||||
if ! grep -Fxq $LINE<<<"$NEW_FAILING"; then
|
||||
echo "::warning ::Congrats! The gnu test $LINE is now passing!"
|
||||
fi
|
||||
done
|
||||
for LINE in $NEW_FAILING; do
|
||||
if ! grep -Fxq $LINE<<<"$OLD_FAILING"; then
|
||||
echo "::error ::gnu test failed: $LINE. $LINE is passing on 'main'. Maybe you have to rebase?"
|
||||
fi
|
||||
done
|
||||
./findutils/util/diff-gnu.sh ./dl ./findutils.gnu
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -118,20 +82,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout BFS
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: tavianator/bfs
|
||||
path: bfs
|
||||
ref: '2.3.1'
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
ref: '2.6.2'
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -144,36 +108,11 @@ jobs:
|
||||
run: |
|
||||
cd findutils
|
||||
bash util/build-bfs.sh ||:
|
||||
- name: Extract testing info
|
||||
shell: bash
|
||||
run: |
|
||||
LOG_FILE=bfs/tests.log
|
||||
if test -f "$LOG_FILE"; then
|
||||
PASS=$(sed -n "s/^tests passed: \(.*\)/\1/p" "$LOG_FILE"|head -n1)
|
||||
FAIL=$(sed -n "s/^tests failed: \(.*\)/\1/p" "$LOG_FILE"|head -n1)
|
||||
TOTAL=$(($PASS + $FAIL))
|
||||
if [[ "$TOTAL" -eq 0 || "$TOTAL" -eq 1 ]]; then
|
||||
echo "Error in the execution, failing early"
|
||||
exit 1
|
||||
fi
|
||||
output="BFS tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL"
|
||||
echo "${output}"
|
||||
if [[ "$FAIL" -gt 0 || "$ERROR" -gt 0 ]]; then echo "::warning ::${output}" ; fi
|
||||
jq -n \
|
||||
--arg date "$(date --rfc-email)" \
|
||||
--arg sha "$GITHUB_SHA" \
|
||||
--arg total "$TOTAL" \
|
||||
--arg pass "$PASS" \
|
||||
--arg fail "$FAIL" \
|
||||
'{($date): { sha: $sha, total: $total, pass: $pass, fail: $fail, }}' > bfs-result.json
|
||||
else
|
||||
echo "::error ::Failed to get summary of test results"
|
||||
fi
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: bfs-test-report
|
||||
path: bfs/tests.log
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: bfs-result
|
||||
path: bfs-result.json
|
||||
@@ -198,18 +137,7 @@ jobs:
|
||||
- name: Compare failing tests against main
|
||||
shell: bash
|
||||
run: |
|
||||
OLD_FAILING=$(sed -n "s/^\([[:print:]]\+\) failed\!/\1/p" dl/tests.log | sort)
|
||||
NEW_FAILING=$(sed -n "s/^\([[:print:]]\+\) failed\!/\1/p" bfs/tests.log | sort)
|
||||
for LINE in $OLD_FAILING; do
|
||||
if ! grep -Fxq $LINE<<<"$NEW_FAILING"; then
|
||||
echo "::warning ::Congrats! The bfs test $LINE is now passing!"
|
||||
fi
|
||||
done
|
||||
for LINE in $NEW_FAILING; do
|
||||
if ! grep -Fxq $LINE<<<"$OLD_FAILING"; then
|
||||
echo "::error ::bfs test failed: $LINE. $LINE is passing on 'main'. Maybe you have to rebase?"
|
||||
fi
|
||||
done
|
||||
./findutils/util/diff-bfs.sh dl/tests.log bfs/tests.log
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: rust-linting
|
||||
name: Rust linting
|
||||
description: Run cargo fmt on files included in the commit.
|
||||
entry: cargo +nightly fmt --
|
||||
pass_filenames: true
|
||||
types: [file, rust]
|
||||
language: system
|
||||
- id: rust-clippy
|
||||
name: Rust clippy
|
||||
description: Run cargo clippy on files included in the commit.
|
||||
entry: cargo +nightly clippy --workspace --all-targets --all-features --
|
||||
pass_filenames: false
|
||||
types: [file, rust]
|
||||
language: system
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
language: rust
|
||||
|
||||
sudo: false
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
rust:
|
||||
- stable
|
||||
- nightly
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
cache: cargo
|
||||
|
||||
before_cache: |
|
||||
if [[ "$TRAVIS_RUST_VERSION" = nightly ]]; then
|
||||
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f
|
||||
fi
|
||||
|
||||
script:
|
||||
- cargo build --verbose
|
||||
- cargo test --verbose
|
||||
|
||||
after_script: |
|
||||
if [[ "$TRAVIS_OS_NAME" = linux && "$TRAVIS_RUST_VERSION" = nightly ]]; then
|
||||
cargo tarpaulin --out Xml
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
fi
|
||||
Generated
+945
-159
File diff suppressed because it is too large
Load Diff
+9
-8
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "findutils"
|
||||
version = "0.2.0"
|
||||
version = "0.4.2"
|
||||
homepage = "https://github.com/uutils/findutils"
|
||||
repository = "https://github.com/uutils/findutils"
|
||||
edition = "2018"
|
||||
@@ -12,18 +12,19 @@ authors = ["uutils developers"]
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
clap = "2.34"
|
||||
glob = "0.3"
|
||||
faccess = "0.2.4"
|
||||
walkdir = "2.3"
|
||||
regex = "1.5"
|
||||
once_cell = "1.9"
|
||||
onig = "6.3"
|
||||
uucore = { version = "0.0.12", features = ["entries", "fs", "fsext"] }
|
||||
regex = "1.7"
|
||||
once_cell = "1.18"
|
||||
onig = "6.4"
|
||||
uucore = { version = "0.0.20", features = ["entries", "fs", "fsext", "mode"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2"
|
||||
filetime = "0.2"
|
||||
predicates = "2"
|
||||
serial_test = "0.5"
|
||||
nix = "0.26"
|
||||
predicates = "3"
|
||||
serial_test = "2.0"
|
||||
tempfile = "3"
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -19,3 +19,5 @@ bash util/build-gnu.sh tests/misc/help-version.sh
|
||||
|
||||

|
||||

|
||||
|
||||
For more details, see https://github.com/uutils/findutils-tracking/
|
||||
|
||||
+1
-4
@@ -4,12 +4,9 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
extern crate findutils;
|
||||
extern crate glob;
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
let strs: Vec<&str> = args.iter().map(|s| s.as_ref()).collect();
|
||||
let strs: Vec<&str> = args.iter().map(std::convert::AsRef::as_ref).collect();
|
||||
let deps = findutils::find::StandardDependencies::new();
|
||||
std::process::exit(findutils::find::find_main(&strs, &deps));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2022 Tavian Barnes
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use faccess::PathExt;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
|
||||
/// Matcher for -{read,writ,execut}able.
|
||||
pub enum AccessMatcher {
|
||||
Readable,
|
||||
Writable,
|
||||
Executable,
|
||||
}
|
||||
|
||||
impl Matcher for AccessMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
let path = file_info.path();
|
||||
|
||||
match self {
|
||||
Self::Readable => path.readable(),
|
||||
Self::Writable => path.writable(),
|
||||
Self::Executable => path.executable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::find::matchers::tests::get_dir_entry_for;
|
||||
use crate::find::matchers::Matcher;
|
||||
use crate::find::tests::FakeDependencies;
|
||||
|
||||
#[test]
|
||||
fn access_matcher() {
|
||||
let file_info = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
assert!(
|
||||
AccessMatcher::Readable.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"file should be readable"
|
||||
);
|
||||
|
||||
assert!(
|
||||
AccessMatcher::Writable.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"file should be writable"
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
assert!(
|
||||
!AccessMatcher::Executable.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"file should not be executable"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,10 @@ use super::{Matcher, MatcherIO};
|
||||
pub struct DeleteMatcher;
|
||||
|
||||
impl DeleteMatcher {
|
||||
pub fn new() -> DeleteMatcher {
|
||||
pub fn new() -> Self {
|
||||
DeleteMatcher
|
||||
}
|
||||
|
||||
pub fn new_box() -> io::Result<Box<dyn Matcher>> {
|
||||
Ok(Box::new(DeleteMatcher::new()))
|
||||
}
|
||||
|
||||
fn delete(&self, file_path: &Path, file_type: FileType) -> io::Result<()> {
|
||||
if file_type.is_dir() {
|
||||
fs::remove_dir(file_path)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2021 Collabora, Ltd.
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use std::{
|
||||
fs::read_dir,
|
||||
io::{stderr, Write},
|
||||
};
|
||||
|
||||
use super::Matcher;
|
||||
|
||||
pub struct EmptyMatcher;
|
||||
|
||||
impl EmptyMatcher {
|
||||
pub fn new() -> EmptyMatcher {
|
||||
EmptyMatcher
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for EmptyMatcher {
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
if file_info.file_type().is_file() {
|
||||
match file_info.metadata() {
|
||||
Ok(meta) => meta.len() == 0,
|
||||
Err(err) => {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error getting size for {}: {}",
|
||||
file_info.path().display(),
|
||||
err
|
||||
)
|
||||
.unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
} else if file_info.file_type().is_dir() {
|
||||
match read_dir(file_info.path()) {
|
||||
Ok(mut it) => it.next().is_none(),
|
||||
Err(err) => {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error getting contents of {}: {}",
|
||||
file_info.path().display(),
|
||||
err
|
||||
)
|
||||
.unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tempfile::Builder;
|
||||
|
||||
use super::*;
|
||||
use crate::find::matchers::tests::get_dir_entry_for;
|
||||
use crate::find::matchers::Matcher;
|
||||
use crate::find::tests::FakeDependencies;
|
||||
|
||||
#[test]
|
||||
fn empty_files() {
|
||||
let empty_file_info = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let nonempty_file_info = get_dir_entry_for("test_data/size", "512bytes");
|
||||
|
||||
let matcher = EmptyMatcher::new();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
assert!(matcher.matches(&empty_file_info, &mut deps.new_matcher_io()));
|
||||
assert!(!matcher.matches(&nonempty_file_info, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_directories() {
|
||||
let temp_dir = Builder::new()
|
||||
.prefix("empty_directories")
|
||||
.tempdir()
|
||||
.unwrap();
|
||||
let temp_dir_path = temp_dir.path().to_string_lossy();
|
||||
let subdir_name = "subdir";
|
||||
std::fs::create_dir(temp_dir.path().join(subdir_name)).unwrap();
|
||||
|
||||
let matcher = EmptyMatcher::new();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let file_info = get_dir_entry_for(&temp_dir_path, subdir_name);
|
||||
assert!(matcher.matches(&file_info, &mut deps.new_matcher_io()));
|
||||
|
||||
std::fs::File::create(temp_dir.path().join(subdir_name).join("a")).unwrap();
|
||||
|
||||
let file_info = get_dir_entry_for(&temp_dir_path, subdir_name);
|
||||
assert!(!matcher.matches(&file_info, &mut deps.new_matcher_io()));
|
||||
}
|
||||
}
|
||||
+15
-22
@@ -14,7 +14,7 @@ use walkdir::DirEntry;
|
||||
use super::{Matcher, MatcherIO};
|
||||
|
||||
enum Arg {
|
||||
Filename,
|
||||
FileArg(Vec<OsString>),
|
||||
LiteralArg(OsString),
|
||||
}
|
||||
|
||||
@@ -29,33 +29,26 @@ impl SingleExecMatcher {
|
||||
executable: &str,
|
||||
args: &[&str],
|
||||
exec_in_parent_dir: bool,
|
||||
) -> Result<SingleExecMatcher, Box<dyn Error>> {
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let transformed_args = args
|
||||
.iter()
|
||||
.map(|&a| match a {
|
||||
"{}" => Arg::Filename,
|
||||
_ => Arg::LiteralArg(OsString::from(a)),
|
||||
.map(|&a| {
|
||||
let parts = a.split("{}").collect::<Vec<_>>();
|
||||
if parts.len() == 1 {
|
||||
// No {} present
|
||||
Arg::LiteralArg(OsString::from(a))
|
||||
} else {
|
||||
Arg::FileArg(parts.iter().map(OsString::from).collect())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(SingleExecMatcher {
|
||||
Ok(Self {
|
||||
executable: executable.to_string(),
|
||||
args: transformed_args,
|
||||
exec_in_parent_dir,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_box(
|
||||
executable: &str,
|
||||
args: &[&str],
|
||||
exec_in_parent_dir: bool,
|
||||
) -> Result<Box<dyn Matcher>, Box<dyn Error>> {
|
||||
Ok(Box::new(SingleExecMatcher::new(
|
||||
executable,
|
||||
args,
|
||||
exec_in_parent_dir,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for SingleExecMatcher {
|
||||
@@ -72,10 +65,10 @@ impl Matcher for SingleExecMatcher {
|
||||
};
|
||||
|
||||
for arg in &self.args {
|
||||
command.arg(match *arg {
|
||||
Arg::LiteralArg(ref a) => a.as_os_str(),
|
||||
Arg::Filename => path_to_file.as_os_str(),
|
||||
});
|
||||
match *arg {
|
||||
Arg::LiteralArg(ref a) => command.arg(a.as_os_str()),
|
||||
Arg::FileArg(ref parts) => command.arg(&parts.join(path_to_file.as_os_str())),
|
||||
};
|
||||
}
|
||||
if self.exec_in_parent_dir {
|
||||
match file_info.path().parent() {
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
// Copyright 2022 Tavian Barnes
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use onig::{self, Regex, RegexOptions, Syntax};
|
||||
|
||||
/// Parse a string as a POSIX Basic Regular Expression.
|
||||
fn parse_bre(expr: &str, options: RegexOptions) -> Result<Regex, onig::Error> {
|
||||
let bre = Syntax::posix_basic();
|
||||
Regex::with_options(expr, bre.options() | options, bre)
|
||||
}
|
||||
|
||||
/// Push a literal character onto a regex, escaping it if necessary.
|
||||
fn regex_push_literal(regex: &mut String, ch: char) {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_03
|
||||
if matches!(ch, '.' | '[' | '\\' | '*' | '^' | '$') {
|
||||
regex.push('\\');
|
||||
}
|
||||
regex.push(ch);
|
||||
}
|
||||
|
||||
/// Extracts a bracket expression from a glob.
|
||||
fn extract_bracket_expr(pattern: &str) -> Option<(String, &str)> {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01
|
||||
//
|
||||
// If an open bracket introduces a bracket expression as in XBD RE Bracket Expression,
|
||||
// except that the <exclamation-mark> character ( '!' ) shall replace the <circumflex>
|
||||
// character ( '^' ) in its role in a non-matching list in the regular expression notation,
|
||||
// it shall introduce a pattern bracket expression. A bracket expression starting with an
|
||||
// unquoted <circumflex> character produces unspecified results. Otherwise, '[' shall match
|
||||
// the character itself.
|
||||
//
|
||||
// To check for valid bracket expressions, we scan for the closing bracket and
|
||||
// attempt to parse that segment as a regex. If that fails, we treat the '['
|
||||
// literally.
|
||||
|
||||
let mut expr = "[".to_string();
|
||||
|
||||
let mut chars = pattern.chars();
|
||||
let mut next = chars.next();
|
||||
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
|
||||
//
|
||||
// 3. A non-matching list expression begins with a <circumflex> ( '^' ) ...
|
||||
//
|
||||
// (but in a glob, '!' is used instead of '^')
|
||||
if next == Some('!') {
|
||||
expr.push('^');
|
||||
next = chars.next();
|
||||
}
|
||||
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
|
||||
//
|
||||
// 1. ... The <right-square-bracket> ( ']' ) shall lose its special meaning and represent
|
||||
// itself in a bracket expression if it occurs first in the list (after an initial
|
||||
// <circumflex> ( '^' ), if any).
|
||||
if next == Some(']') {
|
||||
expr.push(']');
|
||||
next = chars.next();
|
||||
}
|
||||
|
||||
while let Some(ch) = next {
|
||||
expr.push(ch);
|
||||
|
||||
match ch {
|
||||
'[' => {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
|
||||
//
|
||||
// 4. A collating symbol is a collating element enclosed within bracket-period
|
||||
// ( "[." and ".]" ) delimiters. ...
|
||||
//
|
||||
// 5. An equivalence class expression shall ... be expressed by enclosing any
|
||||
// one of the collating elements in the equivalence class within bracket-
|
||||
// equal ( "[=" and "=]" ) delimiters.
|
||||
//
|
||||
// 6. ... A character class expression is expressed as a character class name
|
||||
// enclosed within bracket- <colon> ( "[:" and ":]" ) delimiters.
|
||||
next = chars.next();
|
||||
if let Some(delim) = next {
|
||||
expr.push(delim);
|
||||
|
||||
if matches!(delim, '.' | '=' | ':') {
|
||||
let rest = chars.as_str();
|
||||
let end = rest.find([delim, ']'])? + 2;
|
||||
expr.push_str(&rest[..end]);
|
||||
chars = rest[end..].chars();
|
||||
}
|
||||
}
|
||||
}
|
||||
']' => {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
|
||||
//
|
||||
// 1. ... The <right-square-bracket> ( ']' ) shall ... terminate the bracket
|
||||
// expression, unless it appears in a collating symbol (such as "[.].]" ) or is
|
||||
// the ending <right-square-bracket> for a collating symbol, equivalence class,
|
||||
// or character class.
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
next = chars.next();
|
||||
}
|
||||
|
||||
if parse_bre(&expr, RegexOptions::REGEX_OPTION_NONE).is_ok() {
|
||||
Some((expr, chars.as_str()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a POSIX glob into a POSIX Basic Regular Expression
|
||||
fn glob_to_regex(pattern: &str) -> String {
|
||||
let mut regex = String::new();
|
||||
|
||||
let mut chars = pattern.chars();
|
||||
while let Some(ch) = chars.next() {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13
|
||||
match ch {
|
||||
'?' => regex.push('.'),
|
||||
'*' => regex.push_str(".*"),
|
||||
'\\' => {
|
||||
if let Some(ch) = chars.next() {
|
||||
regex_push_literal(&mut regex, ch);
|
||||
} else {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html
|
||||
//
|
||||
// If pattern ends with an unescaped <backslash>, fnmatch() shall return a
|
||||
// non-zero value (indicating either no match or an error).
|
||||
//
|
||||
// Most implementations return FNM_NOMATCH in this case, so return a regex that
|
||||
// never matches.
|
||||
return "$.".to_string();
|
||||
}
|
||||
}
|
||||
'[' => {
|
||||
if let Some((expr, rest)) = extract_bracket_expr(chars.as_str()) {
|
||||
regex.push_str(&expr);
|
||||
chars = rest.chars();
|
||||
} else {
|
||||
regex_push_literal(&mut regex, ch);
|
||||
}
|
||||
}
|
||||
_ => regex_push_literal(&mut regex, ch),
|
||||
}
|
||||
}
|
||||
|
||||
regex
|
||||
}
|
||||
|
||||
/// An fnmatch()-style glob matcher.
|
||||
pub struct Pattern {
|
||||
regex: Regex,
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
/// Parse an fnmatch()-style glob.
|
||||
pub fn new(pattern: &str, caseless: bool) -> Self {
|
||||
let options = if caseless {
|
||||
RegexOptions::REGEX_OPTION_IGNORECASE
|
||||
} else {
|
||||
RegexOptions::REGEX_OPTION_NONE
|
||||
};
|
||||
|
||||
// As long as glob_to_regex() is correct, this should never fail
|
||||
let regex = parse_bre(&glob_to_regex(pattern), options).unwrap();
|
||||
Self { regex }
|
||||
}
|
||||
|
||||
/// Test if this pattern matches a string.
|
||||
pub fn matches(&self, string: &str) -> bool {
|
||||
self.regex.is_match(string)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn literals() {
|
||||
assert_eq!(glob_to_regex(r"foo.bar"), r"foo\.bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regex_special() {
|
||||
assert_eq!(glob_to_regex(r"^foo.bar$"), r"\^foo\.bar\$");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcards() {
|
||||
assert_eq!(glob_to_regex(r"foo?bar*baz"), r"foo.bar.*baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escapes() {
|
||||
assert_eq!(glob_to_regex(r"fo\o\?bar\*baz\\"), r"foo?bar\*baz\\");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incomplete_escape() {
|
||||
assert_eq!(glob_to_regex(r"foo\"), r"$.")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_brackets() {
|
||||
assert_eq!(glob_to_regex(r"foo[bar][!baz]"), r"foo[bar][^baz]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_brackets() {
|
||||
assert_eq!(
|
||||
glob_to_regex(r"[!]!.*[\[.].][=]=][:space:]-]"),
|
||||
r"[^]!.*[\[.].][=]=][:space:]-]"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_brackets() {
|
||||
assert_eq!(glob_to_regex(r"foo[bar[!baz"), r"foo\[bar\[!baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_matches() {
|
||||
assert!(Pattern::new(r"foo*bar", false).matches("foo--bar"));
|
||||
|
||||
assert!(!Pattern::new(r"foo*bar", false).matches("bar--foo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_matches() {
|
||||
assert!(Pattern::new(r"foo*BAR", true).matches("FOO--bar"));
|
||||
|
||||
assert!(!Pattern::new(r"foo*BAR", true).matches("BAR--foo"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2017 Google Inc.
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use std::io::{stderr, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::glob::Pattern;
|
||||
use super::{Matcher, MatcherIO};
|
||||
|
||||
fn read_link_target(file_info: &DirEntry) -> Option<PathBuf> {
|
||||
match file_info.path().read_link() {
|
||||
Ok(target) => Some(target),
|
||||
Err(err) => {
|
||||
// If it's not a symlink, then it's not an error that should be
|
||||
// shown.
|
||||
if err.kind() != std::io::ErrorKind::InvalidInput {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error reading target of {}: {}",
|
||||
file_info.path().display(),
|
||||
err
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This matcher makes a comparison of the link target against a shell wildcard
|
||||
/// pattern. See `glob::Pattern` for details on the exact syntax.
|
||||
pub struct LinkNameMatcher {
|
||||
pattern: Pattern,
|
||||
}
|
||||
|
||||
impl LinkNameMatcher {
|
||||
pub fn new(pattern_string: &str, caseless: bool) -> LinkNameMatcher {
|
||||
let pattern = Pattern::new(pattern_string, caseless);
|
||||
Self { pattern }
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for LinkNameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
if let Some(target) = read_link_target(file_info) {
|
||||
self.pattern.matches(&target.to_string_lossy())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::find::matchers::tests::get_dir_entry_for;
|
||||
use crate::find::matchers::Matcher;
|
||||
use crate::find::tests::FakeDependencies;
|
||||
|
||||
use std::io::ErrorKind;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::symlink;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::symlink_file;
|
||||
|
||||
fn create_file_link() {
|
||||
#[cfg(unix)]
|
||||
if let Err(e) = symlink("abbbc", "test_data/links/link-f") {
|
||||
assert!(
|
||||
e.kind() == ErrorKind::AlreadyExists,
|
||||
"Failed to create sym link: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if let Err(e) = symlink_file("abbbc", "test_data/links/link-f") {
|
||||
assert!(
|
||||
e.kind() == ErrorKind::AlreadyExists,
|
||||
"Failed to create sym link: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_against_link_target() {
|
||||
create_file_link();
|
||||
|
||||
let link_f = get_dir_entry_for("test_data/links", "link-f");
|
||||
let matcher = LinkNameMatcher::new("ab?bc", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&link_f, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_matches_against_link_target() {
|
||||
create_file_link();
|
||||
|
||||
let link_f = get_dir_entry_for("test_data/links", "link-f");
|
||||
let matcher = LinkNameMatcher::new("AbB?c", true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&link_f, &mut deps.new_matcher_io()));
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@ pub struct AndMatcher {
|
||||
}
|
||||
|
||||
impl AndMatcher {
|
||||
pub fn new(submatchers: Vec<Box<dyn Matcher>>) -> AndMatcher {
|
||||
AndMatcher { submatchers }
|
||||
pub fn new(submatchers: Vec<Box<dyn Matcher>>) -> Self {
|
||||
Self { submatchers }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,16 @@ impl Matcher for AndMatcher {
|
||||
/// place. If the nth sub-matcher returns false, then we immediately return
|
||||
/// and don't make any further calls.
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
self.submatchers
|
||||
.iter()
|
||||
.all(|x| x.matches(dir_entry, matcher_io))
|
||||
for matcher in &self.submatchers {
|
||||
if !matcher.matches(dir_entry, matcher_io) {
|
||||
return false;
|
||||
}
|
||||
if matcher_io.should_quit() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
@@ -62,14 +69,14 @@ pub struct AndMatcherBuilder {
|
||||
}
|
||||
|
||||
impl AndMatcherBuilder {
|
||||
pub fn new() -> AndMatcherBuilder {
|
||||
AndMatcherBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
submatchers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_and_condition(&mut self, matcher: Box<dyn Matcher>) {
|
||||
self.submatchers.push(matcher);
|
||||
pub fn new_and_condition(&mut self, matcher: impl Matcher) {
|
||||
self.submatchers.push(matcher.into_box());
|
||||
}
|
||||
|
||||
/// Builds a Matcher: consuming the builder in the process.
|
||||
@@ -79,9 +86,7 @@ impl AndMatcherBuilder {
|
||||
// safe to unwrap: we've just checked the size
|
||||
return self.submatchers.pop().unwrap();
|
||||
}
|
||||
let matcher = Box::new(AndMatcher::new(self.submatchers));
|
||||
self.submatchers = Vec::new();
|
||||
matcher
|
||||
AndMatcher::new(self.submatchers).into_box()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +99,8 @@ pub struct OrMatcher {
|
||||
}
|
||||
|
||||
impl OrMatcher {
|
||||
pub fn new(submatchers: Vec<Box<dyn Matcher>>) -> OrMatcher {
|
||||
OrMatcher { submatchers }
|
||||
pub fn new(submatchers: Vec<Box<dyn Matcher>>) -> Self {
|
||||
Self { submatchers }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +109,16 @@ impl Matcher for OrMatcher {
|
||||
/// place. If the nth sub-matcher returns true, then we immediately return
|
||||
/// and don't make any further calls.
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
self.submatchers
|
||||
.iter()
|
||||
.any(|x| x.matches(dir_entry, matcher_io))
|
||||
for matcher in &self.submatchers {
|
||||
if matcher.matches(dir_entry, matcher_io) {
|
||||
return true;
|
||||
}
|
||||
if matcher_io.should_quit() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
@@ -131,7 +143,7 @@ pub struct OrMatcherBuilder {
|
||||
}
|
||||
|
||||
impl OrMatcherBuilder {
|
||||
pub fn new_and_condition(&mut self, matcher: Box<dyn Matcher>) {
|
||||
pub fn new_and_condition(&mut self, matcher: impl Matcher) {
|
||||
// safe to unwrap. submatchers always has at least one member
|
||||
self.submatchers
|
||||
.last_mut()
|
||||
@@ -151,8 +163,8 @@ impl OrMatcherBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new() -> OrMatcherBuilder {
|
||||
let mut o = OrMatcherBuilder {
|
||||
pub fn new() -> Self {
|
||||
let mut o = Self {
|
||||
submatchers: Vec::new(),
|
||||
};
|
||||
o.submatchers.push(AndMatcherBuilder::new());
|
||||
@@ -170,7 +182,7 @@ impl OrMatcherBuilder {
|
||||
for x in self.submatchers {
|
||||
submatchers.push(x.build());
|
||||
}
|
||||
Box::new(OrMatcher::new(submatchers))
|
||||
OrMatcher::new(submatchers).into_box()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,8 +196,8 @@ pub struct ListMatcher {
|
||||
}
|
||||
|
||||
impl ListMatcher {
|
||||
pub fn new(submatchers: Vec<Box<dyn Matcher>>) -> ListMatcher {
|
||||
ListMatcher { submatchers }
|
||||
pub fn new(submatchers: Vec<Box<dyn Matcher>>) -> Self {
|
||||
Self { submatchers }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +208,9 @@ impl Matcher for ListMatcher {
|
||||
let mut rc = false;
|
||||
for matcher in &self.submatchers {
|
||||
rc = matcher.matches(dir_entry, matcher_io);
|
||||
if matcher_io.should_quit() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
rc
|
||||
}
|
||||
@@ -222,7 +237,7 @@ pub struct ListMatcherBuilder {
|
||||
}
|
||||
|
||||
impl ListMatcherBuilder {
|
||||
pub fn new_and_condition(&mut self, matcher: Box<dyn Matcher>) {
|
||||
pub fn new_and_condition(&mut self, matcher: impl Matcher) {
|
||||
// safe to unwrap. submatchers always has at least one member
|
||||
self.submatchers
|
||||
.last_mut()
|
||||
@@ -265,8 +280,8 @@ impl ListMatcherBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new() -> ListMatcherBuilder {
|
||||
let mut o = ListMatcherBuilder {
|
||||
pub fn new() -> Self {
|
||||
let mut o = Self {
|
||||
submatchers: Vec::new(),
|
||||
};
|
||||
o.submatchers.push(OrMatcherBuilder::new());
|
||||
@@ -291,12 +306,6 @@ impl ListMatcherBuilder {
|
||||
/// A simple matcher that always matches.
|
||||
pub struct TrueMatcher;
|
||||
|
||||
impl TrueMatcher {
|
||||
pub fn new_box() -> Box<dyn Matcher> {
|
||||
Box::new(TrueMatcher {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for TrueMatcher {
|
||||
fn matches(&self, _dir_entry: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
true
|
||||
@@ -312,24 +321,16 @@ impl Matcher for FalseMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
impl FalseMatcher {
|
||||
pub fn new_box() -> Box<dyn Matcher> {
|
||||
Box::new(FalseMatcher {})
|
||||
}
|
||||
}
|
||||
|
||||
/// Matcher that wraps another matcher and inverts matching criteria.
|
||||
pub struct NotMatcher {
|
||||
submatcher: Box<dyn Matcher>,
|
||||
}
|
||||
|
||||
impl NotMatcher {
|
||||
pub fn new(submatcher: Box<dyn Matcher>) -> NotMatcher {
|
||||
NotMatcher { submatcher }
|
||||
}
|
||||
|
||||
pub fn new_box(submatcher: Box<dyn Matcher>) -> Box<NotMatcher> {
|
||||
Box::new(NotMatcher::new(submatcher))
|
||||
pub fn new(submatcher: impl Matcher) -> Self {
|
||||
Self {
|
||||
submatcher: submatcher.into_box(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,13 +356,16 @@ impl Matcher for NotMatcher {
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::find::matchers::quit::QuitMatcher;
|
||||
use crate::find::matchers::tests::get_dir_entry_for;
|
||||
use crate::find::matchers::{Matcher, MatcherIO};
|
||||
use crate::find::tests::FakeDependencies;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
/// Simple Matcher impl that has side effects
|
||||
pub struct HasSideEffects {}
|
||||
pub struct HasSideEffects;
|
||||
|
||||
impl Matcher for HasSideEffects {
|
||||
fn matches(&self, _: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
@@ -373,9 +377,13 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSideEffects {
|
||||
pub fn new_box() -> Box<dyn Matcher> {
|
||||
Box::new(HasSideEffects {})
|
||||
/// Matcher that counts its invocations
|
||||
struct Counter(Rc<RefCell<u32>>);
|
||||
|
||||
impl Matcher for Counter {
|
||||
fn matches(&self, _: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
*self.0.borrow_mut() += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,12 +394,12 @@ mod tests {
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
// start with one matcher returning true
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
assert!(builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
builder = AndMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
@@ -402,13 +410,13 @@ mod tests {
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
// start with one matcher returning false
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
let mut builder = OrMatcherBuilder::new();
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
builder.new_or_condition("-o").unwrap();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
assert!(builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
@@ -419,21 +427,21 @@ mod tests {
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
// result should always match that of the last pushed submatcher
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
builder = ListMatcherBuilder::new();
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
builder.new_list_condition().unwrap();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
assert!(builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
builder = ListMatcherBuilder::new();
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
builder.new_list_condition().unwrap();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_list_condition().unwrap();
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
@@ -460,12 +468,12 @@ mod tests {
|
||||
let mut builder = AndMatcherBuilder::new();
|
||||
|
||||
// start with one matcher with no side effects false
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
assert!(!builder.build().has_side_effects());
|
||||
|
||||
builder = AndMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(HasSideEffects::new_box());
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(HasSideEffects);
|
||||
assert!(builder.build().has_side_effects());
|
||||
}
|
||||
|
||||
@@ -474,12 +482,12 @@ mod tests {
|
||||
let mut builder = OrMatcherBuilder::new();
|
||||
|
||||
// start with one matcher with no side effects false
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
assert!(!builder.build().has_side_effects());
|
||||
|
||||
builder = OrMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(HasSideEffects::new_box());
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(HasSideEffects);
|
||||
assert!(builder.build().has_side_effects());
|
||||
}
|
||||
|
||||
@@ -488,12 +496,12 @@ mod tests {
|
||||
let mut builder = ListMatcherBuilder::new();
|
||||
|
||||
// start with one matcher with no side effects false
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
assert!(!builder.build().has_side_effects());
|
||||
|
||||
builder = ListMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(HasSideEffects::new_box());
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(HasSideEffects);
|
||||
assert!(builder.build().has_side_effects());
|
||||
}
|
||||
|
||||
@@ -512,8 +520,8 @@ mod tests {
|
||||
#[test]
|
||||
fn not_matches_works() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let not_true = NotMatcher::new(TrueMatcher::new_box());
|
||||
let not_false = NotMatcher::new(FalseMatcher::new_box());
|
||||
let not_true = NotMatcher::new(TrueMatcher);
|
||||
let not_false = NotMatcher::new(FalseMatcher);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!not_true.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert!(not_false.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
@@ -521,9 +529,64 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn not_has_side_effects_works() {
|
||||
let has_fx = NotMatcher::new(HasSideEffects::new_box());
|
||||
let hasnt_fx = NotMatcher::new(FalseMatcher::new_box());
|
||||
let has_fx = NotMatcher::new(HasSideEffects);
|
||||
let has_no_fx = NotMatcher::new(FalseMatcher);
|
||||
assert!(has_fx.has_side_effects());
|
||||
assert!(!hasnt_fx.has_side_effects());
|
||||
assert!(!has_no_fx.has_side_effects());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn and_quit_works() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let mut builder = AndMatcherBuilder::new();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let before = Rc::new(RefCell::new(0));
|
||||
let after = Rc::new(RefCell::new(0));
|
||||
builder.new_and_condition(Counter(before.clone()));
|
||||
builder.new_and_condition(QuitMatcher);
|
||||
builder.new_and_condition(Counter(after.clone()));
|
||||
builder.build().matches(&abbbc, &mut deps.new_matcher_io());
|
||||
|
||||
assert_eq!(*before.borrow(), 1);
|
||||
assert_eq!(*after.borrow(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_quit_works() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let mut builder = OrMatcherBuilder::new();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let before = Rc::new(RefCell::new(0));
|
||||
let after = Rc::new(RefCell::new(0));
|
||||
builder.new_and_condition(Counter(before.clone()));
|
||||
builder.new_or_condition("-o").unwrap();
|
||||
builder.new_and_condition(QuitMatcher);
|
||||
builder.new_or_condition("-o").unwrap();
|
||||
builder.new_and_condition(Counter(after.clone()));
|
||||
builder.build().matches(&abbbc, &mut deps.new_matcher_io());
|
||||
|
||||
assert_eq!(*before.borrow(), 1);
|
||||
assert_eq!(*after.borrow(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_quit_works() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let mut builder = ListMatcherBuilder::new();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let before = Rc::new(RefCell::new(0));
|
||||
let after = Rc::new(RefCell::new(0));
|
||||
builder.new_and_condition(Counter(before.clone()));
|
||||
builder.new_list_condition().unwrap();
|
||||
builder.new_and_condition(QuitMatcher);
|
||||
builder.new_list_condition().unwrap();
|
||||
builder.new_and_condition(Counter(after.clone()));
|
||||
builder.build().matches(&abbbc, &mut deps.new_matcher_io());
|
||||
|
||||
assert_eq!(*before.borrow(), 1);
|
||||
assert_eq!(*after.borrow(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
+148
-47
@@ -4,16 +4,23 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
mod access;
|
||||
mod delete;
|
||||
mod empty;
|
||||
pub mod exec;
|
||||
mod glob;
|
||||
mod lname;
|
||||
mod logical_matchers;
|
||||
mod name;
|
||||
mod path;
|
||||
mod perm;
|
||||
mod printer;
|
||||
mod printf;
|
||||
mod prune;
|
||||
mod quit;
|
||||
mod regex;
|
||||
mod size;
|
||||
mod stat;
|
||||
mod time;
|
||||
mod type_matcher;
|
||||
|
||||
@@ -23,12 +30,34 @@ use std::time::SystemTime;
|
||||
use std::{error::Error, str::FromStr};
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use self::access::AccessMatcher;
|
||||
use self::delete::DeleteMatcher;
|
||||
use self::empty::EmptyMatcher;
|
||||
use self::exec::SingleExecMatcher;
|
||||
use self::lname::LinkNameMatcher;
|
||||
use self::logical_matchers::{
|
||||
AndMatcherBuilder, FalseMatcher, ListMatcherBuilder, NotMatcher, TrueMatcher,
|
||||
};
|
||||
use self::name::NameMatcher;
|
||||
use self::path::PathMatcher;
|
||||
use self::perm::PermMatcher;
|
||||
use self::printer::{PrintDelimiter, Printer};
|
||||
use self::printf::Printf;
|
||||
use self::prune::PruneMatcher;
|
||||
use self::quit::QuitMatcher;
|
||||
use self::regex::RegexMatcher;
|
||||
use self::size::SizeMatcher;
|
||||
use self::stat::{InodeMatcher, LinksMatcher};
|
||||
use self::time::{FileTimeMatcher, FileTimeType, NewerMatcher};
|
||||
use self::type_matcher::TypeMatcher;
|
||||
|
||||
use super::{Config, Dependencies};
|
||||
|
||||
/// Struct holding references to outputs and any inputs that can't be derived
|
||||
/// from the file/directory info.
|
||||
pub struct MatcherIO<'a> {
|
||||
should_skip_dir: bool,
|
||||
quit: bool,
|
||||
deps: &'a dyn Dependencies<'a>,
|
||||
}
|
||||
|
||||
@@ -37,6 +66,7 @@ impl<'a> MatcherIO<'a> {
|
||||
MatcherIO {
|
||||
deps,
|
||||
should_skip_dir: false,
|
||||
quit: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +78,14 @@ impl<'a> MatcherIO<'a> {
|
||||
self.should_skip_dir
|
||||
}
|
||||
|
||||
pub fn quit(&mut self) {
|
||||
self.quit = true;
|
||||
}
|
||||
|
||||
pub fn should_quit(&self) -> bool {
|
||||
self.quit
|
||||
}
|
||||
|
||||
pub fn now(&self) -> SystemTime {
|
||||
self.deps.now()
|
||||
}
|
||||
@@ -57,7 +95,15 @@ impl<'a> MatcherIO<'a> {
|
||||
/// is what's being searched for. To a first order approximation, find consists
|
||||
/// of building a chain of Matcher objects, and then walking a directory tree,
|
||||
/// passing each entry to the chain of Matchers.
|
||||
pub trait Matcher {
|
||||
pub trait Matcher: 'static {
|
||||
/// Boxes this matcher as a trait object.
|
||||
fn into_box(self) -> Box<dyn Matcher>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Box::new(self)
|
||||
}
|
||||
|
||||
/// Returns whether the given file matches the object's predicate.
|
||||
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool;
|
||||
|
||||
@@ -80,6 +126,28 @@ pub trait Matcher {
|
||||
fn finished(&self) {}
|
||||
}
|
||||
|
||||
impl Matcher for Box<dyn Matcher> {
|
||||
fn into_box(self) -> Box<dyn Matcher> {
|
||||
self
|
||||
}
|
||||
|
||||
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
(**self).matches(file_info, matcher_io)
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
(**self).has_side_effects()
|
||||
}
|
||||
|
||||
fn finished_dir(&self, finished_directory: &Path) {
|
||||
(**self).finished_dir(finished_directory)
|
||||
}
|
||||
|
||||
fn finished(&self) {
|
||||
(**self).finished()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ComparableValue {
|
||||
MoreThan(u64),
|
||||
EqualTo(u64),
|
||||
@@ -115,10 +183,9 @@ pub fn build_top_level_matcher(
|
||||
|
||||
// if the matcher doesn't have any side-effects, then we default to printing
|
||||
if !top_level_matcher.has_side_effects() {
|
||||
let mut new_and_matcher = logical_matchers::AndMatcherBuilder::new();
|
||||
let mut new_and_matcher = AndMatcherBuilder::new();
|
||||
new_and_matcher.new_and_condition(top_level_matcher);
|
||||
new_and_matcher
|
||||
.new_and_condition(printer::Printer::new_box(printer::PrintDelimiter::Newline));
|
||||
new_and_matcher.new_and_condition(Printer::new(PrintDelimiter::Newline));
|
||||
return Ok(new_and_matcher.build());
|
||||
}
|
||||
Ok(top_level_matcher)
|
||||
@@ -198,7 +265,7 @@ fn build_matcher_tree(
|
||||
arg_index: usize,
|
||||
expecting_bracket: bool,
|
||||
) -> Result<(usize, Box<dyn Matcher>), Box<dyn Error>> {
|
||||
let mut top_level_matcher = logical_matchers::ListMatcherBuilder::new();
|
||||
let mut top_level_matcher = ListMatcherBuilder::new();
|
||||
|
||||
let mut regex_type = regex::RegexType::default();
|
||||
|
||||
@@ -210,31 +277,39 @@ fn build_matcher_tree(
|
||||
let mut invert_next_matcher = false;
|
||||
while i < args.len() {
|
||||
let possible_submatcher = match args[i] {
|
||||
"-print" => Some(printer::Printer::new_box(printer::PrintDelimiter::Newline)),
|
||||
"-print0" => Some(printer::Printer::new_box(printer::PrintDelimiter::Null)),
|
||||
"-print" => Some(Printer::new(PrintDelimiter::Newline).into_box()),
|
||||
"-print0" => Some(Printer::new(PrintDelimiter::Null).into_box()),
|
||||
"-printf" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(printf::Printf::new_box(args[i])?)
|
||||
Some(Printf::new(args[i])?.into_box())
|
||||
}
|
||||
"-true" => Some(logical_matchers::TrueMatcher::new_box()),
|
||||
"-false" => Some(logical_matchers::FalseMatcher::new_box()),
|
||||
"-name" => {
|
||||
"-true" => Some(TrueMatcher.into_box()),
|
||||
"-false" => Some(FalseMatcher.into_box()),
|
||||
"-lname" | "-ilname" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(name::NameMatcher::new_box(args[i])?)
|
||||
Some(LinkNameMatcher::new(args[i], args[i - 1].starts_with("-i")).into_box())
|
||||
}
|
||||
"-iname" => {
|
||||
"-name" | "-iname" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(name::CaselessNameMatcher::new_box(args[i])?)
|
||||
Some(NameMatcher::new(args[i], args[i - 1].starts_with("-i")).into_box())
|
||||
}
|
||||
"-path" | "-ipath" | "-wholename" | "-iwholename" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(PathMatcher::new(args[i], args[i - 1].starts_with("-i")).into_box())
|
||||
}
|
||||
"-readable" => Some(AccessMatcher::Readable.into_box()),
|
||||
"-regextype" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
@@ -248,49 +323,49 @@ fn build_matcher_tree(
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(regex::RegexMatcher::new_box(regex_type, args[i], false)?)
|
||||
Some(RegexMatcher::new(regex_type, args[i], false)?.into_box())
|
||||
}
|
||||
"-iregex" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(regex::RegexMatcher::new_box(regex_type, args[i], true)?)
|
||||
Some(RegexMatcher::new(regex_type, args[i], true)?.into_box())
|
||||
}
|
||||
"-type" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(type_matcher::TypeMatcher::new_box(args[i])?)
|
||||
Some(TypeMatcher::new(args[i])?.into_box())
|
||||
}
|
||||
"-delete" => {
|
||||
// -delete implicitly requires -depth
|
||||
config.depth_first = true;
|
||||
Some(delete::DeleteMatcher::new_box()?)
|
||||
Some(DeleteMatcher::new().into_box())
|
||||
}
|
||||
"-newer" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(time::NewerMatcher::new_box(args[i])?)
|
||||
Some(NewerMatcher::new(args[i])?.into_box())
|
||||
}
|
||||
"-mtime" | "-atime" | "-ctime" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
let file_time_type = match args[i] {
|
||||
"-atime" => time::FileTimeType::Accessed,
|
||||
"-ctime" => time::FileTimeType::Created,
|
||||
"-mtime" => time::FileTimeType::Modified,
|
||||
"-atime" => FileTimeType::Accessed,
|
||||
"-ctime" => FileTimeType::Created,
|
||||
"-mtime" => FileTimeType::Modified,
|
||||
// This shouldn't be possible. We've already checked the value
|
||||
// is one of those three values.
|
||||
_ => unreachable!("Encountered unexpected value {}", args[i]),
|
||||
};
|
||||
let days = convert_arg_to_comparable_value(args[i], args[i + 1])?;
|
||||
i += 1;
|
||||
Some(time::FileTimeMatcher::new_box(file_time_type, days))
|
||||
Some(FileTimeMatcher::new(file_time_type, days).into_box())
|
||||
}
|
||||
"-size" => {
|
||||
if i >= args.len() - 1 {
|
||||
@@ -299,8 +374,9 @@ fn build_matcher_tree(
|
||||
let (size, unit) =
|
||||
convert_arg_to_comparable_value_and_suffix(args[i], args[i + 1])?;
|
||||
i += 1;
|
||||
Some(size::SizeMatcher::new_box(size, &unit)?)
|
||||
Some(SizeMatcher::new(size, &unit)?.into_box())
|
||||
}
|
||||
"-empty" => Some(EmptyMatcher::new().into_box()),
|
||||
"-exec" | "-execdir" => {
|
||||
let mut arg_index = i + 1;
|
||||
while arg_index < args.len() && args[arg_index] != ";" {
|
||||
@@ -322,20 +398,38 @@ fn build_matcher_tree(
|
||||
let executable = args[i + 1];
|
||||
let exec_args = &args[i + 2..arg_index];
|
||||
i = arg_index;
|
||||
Some(exec::SingleExecMatcher::new_box(
|
||||
executable,
|
||||
exec_args,
|
||||
expression == "-execdir",
|
||||
)?)
|
||||
Some(
|
||||
SingleExecMatcher::new(executable, exec_args, expression == "-execdir")?
|
||||
.into_box(),
|
||||
)
|
||||
}
|
||||
"-inum" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
let inum = convert_arg_to_comparable_value(args[i], args[i + 1])?;
|
||||
i += 1;
|
||||
Some(InodeMatcher::new(inum)?.into_box())
|
||||
}
|
||||
"-links" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
let inum = convert_arg_to_comparable_value(args[i], args[i + 1])?;
|
||||
i += 1;
|
||||
Some(LinksMatcher::new(inum)?.into_box())
|
||||
}
|
||||
"-executable" => Some(AccessMatcher::Executable.into_box()),
|
||||
"-perm" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(perm::PermMatcher::new_box(args[i])?)
|
||||
Some(PermMatcher::new(args[i])?.into_box())
|
||||
}
|
||||
"-prune" => Some(prune::PruneMatcher::new_box()),
|
||||
"-prune" => Some(PruneMatcher::new().into_box()),
|
||||
"-quit" => Some(QuitMatcher.into_box()),
|
||||
"-writable" => Some(AccessMatcher::Writable.into_box()),
|
||||
"-not" | "!" => {
|
||||
if !are_more_expressions(args, i) {
|
||||
return Err(From::from(format!(
|
||||
@@ -346,7 +440,7 @@ fn build_matcher_tree(
|
||||
invert_next_matcher = !invert_next_matcher;
|
||||
None
|
||||
}
|
||||
"-a" => {
|
||||
"-and" | "-a" => {
|
||||
if !are_more_expressions(args, i) {
|
||||
return Err(From::from(format!(
|
||||
"expected an expression after {}",
|
||||
@@ -392,6 +486,11 @@ fn build_matcher_tree(
|
||||
config.depth_first = true;
|
||||
None
|
||||
}
|
||||
"-mount" | "-xdev" => {
|
||||
// TODO add warning if it appears after actual testing criterion
|
||||
config.same_file_system = true;
|
||||
None
|
||||
}
|
||||
"-sorted" => {
|
||||
// TODO add warning if it appears after actual testing criterion
|
||||
config.sorted_output = true;
|
||||
@@ -426,8 +525,7 @@ fn build_matcher_tree(
|
||||
};
|
||||
if let Some(submatcher) = possible_submatcher {
|
||||
if invert_next_matcher {
|
||||
top_level_matcher
|
||||
.new_and_condition(logical_matchers::NotMatcher::new_box(submatcher));
|
||||
top_level_matcher.new_and_condition(NotMatcher::new(submatcher));
|
||||
invert_next_matcher = false;
|
||||
} else {
|
||||
top_level_matcher.new_and_condition(submatcher);
|
||||
@@ -513,7 +611,7 @@ mod tests {
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let matcher =
|
||||
build_top_level_matcher(&[arg, "-name", "doesntexist"], &mut config).unwrap();
|
||||
build_top_level_matcher(&[arg, "-name", "does_not_exist"], &mut config).unwrap();
|
||||
|
||||
assert!(matcher.matches(&abbbc_lower, &mut deps.new_matcher_io()));
|
||||
assert_eq!(
|
||||
@@ -554,7 +652,8 @@ mod tests {
|
||||
|
||||
config = Config::default();
|
||||
let matcher =
|
||||
build_top_level_matcher(&[arg, arg, "-name", "doesntexist"], &mut config).unwrap();
|
||||
build_top_level_matcher(&[arg, arg, "-name", "does_not_exist"], &mut config)
|
||||
.unwrap();
|
||||
|
||||
assert!(!matcher.matches(&abbbc_lower, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -624,17 +723,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn build_top_level_matcher_dash_a_works() {
|
||||
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");
|
||||
let mut config = Config::default();
|
||||
let deps = FakeDependencies::new();
|
||||
for arg in &["-a", "-and"] {
|
||||
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");
|
||||
let mut config = Config::default();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
// build a matcher using an explicit -a argument
|
||||
let matcher = build_top_level_matcher(&["-true", "-a", "-true"], &mut config).unwrap();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert_eq!(
|
||||
deps.get_output_as_string(),
|
||||
fix_up_slashes("./test_data/simple/abbbc\n")
|
||||
);
|
||||
// build a matcher using an explicit -a argument
|
||||
let matcher = build_top_level_matcher(&["-true", arg, "-true"], &mut config).unwrap();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert_eq!(
|
||||
deps.get_output_as_string(),
|
||||
fix_up_slashes("./test_data/simple/abbbc\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1025,7 +1126,7 @@ mod tests {
|
||||
fn build_top_level_matcher_perm_bad() {
|
||||
let mut config = Config::default();
|
||||
if let Err(e) = build_top_level_matcher(&["-perm", "foo"], &mut config) {
|
||||
assert!(e.to_string().contains("invalid mode"));
|
||||
assert!(e.to_string().contains("invalid operator"));
|
||||
} else {
|
||||
panic!("-perm with bad mode pattern should fail");
|
||||
}
|
||||
|
||||
+55
-57
@@ -4,79 +4,69 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use glob::Pattern;
|
||||
use glob::PatternError;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::glob::Pattern;
|
||||
use super::{Matcher, MatcherIO};
|
||||
|
||||
/// This matcher makes a case-sensitive comparison of the name against a
|
||||
/// shell wildcard pattern. See `glob::Pattern` for details on the exact
|
||||
/// syntax.
|
||||
/// This matcher makes a comparison of the name against a shell wildcard
|
||||
/// pattern. See `glob::Pattern` for details on the exact syntax.
|
||||
pub struct NameMatcher {
|
||||
pattern: Pattern,
|
||||
}
|
||||
|
||||
impl NameMatcher {
|
||||
pub fn new(pattern_string: &str) -> Result<NameMatcher, PatternError> {
|
||||
let p = Pattern::new(pattern_string)?;
|
||||
Ok(NameMatcher { pattern: p })
|
||||
}
|
||||
|
||||
pub fn new_box(pattern_string: &str) -> Result<Box<dyn Matcher>, PatternError> {
|
||||
Ok(Box::new(NameMatcher::new(pattern_string)?))
|
||||
pub fn new(pattern_string: &str, caseless: bool) -> Self {
|
||||
let pattern = Pattern::new(pattern_string, caseless);
|
||||
Self { pattern }
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for NameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
self.pattern
|
||||
.matches(file_info.file_name().to_string_lossy().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// This matcher makes a case-insensitive comparison of the name against a
|
||||
/// shell wildcard pattern. See `glob::Pattern` for details on the exact
|
||||
/// syntax.
|
||||
pub struct CaselessNameMatcher {
|
||||
pattern: Pattern,
|
||||
}
|
||||
|
||||
impl CaselessNameMatcher {
|
||||
pub fn new(pattern_string: &str) -> Result<CaselessNameMatcher, PatternError> {
|
||||
let p = Pattern::new(&pattern_string.to_lowercase())?;
|
||||
Ok(CaselessNameMatcher { pattern: p })
|
||||
}
|
||||
|
||||
pub fn new_box(pattern_string: &str) -> Result<Box<dyn Matcher>, PatternError> {
|
||||
Ok(Box::new(CaselessNameMatcher::new(pattern_string)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Matcher for CaselessNameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
self.pattern.matches(
|
||||
file_info
|
||||
.file_name()
|
||||
.to_string_lossy()
|
||||
.to_lowercase()
|
||||
.as_ref(),
|
||||
)
|
||||
let name = file_info.file_name().to_string_lossy();
|
||||
self.pattern.matches(&name)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::find::matchers::tests::get_dir_entry_for;
|
||||
use crate::find::matchers::Matcher;
|
||||
use crate::find::tests::FakeDependencies;
|
||||
|
||||
use std::io::ErrorKind;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::symlink_file;
|
||||
|
||||
fn create_file_link() {
|
||||
#[cfg(unix)]
|
||||
if let Err(e) = symlink("abbbc", "test_data/links/link-f") {
|
||||
assert!(
|
||||
e.kind() == ErrorKind::AlreadyExists,
|
||||
"Failed to create sym link: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if let Err(e) = symlink_file("abbbc", "test_data/links/link-f") {
|
||||
assert!(
|
||||
e.kind() == ErrorKind::AlreadyExists,
|
||||
"Failed to create sym link: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matching_with_wrong_case_returns_false() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = NameMatcher::new(&"A*C".to_string()).unwrap();
|
||||
let matcher = NameMatcher::new("A*C", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -84,7 +74,7 @@ mod tests {
|
||||
#[test]
|
||||
fn matching_with_right_case_returns_true() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = NameMatcher::new(&"abb?c".to_string()).unwrap();
|
||||
let matcher = NameMatcher::new("abb?c", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -92,21 +82,25 @@ mod tests {
|
||||
#[test]
|
||||
fn not_matching_returns_false() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = NameMatcher::new(&"shouldn't match".to_string()).unwrap();
|
||||
let matcher = NameMatcher::new("shouldn't match", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cant_create_with_invalid_pattern() {
|
||||
let result = NameMatcher::new(&"a**c".to_string());
|
||||
assert!(result.is_err());
|
||||
fn matches_against_link_file_name() {
|
||||
create_file_link();
|
||||
|
||||
let link_f = get_dir_entry_for("test_data/links", "link-f");
|
||||
let matcher = NameMatcher::new("link?f", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&link_f, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_matching_with_wrong_case_returns_true() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = CaselessNameMatcher::new(&"A*C".to_string()).unwrap();
|
||||
let matcher = NameMatcher::new("A*C", true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -114,7 +108,7 @@ mod tests {
|
||||
#[test]
|
||||
fn caseless_matching_with_right_case_returns_true() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = CaselessNameMatcher::new(&"abb?c".to_string()).unwrap();
|
||||
let matcher = NameMatcher::new("abb?c", true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -122,14 +116,18 @@ mod tests {
|
||||
#[test]
|
||||
fn caseless_not_matching_returns_false() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = CaselessNameMatcher::new(&"shouldn't match".to_string()).unwrap();
|
||||
let matcher = NameMatcher::new("shouldn't match", true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_cant_create_with_invalid_pattern() {
|
||||
let result = CaselessNameMatcher::new(&"a**c".to_string());
|
||||
assert!(result.is_err());
|
||||
fn caseless_matches_against_link_file_name() {
|
||||
create_file_link();
|
||||
|
||||
let link_f = get_dir_entry_for("test_data/links", "link-f");
|
||||
let matcher = NameMatcher::new("linK?f", true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&link_f, &mut deps.new_matcher_io()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2017 Google Inc.
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::glob::Pattern;
|
||||
use super::{Matcher, MatcherIO};
|
||||
|
||||
/// This matcher makes a comparison of the path against a shell wildcard
|
||||
/// pattern. See `glob::Pattern` for details on the exact syntax.
|
||||
pub struct PathMatcher {
|
||||
pattern: Pattern,
|
||||
}
|
||||
|
||||
impl PathMatcher {
|
||||
pub fn new(pattern_string: &str, caseless: bool) -> Self {
|
||||
let pattern = Pattern::new(pattern_string, caseless);
|
||||
Self { pattern }
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for PathMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
let path = file_info.path().to_string_lossy();
|
||||
self.pattern.matches(&path)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::find::matchers::tests::get_dir_entry_for;
|
||||
use crate::find::matchers::Matcher;
|
||||
use crate::find::tests::FakeDependencies;
|
||||
|
||||
// Variants of fix_up_slashes that properly escape the forward slashes for
|
||||
// being in a glob.
|
||||
#[cfg(windows)]
|
||||
fn fix_up_glob_slashes(re: &str) -> String {
|
||||
re.replace("/", "\\\\")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn fix_up_glob_slashes(re: &str) -> String {
|
||||
re.to_owned()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matching_against_whole_path() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = PathMatcher::new(&fix_up_glob_slashes("test_*/*/a*c"), false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_matching_against_just_name() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = PathMatcher::new("a*c", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_matching_against_wrong_case() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = PathMatcher::new(&fix_up_glob_slashes("test_*/*/A*C"), false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_matching() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = PathMatcher::new(&fix_up_glob_slashes("test_*/*/A*C"), true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user