mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3f4224f12 | |||
| 8fc5ddb299 | |||
| b4f22f0747 | |||
| db8e265547 | |||
| f9d74ada2e | |||
| 907f3841ab | |||
| 888d3cd516 | |||
| f2e49d8d48 | |||
| d847a7bf7e | |||
| abf851dd7d | |||
| d9d2a4e865 | |||
| 9b0fea9880 | |||
| a7a73c325d | |||
| 465856ce24 | |||
| e456c23d5c | |||
| 4dc350e5fb | |||
| fcc929a978 | |||
| 423453eaf8 | |||
| 79986a26f2 | |||
| de1bc84812 | |||
| f0cd3748b8 | |||
| 780d106b34 | |||
| 4043c00bde | |||
| f0c3562aca | |||
| d7bf7da4a1 | |||
| 73dcd94827 | |||
| 60e4a8cf92 | |||
| 8b295e7d8a | |||
| 2dc916f980 | |||
| 19012452e1 | |||
| 6042d5a47f | |||
| 45e5964e6a | |||
| 9544f78977 | |||
| 5be00c85d7 | |||
| 8771861b0c | |||
| c4a56e9c3f | |||
| 3b222d6040 | |||
| 6f53585a4c | |||
| 1b9904eb5f | |||
| 4807264faa | |||
| 9f44977270 | |||
| 7f4423e703 | |||
| e9634f1ed4 | |||
| 37a543b9d6 | |||
| 9c11f11045 | |||
| a0b5713b61 | |||
| 5d4b9ca5e2 | |||
| 3bf5567397 | |||
| bbc9f06543 | |||
| 8bfa9d200b | |||
| a235b35ac3 | |||
| ca252c9727 | |||
| c995e25eb4 | |||
| fd54f9f72a | |||
| f145469cfd | |||
| f2202dc23f | |||
| fa4cce8a95 | |||
| 7136628c45 | |||
| db9d69f8eb | |||
| 42075f53f0 | |||
| 57b670680c | |||
| cf8a29d73b | |||
| bc715f84fa | |||
| 8736cde582 | |||
| af1741ba85 | |||
| 5b19ea2125 | |||
| d329702b69 | |||
| 84e4be85c1 | |||
| 1979483175 | |||
| a750b3225a | |||
| a2e350ce96 | |||
| d35aebf5ed | |||
| 9502a7c4eb | |||
| f74ef7c2af | |||
| 3ec8d4da46 | |||
| 62e370179b | |||
| 98fd45a182 | |||
| 665165b297 | |||
| d78f44a923 | |||
| a2631ae925 | |||
| c2279ffae8 | |||
| fba701b928 | |||
| d7057e09b9 | |||
| add14dbebf | |||
| ed97c851aa | |||
| 0ca2450ab2 | |||
| 52ee81296c | |||
| a0c9f2dfd4 | |||
| bd759d5e21 | |||
| 3badb05772 | |||
| ab1c2bacb7 | |||
| 393f0b9672 | |||
| 18dfc9b0cc | |||
| 0693242553 | |||
| aa6ff46078 | |||
| ab03198152 | |||
| b5e7dd52d9 | |||
| b91820de17 | |||
| 6fe2c9c649 | |||
| 70b7b3633e | |||
| 92c60597ec | |||
| 0cfb381cd9 | |||
| ec0fff41e3 | |||
| 288f7f07a2 | |||
| c0b8cd0b3b | |||
| 5370f7f999 | |||
| baa09baf47 | |||
| bb667e388a | |||
| 415d17deed | |||
| acd5c4b8b2 | |||
| 2084d9d91b | |||
| 14d4629519 | |||
| 6a8379520f | |||
| d4c40acb9c | |||
| 89ca20cba5 | |||
| d8e2ce20ce | |||
| d94a8e9093 | |||
| f32425a83c | |||
| fd8ef0bb26 | |||
| 93469e6f97 | |||
| b801c97830 | |||
| da0de660ae |
@@ -53,9 +53,9 @@ jobs:
|
||||
- run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- name: Check
|
||||
- name: Test
|
||||
run: |
|
||||
cargo check
|
||||
cargo test
|
||||
|
||||
fmt:
|
||||
name: cargo fmt --all -- --check
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
- run: rustup component add clippy
|
||||
- name: cargo clippy
|
||||
run: |
|
||||
cargo clippy -- -D warnings
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
|
||||
grcov:
|
||||
name: Code coverage
|
||||
@@ -117,8 +117,8 @@ jobs:
|
||||
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'
|
||||
RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off'
|
||||
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off"
|
||||
RUSTDOCFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off"
|
||||
|
||||
- name: Generate coverage data
|
||||
id: grcov
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [External-testsuites]
|
||||
types: [completed]
|
||||
|
||||
name: Comment Test results on the PR
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
upload-pr-comment:
|
||||
if: ${{ github.event.workflow_run.event == 'pull_request' }}
|
||||
|
||||
name: Upload PR comment
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: List Annotations
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
let artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{ github.event.workflow_run.id }},
|
||||
});
|
||||
|
||||
// List all artifacts
|
||||
let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "comment"
|
||||
})[0];
|
||||
|
||||
// Download the artifact to github.workspace
|
||||
let download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
|
||||
let fs = require('fs');
|
||||
fs.writeFileSync('${{ github.workspace }}/comment.zip', Buffer.from(download.data));
|
||||
|
||||
- run: unzip comment.zip
|
||||
|
||||
- name: Comment on PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
let fs = require('fs');
|
||||
let annotations = JSON.parse(fs.readFileSync('./annotations.json', 'utf8'));
|
||||
|
||||
let annotationContent = annotations
|
||||
.data
|
||||
.map(annotation => `${annotation.run}: ${annotation.annotation.message}`)
|
||||
.join('\n');
|
||||
|
||||
// check if no changes
|
||||
let gnuTestReport = annotationContent.includes('Run GNU findutils tests: Gnu tests No changes');
|
||||
let bfsTestReport = annotationContent.includes('Run BFS tests: BFS tests No changes');
|
||||
|
||||
if (gnuTestReport && bfsTestReport) {
|
||||
console.log('No changes');
|
||||
return;
|
||||
}
|
||||
|
||||
// Comment on the PR
|
||||
github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: annotations.pull_request_number,
|
||||
body: 'Commit ${{ github.event.workflow_run.head_sha }} has GNU testsuite comparison:\n```\n' + annotationContent + '\n```\n'
|
||||
});
|
||||
+187
-136
@@ -1,149 +1,200 @@
|
||||
on: [push, pull_request]
|
||||
|
||||
name: External testsuites
|
||||
name: External-testsuites
|
||||
|
||||
jobs:
|
||||
gnu-tests:
|
||||
name: Run GNU findutils tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout GNU findutils
|
||||
uses: actions/checkout@v4
|
||||
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
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
# Enable sources & install dependencies
|
||||
sudo find /etc/apt/sources.list* -type f -exec sed -i 'p; s/^deb /deb-src /' '{}' +
|
||||
sudo apt-get update
|
||||
sudo apt-get build-dep findutils
|
||||
- name: Run GNU tests
|
||||
shell: bash
|
||||
run: |
|
||||
cd findutils
|
||||
bash util/build-gnu.sh ||:
|
||||
- name: Extract testing info
|
||||
shell: bash
|
||||
run: |
|
||||
- name: Upload gnu-test-report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gnu-test-report
|
||||
path: |
|
||||
findutils.gnu/find/testsuite/*.log
|
||||
findutils.gnu/xargs/testsuite/*.log
|
||||
findutils.gnu/tests/**/*.log
|
||||
- name: Upload gnu-result
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gnu-result
|
||||
path: gnu-result.json
|
||||
- name: Download the result
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: gnu-result
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Download the log
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: gnu-test-report
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Compare failing tests against master
|
||||
shell: bash
|
||||
run: |
|
||||
./findutils/util/diff-gnu.sh ./dl ./findutils.gnu
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
mv dl/gnu-result.json latest-gnu-result.json
|
||||
python findutils/util/compare_gnu_result.py
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout GNU findutils
|
||||
uses: actions/checkout@v4
|
||||
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
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
# Enable sources & install dependencies
|
||||
sudo find /etc/apt/sources.list* -type f -exec sed -i 'p; s/^deb /deb-src /' '{}' +
|
||||
sudo apt-get update
|
||||
sudo apt-get build-dep findutils
|
||||
- name: Run GNU tests
|
||||
shell: bash
|
||||
run: |
|
||||
cd findutils
|
||||
bash util/build-gnu.sh ||:
|
||||
- name: Extract testing info
|
||||
shell: bash
|
||||
run: |
|
||||
- name: Upload gnu-test-report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gnu-test-report
|
||||
path: |
|
||||
findutils.gnu/find/testsuite/*.log
|
||||
findutils.gnu/xargs/testsuite/*.log
|
||||
findutils.gnu/tests/**/*.log
|
||||
- name: Upload gnu-result
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gnu-result
|
||||
path: gnu-result.json
|
||||
- name: Download the result
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: gnu-result
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Download the log
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: gnu-test-report
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Compare failing tests against master
|
||||
shell: bash
|
||||
run: |
|
||||
./findutils/util/diff-gnu.sh ./dl ./findutils.gnu
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
mv dl/gnu-result.json latest-gnu-result.json
|
||||
python findutils/util/compare_gnu_result.py
|
||||
|
||||
bfs-tests:
|
||||
name: Run BFS tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout BFS
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: tavianator/bfs
|
||||
path: bfs
|
||||
ref: '3.1.3'
|
||||
- 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: |
|
||||
# Enable sources & install dependencies
|
||||
sudo find /etc/apt/sources.list* -type f -exec sed -i 'p; s/^deb /deb-src /' '{}' +
|
||||
sudo apt-get update
|
||||
sudo apt-get build-dep bfs
|
||||
- name: Run BFS tests
|
||||
shell: bash
|
||||
run: |
|
||||
cd findutils
|
||||
bash util/build-bfs.sh ||:
|
||||
- name: Upload bfs-test-report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bfs-test-report
|
||||
path: bfs/tests.log
|
||||
- name: Upload bfs-result
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bfs-result
|
||||
path: bfs-result.json
|
||||
- name: Download the result
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: bfs-result
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Download the log
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: bfs-test-report
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Compare failing tests against main
|
||||
shell: bash
|
||||
run: |
|
||||
./findutils/util/diff-bfs.sh dl/tests.log bfs/tests.log
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
mv dl/bfs-result.json latest-bfs-result.json
|
||||
python findutils/util/compare_bfs_result.py
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout BFS
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: tavianator/bfs
|
||||
path: bfs
|
||||
ref: "4.0"
|
||||
- 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: |
|
||||
# Enable sources & install dependencies
|
||||
sudo find /etc/apt/sources.list* -type f -exec sed -i 'p; s/^deb /deb-src /' '{}' +
|
||||
sudo apt-get update
|
||||
sudo apt-get build-dep bfs
|
||||
- name: Run BFS tests
|
||||
shell: bash
|
||||
run: |
|
||||
cd findutils
|
||||
bash util/build-bfs.sh ||:
|
||||
- name: Upload bfs-test-report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bfs-test-report
|
||||
path: bfs/tests.log
|
||||
- name: Upload bfs-result
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bfs-result
|
||||
path: bfs-result.json
|
||||
- name: Download the result
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: bfs-result
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Download the log
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: bfs-test-report
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Compare failing tests against main
|
||||
shell: bash
|
||||
run: |
|
||||
./findutils/util/diff-bfs.sh dl/tests.log bfs/tests.log
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
mv dl/bfs-result.json latest-bfs-result.json
|
||||
python findutils/util/compare_bfs_result.py
|
||||
|
||||
upload-annotations:
|
||||
name: Upload annotations
|
||||
runs-on: ubuntu-latest
|
||||
needs: [gnu-tests, bfs-tests]
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
steps:
|
||||
- name: List Annotations
|
||||
uses: actions/github-script@v7
|
||||
|
||||
with:
|
||||
script: |
|
||||
let runs = await github.rest.checks.listForRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: '${{ github.event.pull_request.head.sha }}'
|
||||
});
|
||||
|
||||
let names = ['Run GNU findutils tests', 'Run BFS tests'];
|
||||
let results = [];
|
||||
runs.data.check_runs.filter(check => names.includes(check.name)).forEach(run => results.push(run));
|
||||
|
||||
let annotations = { data: [], pull_request_number: '${{ github.event.number }}' };
|
||||
for (let result of results) {
|
||||
let run = await github.rest.checks.listAnnotations({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
check_run_id: result.id
|
||||
});
|
||||
|
||||
run.data.forEach(data => {
|
||||
annotations.data.push({
|
||||
run: result.name,
|
||||
annotation: data
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Remove duplicate items.
|
||||
annotations.data = annotations.data.filter((value, index, self) =>
|
||||
self.findIndex(v => v.annotation.message === value.annotation.message) === index);
|
||||
|
||||
let fs = require('fs');
|
||||
fs.writeFileSync('${{ github.workspace }}/annotations.json', JSON.stringify(annotations));
|
||||
|
||||
- name: Upload annotations
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: comment
|
||||
path: annotations.json
|
||||
Generated
+164
-114
File diff suppressed because it is too large
Load Diff
+13
-7
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "findutils"
|
||||
version = "0.5.0"
|
||||
version = "0.7.0"
|
||||
homepage = "https://github.com/uutils/findutils"
|
||||
repository = "https://github.com/uutils/findutils"
|
||||
edition = "2021"
|
||||
@@ -10,21 +10,22 @@ description = "Rust implementation of GNU findutils"
|
||||
authors = ["uutils developers"]
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.37"
|
||||
chrono = "0.4.38"
|
||||
clap = "4.5"
|
||||
faccess = "0.2.4"
|
||||
walkdir = "2.5"
|
||||
regex = "1.7"
|
||||
regex = "1.10"
|
||||
once_cell = "1.19"
|
||||
onig = { version = "6.4", default-features = false }
|
||||
uucore = { version = "0.0.25", features = ["entries", "fs", "fsext", "mode"] }
|
||||
uucore = { version = "0.0.27", features = ["entries", "fs", "fsext", "mode"] }
|
||||
nix = { version = "0.29", features = ["fs", "user"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2"
|
||||
filetime = "0.2"
|
||||
nix = { version = "0.28", features = ["fs"] }
|
||||
nix = { version = "0.29", features = ["fs"] }
|
||||
predicates = "3"
|
||||
serial_test = "3.0"
|
||||
serial_test = "3.1"
|
||||
tempfile = "3"
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
@@ -54,6 +55,11 @@ ci = ["github"]
|
||||
# The installers to generate for each app
|
||||
installers = []
|
||||
# Target platforms to build apps for (Rust target-triple syntax)
|
||||
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
|
||||
targets = [
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-pc-windows-msvc",
|
||||
]
|
||||
# Publish jobs to run in CI
|
||||
pr-run-mode = "plan"
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
fn main() {
|
||||
// Ignores the SIGPIPE signal.
|
||||
// This is to solve the problem that when find is used with a pipe character,
|
||||
// the downstream software of the standard output stream closes the pipe and triggers a panic.
|
||||
uucore::panic::mute_sigpipe_panic();
|
||||
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
let strs: Vec<&str> = args.iter().map(std::convert::AsRef::as_ref).collect();
|
||||
let deps = findutils::find::StandardDependencies::new();
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use faccess::PathExt;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
/// Matcher for -{read,writ,execut}able.
|
||||
pub enum AccessMatcher {
|
||||
@@ -17,7 +16,7 @@ pub enum AccessMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for AccessMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
let path = file_info.path();
|
||||
|
||||
match self {
|
||||
|
||||
@@ -7,13 +7,10 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use std::fs::{self, FileType};
|
||||
use std::fs;
|
||||
use std::io::{self, stderr, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
pub struct DeleteMatcher;
|
||||
|
||||
@@ -22,17 +19,17 @@ impl DeleteMatcher {
|
||||
DeleteMatcher
|
||||
}
|
||||
|
||||
fn delete(&self, file_path: &Path, file_type: FileType) -> io::Result<()> {
|
||||
if file_type.is_dir() {
|
||||
fs::remove_dir(file_path)
|
||||
fn delete(&self, entry: &WalkEntry) -> io::Result<()> {
|
||||
if entry.file_type().is_dir() && !entry.path_is_symlink() {
|
||||
fs::remove_dir(entry.path())
|
||||
} else {
|
||||
fs::remove_file(file_path)
|
||||
fs::remove_file(entry.path())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for DeleteMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
let path = file_info.path();
|
||||
let path_str = path.to_string_lossy();
|
||||
|
||||
@@ -43,9 +40,10 @@ impl Matcher for DeleteMatcher {
|
||||
return true;
|
||||
}
|
||||
|
||||
match self.delete(path, file_info.file_type()) {
|
||||
match self.delete(file_info) {
|
||||
Ok(()) => true,
|
||||
Err(e) => {
|
||||
matcher_io.set_exit_code(1);
|
||||
writeln!(&mut stderr(), "Failed to delete {path_str}: {e}").unwrap();
|
||||
false
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
io::{stderr, Write},
|
||||
};
|
||||
|
||||
use super::Matcher;
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
pub struct EmptyMatcher;
|
||||
|
||||
@@ -20,7 +20,7 @@ impl EmptyMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for EmptyMatcher {
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
if file_info.file_type().is_file() {
|
||||
match file_info.metadata() {
|
||||
Ok(meta) => meta.len() == 0,
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
//! Paths encountered during a walk.
|
||||
|
||||
use std::cell::OnceCell;
|
||||
use std::error::Error;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::fs::{self, Metadata};
|
||||
use std::io::{self, ErrorKind};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::Follow;
|
||||
|
||||
/// Wrapper for a directory entry.
|
||||
#[derive(Debug)]
|
||||
enum Entry {
|
||||
/// Wraps an explicit path and depth.
|
||||
Explicit(PathBuf, usize),
|
||||
/// Wraps a WalkDir entry.
|
||||
WalkDir(DirEntry),
|
||||
}
|
||||
|
||||
/// File types.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum FileType {
|
||||
Unknown,
|
||||
Fifo,
|
||||
CharDevice,
|
||||
Directory,
|
||||
BlockDevice,
|
||||
Regular,
|
||||
Symlink,
|
||||
Socket,
|
||||
}
|
||||
|
||||
impl FileType {
|
||||
pub fn is_dir(self) -> bool {
|
||||
self == Self::Directory
|
||||
}
|
||||
|
||||
pub fn is_file(self) -> bool {
|
||||
self == Self::Regular
|
||||
}
|
||||
|
||||
pub fn is_symlink(self) -> bool {
|
||||
self == Self::Symlink
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fs::FileType> for FileType {
|
||||
fn from(t: fs::FileType) -> FileType {
|
||||
if t.is_dir() {
|
||||
return FileType::Directory;
|
||||
}
|
||||
if t.is_file() {
|
||||
return FileType::Regular;
|
||||
}
|
||||
if t.is_symlink() {
|
||||
return FileType::Symlink;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if t.is_fifo() {
|
||||
return FileType::Fifo;
|
||||
}
|
||||
if t.is_char_device() {
|
||||
return FileType::CharDevice;
|
||||
}
|
||||
if t.is_block_device() {
|
||||
return FileType::BlockDevice;
|
||||
}
|
||||
if t.is_socket() {
|
||||
return FileType::Socket;
|
||||
}
|
||||
}
|
||||
|
||||
FileType::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
/// An error encountered while walking a file system.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WalkError {
|
||||
/// The path that caused the error, if known.
|
||||
path: Option<PathBuf>,
|
||||
/// The depth below the root path, if known.
|
||||
depth: Option<usize>,
|
||||
/// The io::Error::raw_os_error(), if known.
|
||||
raw: Option<i32>,
|
||||
}
|
||||
|
||||
impl WalkError {
|
||||
/// Get the path this error occurred on, if known.
|
||||
pub fn path(&self) -> Option<&Path> {
|
||||
self.path.as_deref()
|
||||
}
|
||||
|
||||
/// Get the traversal depth when this error occurred, if known.
|
||||
pub fn depth(&self) -> Option<usize> {
|
||||
self.depth
|
||||
}
|
||||
|
||||
/// Get the kind of I/O error.
|
||||
pub fn kind(&self) -> ErrorKind {
|
||||
io::Error::from(self).kind()
|
||||
}
|
||||
|
||||
/// Check for ErrorKind::{NotFound,NotADirectory}.
|
||||
pub fn is_not_found(&self) -> bool {
|
||||
if self.kind() == ErrorKind::NotFound {
|
||||
return true;
|
||||
}
|
||||
|
||||
// NotADirectory is nightly-only
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if self.raw == Some(uucore::libc::ENOTDIR) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check for ErrorKind::FilesystemLoop.
|
||||
pub fn is_loop(&self) -> bool {
|
||||
#[cfg(unix)]
|
||||
return self.raw == Some(uucore::libc::ELOOP);
|
||||
|
||||
#[cfg(not(unix))]
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for WalkError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
let ioe = io::Error::from(self);
|
||||
if let Some(path) = &self.path {
|
||||
write!(f, "{}: {}", path.display(), ioe)
|
||||
} else {
|
||||
write!(f, "{}", ioe)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for WalkError {}
|
||||
|
||||
impl From<io::Error> for WalkError {
|
||||
fn from(e: io::Error) -> WalkError {
|
||||
WalkError::from(&e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&io::Error> for WalkError {
|
||||
fn from(e: &io::Error) -> WalkError {
|
||||
WalkError {
|
||||
path: None,
|
||||
depth: None,
|
||||
raw: e.raw_os_error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<walkdir::Error> for WalkError {
|
||||
fn from(e: walkdir::Error) -> WalkError {
|
||||
WalkError::from(&e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&walkdir::Error> for WalkError {
|
||||
fn from(e: &walkdir::Error) -> WalkError {
|
||||
WalkError {
|
||||
path: e.path().map(|p| p.to_owned()),
|
||||
depth: Some(e.depth()),
|
||||
raw: e.io_error().and_then(|e| e.raw_os_error()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WalkError> for io::Error {
|
||||
fn from(e: WalkError) -> io::Error {
|
||||
io::Error::from(&e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&WalkError> for io::Error {
|
||||
fn from(e: &WalkError) -> io::Error {
|
||||
e.raw
|
||||
.map(io::Error::from_raw_os_error)
|
||||
.unwrap_or_else(|| ErrorKind::Other.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// A path encountered while walking a file system.
|
||||
#[derive(Debug)]
|
||||
pub struct WalkEntry {
|
||||
/// The wrapped path/dirent.
|
||||
inner: Entry,
|
||||
/// Whether to follow symlinks.
|
||||
follow: Follow,
|
||||
/// Cached metadata.
|
||||
meta: OnceCell<Result<Metadata, WalkError>>,
|
||||
}
|
||||
|
||||
impl WalkEntry {
|
||||
/// Create a new WalkEntry for a specific file.
|
||||
pub fn new(path: impl Into<PathBuf>, depth: usize, follow: Follow) -> Self {
|
||||
Self {
|
||||
inner: Entry::Explicit(path.into(), depth),
|
||||
follow,
|
||||
meta: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a [walkdir::DirEntry] to a [WalkEntry]. Errors due to broken symbolic links will be
|
||||
/// converted to valid entries, but other errors will be propagated.
|
||||
pub fn from_walkdir(
|
||||
result: walkdir::Result<DirEntry>,
|
||||
follow: Follow,
|
||||
) -> Result<WalkEntry, WalkError> {
|
||||
let result = result.map_err(WalkError::from);
|
||||
|
||||
match result {
|
||||
Ok(entry) => {
|
||||
let ret = if entry.depth() == 0 && follow != Follow::Never {
|
||||
// DirEntry::file_type() is wrong for root symlinks when follow_root_links is set
|
||||
Self::new(entry.path(), 0, follow)
|
||||
} else {
|
||||
Self {
|
||||
inner: Entry::WalkDir(entry),
|
||||
follow,
|
||||
meta: OnceCell::new(),
|
||||
}
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
Err(e) if e.is_not_found() => {
|
||||
// Detect broken symlinks and replace them with explicit entries
|
||||
if let (Some(path), Some(depth)) = (e.path(), e.depth()) {
|
||||
if let Ok(meta) = path.symlink_metadata() {
|
||||
return Ok(WalkEntry {
|
||||
inner: Entry::Explicit(path.into(), depth),
|
||||
follow: Follow::Never,
|
||||
meta: Ok(meta).into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Err(e)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path to this entry.
|
||||
pub fn path(&self) -> &Path {
|
||||
match &self.inner {
|
||||
Entry::Explicit(path, _) => path.as_path(),
|
||||
Entry::WalkDir(ent) => ent.path(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path to this entry.
|
||||
pub fn into_path(self) -> PathBuf {
|
||||
match self.inner {
|
||||
Entry::Explicit(path, _) => path,
|
||||
Entry::WalkDir(ent) => ent.into_path(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the name of this entry.
|
||||
pub fn file_name(&self) -> &OsStr {
|
||||
match &self.inner {
|
||||
Entry::Explicit(path, _) => {
|
||||
// Path::file_name() only works if the last component is normal
|
||||
path.components()
|
||||
.last()
|
||||
.map(|c| c.as_os_str())
|
||||
.unwrap_or_else(|| path.as_os_str())
|
||||
}
|
||||
Entry::WalkDir(ent) => ent.file_name(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the depth of this entry below the root.
|
||||
pub fn depth(&self) -> usize {
|
||||
match &self.inner {
|
||||
Entry::Explicit(_, depth) => *depth,
|
||||
Entry::WalkDir(ent) => ent.depth(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get whether symbolic links are followed for this entry.
|
||||
pub fn follow(&self) -> bool {
|
||||
self.follow.follow_at_depth(self.depth())
|
||||
}
|
||||
|
||||
/// Get the metadata on a cache miss.
|
||||
fn get_metadata(&self) -> Result<Metadata, WalkError> {
|
||||
self.follow.metadata_at_depth(self.path(), self.depth())
|
||||
}
|
||||
|
||||
/// Get the [Metadata] for this entry, following symbolic links if appropriate.
|
||||
/// Multiple calls to this function will cache and re-use the same [Metadata].
|
||||
pub fn metadata(&self) -> Result<&Metadata, WalkError> {
|
||||
let result = self.meta.get_or_init(|| match &self.inner {
|
||||
Entry::Explicit(_, _) => Ok(self.get_metadata()?),
|
||||
Entry::WalkDir(ent) => Ok(ent.metadata()?),
|
||||
});
|
||||
result.as_ref().map_err(|e| e.clone())
|
||||
}
|
||||
|
||||
/// Get the file type of this entry.
|
||||
pub fn file_type(&self) -> FileType {
|
||||
match &self.inner {
|
||||
Entry::Explicit(_, _) => self
|
||||
.metadata()
|
||||
.map(|m| m.file_type().into())
|
||||
.unwrap_or(FileType::Unknown),
|
||||
Entry::WalkDir(ent) => ent.file_type().into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether this entry is a symbolic link, regardless of whether links
|
||||
/// are being followed.
|
||||
pub fn path_is_symlink(&self) -> bool {
|
||||
match &self.inner {
|
||||
Entry::Explicit(path, _) => {
|
||||
if self.follow() {
|
||||
path.symlink_metadata()
|
||||
.is_ok_and(|m| m.file_type().is_symlink())
|
||||
} else {
|
||||
self.file_type().is_symlink()
|
||||
}
|
||||
}
|
||||
Entry::WalkDir(ent) => ent.path_is_symlink(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,8 @@ use std::ffi::OsString;
|
||||
use std::io::{stderr, Write};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
enum Arg {
|
||||
FileArg(Vec<OsString>),
|
||||
@@ -52,7 +51,7 @@ impl SingleExecMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for SingleExecMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
let mut command = Command::new(&self.executable);
|
||||
let path_to_file = if self.exec_in_parent_dir {
|
||||
if let Some(f) = file_info.path().file_name() {
|
||||
@@ -67,7 +66,7 @@ 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())),
|
||||
Arg::FileArg(ref parts) => command.arg(parts.join(path_to_file.as_os_str())),
|
||||
};
|
||||
}
|
||||
if self.exec_in_parent_dir {
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// This file is part of the uutils findutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
/// The latest mapping from dev_id to fs_type, used for saving mount info reads
|
||||
#[cfg(unix)]
|
||||
pub struct Cache {
|
||||
dev_id: String,
|
||||
fs_type: String,
|
||||
}
|
||||
|
||||
/// Get the filesystem type of a file.
|
||||
/// 1. get the metadata of the file
|
||||
/// 2. get the device ID of the metadata
|
||||
/// 3. search the cache, then the filesystem list
|
||||
///
|
||||
/// Returns an empty string when no file system list matches.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the metadata could not be read.
|
||||
/// Returns an error if the filesystem list could not be read.
|
||||
///
|
||||
/// This is only supported on Unix.
|
||||
#[cfg(unix)]
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
error::Error,
|
||||
io::{stderr, Write},
|
||||
path::Path,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
pub fn get_file_system_type(
|
||||
path: &Path,
|
||||
cache: &RefCell<Option<Cache>>,
|
||||
) -> Result<String, Box<dyn Error>> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
// use symlink_metadata (lstat under the hood) instead of metadata (stat) to make sure that it
|
||||
// does not return an error when there is a (broken) symlink; this is aligned with GNU find.
|
||||
let metadata = match path.symlink_metadata() {
|
||||
Ok(metadata) => metadata,
|
||||
Err(err) => Err(err)?,
|
||||
};
|
||||
let dev_id = metadata.dev().to_string();
|
||||
|
||||
if let Some(cache) = cache.borrow().as_ref() {
|
||||
if cache.dev_id == dev_id {
|
||||
return Ok(cache.fs_type.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let fs_list = match uucore::fsext::read_fs_list() {
|
||||
Ok(fs_list) => fs_list,
|
||||
Err(err) => Err(err)?,
|
||||
};
|
||||
let result = fs_list
|
||||
.into_iter()
|
||||
.find(|fs| fs.dev_id == dev_id)
|
||||
.map_or_else(String::new, |fs| fs.fs_type);
|
||||
|
||||
// cache the latest query if not a match before
|
||||
cache.replace(Some(Cache {
|
||||
dev_id,
|
||||
fs_type: result.clone(),
|
||||
}));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// This matcher handles the -fstype argument.
|
||||
/// It matches the filesystem type of the file.
|
||||
///
|
||||
/// This is only supported on Unix.
|
||||
pub struct FileSystemMatcher {
|
||||
#[cfg(unix)]
|
||||
fs_text: String,
|
||||
#[cfg(unix)]
|
||||
cache: RefCell<Option<Cache>>,
|
||||
}
|
||||
|
||||
impl FileSystemMatcher {
|
||||
#[cfg(unix)]
|
||||
pub fn new(fs_text: String) -> Self {
|
||||
Self {
|
||||
fs_text,
|
||||
cache: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub fn new(_fs_text: String) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for FileSystemMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
match get_file_system_type(file_info.path(), &self.cache) {
|
||||
Ok(result) => result == self.fs_text,
|
||||
Err(_) => {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error getting filesystem type for {}",
|
||||
file_info.path().to_string_lossy()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn matches(&self, _file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_fs_matcher() {
|
||||
use crate::find::{
|
||||
matchers::{
|
||||
fs::{get_file_system_type, Cache},
|
||||
tests::get_dir_entry_for,
|
||||
Matcher,
|
||||
},
|
||||
tests::FakeDependencies,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::fs::File;
|
||||
use tempfile::Builder;
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
let mut matcher_io = deps.new_matcher_io();
|
||||
|
||||
// create temp file and get its fs type
|
||||
// We pass this file and the corresponding file system type into the Matcher for comparison.
|
||||
let temp_dir = Builder::new().prefix("fs_matcher").tempdir().unwrap();
|
||||
let foo_path = temp_dir.path().join("foo");
|
||||
let _ = File::create(foo_path).expect("create temp file");
|
||||
let file_info = get_dir_entry_for(&temp_dir.path().to_string_lossy(), "foo");
|
||||
|
||||
// create an empty cache for initial fs type lookup
|
||||
let empty_cache = RefCell::new(None);
|
||||
let target_fs_type = get_file_system_type(file_info.path(), &empty_cache).unwrap();
|
||||
|
||||
// should work with unmatched cache, and the cache should be set to the last query result
|
||||
let unmatched_cache = RefCell::new(Some(Cache {
|
||||
dev_id: "foo".to_string(),
|
||||
fs_type: "bar".to_string(),
|
||||
}));
|
||||
let target_fs_type_unmatched_cache =
|
||||
get_file_system_type(file_info.path(), &unmatched_cache).unwrap();
|
||||
assert_eq!(
|
||||
target_fs_type, target_fs_type_unmatched_cache,
|
||||
"get_file_system_type should return correct result with unmatched cache"
|
||||
);
|
||||
assert_eq!(
|
||||
unmatched_cache.borrow().as_ref().unwrap().fs_type,
|
||||
target_fs_type,
|
||||
"get_file_system_type should set the cache to the last query result"
|
||||
);
|
||||
|
||||
// should match fs type
|
||||
let matcher = super::FileSystemMatcher::new(target_fs_type.clone());
|
||||
assert!(
|
||||
matcher.matches(&file_info, &mut matcher_io),
|
||||
"{} should match {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
target_fs_type
|
||||
);
|
||||
|
||||
// should not match fs type
|
||||
let matcher = super::FileSystemMatcher::new(target_fs_type.clone() + "foo");
|
||||
assert!(
|
||||
!matcher.matches(&file_info, &mut matcher_io),
|
||||
"{} should not match {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
target_fs_type
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// This file is part of the uutils findutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
#[cfg(unix)]
|
||||
use nix::unistd::Group;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
pub struct GroupMatcher {
|
||||
gid: Option<u32>,
|
||||
}
|
||||
|
||||
impl GroupMatcher {
|
||||
#[cfg(unix)]
|
||||
pub fn from_group_name(group: &str) -> GroupMatcher {
|
||||
// get gid from group name
|
||||
let Ok(group) = Group::from_name(group) else {
|
||||
return GroupMatcher { gid: None };
|
||||
};
|
||||
|
||||
let Some(group) = group else {
|
||||
// This if branch is to determine whether a certain group exists in the system.
|
||||
// If a certain group does not exist in the system,
|
||||
// the result will need to be returned according to
|
||||
// the flag bit of whether to invert the result.
|
||||
return GroupMatcher { gid: None };
|
||||
};
|
||||
|
||||
GroupMatcher {
|
||||
gid: Some(group.gid.as_raw()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn from_gid(gid: u32) -> GroupMatcher {
|
||||
GroupMatcher { gid: Some(gid) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn from_group_name(_group: &str) -> GroupMatcher {
|
||||
GroupMatcher { gid: None }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn from_gid(_gid: u32) -> GroupMatcher {
|
||||
GroupMatcher { gid: None }
|
||||
}
|
||||
|
||||
pub fn gid(&self) -> &Option<u32> {
|
||||
&self.gid
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for GroupMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
let Ok(metadata) = file_info.metadata() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let file_gid = metadata.gid();
|
||||
|
||||
// When matching the -group parameter in find/matcher/mod.rs,
|
||||
// it has been judged that the group does not exist and an error is returned.
|
||||
// So use unwarp() directly here.
|
||||
self.gid.unwrap() == file_gid
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn matches(&self, _file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
// The user group acquisition function for Windows systems is not implemented in MetadataExt,
|
||||
// so it is somewhat difficult to implement it. :(
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NoGroupMatcher {}
|
||||
|
||||
impl Matcher for NoGroupMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
use nix::unistd::Gid;
|
||||
|
||||
if file_info.path().is_symlink() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Ok(metadata) = file_info.metadata() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Ok(gid) = Group::from_gid(Gid::from_raw(metadata.gid())) else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Some(_group) = gid else {
|
||||
return true;
|
||||
};
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn matches(&self, _file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_group_matcher() {
|
||||
use crate::find::matchers::{group::GroupMatcher, tests::get_dir_entry_for, Matcher};
|
||||
use crate::find::tests::FakeDependencies;
|
||||
use chrono::Local;
|
||||
use nix::unistd::{Gid, Group};
|
||||
use std::fs::File;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use tempfile::Builder;
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
let mut matcher_io = deps.new_matcher_io();
|
||||
|
||||
let temp_dir = Builder::new().prefix("group_matcher").tempdir().unwrap();
|
||||
let foo_path = temp_dir.path().join("foo");
|
||||
let _ = File::create(foo_path).expect("create temp file");
|
||||
let file_info = get_dir_entry_for(&temp_dir.path().to_string_lossy(), "foo");
|
||||
let file_gid = file_info.metadata().unwrap().gid();
|
||||
let file_group = Group::from_gid(Gid::from_raw(file_gid))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.name;
|
||||
|
||||
let matcher = super::GroupMatcher::from_group_name(file_group.as_str());
|
||||
assert!(
|
||||
matcher.matches(&file_info, &mut matcher_io),
|
||||
"group should match"
|
||||
);
|
||||
|
||||
// Testing a non-existent group name
|
||||
let time_string = Local::now().format("%Y%m%d%H%M%S").to_string();
|
||||
let matcher = GroupMatcher::from_group_name(time_string.as_str());
|
||||
assert!(
|
||||
matcher.gid().is_none(),
|
||||
"group name {} should not exist",
|
||||
time_string
|
||||
);
|
||||
|
||||
// Testing group id
|
||||
let matcher = GroupMatcher::from_gid(file_gid);
|
||||
assert!(
|
||||
matcher.gid().is_some(),
|
||||
"group id {} should exist",
|
||||
file_gid
|
||||
);
|
||||
assert!(
|
||||
matcher.matches(&file_info, &mut matcher_io),
|
||||
"group id should match"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,10 @@
|
||||
use std::io::{stderr, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::glob::Pattern;
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
fn read_link_target(file_info: &DirEntry) -> Option<PathBuf> {
|
||||
fn read_link_target(file_info: &WalkEntry) -> Option<PathBuf> {
|
||||
match file_info.path().read_link() {
|
||||
Ok(target) => Some(target),
|
||||
Err(err) => {
|
||||
@@ -47,7 +45,7 @@ impl LinkNameMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for LinkNameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
if let Some(target) = read_link_target(file_info) {
|
||||
self.pattern.matches(&target.to_string_lossy())
|
||||
} else {
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
//! to "-foo -o ( -bar -baz )", not "( -foo -o -bar ) -baz").
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
/// This matcher contains a collection of other matchers. A file only matches
|
||||
/// if it matches ALL the contained sub-matchers. For sub-matchers that have
|
||||
@@ -33,7 +32,7 @@ impl Matcher for AndMatcher {
|
||||
/// Returns true if all sub-matchers return true. Short-circuiting does take
|
||||
/// 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 {
|
||||
fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
for matcher in &self.submatchers {
|
||||
if !matcher.matches(dir_entry, matcher_io) {
|
||||
return false;
|
||||
@@ -109,7 +108,7 @@ impl Matcher for OrMatcher {
|
||||
/// Returns true if any sub-matcher returns true. Short-circuiting does take
|
||||
/// 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 {
|
||||
fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
for matcher in &self.submatchers {
|
||||
if matcher.matches(dir_entry, matcher_io) {
|
||||
return true;
|
||||
@@ -206,7 +205,7 @@ impl ListMatcher {
|
||||
impl Matcher for ListMatcher {
|
||||
/// Calls matches on all submatcher objects, with no short-circuiting.
|
||||
/// Returns the result of the call to the final submatcher
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
let mut rc = false;
|
||||
for matcher in &self.submatchers {
|
||||
rc = matcher.matches(dir_entry, matcher_io);
|
||||
@@ -311,7 +310,7 @@ impl ListMatcherBuilder {
|
||||
pub struct TrueMatcher;
|
||||
|
||||
impl Matcher for TrueMatcher {
|
||||
fn matches(&self, _dir_entry: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, _dir_entry: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -320,7 +319,7 @@ impl Matcher for TrueMatcher {
|
||||
pub struct FalseMatcher;
|
||||
|
||||
impl Matcher for FalseMatcher {
|
||||
fn matches(&self, _dir_entry: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, _dir_entry: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -339,7 +338,7 @@ impl NotMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for NotMatcher {
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
!self.submatcher.matches(dir_entry, matcher_io)
|
||||
}
|
||||
|
||||
@@ -370,7 +369,7 @@ mod tests {
|
||||
pub struct HasSideEffects;
|
||||
|
||||
impl Matcher for HasSideEffects {
|
||||
fn matches(&self, _: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, _: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -383,7 +382,7 @@ mod tests {
|
||||
struct Counter(Rc<RefCell<u32>>);
|
||||
|
||||
impl Matcher for Counter {
|
||||
fn matches(&self, _: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, _: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
*self.0.borrow_mut() += 1;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
// This file is part of the uutils findutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use chrono::DateTime;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{stderr, Write},
|
||||
};
|
||||
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
#[cfg(unix)]
|
||||
fn format_permissions(mode: uucore::libc::mode_t) -> String {
|
||||
let file_type = match mode & (uucore::libc::S_IFMT as uucore::libc::mode_t) {
|
||||
uucore::libc::S_IFDIR => "d",
|
||||
uucore::libc::S_IFREG => "-",
|
||||
_ => "?",
|
||||
};
|
||||
|
||||
// S_$$USR means "user permissions"
|
||||
let user_perms = format!(
|
||||
"{}{}{}",
|
||||
if mode & uucore::libc::S_IRUSR != 0 {
|
||||
"r"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IWUSR != 0 {
|
||||
"w"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IXUSR != 0 {
|
||||
"x"
|
||||
} else {
|
||||
"-"
|
||||
}
|
||||
);
|
||||
|
||||
// S_$$GRP means "group permissions"
|
||||
let group_perms = format!(
|
||||
"{}{}{}",
|
||||
if mode & uucore::libc::S_IRGRP != 0 {
|
||||
"r"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IWGRP != 0 {
|
||||
"w"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IXGRP != 0 {
|
||||
"x"
|
||||
} else {
|
||||
"-"
|
||||
}
|
||||
);
|
||||
|
||||
// S_$$OTH means "other permissions"
|
||||
let other_perms = format!(
|
||||
"{}{}{}",
|
||||
if mode & uucore::libc::S_IROTH != 0 {
|
||||
"r"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IWOTH != 0 {
|
||||
"w"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IXOTH != 0 {
|
||||
"x"
|
||||
} else {
|
||||
"-"
|
||||
}
|
||||
);
|
||||
|
||||
format!("{}{}{}{}", file_type, user_perms, group_perms, other_perms)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn format_permissions(file_attributes: u32) -> String {
|
||||
let mut attributes = Vec::new();
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
||||
if file_attributes & 0x0001 != 0 {
|
||||
attributes.push("read-only");
|
||||
}
|
||||
if file_attributes & 0x0002 != 0 {
|
||||
attributes.push("hidden");
|
||||
}
|
||||
if file_attributes & 0x0004 != 0 {
|
||||
attributes.push("system");
|
||||
}
|
||||
if file_attributes & 0x0020 != 0 {
|
||||
attributes.push("archive");
|
||||
}
|
||||
if file_attributes & 0x0040 != 0 {
|
||||
attributes.push("compressed");
|
||||
}
|
||||
if file_attributes & 0x0080 != 0 {
|
||||
attributes.push("offline");
|
||||
}
|
||||
|
||||
attributes.join(", ")
|
||||
}
|
||||
|
||||
pub struct Ls {
|
||||
output_file: Option<File>,
|
||||
}
|
||||
|
||||
impl Ls {
|
||||
pub fn new(output_file: Option<File>) -> Self {
|
||||
Self { output_file }
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn print(&self, file_info: &WalkEntry, mut out: impl Write, print_error_message: bool) {
|
||||
use nix::unistd::{Gid, Group, Uid, User};
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
|
||||
let metadata = file_info.metadata().unwrap();
|
||||
|
||||
let inode_number = metadata.ino();
|
||||
let number_of_blocks = {
|
||||
let size = metadata.size();
|
||||
let number_of_blocks = size / 1024;
|
||||
let remainder = number_of_blocks % 4;
|
||||
|
||||
if remainder == 0 {
|
||||
if number_of_blocks == 0 {
|
||||
4
|
||||
} else {
|
||||
number_of_blocks
|
||||
}
|
||||
} else {
|
||||
number_of_blocks + (4 - (remainder))
|
||||
}
|
||||
};
|
||||
let permission =
|
||||
{ format_permissions(metadata.permissions().mode() as uucore::libc::mode_t) };
|
||||
let hard_links = metadata.nlink();
|
||||
let user = {
|
||||
let uid = metadata.uid();
|
||||
User::from_uid(Uid::from_raw(uid)).unwrap().unwrap().name
|
||||
};
|
||||
let group = {
|
||||
let gid = metadata.gid();
|
||||
Group::from_gid(Gid::from_raw(gid)).unwrap().unwrap().name
|
||||
};
|
||||
let size = metadata.size();
|
||||
let last_modified = {
|
||||
let system_time = metadata.modified().unwrap();
|
||||
let now_utc: DateTime<chrono::Utc> = system_time.into();
|
||||
now_utc.format("%b %e %H:%M")
|
||||
};
|
||||
let path = file_info.path().to_string_lossy();
|
||||
|
||||
match writeln!(
|
||||
out,
|
||||
" {:<4} {:>6} {:<10} {:>3} {:<8} {:<8} {:>8} {} {}",
|
||||
inode_number,
|
||||
number_of_blocks,
|
||||
permission,
|
||||
hard_links,
|
||||
user,
|
||||
group,
|
||||
size,
|
||||
last_modified,
|
||||
path,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
if print_error_message {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error writing {:?} for {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
e
|
||||
)
|
||||
.unwrap();
|
||||
uucore::error::set_exit_code(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn print(&self, file_info: &WalkEntry, mut out: impl Write, print_error_message: bool) {
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
|
||||
let metadata = file_info.metadata().unwrap();
|
||||
|
||||
let inode_number = 0;
|
||||
let number_of_blocks = {
|
||||
let size = metadata.file_size();
|
||||
let number_of_blocks = size / 1024;
|
||||
let remainder = number_of_blocks % 4;
|
||||
|
||||
if remainder == 0 {
|
||||
if number_of_blocks == 0 {
|
||||
4
|
||||
} else {
|
||||
number_of_blocks
|
||||
}
|
||||
} else {
|
||||
number_of_blocks + (4 - (remainder))
|
||||
}
|
||||
};
|
||||
let permission = { format_permissions(metadata.file_attributes()) };
|
||||
let hard_links = 0;
|
||||
let user = 0;
|
||||
let group = 0;
|
||||
let size = metadata.file_size();
|
||||
let last_modified = {
|
||||
let system_time = metadata.modified().unwrap();
|
||||
let now_utc: DateTime<chrono::Utc> = system_time.into();
|
||||
now_utc.format("%b %e %H:%M")
|
||||
};
|
||||
let path = file_info.path().to_string_lossy();
|
||||
|
||||
match write!(
|
||||
out,
|
||||
" {:<4} {:>6} {:<10} {:>3} {:<8} {:<8} {:>8} {} {}\n",
|
||||
inode_number,
|
||||
number_of_blocks,
|
||||
permission,
|
||||
hard_links,
|
||||
user,
|
||||
group,
|
||||
size,
|
||||
last_modified,
|
||||
path,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
if print_error_message {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error writing {:?} for {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
e
|
||||
)
|
||||
.unwrap();
|
||||
uucore::error::set_exit_code(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for Ls {
|
||||
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
if let Some(file) = &self.output_file {
|
||||
self.print(file_info, file, true);
|
||||
} else {
|
||||
self.print(
|
||||
file_info,
|
||||
&mut *matcher_io.deps.get_output().borrow_mut(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_format_permissions() {
|
||||
use super::format_permissions;
|
||||
|
||||
let mode: uucore::libc::mode_t = 0o100644;
|
||||
let expected = "-rw-r--r--";
|
||||
assert_eq!(format_permissions(mode), expected);
|
||||
|
||||
let mode: uucore::libc::mode_t = 0o040755;
|
||||
let expected = "drwxr-xr-x";
|
||||
assert_eq!(format_permissions(mode), expected);
|
||||
|
||||
let mode: uucore::libc::mode_t = 0o100777;
|
||||
let expected = "-rwxrwxrwx";
|
||||
assert_eq!(format_permissions(mode), expected);
|
||||
}
|
||||
}
|
||||
+487
-78
File diff suppressed because it is too large
Load Diff
@@ -4,10 +4,8 @@
|
||||
// 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};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
/// This matcher makes a comparison of the name against a shell wildcard
|
||||
/// pattern. See `glob::Pattern` for details on the exact syntax.
|
||||
@@ -23,7 +21,7 @@ impl NameMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for NameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
let name = file_info.file_name().to_string_lossy();
|
||||
self.pattern.matches(&name)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
// 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};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
/// This matcher makes a comparison of the path against a shell wildcard
|
||||
/// pattern. See `glob::Pattern` for details on the exact syntax.
|
||||
@@ -23,7 +21,7 @@ impl PathMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for PathMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
let path = file_info.path().to_string_lossy();
|
||||
self.pattern.matches(&path)
|
||||
}
|
||||
|
||||
@@ -12,9 +12,8 @@ use std::error::Error;
|
||||
use std::io::{stderr, Write};
|
||||
#[cfg(unix)]
|
||||
use uucore::mode::{parse_numeric, parse_symbolic};
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[cfg(unix)]
|
||||
@@ -101,7 +100,7 @@ impl PermMatcher {
|
||||
|
||||
impl Matcher for PermMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
match file_info.metadata() {
|
||||
Ok(metadata) => {
|
||||
@@ -127,7 +126,7 @@ impl Matcher for PermMatcher {
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn matches(&self, _dummy_file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, _dummy_file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Permission matching not available on this platform!"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user