mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| adda4c1ba5 |
+6
-10
@@ -1,12 +1,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- 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
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "07:00"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
+95
-50
@@ -10,12 +10,12 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- 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
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
# 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'
|
||||
|
||||
- name: Check
|
||||
run: |
|
||||
cargo check
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
|
||||
test:
|
||||
name: cargo test
|
||||
@@ -37,12 +37,12 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- 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
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
# For bindgen: https://github.com/rust-lang/rust-bindgen/issues/1797
|
||||
- uses: KyleMayes/install-llvm-action@v1
|
||||
@@ -53,40 +53,41 @@ jobs:
|
||||
- run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- name: Check
|
||||
run: |
|
||||
cargo check
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
|
||||
fmt:
|
||||
name: cargo fmt --all -- --check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- name: cargo fmt
|
||||
run: |
|
||||
cargo fmt --all -- --check
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
name: cargo clippy -- -D warnings
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add clippy
|
||||
- name: cargo clippy
|
||||
run: |
|
||||
cargo clippy -- -D warnings
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
grcov:
|
||||
name: Code coverage
|
||||
@@ -101,20 +102,27 @@ jobs:
|
||||
- "--all-features"
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install nightly --no-self-update -c rustfmt --profile minimal
|
||||
rustup default nightly
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
override: true
|
||||
|
||||
- name: "`grcov` ~ install"
|
||||
run: cargo install grcov
|
||||
- name: Install grcov
|
||||
uses: actions-rs/install@v0.1
|
||||
with:
|
||||
crate: grcov
|
||||
version: latest
|
||||
use-tool-cache: true
|
||||
|
||||
- name: cargo test
|
||||
run: |
|
||||
cargo test --all --no-fail-fast ${{ matrix.cargo_flags }}
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --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'
|
||||
@@ -122,6 +130,7 @@ jobs:
|
||||
|
||||
- name: Generate coverage data
|
||||
id: grcov
|
||||
# uses: actions-rs/grcov@v0.1
|
||||
run: |
|
||||
grcov target/debug/ \
|
||||
--branch \
|
||||
@@ -138,15 +147,51 @@ jobs:
|
||||
--service-name "GitHub Actions" \
|
||||
--service-number ${{ github.run_id }}
|
||||
- name: Upload coverage as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lcov.info
|
||||
# path: ${{ steps.grcov.outputs.report }}
|
||||
path: lcov.info
|
||||
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v1
|
||||
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: External testsuites
|
||||
name: GNU findutils compatibility tests
|
||||
|
||||
jobs:
|
||||
gnu-tests:
|
||||
@@ -8,21 +8,21 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout GNU findutils
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: gnu-mirror-unofficial/findutils
|
||||
path: findutils.gnu
|
||||
ref: 5768a03ddfb5e18b1682e339d6cdd24ff721c510
|
||||
submodules: true
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -38,14 +38,39 @@ jobs:
|
||||
- name: Extract testing info
|
||||
shell: bash
|
||||
run: |
|
||||
- uses: actions/upload-artifact@v3
|
||||
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
|
||||
with:
|
||||
name: gnu-test-report
|
||||
path: |
|
||||
findutils.gnu/find/testsuite/*.log
|
||||
findutils.gnu/xargs/testsuite/*.log
|
||||
findutils.gnu/tests/**/*.log
|
||||
- uses: actions/upload-artifact@v3
|
||||
path: findutils.gnu/tests/**/*.log
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: gnu-result
|
||||
path: gnu-result.json
|
||||
@@ -70,7 +95,18 @@ jobs:
|
||||
- name: Compare failing tests against master
|
||||
shell: bash
|
||||
run: |
|
||||
./findutils/util/diff-gnu.sh ./dl ./findutils.gnu
|
||||
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
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -82,20 +118,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout BFS
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: tavianator/bfs
|
||||
path: bfs
|
||||
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
|
||||
ref: '2.3.1'
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -108,11 +144,36 @@ jobs:
|
||||
run: |
|
||||
cd findutils
|
||||
bash util/build-bfs.sh ||:
|
||||
- uses: actions/upload-artifact@v3
|
||||
- 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
|
||||
with:
|
||||
name: bfs-test-report
|
||||
path: bfs/tests.log
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: bfs-result
|
||||
path: bfs-result.json
|
||||
@@ -137,7 +198,18 @@ jobs:
|
||||
- name: Compare failing tests against main
|
||||
shell: bash
|
||||
run: |
|
||||
./findutils/util/diff-bfs.sh dl/tests.log bfs/tests.log
|
||||
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
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
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
|
||||
Generated
+100
-556
File diff suppressed because it is too large
Load Diff
+8
-9
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "findutils"
|
||||
version = "0.4.1"
|
||||
version = "0.3.0"
|
||||
homepage = "https://github.com/uutils/findutils"
|
||||
repository = "https://github.com/uutils/findutils"
|
||||
edition = "2018"
|
||||
@@ -12,19 +12,18 @@ authors = ["uutils developers"]
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
clap = "2.34"
|
||||
faccess = "0.2.4"
|
||||
glob = "0.3"
|
||||
walkdir = "2.3"
|
||||
regex = "1.7"
|
||||
once_cell = "1.17"
|
||||
onig = "6.4"
|
||||
uucore = { version = "0.0.12", features = ["entries", "fs", "fsext", "mode"] }
|
||||
regex = "1.5"
|
||||
once_cell = "1.9"
|
||||
onig = "6.3"
|
||||
uucore = { version = "0.0.12", features = ["entries", "fs", "fsext"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2"
|
||||
filetime = "0.2"
|
||||
nix = "0.26"
|
||||
predicates = "3"
|
||||
serial_test = "2.0"
|
||||
predicates = "2"
|
||||
serial_test = "0.5"
|
||||
tempfile = "3"
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -19,5 +19,3 @@ bash util/build-gnu.sh tests/misc/help-version.sh
|
||||
|
||||

|
||||

|
||||
|
||||
For more details, see https://github.com/uutils/findutils-tracking/
|
||||
|
||||
+4
-1
@@ -4,9 +4,12 @@
|
||||
// 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(std::convert::AsRef::as_ref).collect();
|
||||
let strs: Vec<&str> = args.iter().map(|s| s.as_ref()).collect();
|
||||
let deps = findutils::find::StandardDependencies::new();
|
||||
std::process::exit(findutils::find::find_main(&strs, &deps));
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
// 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,10 +18,14 @@ use super::{Matcher, MatcherIO};
|
||||
pub struct DeleteMatcher;
|
||||
|
||||
impl DeleteMatcher {
|
||||
pub fn new() -> Self {
|
||||
pub fn new() -> DeleteMatcher {
|
||||
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)
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
// 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()));
|
||||
}
|
||||
}
|
||||
+22
-15
@@ -14,7 +14,7 @@ use walkdir::DirEntry;
|
||||
use super::{Matcher, MatcherIO};
|
||||
|
||||
enum Arg {
|
||||
FileArg(Vec<OsString>),
|
||||
Filename,
|
||||
LiteralArg(OsString),
|
||||
}
|
||||
|
||||
@@ -29,26 +29,33 @@ impl SingleExecMatcher {
|
||||
executable: &str,
|
||||
args: &[&str],
|
||||
exec_in_parent_dir: bool,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
) -> Result<SingleExecMatcher, Box<dyn Error>> {
|
||||
let transformed_args = args
|
||||
.iter()
|
||||
.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())
|
||||
}
|
||||
.map(|&a| match a {
|
||||
"{}" => Arg::Filename,
|
||||
_ => Arg::LiteralArg(OsString::from(a)),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
Ok(SingleExecMatcher {
|
||||
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 {
|
||||
@@ -65,10 +72,10 @@ impl Matcher for SingleExecMatcher {
|
||||
};
|
||||
|
||||
for arg in &self.args {
|
||||
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())),
|
||||
};
|
||||
command.arg(match *arg {
|
||||
Arg::LiteralArg(ref a) => a.as_os_str(),
|
||||
Arg::Filename => path_to_file.as_os_str(),
|
||||
});
|
||||
}
|
||||
if self.exec_in_parent_dir {
|
||||
match file_info.path().parent() {
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
// 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"));
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
// 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>>) -> Self {
|
||||
Self { submatchers }
|
||||
pub fn new(submatchers: Vec<Box<dyn Matcher>>) -> AndMatcher {
|
||||
AndMatcher { submatchers }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,16 +35,9 @@ 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 {
|
||||
for matcher in &self.submatchers {
|
||||
if !matcher.matches(dir_entry, matcher_io) {
|
||||
return false;
|
||||
}
|
||||
if matcher_io.should_quit() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
self.submatchers
|
||||
.iter()
|
||||
.all(|x| x.matches(dir_entry, matcher_io))
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
@@ -69,14 +62,14 @@ pub struct AndMatcherBuilder {
|
||||
}
|
||||
|
||||
impl AndMatcherBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pub fn new() -> AndMatcherBuilder {
|
||||
AndMatcherBuilder {
|
||||
submatchers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_and_condition(&mut self, matcher: impl Matcher) {
|
||||
self.submatchers.push(matcher.into_box());
|
||||
pub fn new_and_condition(&mut self, matcher: Box<dyn Matcher>) {
|
||||
self.submatchers.push(matcher);
|
||||
}
|
||||
|
||||
/// Builds a Matcher: consuming the builder in the process.
|
||||
@@ -86,7 +79,9 @@ impl AndMatcherBuilder {
|
||||
// safe to unwrap: we've just checked the size
|
||||
return self.submatchers.pop().unwrap();
|
||||
}
|
||||
AndMatcher::new(self.submatchers).into_box()
|
||||
let matcher = Box::new(AndMatcher::new(self.submatchers));
|
||||
self.submatchers = Vec::new();
|
||||
matcher
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,8 +94,8 @@ pub struct OrMatcher {
|
||||
}
|
||||
|
||||
impl OrMatcher {
|
||||
pub fn new(submatchers: Vec<Box<dyn Matcher>>) -> Self {
|
||||
Self { submatchers }
|
||||
pub fn new(submatchers: Vec<Box<dyn Matcher>>) -> OrMatcher {
|
||||
OrMatcher { submatchers }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,16 +104,9 @@ 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 {
|
||||
for matcher in &self.submatchers {
|
||||
if matcher.matches(dir_entry, matcher_io) {
|
||||
return true;
|
||||
}
|
||||
if matcher_io.should_quit() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
self.submatchers
|
||||
.iter()
|
||||
.any(|x| x.matches(dir_entry, matcher_io))
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
@@ -143,7 +131,7 @@ pub struct OrMatcherBuilder {
|
||||
}
|
||||
|
||||
impl OrMatcherBuilder {
|
||||
pub fn new_and_condition(&mut self, matcher: impl Matcher) {
|
||||
pub fn new_and_condition(&mut self, matcher: Box<dyn Matcher>) {
|
||||
// safe to unwrap. submatchers always has at least one member
|
||||
self.submatchers
|
||||
.last_mut()
|
||||
@@ -163,8 +151,8 @@ impl OrMatcherBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
let mut o = Self {
|
||||
pub fn new() -> OrMatcherBuilder {
|
||||
let mut o = OrMatcherBuilder {
|
||||
submatchers: Vec::new(),
|
||||
};
|
||||
o.submatchers.push(AndMatcherBuilder::new());
|
||||
@@ -182,7 +170,7 @@ impl OrMatcherBuilder {
|
||||
for x in self.submatchers {
|
||||
submatchers.push(x.build());
|
||||
}
|
||||
OrMatcher::new(submatchers).into_box()
|
||||
Box::new(OrMatcher::new(submatchers))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +184,8 @@ pub struct ListMatcher {
|
||||
}
|
||||
|
||||
impl ListMatcher {
|
||||
pub fn new(submatchers: Vec<Box<dyn Matcher>>) -> Self {
|
||||
Self { submatchers }
|
||||
pub fn new(submatchers: Vec<Box<dyn Matcher>>) -> ListMatcher {
|
||||
ListMatcher { submatchers }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,9 +196,6 @@ 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
|
||||
}
|
||||
@@ -237,7 +222,7 @@ pub struct ListMatcherBuilder {
|
||||
}
|
||||
|
||||
impl ListMatcherBuilder {
|
||||
pub fn new_and_condition(&mut self, matcher: impl Matcher) {
|
||||
pub fn new_and_condition(&mut self, matcher: Box<dyn Matcher>) {
|
||||
// safe to unwrap. submatchers always has at least one member
|
||||
self.submatchers
|
||||
.last_mut()
|
||||
@@ -280,8 +265,8 @@ impl ListMatcherBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
let mut o = Self {
|
||||
pub fn new() -> ListMatcherBuilder {
|
||||
let mut o = ListMatcherBuilder {
|
||||
submatchers: Vec::new(),
|
||||
};
|
||||
o.submatchers.push(OrMatcherBuilder::new());
|
||||
@@ -306,6 +291,12 @@ 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
|
||||
@@ -321,16 +312,24 @@ 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: impl Matcher) -> Self {
|
||||
Self {
|
||||
submatcher: submatcher.into_box(),
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,16 +355,13 @@ 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 {
|
||||
@@ -377,13 +373,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
impl HasSideEffects {
|
||||
pub fn new_box() -> Box<dyn Matcher> {
|
||||
Box::new(HasSideEffects {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,12 +386,12 @@ mod tests {
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
// start with one matcher returning true
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
builder = AndMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
@@ -410,13 +402,13 @@ mod tests {
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
// start with one matcher returning false
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
let mut builder = OrMatcherBuilder::new();
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_or_condition("-o").unwrap();
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
@@ -427,21 +419,21 @@ mod tests {
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
// result should always match that of the last pushed submatcher
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
builder = ListMatcherBuilder::new();
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_list_condition().unwrap();
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
builder = ListMatcherBuilder::new();
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_list_condition().unwrap();
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_list_condition().unwrap();
|
||||
builder.new_and_condition(FalseMatcher);
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
@@ -468,12 +460,12 @@ mod tests {
|
||||
let mut builder = AndMatcherBuilder::new();
|
||||
|
||||
// start with one matcher with no side effects false
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(!builder.build().has_side_effects());
|
||||
|
||||
builder = AndMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(HasSideEffects);
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(HasSideEffects::new_box());
|
||||
assert!(builder.build().has_side_effects());
|
||||
}
|
||||
|
||||
@@ -482,12 +474,12 @@ mod tests {
|
||||
let mut builder = OrMatcherBuilder::new();
|
||||
|
||||
// start with one matcher with no side effects false
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(!builder.build().has_side_effects());
|
||||
|
||||
builder = OrMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(HasSideEffects);
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(HasSideEffects::new_box());
|
||||
assert!(builder.build().has_side_effects());
|
||||
}
|
||||
|
||||
@@ -496,12 +488,12 @@ mod tests {
|
||||
let mut builder = ListMatcherBuilder::new();
|
||||
|
||||
// start with one matcher with no side effects false
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(!builder.build().has_side_effects());
|
||||
|
||||
builder = ListMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher);
|
||||
builder.new_and_condition(HasSideEffects);
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(HasSideEffects::new_box());
|
||||
assert!(builder.build().has_side_effects());
|
||||
}
|
||||
|
||||
@@ -520,8 +512,8 @@ mod tests {
|
||||
#[test]
|
||||
fn not_matches_works() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let not_true = NotMatcher::new(TrueMatcher);
|
||||
let not_false = NotMatcher::new(FalseMatcher);
|
||||
let not_true = NotMatcher::new(TrueMatcher::new_box());
|
||||
let not_false = NotMatcher::new(FalseMatcher::new_box());
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!not_true.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert!(not_false.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
@@ -529,64 +521,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn not_has_side_effects_works() {
|
||||
let has_fx = NotMatcher::new(HasSideEffects);
|
||||
let has_no_fx = NotMatcher::new(FalseMatcher);
|
||||
let has_fx = NotMatcher::new(HasSideEffects::new_box());
|
||||
let hasnt_fx = NotMatcher::new(FalseMatcher::new_box());
|
||||
assert!(has_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);
|
||||
assert!(!hasnt_fx.has_side_effects());
|
||||
}
|
||||
}
|
||||
|
||||
+47
-148
@@ -4,23 +4,16 @@
|
||||
// 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;
|
||||
|
||||
@@ -30,34 +23,12 @@ 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>,
|
||||
}
|
||||
|
||||
@@ -66,7 +37,6 @@ impl<'a> MatcherIO<'a> {
|
||||
MatcherIO {
|
||||
deps,
|
||||
should_skip_dir: false,
|
||||
quit: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,14 +48,6 @@ 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()
|
||||
}
|
||||
@@ -95,15 +57,7 @@ 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: 'static {
|
||||
/// Boxes this matcher as a trait object.
|
||||
fn into_box(self) -> Box<dyn Matcher>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Box::new(self)
|
||||
}
|
||||
|
||||
pub trait Matcher {
|
||||
/// Returns whether the given file matches the object's predicate.
|
||||
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool;
|
||||
|
||||
@@ -126,28 +80,6 @@ pub trait Matcher: 'static {
|
||||
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),
|
||||
@@ -183,9 +115,10 @@ 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 = AndMatcherBuilder::new();
|
||||
let mut new_and_matcher = logical_matchers::AndMatcherBuilder::new();
|
||||
new_and_matcher.new_and_condition(top_level_matcher);
|
||||
new_and_matcher.new_and_condition(Printer::new(PrintDelimiter::Newline));
|
||||
new_and_matcher
|
||||
.new_and_condition(printer::Printer::new_box(printer::PrintDelimiter::Newline));
|
||||
return Ok(new_and_matcher.build());
|
||||
}
|
||||
Ok(top_level_matcher)
|
||||
@@ -265,7 +198,7 @@ fn build_matcher_tree(
|
||||
arg_index: usize,
|
||||
expecting_bracket: bool,
|
||||
) -> Result<(usize, Box<dyn Matcher>), Box<dyn Error>> {
|
||||
let mut top_level_matcher = ListMatcherBuilder::new();
|
||||
let mut top_level_matcher = logical_matchers::ListMatcherBuilder::new();
|
||||
|
||||
let mut regex_type = regex::RegexType::default();
|
||||
|
||||
@@ -277,39 +210,31 @@ fn build_matcher_tree(
|
||||
let mut invert_next_matcher = false;
|
||||
while i < args.len() {
|
||||
let possible_submatcher = match args[i] {
|
||||
"-print" => Some(Printer::new(PrintDelimiter::Newline).into_box()),
|
||||
"-print0" => Some(Printer::new(PrintDelimiter::Null).into_box()),
|
||||
"-print" => Some(printer::Printer::new_box(printer::PrintDelimiter::Newline)),
|
||||
"-print0" => Some(printer::Printer::new_box(printer::PrintDelimiter::Null)),
|
||||
"-printf" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(Printf::new(args[i])?.into_box())
|
||||
Some(printf::Printf::new_box(args[i])?)
|
||||
}
|
||||
"-true" => Some(TrueMatcher.into_box()),
|
||||
"-false" => Some(FalseMatcher.into_box()),
|
||||
"-lname" | "-ilname" => {
|
||||
"-true" => Some(logical_matchers::TrueMatcher::new_box()),
|
||||
"-false" => Some(logical_matchers::FalseMatcher::new_box()),
|
||||
"-name" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(LinkNameMatcher::new(args[i], args[i - 1].starts_with("-i")).into_box())
|
||||
Some(name::NameMatcher::new_box(args[i])?)
|
||||
}
|
||||
"-name" | "-iname" => {
|
||||
"-iname" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(NameMatcher::new(args[i], args[i - 1].starts_with("-i")).into_box())
|
||||
Some(name::CaselessNameMatcher::new_box(args[i])?)
|
||||
}
|
||||
"-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])));
|
||||
@@ -323,49 +248,49 @@ fn build_matcher_tree(
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(RegexMatcher::new(regex_type, args[i], false)?.into_box())
|
||||
Some(regex::RegexMatcher::new_box(regex_type, args[i], false)?)
|
||||
}
|
||||
"-iregex" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(RegexMatcher::new(regex_type, args[i], true)?.into_box())
|
||||
Some(regex::RegexMatcher::new_box(regex_type, args[i], true)?)
|
||||
}
|
||||
"-type" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(TypeMatcher::new(args[i])?.into_box())
|
||||
Some(type_matcher::TypeMatcher::new_box(args[i])?)
|
||||
}
|
||||
"-delete" => {
|
||||
// -delete implicitly requires -depth
|
||||
config.depth_first = true;
|
||||
Some(DeleteMatcher::new().into_box())
|
||||
Some(delete::DeleteMatcher::new_box()?)
|
||||
}
|
||||
"-newer" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(NewerMatcher::new(args[i])?.into_box())
|
||||
Some(time::NewerMatcher::new_box(args[i])?)
|
||||
}
|
||||
"-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" => FileTimeType::Accessed,
|
||||
"-ctime" => FileTimeType::Created,
|
||||
"-mtime" => FileTimeType::Modified,
|
||||
"-atime" => time::FileTimeType::Accessed,
|
||||
"-ctime" => time::FileTimeType::Created,
|
||||
"-mtime" => time::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(FileTimeMatcher::new(file_time_type, days).into_box())
|
||||
Some(time::FileTimeMatcher::new_box(file_time_type, days))
|
||||
}
|
||||
"-size" => {
|
||||
if i >= args.len() - 1 {
|
||||
@@ -374,9 +299,8 @@ fn build_matcher_tree(
|
||||
let (size, unit) =
|
||||
convert_arg_to_comparable_value_and_suffix(args[i], args[i + 1])?;
|
||||
i += 1;
|
||||
Some(SizeMatcher::new(size, &unit)?.into_box())
|
||||
Some(size::SizeMatcher::new_box(size, &unit)?)
|
||||
}
|
||||
"-empty" => Some(EmptyMatcher::new().into_box()),
|
||||
"-exec" | "-execdir" => {
|
||||
let mut arg_index = i + 1;
|
||||
while arg_index < args.len() && args[arg_index] != ";" {
|
||||
@@ -398,38 +322,20 @@ fn build_matcher_tree(
|
||||
let executable = args[i + 1];
|
||||
let exec_args = &args[i + 2..arg_index];
|
||||
i = arg_index;
|
||||
Some(
|
||||
SingleExecMatcher::new(executable, exec_args, expression == "-execdir")?
|
||||
.into_box(),
|
||||
)
|
||||
Some(exec::SingleExecMatcher::new_box(
|
||||
executable,
|
||||
exec_args,
|
||||
expression == "-execdir",
|
||||
)?)
|
||||
}
|
||||
"-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(PermMatcher::new(args[i])?.into_box())
|
||||
Some(perm::PermMatcher::new_box(args[i])?)
|
||||
}
|
||||
"-prune" => Some(PruneMatcher::new().into_box()),
|
||||
"-quit" => Some(QuitMatcher.into_box()),
|
||||
"-writable" => Some(AccessMatcher::Writable.into_box()),
|
||||
"-prune" => Some(prune::PruneMatcher::new_box()),
|
||||
"-not" | "!" => {
|
||||
if !are_more_expressions(args, i) {
|
||||
return Err(From::from(format!(
|
||||
@@ -440,7 +346,7 @@ fn build_matcher_tree(
|
||||
invert_next_matcher = !invert_next_matcher;
|
||||
None
|
||||
}
|
||||
"-and" | "-a" => {
|
||||
"-a" => {
|
||||
if !are_more_expressions(args, i) {
|
||||
return Err(From::from(format!(
|
||||
"expected an expression after {}",
|
||||
@@ -486,11 +392,6 @@ 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;
|
||||
@@ -525,7 +426,8 @@ fn build_matcher_tree(
|
||||
};
|
||||
if let Some(submatcher) = possible_submatcher {
|
||||
if invert_next_matcher {
|
||||
top_level_matcher.new_and_condition(NotMatcher::new(submatcher));
|
||||
top_level_matcher
|
||||
.new_and_condition(logical_matchers::NotMatcher::new_box(submatcher));
|
||||
invert_next_matcher = false;
|
||||
} else {
|
||||
top_level_matcher.new_and_condition(submatcher);
|
||||
@@ -611,7 +513,7 @@ mod tests {
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let matcher =
|
||||
build_top_level_matcher(&[arg, "-name", "does_not_exist"], &mut config).unwrap();
|
||||
build_top_level_matcher(&[arg, "-name", "doesntexist"], &mut config).unwrap();
|
||||
|
||||
assert!(matcher.matches(&abbbc_lower, &mut deps.new_matcher_io()));
|
||||
assert_eq!(
|
||||
@@ -652,8 +554,7 @@ mod tests {
|
||||
|
||||
config = Config::default();
|
||||
let matcher =
|
||||
build_top_level_matcher(&[arg, arg, "-name", "does_not_exist"], &mut config)
|
||||
.unwrap();
|
||||
build_top_level_matcher(&[arg, arg, "-name", "doesntexist"], &mut config).unwrap();
|
||||
|
||||
assert!(!matcher.matches(&abbbc_lower, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -723,19 +624,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn build_top_level_matcher_dash_a_works() {
|
||||
for arg in &["-a", "-and"] {
|
||||
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");
|
||||
let mut config = Config::default();
|
||||
let deps = FakeDependencies::new();
|
||||
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", 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")
|
||||
);
|
||||
}
|
||||
// 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")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1126,7 +1025,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 operator"));
|
||||
assert!(e.to_string().contains("invalid mode"));
|
||||
} else {
|
||||
panic!("-perm with bad mode pattern should fail");
|
||||
}
|
||||
|
||||
+57
-55
@@ -4,69 +4,79 @@
|
||||
// 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 comparison of the name against a shell wildcard
|
||||
/// pattern. See `glob::Pattern` for details on the exact syntax.
|
||||
/// This matcher makes a case-sensitive 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, caseless: bool) -> Self {
|
||||
let pattern = Pattern::new(pattern_string, caseless);
|
||||
Self { pattern }
|
||||
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)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for NameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
let name = file_info.file_name().to_string_lossy();
|
||||
self.pattern.matches(&name)
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[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", false);
|
||||
let matcher = NameMatcher::new(&"A*C".to_string()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -74,7 +84,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", false);
|
||||
let matcher = NameMatcher::new(&"abb?c".to_string()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -82,25 +92,21 @@ 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", false);
|
||||
let matcher = NameMatcher::new(&"shouldn't match".to_string()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
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()));
|
||||
fn cant_create_with_invalid_pattern() {
|
||||
let result = NameMatcher::new(&"a**c".to_string());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_matching_with_wrong_case_returns_true() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = NameMatcher::new("A*C", true);
|
||||
let matcher = CaselessNameMatcher::new(&"A*C".to_string()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -108,7 +114,7 @@ mod tests {
|
||||
#[test]
|
||||
fn caseless_matching_with_right_case_returns_true() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = NameMatcher::new("abb?c", true);
|
||||
let matcher = CaselessNameMatcher::new(&"abb?c".to_string()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -116,18 +122,14 @@ mod tests {
|
||||
#[test]
|
||||
fn caseless_not_matching_returns_false() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = NameMatcher::new("shouldn't match", true);
|
||||
let matcher = CaselessNameMatcher::new(&"shouldn't match".to_string()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
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()));
|
||||
fn caseless_cant_create_with_invalid_pattern() {
|
||||
let result = CaselessNameMatcher::new(&"a**c".to_string());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
// 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()));
|
||||
}
|
||||
}
|
||||
+376
-89
File diff suppressed because it is too large
Load Diff
@@ -28,8 +28,12 @@ pub struct Printer {
|
||||
}
|
||||
|
||||
impl Printer {
|
||||
pub fn new(delimiter: PrintDelimiter) -> Self {
|
||||
Self { delimiter }
|
||||
pub fn new(delimiter: PrintDelimiter) -> Printer {
|
||||
Printer { delimiter }
|
||||
}
|
||||
|
||||
pub fn new_box(delimiter: PrintDelimiter) -> Box<dyn Matcher> {
|
||||
Box::new(Printer::new(delimiter))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user