mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| adda4c1ba5 | |||
| 250867d99c | |||
| c61af2ae0e | |||
| c616c69854 | |||
| bec239f9d6 | |||
| ab25ef8d1a | |||
| 949e76b5b9 | |||
| 1e67813594 | |||
| 3a1c42123f | |||
| 8fe924cb68 | |||
| d432d665d1 | |||
| 96b353b7f5 | |||
| 4d9ede3c89 | |||
| 0298d20481 | |||
| e52b4f2e46 | |||
| bdfd492a0a | |||
| 79d4bc4b2f | |||
| 165d72efd2 | |||
| 97ea4ae294 | |||
| 50d2bde28f | |||
| 8e3093b4e8 | |||
| b8b3c32a1c | |||
| 27e8e4a186 | |||
| 81402f3c59 | |||
| dd443b977c | |||
| d38536222f | |||
| fdf0707885 | |||
| 0ea822cecb | |||
| 87ceb10073 | |||
| a99eae5136 | |||
| aa4713e766 | |||
| 6078cdc66a | |||
| ab451cf014 | |||
| 8b7bf9e0d1 | |||
| 1dff91e3c5 | |||
| bf478a140d | |||
| 4ead9a3c62 | |||
| bcb7fab917 | |||
| 94c830661b | |||
| 28989171ab | |||
| ac576f50b0 | |||
| 2a39bf143b | |||
| 3d65249b4e | |||
| 40cd5719c2 | |||
| 0253f24657 | |||
| b9b899cb13 | |||
| f9e736d194 | |||
| 49ee992dcd | |||
| 1dcd2d5d77 | |||
| 5336410602 | |||
| 9b319c7474 | |||
| d7e7c7f7fb | |||
| d9c68c27c3 | |||
| 420cbe826b | |||
| 48ca34914f | |||
| 9127763651 | |||
| 540842a241 | |||
| 1f760bc2a5 | |||
| 0f05b1e932 | |||
| 52adf59b5c | |||
| 35ef54a2b3 | |||
| eedda44ac9 | |||
| 8f9334479e | |||
| 2b5eca5355 | |||
| d7fcd6c1ee | |||
| 8472a9e982 | |||
| 73593f30ae | |||
| 93be4b7050 | |||
| b3a2767bcb | |||
| 80938e1ff3 | |||
| bda0bac489 | |||
| aab63c089d | |||
| 0069dd3e71 | |||
| 7cc30e3c07 | |||
| 810a2df417 | |||
| c20333c9dd | |||
| 03923ae435 | |||
| 88cbbff332 | |||
| 85e96152f6 | |||
| 87c5caa779 | |||
| 8ecc66fc46 | |||
| 595d832f8f | |||
| 49d78548b2 |
@@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "07:00"
|
||||
open-pull-requests-limit: 10
|
||||
@@ -16,6 +16,16 @@ jobs:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
# For bindgen: https://github.com/rust-lang/rust-bindgen/issues/1797
|
||||
- uses: KyleMayes/install-llvm-action@v1
|
||||
if: matrix.os == 'windows-latest'
|
||||
with:
|
||||
version: "11.0"
|
||||
directory: ${{ runner.temp }}/llvm
|
||||
- run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
@@ -33,6 +43,16 @@ jobs:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
# For bindgen: https://github.com/rust-lang/rust-bindgen/issues/1797
|
||||
- uses: KyleMayes/install-llvm-action@v1
|
||||
if: matrix.os == 'windows-latest'
|
||||
with:
|
||||
version: "11.0"
|
||||
directory: ${{ runner.temp }}/llvm
|
||||
- run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
@@ -68,3 +88,110 @@ jobs:
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
grcov:
|
||||
name: Code coverage
|
||||
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: Install grcov
|
||||
uses: actions-rs/install@v0.1
|
||||
with:
|
||||
crate: grcov
|
||||
version: latest
|
||||
use-tool-cache: true
|
||||
|
||||
- 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'
|
||||
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
|
||||
# uses: actions-rs/grcov@v0.1
|
||||
run: |
|
||||
grcov target/debug/ \
|
||||
--branch \
|
||||
--llvm \
|
||||
--source-dir . \
|
||||
--output-path lcov.info \
|
||||
--ignore-not-existing \
|
||||
--excl-line "#\\[derive\\(" \
|
||||
--excl-br-line "#\\[derive\\(" \
|
||||
--excl-start "#\\[cfg\\(test\\)\\]" \
|
||||
--excl-br-start "#\\[cfg\\(test\\)\\]" \
|
||||
--commit-sha ${{ github.sha }} \
|
||||
--service-job-id ${{ github.job }} \
|
||||
--service-name "GitHub Actions" \
|
||||
--service-number ${{ github.run_id }}
|
||||
- name: Upload coverage as artifact
|
||||
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@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
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
on: [push, pull_request]
|
||||
|
||||
name: GNU findutils compatibility tests
|
||||
|
||||
jobs:
|
||||
gnu-tests:
|
||||
name: Run GNU findutils tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout GNU findutils
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: gnu-mirror-unofficial/findutils
|
||||
path: findutils.gnu
|
||||
ref: 5768a03ddfb5e18b1682e339d6cdd24ff721c510
|
||||
submodules: true
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Install 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: |
|
||||
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/tests/**/*.log
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: gnu-result
|
||||
path: gnu-result.json
|
||||
- name: Download the result
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
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@v2
|
||||
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: |
|
||||
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: |
|
||||
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@v2
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout BFS
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: tavianator/bfs
|
||||
path: bfs
|
||||
ref: '2.3.1'
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- 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: 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@v2
|
||||
with:
|
||||
name: bfs-result
|
||||
path: bfs-result.json
|
||||
- name: Download the result
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
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@v2
|
||||
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: |
|
||||
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: |
|
||||
mv dl/bfs-result.json latest-bfs-result.json
|
||||
python findutils/util/compare_bfs_result.py
|
||||
+1
-2
@@ -3,5 +3,4 @@ target
|
||||
.project
|
||||
.cargo
|
||||
.settings
|
||||
test_data/links/link-f
|
||||
test_data/links/link-d
|
||||
test_data/links/link-*
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Names should be added to this file like so:
|
||||
# # Name or Organization <email address>
|
||||
#
|
||||
# Google Inc. <opensource@google.com>
|
||||
Generated
+487
-70
File diff suppressed because it is too large
Load Diff
+15
-5
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "findutils"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
homepage = "https://github.com/uutils/findutils"
|
||||
repository = "https://github.com/uutils/findutils"
|
||||
edition = "2018"
|
||||
@@ -10,20 +10,30 @@ description = "Rust implementation of GNU findutils"
|
||||
authors = ["uutils developers"]
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
clap = "2.34"
|
||||
glob = "0.3"
|
||||
walkdir = "2.3"
|
||||
tempdir = "0.3"
|
||||
regex = "1.4"
|
||||
regex = "1.5"
|
||||
once_cell = "1.9"
|
||||
onig = "6.3"
|
||||
uucore = { version = "0.0.12", features = ["entries", "fs", "fsext"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1"
|
||||
predicates = "1"
|
||||
assert_cmd = "2"
|
||||
filetime = "0.2"
|
||||
predicates = "2"
|
||||
serial_test = "0.5"
|
||||
tempfile = "3"
|
||||
|
||||
[[bin]]
|
||||
name = "find"
|
||||
path = "src/find/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "xargs"
|
||||
path = "src/xargs/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "testing-commandline"
|
||||
path = "src/testing/commandline/main.rs"
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
# findutils
|
||||
|
||||
[](https://travis-ci.org/uutils/findutils)
|
||||
[](https://ci.appveyor.com/project/Arcterus/findutils/branch/master)
|
||||
[](https://crates.io/crates/findutils)
|
||||
[](https://deps.rs/repo/github/uutils/findutils)
|
||||
[](https://codecov.io/gh/uutils/findutils)
|
||||
|
||||
Rust implementation of [GNU findutils](https://www.gnu.org/software/findutils/).
|
||||
|
||||
## Run the GNU testsuite on rust/findutils:
|
||||
|
||||
```
|
||||
bash util/build-gnu.sh
|
||||
|
||||
# To run a specific test:
|
||||
bash util/build-gnu.sh tests/misc/help-version.sh
|
||||
```
|
||||
|
||||
## Comparing with GNU
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -63,8 +63,8 @@ impl Matcher for DeleteMatcher {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs::File;
|
||||
use tempdir::TempDir;
|
||||
use std::fs::{create_dir, File};
|
||||
use tempfile::Builder;
|
||||
|
||||
use super::*;
|
||||
use crate::find::matchers::tests::get_dir_entry_for;
|
||||
@@ -75,9 +75,11 @@ mod tests {
|
||||
let matcher = DeleteMatcher::new();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let temp_dir = TempDir::new_in("test_data", "delete_matcher").expect("made temp dir");
|
||||
let temp_dir = Builder::new().prefix("test_data").tempdir().unwrap();
|
||||
|
||||
let temp_dir_path = temp_dir.path().to_string_lossy();
|
||||
File::create(temp_dir.path().join("test")).expect("created test file");
|
||||
create_dir(temp_dir.path().join("test_dir")).expect("created test directory");
|
||||
let test_entry = get_dir_entry_for(&temp_dir_path, "test");
|
||||
assert!(
|
||||
matcher.matches(&test_entry, &mut deps.new_matcher_io()),
|
||||
@@ -88,14 +90,13 @@ mod tests {
|
||||
"DeleteMatcher should actually delete files it matches",
|
||||
);
|
||||
|
||||
let temp_dir_name = temp_dir.path().file_name().unwrap().to_string_lossy();
|
||||
let temp_dir_entry = get_dir_entry_for("test_data", &temp_dir_name);
|
||||
let temp_dir_entry = get_dir_entry_for(&temp_dir_path, "test_dir");
|
||||
assert!(
|
||||
matcher.matches(&temp_dir_entry, &mut deps.new_matcher_io()),
|
||||
"DeleteMatcher should match directories",
|
||||
);
|
||||
assert!(
|
||||
!temp_dir.path().exists(),
|
||||
!temp_dir.path().join("test_dir").exists(),
|
||||
"DeleteMatcher should actually delete (empty) directories it matches",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,10 +78,17 @@ impl Matcher for SingleExecMatcher {
|
||||
});
|
||||
}
|
||||
if self.exec_in_parent_dir {
|
||||
if file_info.path() == Path::new(".") {
|
||||
command.current_dir(file_info.path());
|
||||
} else if let Some(parent) = file_info.path().parent() {
|
||||
command.current_dir(parent);
|
||||
match file_info.path().parent() {
|
||||
None => {
|
||||
// Root paths like "/" have no parent. Run them from the root to match GNU find.
|
||||
command.current_dir(file_info.path());
|
||||
}
|
||||
Some(parent) if parent == Path::new("") => {
|
||||
// Paths like "foo" have a parent of "". Avoid chdir("").
|
||||
}
|
||||
Some(parent) => {
|
||||
command.current_dir(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
match command.status() {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! to "-foo -o ( -bar -baz )", not "( -foo -o -bar ) -baz").
|
||||
use std::error::Error;
|
||||
use std::iter::Iterator;
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
@@ -44,7 +44,7 @@ impl Matcher for AndMatcher {
|
||||
self.submatchers.iter().any(|x| x.has_side_effects())
|
||||
}
|
||||
|
||||
fn finished_dir(&self, dir: &PathBuf) {
|
||||
fn finished_dir(&self, dir: &Path) {
|
||||
for m in &self.submatchers {
|
||||
m.finished_dir(dir);
|
||||
}
|
||||
@@ -113,7 +113,7 @@ impl Matcher for OrMatcher {
|
||||
self.submatchers.iter().any(|x| x.has_side_effects())
|
||||
}
|
||||
|
||||
fn finished_dir(&self, dir: &PathBuf) {
|
||||
fn finished_dir(&self, dir: &Path) {
|
||||
for m in &self.submatchers {
|
||||
m.finished_dir(dir);
|
||||
}
|
||||
@@ -204,7 +204,7 @@ impl Matcher for ListMatcher {
|
||||
self.submatchers.iter().any(|x| x.has_side_effects())
|
||||
}
|
||||
|
||||
fn finished_dir(&self, dir: &PathBuf) {
|
||||
fn finished_dir(&self, dir: &Path) {
|
||||
for m in &self.submatchers {
|
||||
m.finished_dir(dir);
|
||||
}
|
||||
@@ -342,7 +342,7 @@ impl Matcher for NotMatcher {
|
||||
self.submatcher.has_side_effects()
|
||||
}
|
||||
|
||||
fn finished_dir(&self, dir: &PathBuf) {
|
||||
fn finished_dir(&self, dir: &Path) {
|
||||
self.submatcher.finished_dir(dir);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,15 +10,17 @@ mod logical_matchers;
|
||||
mod name;
|
||||
mod perm;
|
||||
mod printer;
|
||||
mod printf;
|
||||
mod prune;
|
||||
mod regex;
|
||||
mod size;
|
||||
mod time;
|
||||
mod type_matcher;
|
||||
|
||||
use regex::Regex;
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
use ::regex::Regex;
|
||||
use std::path::Path;
|
||||
use std::time::SystemTime;
|
||||
use std::{error::Error, str::FromStr};
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Config, Dependencies};
|
||||
@@ -70,7 +72,7 @@ pub trait Matcher {
|
||||
}
|
||||
|
||||
/// Notification that find has finished processing a given directory.
|
||||
fn finished_dir(&self, _finished_directory: &PathBuf) {}
|
||||
fn finished_dir(&self, _finished_directory: &Path) {}
|
||||
|
||||
/// Notification that find has finished processing all directories -
|
||||
/// allowing for any cleanup that isn't suitable for destructors (e.g.
|
||||
@@ -115,7 +117,8 @@ pub fn build_top_level_matcher(
|
||||
if !top_level_matcher.has_side_effects() {
|
||||
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::Printer::new_box());
|
||||
new_and_matcher
|
||||
.new_and_condition(printer::Printer::new_box(printer::PrintDelimiter::Newline));
|
||||
return Ok(new_and_matcher.build());
|
||||
}
|
||||
Ok(top_level_matcher)
|
||||
@@ -197,6 +200,8 @@ fn build_matcher_tree(
|
||||
) -> Result<(usize, Box<dyn Matcher>), Box<dyn Error>> {
|
||||
let mut top_level_matcher = logical_matchers::ListMatcherBuilder::new();
|
||||
|
||||
let mut regex_type = regex::RegexType::default();
|
||||
|
||||
// can't use getopts for a variety or reasons:
|
||||
// order of arguments is important
|
||||
// arguments can start with + as well as -
|
||||
@@ -205,7 +210,15 @@ fn build_matcher_tree(
|
||||
let mut invert_next_matcher = false;
|
||||
while i < args.len() {
|
||||
let possible_submatcher = match args[i] {
|
||||
"-print" => Some(printer::Printer::new_box()),
|
||||
"-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::Printf::new_box(args[i])?)
|
||||
}
|
||||
"-true" => Some(logical_matchers::TrueMatcher::new_box()),
|
||||
"-false" => Some(logical_matchers::FalseMatcher::new_box()),
|
||||
"-name" => {
|
||||
@@ -222,6 +235,28 @@ fn build_matcher_tree(
|
||||
i += 1;
|
||||
Some(name::CaselessNameMatcher::new_box(args[i])?)
|
||||
}
|
||||
"-regextype" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
regex_type = regex::RegexType::from_str(args[i])?;
|
||||
None
|
||||
}
|
||||
"-regex" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(regex::RegexMatcher::new_box(regex_type, args[i], false)?)
|
||||
}
|
||||
"-iregex" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(regex::RegexMatcher::new_box(regex_type, args[i], true)?)
|
||||
}
|
||||
"-type" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
@@ -269,7 +304,7 @@ fn build_matcher_tree(
|
||||
"-exec" | "-execdir" => {
|
||||
let mut arg_index = i + 1;
|
||||
while arg_index < args.len() && args[arg_index] != ";" {
|
||||
if args[arg_index] == "+" {
|
||||
if args[arg_index - 1] == "{}" && args[arg_index] == "+" {
|
||||
// MultiExecMatcher isn't written yet
|
||||
return Err(From::from(format!(
|
||||
"{} [args...] + isn't supported yet. \
|
||||
@@ -382,6 +417,10 @@ fn build_matcher_tree(
|
||||
config.help_requested = true;
|
||||
None
|
||||
}
|
||||
"-version" | "--version" => {
|
||||
config.version_requested = true;
|
||||
None
|
||||
}
|
||||
|
||||
_ => return Err(From::from(format!("Unrecognized flag: '{}'", args[i]))),
|
||||
};
|
||||
@@ -419,7 +458,13 @@ mod tests {
|
||||
pub fn get_dir_entry_for(directory: &str, filename: &str) -> DirEntry {
|
||||
for wrapped_dir_entry in WalkDir::new(fix_up_slashes(directory)) {
|
||||
let dir_entry = wrapped_dir_entry.unwrap();
|
||||
if dir_entry.file_name().to_string_lossy() == filename {
|
||||
if dir_entry
|
||||
.path()
|
||||
.strip_prefix(directory)
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
== fix_up_slashes(filename)
|
||||
{
|
||||
return dir_entry;
|
||||
}
|
||||
}
|
||||
@@ -948,6 +993,13 @@ mod tests {
|
||||
.expect("parsing argument list with exec that takes brackets and -os should work");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_top_level_exec_plus_semicolon() {
|
||||
let mut config = Config::default();
|
||||
build_top_level_matcher(&["-exec", "foo", "{}", "foo", "+", ";"], &mut config)
|
||||
.expect("only {} + should be considered a multi-exec");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn build_top_level_matcher_perm() {
|
||||
|
||||
@@ -8,27 +8,46 @@ use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
|
||||
pub enum PrintDelimiter {
|
||||
Newline,
|
||||
Null,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PrintDelimiter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PrintDelimiter::Newline => writeln!(f),
|
||||
PrintDelimiter::Null => write!(f, "\0"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This matcher just prints the name of the file to stdout.
|
||||
pub struct Printer;
|
||||
pub struct Printer {
|
||||
delimiter: PrintDelimiter,
|
||||
}
|
||||
|
||||
impl Printer {
|
||||
pub fn new() -> Printer {
|
||||
Printer {}
|
||||
pub fn new(delimiter: PrintDelimiter) -> Printer {
|
||||
Printer { delimiter }
|
||||
}
|
||||
|
||||
pub fn new_box() -> Box<dyn Matcher> {
|
||||
Box::new(Printer::new())
|
||||
pub fn new_box(delimiter: PrintDelimiter) -> Box<dyn Matcher> {
|
||||
Box::new(Printer::new(delimiter))
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for Printer {
|
||||
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
writeln!(
|
||||
matcher_io.deps.get_output().borrow_mut(),
|
||||
"{}",
|
||||
file_info.path().to_string_lossy()
|
||||
let mut out = matcher_io.deps.get_output().borrow_mut();
|
||||
write!(
|
||||
out,
|
||||
"{}{}",
|
||||
file_info.path().to_string_lossy(),
|
||||
self.delimiter
|
||||
)
|
||||
.unwrap();
|
||||
out.flush().unwrap();
|
||||
true
|
||||
}
|
||||
|
||||
@@ -47,10 +66,10 @@ mod tests {
|
||||
use crate::find::tests::FakeDependencies;
|
||||
|
||||
#[test]
|
||||
fn prints() {
|
||||
fn prints_newline() {
|
||||
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");
|
||||
|
||||
let matcher = Printer::new();
|
||||
let matcher = Printer::new(PrintDelimiter::Newline);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert_eq!(
|
||||
@@ -58,4 +77,17 @@ mod tests {
|
||||
deps.get_output_as_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prints_null() {
|
||||
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");
|
||||
|
||||
let matcher = Printer::new(PrintDelimiter::Null);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert_eq!(
|
||||
fix_up_slashes("./test_data/simple/abbbc\0"),
|
||||
deps.get_output_as_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,241 @@
|
||||
// Copyright 2022 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::{error::Error, fmt, str::FromStr};
|
||||
|
||||
use onig::{Regex, RegexOptions, Syntax};
|
||||
|
||||
use super::Matcher;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseRegexTypeError(String);
|
||||
|
||||
impl Error for ParseRegexTypeError {}
|
||||
|
||||
impl fmt::Display for ParseRegexTypeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Invalid regex type: {} (must be one of {})",
|
||||
self.0,
|
||||
RegexType::VALUES
|
||||
.iter()
|
||||
.map(|t| format!("'{}'", t))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RegexType {
|
||||
Emacs,
|
||||
Grep,
|
||||
PosixBasic,
|
||||
PosixExtended,
|
||||
}
|
||||
|
||||
impl RegexType {
|
||||
pub const VALUES: &'static [RegexType] = &[
|
||||
RegexType::Emacs,
|
||||
RegexType::Grep,
|
||||
RegexType::PosixBasic,
|
||||
RegexType::PosixExtended,
|
||||
];
|
||||
}
|
||||
|
||||
impl fmt::Display for RegexType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RegexType::Emacs => write!(f, "emacs"),
|
||||
RegexType::Grep => write!(f, "grep"),
|
||||
RegexType::PosixBasic => write!(f, "posix-basic"),
|
||||
RegexType::PosixExtended => write!(f, "posix-extended"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RegexType {
|
||||
type Err = ParseRegexTypeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"emacs" => Ok(RegexType::Emacs),
|
||||
"grep" => Ok(RegexType::Grep),
|
||||
"posix-basic" => Ok(RegexType::PosixBasic),
|
||||
"posix-extended" => Ok(RegexType::PosixExtended),
|
||||
_ => Err(ParseRegexTypeError(s.to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RegexType {
|
||||
fn default() -> Self {
|
||||
RegexType::Emacs
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RegexMatcher {
|
||||
regex: Regex,
|
||||
}
|
||||
|
||||
impl RegexMatcher {
|
||||
pub fn new(
|
||||
regex_type: RegexType,
|
||||
pattern: &str,
|
||||
ignore_case: bool,
|
||||
) -> Result<RegexMatcher, Box<dyn Error>> {
|
||||
let syntax = match regex_type {
|
||||
RegexType::Emacs => Syntax::emacs(),
|
||||
RegexType::Grep => Syntax::grep(),
|
||||
RegexType::PosixBasic => Syntax::posix_basic(),
|
||||
RegexType::PosixExtended => Syntax::posix_extended(),
|
||||
};
|
||||
|
||||
let regex = Regex::with_options(
|
||||
pattern,
|
||||
if ignore_case {
|
||||
RegexOptions::REGEX_OPTION_IGNORECASE
|
||||
} else {
|
||||
RegexOptions::REGEX_OPTION_NONE
|
||||
},
|
||||
syntax,
|
||||
)?;
|
||||
Ok(RegexMatcher { regex })
|
||||
}
|
||||
|
||||
pub fn new_box(
|
||||
regex_type: RegexType,
|
||||
pattern: &str,
|
||||
ignore_case: bool,
|
||||
) -> Result<Box<dyn Matcher>, Box<dyn Error>> {
|
||||
Ok(Box::new(RegexMatcher::new(
|
||||
regex_type,
|
||||
pattern,
|
||||
ignore_case,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for RegexMatcher {
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
self.regex
|
||||
.is_match(file_info.path().to_string_lossy().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;
|
||||
|
||||
const POSIX_BASIC_INTERVALS_RE: &str = r".*/ab\{1,3\}c";
|
||||
const POSIX_EXTENDED_INTERVALS_RE: &str = r".*/ab{1,3}c";
|
||||
const EMACS_AND_POSIX_EXTENDED_KLEENE_PLUS: &str = r".*/ab+c";
|
||||
|
||||
// Variants of fix_up_slashes that properly escape the forward slashes for
|
||||
// being in a regex.
|
||||
#[cfg(windows)]
|
||||
fn fix_up_regex_slashes(re: &str) -> String {
|
||||
re.replace("/", "\\\\")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn fix_up_regex_slashes(re: &str) -> String {
|
||||
re.to_owned()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn case_sensitive_matching() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher =
|
||||
RegexMatcher::new(RegexType::Emacs, &fix_up_regex_slashes(".*/ab.BC"), false).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn case_insensitive_matching() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher =
|
||||
RegexMatcher::new(RegexType::Emacs, &fix_up_regex_slashes(".*/ab.BC"), true).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emacs_regex() {
|
||||
// Emacs syntax is mostly the same as POSIX extended but with escaped
|
||||
// brace intervals.
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
|
||||
let matcher = RegexMatcher::new(
|
||||
RegexType::Emacs,
|
||||
&fix_up_regex_slashes(EMACS_AND_POSIX_EXTENDED_KLEENE_PLUS),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
let matcher = RegexMatcher::new(
|
||||
RegexType::Emacs,
|
||||
&fix_up_regex_slashes(POSIX_EXTENDED_INTERVALS_RE),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn posix_basic_regex() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
|
||||
let matcher = RegexMatcher::new(
|
||||
RegexType::PosixBasic,
|
||||
&fix_up_regex_slashes(POSIX_BASIC_INTERVALS_RE),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
let matcher = RegexMatcher::new(
|
||||
RegexType::PosixBasic,
|
||||
&fix_up_regex_slashes(POSIX_EXTENDED_INTERVALS_RE),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn posix_extended_regex() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
|
||||
let matcher = RegexMatcher::new(
|
||||
RegexType::PosixExtended,
|
||||
&fix_up_regex_slashes(POSIX_EXTENDED_INTERVALS_RE),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
let matcher = RegexMatcher::new(
|
||||
RegexType::PosixExtended,
|
||||
&fix_up_regex_slashes(POSIX_BASIC_INTERVALS_RE),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
}
|
||||
@@ -134,8 +134,8 @@ impl FileTimeMatcher {
|
||||
|
||||
pub fn new(file_time_type: FileTimeType, days: ComparableValue) -> FileTimeMatcher {
|
||||
FileTimeMatcher {
|
||||
file_time_type,
|
||||
days,
|
||||
file_time_type,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ mod tests {
|
||||
use std::io::{Read, Write};
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tempdir::TempDir;
|
||||
use tempfile::Builder;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::*;
|
||||
@@ -163,7 +163,7 @@ mod tests {
|
||||
// this file should already exist
|
||||
let old_file = get_dir_entry_for("test_data", "simple");
|
||||
|
||||
let temp_dir = TempDir::new("newer_matcher").unwrap();
|
||||
let temp_dir = Builder::new().prefix("example").tempdir().unwrap();
|
||||
let temp_dir_path = temp_dir.path().to_string_lossy();
|
||||
// this has just been created, so should be newer
|
||||
let new_file_name = "newFile";
|
||||
@@ -287,7 +287,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn file_time_matcher_modified_created_accessed() {
|
||||
let temp_dir = TempDir::new("file_time_matcher_modified_created_accessed").unwrap();
|
||||
let temp_dir = Builder::new()
|
||||
.prefix("file_time_matcher_modified_created_accessed")
|
||||
.tempdir()
|
||||
.unwrap();
|
||||
|
||||
// No easy way to independently set file times. So create it - setting creation time
|
||||
let foo_path = temp_dir.path().join("foo");
|
||||
|
||||
+18
-2
@@ -19,6 +19,7 @@ pub struct Config {
|
||||
max_depth: usize,
|
||||
sorted_output: bool,
|
||||
help_requested: bool,
|
||||
version_requested: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -29,6 +30,7 @@ impl Default for Config {
|
||||
max_depth: usize::max_value(),
|
||||
sorted_output: false,
|
||||
help_requested: false,
|
||||
version_requested: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,6 +147,11 @@ fn do_find<'a>(args: &[&str], deps: &'a dyn Dependencies<'a>) -> Result<u64, Box
|
||||
print_help();
|
||||
return Ok(0);
|
||||
}
|
||||
if paths_and_matcher.config.version_requested {
|
||||
print_version();
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut found_count: u64 = 0;
|
||||
for path in paths_and_matcher.paths {
|
||||
found_count += process_dir(
|
||||
@@ -165,8 +172,13 @@ If no path is supplied then the current working directory is used by default.
|
||||
|
||||
Early alpha implementation. Currently the only expressions supported are
|
||||
-print
|
||||
-print0
|
||||
-printf
|
||||
-name case-sensitive_filename_pattern
|
||||
-iname case-insensitive_filename_pattern
|
||||
-regextype type
|
||||
-regex pattern
|
||||
-iregex pattern
|
||||
-type type_char
|
||||
currently type_char can only be f (for file) or d (for directory)
|
||||
-size [+-]N[bcwkMG]
|
||||
@@ -195,6 +207,10 @@ Early alpha implementation. Currently the only expressions supported are
|
||||
);
|
||||
}
|
||||
|
||||
fn print_version() {
|
||||
println!("find (Rust) {}", env!("CARGO_PKG_VERSION"));
|
||||
}
|
||||
|
||||
/// Does all the work for find.
|
||||
///
|
||||
/// All main has to do is pass in the command-line args and exit the process
|
||||
@@ -218,7 +234,7 @@ mod tests {
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::vec::Vec;
|
||||
use tempdir::TempDir;
|
||||
use tempfile::Builder;
|
||||
|
||||
use crate::find::matchers::MatcherIO;
|
||||
|
||||
@@ -525,7 +541,7 @@ mod tests {
|
||||
fn find_newer() {
|
||||
// create a temp directory and file that are newer than the static
|
||||
// files in the source tree.
|
||||
let new_dir = TempDir::new("find_newer").unwrap();
|
||||
let new_dir = Builder::new().prefix("find_newer").tempdir().unwrap();
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ extern crate regex;
|
||||
extern crate walkdir;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate tempdir;
|
||||
extern crate tempfile;
|
||||
|
||||
pub mod find;
|
||||
pub mod xargs;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use std::env;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::io::{stdin, stdout, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn usage() -> ! {
|
||||
@@ -14,10 +14,19 @@ fn usage() -> ! {
|
||||
std::process::exit(2);
|
||||
}
|
||||
|
||||
enum ExitWith {
|
||||
Failure,
|
||||
UrgentFailure,
|
||||
#[cfg(unix)]
|
||||
Signal,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Config {
|
||||
exit_with_failure: bool,
|
||||
destination_dir: String,
|
||||
exit_with: Option<ExitWith>,
|
||||
print_stdin: bool,
|
||||
no_print_cwd: bool,
|
||||
destination_dir: Option<String>,
|
||||
}
|
||||
|
||||
fn open_file(destination_dir: &str) -> File {
|
||||
@@ -39,20 +48,61 @@ fn open_file(destination_dir: &str) -> File {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_content(mut f: impl Write, config: &Config, args: &[String]) {
|
||||
if !config.no_print_cwd {
|
||||
writeln!(f, "cwd={}", env::current_dir().unwrap().to_string_lossy())
|
||||
.expect("failed to write to file");
|
||||
}
|
||||
|
||||
if config.print_stdin {
|
||||
let mut s = String::new();
|
||||
stdin()
|
||||
.read_to_string(&mut s)
|
||||
.expect("failed to read from stdin");
|
||||
writeln!(f, "stdin={}", s.trim()).expect("failed to write to file");
|
||||
}
|
||||
|
||||
writeln!(f, "args=").expect("failed to write to file");
|
||||
|
||||
// first two args are going to be the path to this executable and
|
||||
// the destination_dir we want to write to. Don't write either of those
|
||||
// as they'll be non-deterministic.
|
||||
for arg in &args[2..] {
|
||||
writeln!(f, "{}", arg).expect("failed to write to file");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = env::args().collect::<Vec<String>>();
|
||||
if args.len() < 2 || args[1] == "-h" || args[1] == "--help" {
|
||||
usage();
|
||||
}
|
||||
let mut config = Config {
|
||||
destination_dir: args[1].clone(),
|
||||
destination_dir: if args[1] != "-" {
|
||||
Some(args[1].clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
for arg in &args[2..] {
|
||||
if arg.starts_with("--") {
|
||||
match arg.as_ref() {
|
||||
"--exit_with_failure" => {
|
||||
config.exit_with_failure = true;
|
||||
config.exit_with = Some(ExitWith::Failure);
|
||||
}
|
||||
"--exit_with_urgent_failure" => {
|
||||
config.exit_with = Some(ExitWith::UrgentFailure);
|
||||
}
|
||||
#[cfg(unix)]
|
||||
"--exit_with_signal" => {
|
||||
config.exit_with = Some(ExitWith::Signal);
|
||||
}
|
||||
"--no_print_cwd" => {
|
||||
config.no_print_cwd = true;
|
||||
}
|
||||
"--print_stdin" => {
|
||||
config.print_stdin = true;
|
||||
}
|
||||
_ => {
|
||||
usage();
|
||||
@@ -61,20 +111,19 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut f = open_file(&config.destination_dir);
|
||||
// first two args are going to be the path to this executable and
|
||||
// the destination_dir we want to write to. Don't write either of those
|
||||
// as they'll be non-deterministic.
|
||||
f.write_fmt(format_args!(
|
||||
"cwd={}\nargs=\n",
|
||||
env::current_dir().unwrap().to_string_lossy()
|
||||
))
|
||||
.expect("failed to write to file");
|
||||
for arg in &args[2..] {
|
||||
f.write_fmt(format_args!("{}\n", arg))
|
||||
.expect("failed to write to file");
|
||||
}
|
||||
if let Some(destination_dir) = &config.destination_dir {
|
||||
write_content(open_file(destination_dir), &config, &args);
|
||||
} else {
|
||||
write_content(stdout(), &config, &args);
|
||||
}
|
||||
|
||||
match config.exit_with {
|
||||
None => std::process::exit(0),
|
||||
Some(ExitWith::Failure) => std::process::exit(2),
|
||||
Some(ExitWith::UrgentFailure) => std::process::exit(255),
|
||||
#[cfg(unix)]
|
||||
Some(ExitWith::Signal) => unsafe {
|
||||
uucore::libc::raise(uucore::libc::SIGINT);
|
||||
},
|
||||
}
|
||||
std::process::exit(if config.exit_with_failure { 2 } else { 0 });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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.
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
std::process::exit(findutils::xargs::xargs_main(
|
||||
&args.iter().map(|s| s.as_ref()).collect::<Vec<&str>>(),
|
||||
))
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user