mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-08-20 22:53:48 -07:00
Compare commits
147 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
13d6c7b1c1 | ||
|
2da6e6d15b | ||
|
aacc7c8a21 | ||
|
3fc1f5f15f | ||
|
d969ebc1e5 | ||
|
5a15029c04 | ||
|
1ef939442f | ||
|
2d5fb3a995 | ||
|
1b1886a1fe | ||
|
064e9ee30e | ||
|
7d2b87deea | ||
|
8ff66992e1 | ||
|
0db9903c8d | ||
|
8ab9b6aba8 | ||
|
21261b8c4d | ||
|
8c2014fe5c | ||
|
f6d46006d1 | ||
|
1efa5c1e18 | ||
|
1d1ca1aa5d | ||
|
6fb52964fc | ||
|
48bf394585 | ||
|
e99de53e31 | ||
|
83ab0a6b53 | ||
|
c3828cb508 | ||
|
27f88139fa | ||
|
ca0aacd674 | ||
|
43eff4397f | ||
|
2d763a9a1b | ||
|
afc820c9e9 | ||
|
b9f2da2782 | ||
|
823255b3fd | ||
|
fcfe98fe58 | ||
|
0434b5034d | ||
|
b004eda8c8 | ||
|
f1b4b182f2 | ||
|
e169881a36 | ||
|
83a4af7cb8 | ||
|
cf91d6e67a | ||
|
0904f55d3e | ||
|
8ead46a3e5 | ||
|
d9744f3b03 | ||
|
a6275648b3 | ||
|
7fc48961ed | ||
|
bd8a7ae793 | ||
|
ff8afcf8aa | ||
|
aebab44e3e | ||
|
ca88b2fd95 | ||
|
57e90533a0 | ||
|
fe07bd7669 | ||
|
95979048c9 | ||
|
5548e538b1 | ||
|
c2f1653ddd | ||
|
78803979c5 | ||
|
85a86eba2b | ||
|
119a58a400 | ||
|
3b7fd442a6 | ||
|
cbc598f245 | ||
|
6dfaec03e8 | ||
|
5fbc4fee64 | ||
|
004370bd16 | ||
|
de4baa1002 | ||
|
163ac157d3 | ||
|
e2362d4d51 | ||
|
d6b59feff8 | ||
|
94305125ef | ||
|
79cbe89deb | ||
|
bf63fe8f25 | ||
|
8bd5950296 | ||
|
6e0539ab91 | ||
|
4649aa9700 | ||
|
c009652e77 | ||
|
b9f7a9ba2b | ||
|
a1960877cf | ||
|
bb0925af91 | ||
|
be117dbafa | ||
|
06dc13ad2d | ||
|
c6c2e69b8f | ||
|
e67c868ddd | ||
|
d33f2e2f70 | ||
|
082edafffa | ||
|
7c8dc332b3 | ||
|
ea961915b5 | ||
|
7943bdfe82 | ||
|
312a7884fc | ||
|
ac02f54c89 | ||
|
24b337b940 | ||
|
a5083f99ce | ||
|
f89cdba5df | ||
|
f7b677d136 | ||
|
3f68a8f3d7 | ||
|
9d738ad0c0 | ||
|
6c5108ed17 | ||
|
e0f1000df6 | ||
|
ea99421ec8 | ||
|
af8c386d5e | ||
|
71d71d2d98 | ||
|
c9ebcbd8ab | ||
|
dec0dc3196 | ||
|
2f0a269f07 | ||
|
0a0893a765 | ||
|
35160a1cdb | ||
|
f1d23c06e3 | ||
|
22b677900f | ||
|
bb6f0f5519 | ||
|
b6ef99ee55 | ||
|
bb8601b2ba | ||
|
02b47b7469 | ||
|
d922b7ac11 | ||
|
2acf25c689 | ||
|
80007698d3 | ||
|
3ad0e83471 | ||
|
eca13f08a2 | ||
|
4f99f82b19 | ||
|
327d74f161 | ||
|
9da0995df4 | ||
|
e9abbc1a02 | ||
|
9bd30e8e48 | ||
|
59212d08d3 | ||
|
6ebebb2aaa | ||
|
e92e2ef813 | ||
|
4a30819302 | ||
|
9b42af96f0 | ||
|
648a65f197 | ||
|
bdf01f46a6 | ||
|
1c775f3a82 | ||
|
e50df40a19 | ||
|
1fa76d2a42 | ||
|
44aa5a417d | ||
|
2c3897585d | ||
|
6e9141a9ca | ||
|
c8e4a84519 | ||
|
f02a50a69d | ||
|
b9c774937f | ||
|
67dd809a80 | ||
|
e0a85678e1 | ||
|
23af5fb043 | ||
|
5dec4b8e37 | ||
|
827082a33a | ||
|
6c2a550e1e | ||
|
8e8fc9c503 | ||
|
2057023dc5 | ||
|
3f2fe0afee | ||
|
56c7ad175a | ||
|
5b7a30846f | ||
|
2a4dba3fbf | ||
|
84d65865e6 | ||
|
d9aaa11873 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: [BurntSushi]
|
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -24,7 +24,7 @@ body:
|
||||
attributes:
|
||||
label: What version of ripgrep are you using?
|
||||
description: Enter the output of `rg --version`.
|
||||
placeholder: ex. ripgrep 13.0.0
|
||||
placeholder: ex. ripgrep 0.2.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
63
.github/workflows/ci.yml
vendored
63
.github/workflows/ci.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
include:
|
||||
- build: pinned
|
||||
os: ubuntu-latest
|
||||
rust: 1.74.0
|
||||
rust: 1.88.0
|
||||
- build: stable
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
@@ -75,6 +75,18 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: aarch64-unknown-linux-gnu
|
||||
- build: stable-arm-gnueabihf
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
- build: stable-arm-musleabihf
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: armv7-unknown-linux-musleabihf
|
||||
- build: stable-arm-musleabi
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: armv7-unknown-linux-musleabi
|
||||
- build: stable-powerpc64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
@@ -87,11 +99,14 @@ jobs:
|
||||
os: macos-latest
|
||||
rust: nightly
|
||||
- build: win-msvc
|
||||
os: windows-2022
|
||||
os: windows-latest
|
||||
rust: nightly
|
||||
- build: win-gnu
|
||||
os: windows-2022
|
||||
os: windows-latest
|
||||
rust: nightly-x86_64-gnu
|
||||
- build: winaarch64-msvc
|
||||
os: windows-11-arm
|
||||
rust: nightly
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -165,7 +180,7 @@ jobs:
|
||||
# 'rg' binary (done in test-complete) with qemu, which is a pain and
|
||||
# doesn't really gain us much. If shell completion works in one place,
|
||||
# it probably works everywhere.
|
||||
if: matrix.target == '' && matrix.os != 'windows-2022'
|
||||
if: matrix.target == '' && !startsWith(matrix.os, 'windows')
|
||||
shell: bash
|
||||
run: ci/test-complete
|
||||
|
||||
@@ -177,6 +192,21 @@ jobs:
|
||||
shell: bash
|
||||
run: ${{ env.CARGO }} test --bin rg ${{ env.TARGET_FLAGS }} flags::defs::tests::available_shorts -- --nocapture
|
||||
|
||||
# Setup and compile on the wasm32-wasip1 target
|
||||
wasm:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Add wasm32-wasip1 target
|
||||
run: rustup target add wasm32-wasip1
|
||||
- name: Basic build
|
||||
run: cargo build --verbose
|
||||
|
||||
rustfmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -203,3 +233,28 @@ jobs:
|
||||
env:
|
||||
RUSTDOCFLAGS: -D warnings
|
||||
run: cargo doc --no-deps --document-private-items --workspace
|
||||
|
||||
fuzz_testing:
|
||||
name: Compile Fuzz Test Targets
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install required packages (Ubuntu)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install g++ --yes
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install Fuzzer
|
||||
run: cargo install cargo-fuzz
|
||||
working-directory: fuzz
|
||||
|
||||
- name: Verify fuzz targets build
|
||||
run: cargo check
|
||||
working-directory: fuzz
|
||||
|
44
.github/workflows/release.yml
vendored
44
.github/workflows/release.yml
vendored
@@ -80,6 +80,24 @@ jobs:
|
||||
target: aarch64-unknown-linux-gnu
|
||||
strip: aarch64-linux-gnu-strip
|
||||
qemu: qemu-aarch64
|
||||
- build: stable-arm-gnueabihf
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
strip: arm-linux-gnueabihf-strip
|
||||
qemu: qemu-arm
|
||||
- build: stable-arm-musleabihf
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: armv7-unknown-linux-musleabihf
|
||||
strip: arm-linux-musleabihf-strip
|
||||
qemu: qemu-arm
|
||||
- build: stable-arm-musleabi
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: armv7-unknown-linux-musleabi
|
||||
strip: arm-linux-musleabi-strip
|
||||
qemu: qemu-arm
|
||||
- build: stable-powerpc64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
@@ -104,6 +122,10 @@ jobs:
|
||||
os: windows-latest
|
||||
rust: nightly-x86_64-gnu
|
||||
target: x86_64-pc-windows-gnu
|
||||
- build: winaarch64-msvc
|
||||
os: windows-11-arm
|
||||
rust: nightly
|
||||
target: aarch64-pc-windows-msvc
|
||||
- build: win32-msvc
|
||||
os: windows-latest
|
||||
rust: nightly
|
||||
@@ -157,7 +179,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
${{ env.CARGO }} build --verbose --release --features pcre2 ${{ env.TARGET_FLAGS }}
|
||||
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
||||
if [[ "${{ matrix.os }}" == windows-* ]]; then
|
||||
bin="target/${{ matrix.target }}/release/rg.exe"
|
||||
else
|
||||
bin="target/${{ matrix.target }}/release/rg"
|
||||
@@ -175,9 +197,9 @@ jobs:
|
||||
run: |
|
||||
docker run --rm -v \
|
||||
"$PWD/target:/target:Z" \
|
||||
"rustembedded/cross:${{ matrix.target }}" \
|
||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||
"${{ matrix.strip }}" \
|
||||
"/target/${{ matrix.target }}/release/rg"
|
||||
"/$BIN"
|
||||
|
||||
- name: Determine archive name
|
||||
shell: bash
|
||||
@@ -210,37 +232,37 @@ jobs:
|
||||
run: |
|
||||
docker run --rm -v \
|
||||
"$PWD/target:/target:Z" \
|
||||
"rustembedded/cross:${{ matrix.target }}" \
|
||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||
"${{ matrix.qemu }}" "/$BIN" --version
|
||||
docker run --rm -v \
|
||||
"$PWD/target:/target:Z" \
|
||||
"rustembedded/cross:${{ matrix.target }}" \
|
||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||
"${{ matrix.qemu }}" "/$BIN" \
|
||||
--generate complete-bash > "$ARCHIVE/complete/rg.bash"
|
||||
docker run --rm -v \
|
||||
"$PWD/target:/target:Z" \
|
||||
"rustembedded/cross:${{ matrix.target }}" \
|
||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||
"${{ matrix.qemu }}" "/$BIN" \
|
||||
--generate complete-fish > "$ARCHIVE/complete/rg.fish"
|
||||
docker run --rm -v \
|
||||
"$PWD/target:/target:Z" \
|
||||
"rustembedded/cross:${{ matrix.target }}" \
|
||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||
"${{ matrix.qemu }}" "/$BIN" \
|
||||
--generate complete-powershell > "$ARCHIVE/complete/_rg.ps1"
|
||||
docker run --rm -v \
|
||||
"$PWD/target:/target:Z" \
|
||||
"rustembedded/cross:${{ matrix.target }}" \
|
||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||
"${{ matrix.qemu }}" "/$BIN" \
|
||||
--generate complete-zsh > "$ARCHIVE/complete/_rg"
|
||||
docker run --rm -v \
|
||||
"$PWD/target:/target:Z" \
|
||||
"rustembedded/cross:${{ matrix.target }}" \
|
||||
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
|
||||
"${{ matrix.qemu }}" "/$BIN" \
|
||||
--generate man > "$ARCHIVE/doc/rg.1"
|
||||
|
||||
- name: Build archive (Windows)
|
||||
shell: bash
|
||||
if: matrix.os == 'windows-latest'
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
run: |
|
||||
7z a "$ARCHIVE.zip" "$ARCHIVE"
|
||||
certutil -hashfile "$ARCHIVE.zip" SHA256 > "$ARCHIVE.zip.sha256"
|
||||
@@ -249,7 +271,7 @@ jobs:
|
||||
|
||||
- name: Build archive (Unix)
|
||||
shell: bash
|
||||
if: matrix.os != 'windows-latest'
|
||||
if: ${{ !startsWith(matrix.os, 'windows') }}
|
||||
run: |
|
||||
tar czf "$ARCHIVE.tar.gz" "$ARCHIVE"
|
||||
shasum -a 256 "$ARCHIVE.tar.gz" > "$ARCHIVE.tar.gz.sha256"
|
||||
|
95
CHANGELOG.md
95
CHANGELOG.md
@@ -1,3 +1,98 @@
|
||||
TBD
|
||||
===
|
||||
Unreleased changes. Release notes have not yet been written.
|
||||
|
||||
Performance improvements:
|
||||
|
||||
* [PERF #2865](https://github.com/BurntSushi/ripgrep/pull/2865):
|
||||
Avoid using path canonicalization on Windows when emitting hyperlinks.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* [BUG #829](https://github.com/BurntSushi/ripgrep/issues/829),
|
||||
[BUG #2731](https://github.com/BurntSushi/ripgrep/issues/2731),
|
||||
[BUG #2747](https://github.com/BurntSushi/ripgrep/issues/2747),
|
||||
[BUG #2778](https://github.com/BurntSushi/ripgrep/issues/2778),
|
||||
[BUG #2836](https://github.com/BurntSushi/ripgrep/issues/2836),
|
||||
[BUG #2933](https://github.com/BurntSushi/ripgrep/pull/2933):
|
||||
Fix bug related to gitignores from parent directories.
|
||||
* [BUG #1332](https://github.com/BurntSushi/ripgrep/issues/1332),
|
||||
[BUG #3001](https://github.com/BurntSushi/ripgrep/issues/3001):
|
||||
Make `rg -vf file` where `file` is empty match everything.
|
||||
* [BUG #2177](https://github.com/BurntSushi/ripgrep/issues/2177):
|
||||
Ignore a UTF-8 BOM marker at the start of `.gitignore` (and similar files).
|
||||
* [BUG #2944](https://github.com/BurntSushi/ripgrep/pull/2944):
|
||||
Fix a bug where the "bytes searched" in `--stats` output could be incorrect.
|
||||
* [BUG #2990](https://github.com/BurntSushi/ripgrep/issues/2990):
|
||||
Fix a bug where ripgrep would mishandle globs that ended with a `.`.
|
||||
* [BUG #3108](https://github.com/BurntSushi/ripgrep/issues/3108):
|
||||
Fix a bug where `-q --files-without-match` inverted the exit code.
|
||||
|
||||
Feature enhancements:
|
||||
|
||||
* Many enhancements to the default set of file types available for filtering.
|
||||
* [FEATURE #1872](https://github.com/BurntSushi/ripgrep/issues/1872):
|
||||
Make `-r/--replace` work with `--json`.
|
||||
* [FEATURE #2708](https://github.com/BurntSushi/ripgrep/pull/2708):
|
||||
Completions for the fish shell take ripgrep's config file into account.
|
||||
* [FEATURE #2841](https://github.com/BurntSushi/ripgrep/pull/2841):
|
||||
Add `italic` to the list of available style attributes in `--color`.
|
||||
* [FEATURE #2842](https://github.com/BurntSushi/ripgrep/pull/2842):
|
||||
Directories containing `.jj` are now treated as git repositories.
|
||||
* [FEATURE #2849](https://github.com/BurntSushi/ripgrep/pull/2849):
|
||||
When using multithreading, schedule files to search in order given on CLI.
|
||||
* [FEATURE #2943](https://github.com/BurntSushi/ripgrep/issues/2943):
|
||||
Add `aarch64` release artifacts for Windows.
|
||||
* [FEATURE #3024](https://github.com/BurntSushi/ripgrep/issues/3024):
|
||||
Add `highlight` color type, for styling non-matching text in a matching line.
|
||||
* [FEATURE #3048](https://github.com/BurntSushi/ripgrep/pull/3048):
|
||||
Globs in ripgrep (and the `globset` crate) now support nested alternates.
|
||||
|
||||
|
||||
14.1.1 (2024-09-08)
|
||||
===================
|
||||
This is a minor release with a bug fix for a matching bug. In particular, a bug
|
||||
was found that could cause ripgrep to ignore lines that should match. That is,
|
||||
false negatives. It is difficult to characterize the specific set of regexes
|
||||
in which this occurs as it requires multiple different optimization strategies
|
||||
to collide and produce an incorrect result. But as one reported example, in
|
||||
ripgrep, the regex `(?i:e.x|ex)` does not match `e-x` when it should. (This
|
||||
bug is a result of an inner literal optimization performed in the `grep-regex`
|
||||
crate and not in the `regex` crate.)
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* [BUG #2884](https://github.com/BurntSushi/ripgrep/issues/2884):
|
||||
Fix bug where ripgrep could miss some matches that it should report.
|
||||
|
||||
Miscellaneous:
|
||||
|
||||
* [MISC #2748](https://github.com/BurntSushi/ripgrep/issues/2748):
|
||||
Remove ripgrep's `simd-accel` feature because it was frequently broken.
|
||||
|
||||
|
||||
14.1.0 (2024-01-06)
|
||||
===================
|
||||
This is a minor release with a few small new features and bug fixes. This
|
||||
release contains a bug fix for unbounded memory growth while walking a
|
||||
directory tree. This release also includes improvements to the completions for
|
||||
the `fish` shell, and release binaries for several additional ARM targets.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* [BUG #2664](https://github.com/BurntSushi/ripgrep/issues/2690):
|
||||
Fix unbounded memory growth in the `ignore` crate.
|
||||
|
||||
Feature enhancements:
|
||||
|
||||
* Added or improved file type filtering for Lean and Meson.
|
||||
* [FEATURE #2684](https://github.com/BurntSushi/ripgrep/issues/2684):
|
||||
Improve completions for the `fish` shell.
|
||||
* [FEATURE #2702](https://github.com/BurntSushi/ripgrep/pull/2702):
|
||||
Add release binaries for `armv7-unknown-linux-gnueabihf`,
|
||||
`armv7-unknown-linux-musleabihf` and `armv7-unknown-linux-musleabi`.
|
||||
|
||||
|
||||
14.0.3 (2023-11-28)
|
||||
===================
|
||||
This is a patch release with a bug fix for the `--sortr` flag.
|
||||
|
410
Cargo.lock
generated
410
Cargo.lock
generated
@@ -4,30 +4,39 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
name = "arbitrary"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.8.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
|
||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
@@ -36,71 +45,73 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
version = "1.2.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
checksum = "4ad45f4f74e4e20eaa392913b7b33a7091c87e59628f4dd27888205ad888843c"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.3"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.15"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.16"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.33"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"packed_simd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -113,16 +124,29 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.14"
|
||||
version = "0.4.16"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"arbitrary",
|
||||
"bstr",
|
||||
"glob",
|
||||
"log",
|
||||
@@ -134,7 +158,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "grep"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
dependencies = [
|
||||
"grep-cli",
|
||||
"grep-matcher",
|
||||
@@ -148,7 +172,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "grep-cli"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"globset",
|
||||
@@ -168,7 +192,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "grep-pcre2"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
dependencies = [
|
||||
"grep-matcher",
|
||||
"log",
|
||||
@@ -177,7 +201,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "grep-printer"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"grep-matcher",
|
||||
@@ -191,7 +215,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "grep-regex"
|
||||
version = "0.1.12"
|
||||
version = "0.1.13"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"grep-matcher",
|
||||
@@ -202,7 +226,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "grep-searcher"
|
||||
version = "0.1.13"
|
||||
version = "0.1.14"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"encoding_rs",
|
||||
@@ -217,7 +241,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.21"
|
||||
version = "0.4.23"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"crossbeam-channel",
|
||||
@@ -233,112 +257,58 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "jemalloc-sys"
|
||||
version = "0.5.4+5.3.0-patched"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac6c1946e1cea1788cbfde01c993b52a10e2da07f4bac608228d1bed20bfebf2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jemallocator"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0de374a9f8e63150e6f5e8a60cc14c668226d7a347d8aee1a45766e3c4dd3bc"
|
||||
dependencies = [
|
||||
"jemalloc-sys",
|
||||
"libc",
|
||||
]
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.27"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
|
||||
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lexopt"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401"
|
||||
checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.150"
|
||||
version = "0.2.174"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.0"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
|
||||
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packed_simd"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f9f08af0c877571712e2e3e686ad79efad9657dbf0f7c3c8ba943ff6c38932d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pcre2"
|
||||
version = "0.2.6"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c9d53a8ea5fc3d3568d3de4bebc12606fd0eb8234c602576f1f1ee4880488a7"
|
||||
checksum = "3be55c43ac18044541d58d897e8f4c55157218428953ebd39d86df3ba0286b2b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
@@ -347,9 +317,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pcre2-sys"
|
||||
version = "0.2.7"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f8f5556f23cf2c0b481949fdfc19a7cd9b27ddcb00ef3477b0f4935cbdaedf2"
|
||||
checksum = "550f5d18fb1b90c20b87e161852c10cde77858c3900c5059b5ad2a1449f11d8a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -358,33 +328,39 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.27"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.70"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.2"
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -394,9 +370,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -405,19 +381,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "ripgrep"
|
||||
version = "14.0.3"
|
||||
version = "14.1.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bstr",
|
||||
"grep",
|
||||
"ignore",
|
||||
"jemallocator",
|
||||
"lexopt",
|
||||
"log",
|
||||
"serde",
|
||||
@@ -425,14 +400,15 @@ dependencies = [
|
||||
"serde_json",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"tikv-jemallocator",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
@@ -443,26 +419,20 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.193"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.193"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -471,20 +441,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.108"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.39"
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -493,62 +470,151 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.0"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
||||
|
||||
[[package]]
|
||||
name = "tikv-jemalloc-sys"
|
||||
version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tikv-jemallocator"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"tikv-jemalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
14
Cargo.toml
14
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ripgrep"
|
||||
version = "14.0.3" #:version
|
||||
version = "14.1.1" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
ripgrep is a line-oriented search tool that recursively searches the current
|
||||
@@ -20,11 +20,12 @@ exclude = [
|
||||
"/pkg/brew",
|
||||
"/benchsuite/",
|
||||
"/scripts/",
|
||||
"/crates/fuzz",
|
||||
]
|
||||
build = "build.rs"
|
||||
autotests = false
|
||||
edition = "2021"
|
||||
rust-version = "1.72"
|
||||
rust-version = "1.88"
|
||||
|
||||
[[bin]]
|
||||
bench = false
|
||||
@@ -51,16 +52,16 @@ members = [
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
bstr = "1.7.0"
|
||||
grep = { version = "0.3.1", path = "crates/grep" }
|
||||
ignore = { version = "0.4.21", path = "crates/ignore" }
|
||||
grep = { version = "0.3.2", path = "crates/grep" }
|
||||
ignore = { version = "0.4.23", path = "crates/ignore" }
|
||||
lexopt = "0.3.0"
|
||||
log = "0.4.5"
|
||||
serde_json = "1.0.23"
|
||||
termcolor = "1.1.0"
|
||||
textwrap = { version = "0.16.0", default-features = false }
|
||||
|
||||
[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.jemallocator]
|
||||
version = "0.5.0"
|
||||
[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.tikv-jemallocator]
|
||||
version = "0.6.0"
|
||||
|
||||
[dev-dependencies]
|
||||
serde = "1.0.77"
|
||||
@@ -68,7 +69,6 @@ serde_derive = "1.0.77"
|
||||
walkdir = "2"
|
||||
|
||||
[features]
|
||||
simd-accel = ["grep/simd-accel"]
|
||||
pcre2 = ["grep/pcre2"]
|
||||
|
||||
[profile.release]
|
||||
|
14
Cross.toml
14
Cross.toml
@@ -1,14 +0,0 @@
|
||||
[target.x86_64-unknown-linux-musl]
|
||||
image = "burntsushi/cross:x86_64-unknown-linux-musl"
|
||||
|
||||
[target.i686-unknown-linux-gnu]
|
||||
image = "burntsushi/cross:i686-unknown-linux-gnu"
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
image = "burntsushi/cross:aarch64-unknown-linux-gnu"
|
||||
|
||||
[target.powerpc64-unknown-linux-gnu]
|
||||
image = "burntsushi/cross:powerpc64-unknown-linux-gnu"
|
||||
|
||||
[target.s390x-unknown-linux-gnu]
|
||||
image = "burntsushi/cross:s390x-unknown-linux-gnu"
|
33
FAQ.md
33
FAQ.md
@@ -94,7 +94,7 @@ Does ripgrep have support for shell auto-completion?
|
||||
|
||||
Yes! If you installed ripgrep through a package manager on a Unix system, then
|
||||
the shell completion files included in the release archive should have been
|
||||
installed for you automatically. If not, you can generate completes using
|
||||
installed for you automatically. If not, you can generate completions using
|
||||
ripgrep's command line interface.
|
||||
|
||||
For **bash**:
|
||||
@@ -113,14 +113,31 @@ $ mkdir -p "$dir"
|
||||
$ rg --generate complete-fish > "$dir/rg.fish"
|
||||
```
|
||||
|
||||
For **zsh**:
|
||||
For **zsh**, the recommended approach is:
|
||||
|
||||
```
|
||||
```zsh
|
||||
$ dir="$HOME/.zsh-complete"
|
||||
$ mkdir -p "$dir"
|
||||
$ rg --generate complete-zsh > "$dir/_rg"
|
||||
```
|
||||
|
||||
And then add `$HOME/.zsh-complete` to your `fpath` in, e.g., your
|
||||
`$HOME/.zshrc` file:
|
||||
|
||||
```zsh
|
||||
fpath=($HOME/.zsh-complete $fpath)
|
||||
```
|
||||
|
||||
Or if you'd prefer to load and generate completions at the same time, you can
|
||||
add the following to your `$HOME/.zshrc` file:
|
||||
|
||||
```zsh
|
||||
$ source <(rg --generate complete-zsh)
|
||||
```
|
||||
|
||||
Note though that while this approach is easier to setup, is generally slower
|
||||
than the previous method, and will add more time to loading your shell prompt.
|
||||
|
||||
For **PowerShell**, create the completions:
|
||||
|
||||
```
|
||||
@@ -248,8 +265,8 @@ The `--colors` flag is a bit more complicated. The general format is:
|
||||
to bold the output or not).
|
||||
* `{value}` is determined by the value of `{attribute}`. If
|
||||
`{attribute}` is `style`, then `{value}` should be one of `nobold`,
|
||||
`bold`, `nointense`, `intense`, `nounderline` or `underline`. If
|
||||
`{attribute}` is `fg` or `bg`, then `{value}` should be a color.
|
||||
`bold`, `nointense`, `intense`, `nounderline`, `underline`, `noitalic` or
|
||||
`italic`. If `{attribute}` is `fg` or `bg`, then `{value}` should be a color.
|
||||
|
||||
A color is specified by either one of eight of English names, a single 256-bit
|
||||
number or an RGB triple (with over 16 million possible values, or "true
|
||||
@@ -268,8 +285,8 @@ As a special case, `--colors '{type}:none'` will clear all colors and styles
|
||||
associated with `{type}`, which lets you start with a clean slate (instead of
|
||||
building on top of ripgrep's default color settings).
|
||||
|
||||
Here's an example that makes highlights the matches with a nice blue background
|
||||
with bolded white text:
|
||||
Here's an example that highlights the matches with a nice blue background with
|
||||
bolded white text:
|
||||
|
||||
```
|
||||
$ rg somepattern \
|
||||
@@ -1038,7 +1055,7 @@ How can I donate to ripgrep or its maintainers?
|
||||
|
||||
I welcome [sponsorship](https://github.com/sponsors/BurntSushi/).
|
||||
|
||||
Or if you'd prefer, donating to a charitably organization that you like would
|
||||
Or if you'd prefer, donating to a charitable organization that you like would
|
||||
also be most welcome. My favorites are:
|
||||
|
||||
* [The Internet Archive](https://archive.org/donate/)
|
||||
|
136
README.md
136
README.md
@@ -42,7 +42,7 @@ This example searches the entire
|
||||
[Linux kernel source tree](https://github.com/BurntSushi/linux)
|
||||
(after running `make defconfig && make -j8`) for `[A-Z]+_SUSPEND`, where
|
||||
all matches must be words. Timings were collected on a system with an Intel
|
||||
i7-6900K 3.2 GHz.
|
||||
i9-12900K 5.2 GHz.
|
||||
|
||||
Please remember that a single benchmark is never enough! See my
|
||||
[blog post on ripgrep](https://blog.burntsushi.net/ripgrep/)
|
||||
@@ -50,13 +50,14 @@ for a very detailed comparison with more benchmarks and analysis.
|
||||
|
||||
| Tool | Command | Line count | Time |
|
||||
| ---- | ------- | ---------- | ---- |
|
||||
| ripgrep (Unicode) | `rg -n -w '[A-Z]+_SUSPEND'` | 452 | **0.136s** |
|
||||
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `git grep -P -n -w '[A-Z]+_SUSPEND'` | 452 | 0.348s |
|
||||
| [ugrep (Unicode)](https://github.com/Genivia/ugrep) | `ugrep -r --ignore-files --no-hidden -I -w '[A-Z]+_SUSPEND'` | 452 | 0.506s |
|
||||
| [The Silver Searcher](https://github.com/ggreer/the_silver_searcher) | `ag -w '[A-Z]+_SUSPEND'` | 452 | 0.654s |
|
||||
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=C git grep -E -n -w '[A-Z]+_SUSPEND'` | 452 | 1.150s |
|
||||
| [ack](https://github.com/beyondgrep/ack3) | `ack -w '[A-Z]+_SUSPEND'` | 452 | 4.054s |
|
||||
| [git grep (Unicode)](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=en_US.UTF-8 git grep -E -n -w '[A-Z]+_SUSPEND'` | 452 | 4.205s |
|
||||
| ripgrep (Unicode) | `rg -n -w '[A-Z]+_SUSPEND'` | 536 | **0.082s** (1.00x) |
|
||||
| [hypergrep](https://github.com/p-ranav/hypergrep) | `hgrep -n -w '[A-Z]+_SUSPEND'` | 536 | 0.167s (2.04x) |
|
||||
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `git grep -P -n -w '[A-Z]+_SUSPEND'` | 536 | 0.273s (3.34x) |
|
||||
| [The Silver Searcher](https://github.com/ggreer/the_silver_searcher) | `ag -w '[A-Z]+_SUSPEND'` | 534 | 0.443s (5.43x) |
|
||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -r --ignore-files --no-hidden -I -w '[A-Z]+_SUSPEND'` | 536 | 0.639s (7.82x) |
|
||||
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=C git grep -E -n -w '[A-Z]+_SUSPEND'` | 536 | 0.727s (8.91x) |
|
||||
| [git grep (Unicode)](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=en_US.UTF-8 git grep -E -n -w '[A-Z]+_SUSPEND'` | 536 | 2.670s (32.70x) |
|
||||
| [ack](https://github.com/beyondgrep/ack3) | `ack -w '[A-Z]+_SUSPEND'` | 2677 | 2.935s (35.94x) |
|
||||
|
||||
Here's another benchmark on the same corpus as above that disregards gitignore
|
||||
files and searches with a whitelist instead. The corpus is the same as in the
|
||||
@@ -65,24 +66,52 @@ doing equivalent work:
|
||||
|
||||
| Tool | Command | Line count | Time |
|
||||
| ---- | ------- | ---------- | ---- |
|
||||
| ripgrep | `rg -uuu -tc -n -w '[A-Z]+_SUSPEND'` | 388 | **0.096s** |
|
||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 388 | 0.493s |
|
||||
| [GNU grep](https://www.gnu.org/software/grep/) | `egrep -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 388 | 0.806s |
|
||||
| ripgrep | `rg -uuu -tc -n -w '[A-Z]+_SUSPEND'` | 447 | **0.063s** (1.00x) |
|
||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 447 | 0.607s (9.62x) |
|
||||
| [GNU grep](https://www.gnu.org/software/grep/) | `grep -E -r -n --include='*.c' --include='*.h' -w '[A-Z]+_SUSPEND'` | 447 | 0.674s (10.69x) |
|
||||
|
||||
And finally, a straight-up comparison between ripgrep, ugrep and GNU grep on a
|
||||
single large file cached in memory
|
||||
(~13GB, [`OpenSubtitles.raw.en.gz`](http://opus.nlpl.eu/download.php?f=OpenSubtitles/v2018/mono/OpenSubtitles.raw.en.gz)):
|
||||
Now we'll move to searching on single large file. Here is a straight-up
|
||||
comparison between ripgrep, ugrep and GNU grep on a file cached in memory
|
||||
(~13GB, [`OpenSubtitles.raw.en.gz`](http://opus.nlpl.eu/download.php?f=OpenSubtitles/v2018/mono/OpenSubtitles.raw.en.gz), decompressed):
|
||||
|
||||
| Tool | Command | Line count | Time |
|
||||
| ---- | ------- | ---------- | ---- |
|
||||
| ripgrep | `rg -w 'Sherlock [A-Z]\w+'` | 7882 | **2.769s** |
|
||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -w 'Sherlock [A-Z]\w+'` | 7882 | 6.802s |
|
||||
| [GNU grep](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 egrep -w 'Sherlock [A-Z]\w+'` | 7882 | 9.027s |
|
||||
| ripgrep (Unicode) | `rg -w 'Sherlock [A-Z]\w+'` | 7882 | **1.042s** (1.00x) |
|
||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -w 'Sherlock [A-Z]\w+'` | 7882 | 1.339s (1.28x) |
|
||||
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 egrep -w 'Sherlock [A-Z]\w+'` | 7882 | 6.577s (6.31x) |
|
||||
|
||||
In the above benchmark, passing the `-n` flag (for showing line numbers)
|
||||
increases the times to `3.423s` for ripgrep and `13.031s` for GNU grep. ugrep
|
||||
increases the times to `1.664s` for ripgrep and `9.484s` for GNU grep. ugrep
|
||||
times are unaffected by the presence or absence of `-n`.
|
||||
|
||||
Beware of performance cliffs though:
|
||||
|
||||
| Tool | Command | Line count | Time |
|
||||
| ---- | ------- | ---------- | ---- |
|
||||
| ripgrep (Unicode) | `rg -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | **1.053s** (1.00x) |
|
||||
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 grep -E -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | 6.234s (5.92x) |
|
||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -w '[A-Z]\w+ Sherlock [A-Z]\w+'` | 485 | 28.973s (27.51x) |
|
||||
|
||||
And performance can drop precipitously across the board when searching big
|
||||
files for patterns without any opportunities for literal optimizations:
|
||||
|
||||
| Tool | Command | Line count | Time |
|
||||
| ---- | ------- | ---------- | ---- |
|
||||
| ripgrep | `rg '[A-Za-z]{30}'` | 6749 | **15.569s** (1.00x) |
|
||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep -E '[A-Za-z]{30}'` | 6749 | 21.857s (1.40x) |
|
||||
| [GNU grep](https://www.gnu.org/software/grep/) | `LC_ALL=C grep -E '[A-Za-z]{30}'` | 6749 | 32.409s (2.08x) |
|
||||
| [GNU grep (Unicode)](https://www.gnu.org/software/grep/) | `LC_ALL=en_US.UTF-8 grep -E '[A-Za-z]{30}'` | 6795 | 8m30s (32.74x) |
|
||||
|
||||
Finally, high match counts also tend to both tank performance and smooth
|
||||
out the differences between tools (because performance is dominated by how
|
||||
quickly one can handle a match and not the algorithm used to detect the match,
|
||||
generally speaking):
|
||||
|
||||
| Tool | Command | Line count | Time |
|
||||
| ---- | ------- | ---------- | ---- |
|
||||
| ripgrep | `rg the` | 83499915 | **6.948s** (1.00x) |
|
||||
| [ugrep](https://github.com/Genivia/ugrep) | `ugrep the` | 83499915 | 11.721s (1.69x) |
|
||||
| [GNU grep](https://www.gnu.org/software/grep/) | `LC_ALL=C grep the` | 83499915 | 15.217s (2.19x) |
|
||||
|
||||
### Why should I use ripgrep?
|
||||
|
||||
@@ -109,7 +138,7 @@ times are unaffected by the presence or absence of `-n`.
|
||||
backreferences in your patterns, which are not supported in ripgrep's default
|
||||
regex engine. PCRE2 support can be enabled with `-P/--pcre2` (use PCRE2
|
||||
always) or `--auto-hybrid-regex` (use PCRE2 only if needed). An alternative
|
||||
syntax is provided via the `--engine (default|pcre2|auto-hybrid)` option.
|
||||
syntax is provided via the `--engine (default|pcre2|auto)` option.
|
||||
* ripgrep has [rudimentary support for replacements](GUIDE.md#replacements),
|
||||
which permit rewriting output based on what was matched.
|
||||
* ripgrep supports [searching files in text encodings](GUIDE.md#file-encoding)
|
||||
@@ -191,6 +220,16 @@ configuration files, passthru, support for searching compressed files,
|
||||
multiline search and opt-in fancy regex support via PCRE2.
|
||||
|
||||
|
||||
### Playground
|
||||
|
||||
If you'd like to try ripgrep before installing, there's an unofficial
|
||||
[playground](https://codapi.org/ripgrep/) and an [interactive
|
||||
tutorial](https://codapi.org/try/ripgrep/).
|
||||
|
||||
If you have any questions about these, please open an issue in the [tutorial
|
||||
repo](https://github.com/nalgeon/tryxinyminutes).
|
||||
|
||||
|
||||
### Installation
|
||||
|
||||
The binary name for ripgrep is `rg`.
|
||||
@@ -263,27 +302,35 @@ and **openSUSE Leap** since 15.1.
|
||||
$ sudo zypper install ripgrep
|
||||
```
|
||||
|
||||
If you're a **RHEL/CentOS 7/8** user, you can install ripgrep from
|
||||
[copr](https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/):
|
||||
If you're a **RHEL/CentOS/Rocky Linux 9** user, you can install ripgrep from
|
||||
the [EPEL](https://docs.fedoraproject.org/en-US/epel/) repository:
|
||||
|
||||
```
|
||||
$ sudo yum install -y yum-utils
|
||||
$ sudo yum-config-manager --add-repo=https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/repo/epel-7/carlwgeorge-ripgrep-epel-7.repo
|
||||
$ sudo yum install ripgrep
|
||||
$ sudo dnf config-manager --set-enabled crb
|
||||
$ sudo dnf install -y \
|
||||
https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm \
|
||||
https://dl.fedoraproject.org/pub/epel/epel-next-release-latest-9.noarch.rpm
|
||||
$ sudo dnf install -y ripgrep
|
||||
```
|
||||
|
||||
If you're a **Nix** user, you can install ripgrep from
|
||||
[nixpkgs](https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/text/ripgrep/default.nix):
|
||||
[nixpkgs](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/ri/ripgrep/package.nix):
|
||||
|
||||
```
|
||||
$ nix-env --install ripgrep
|
||||
```
|
||||
|
||||
If you're a **Flox** user, you can install ripgrep as follows:
|
||||
|
||||
```
|
||||
$ flox install ripgrep
|
||||
```
|
||||
|
||||
If you're a **Guix** user, you can install ripgrep from the official
|
||||
package collection:
|
||||
|
||||
```
|
||||
$ sudo guix install ripgrep
|
||||
$ guix install ripgrep
|
||||
```
|
||||
|
||||
If you're a **Debian** user (or a user of a Debian derivative like **Ubuntu**),
|
||||
@@ -291,8 +338,8 @@ then ripgrep can be installed using a binary `.deb` file provided in each
|
||||
[ripgrep release](https://github.com/BurntSushi/ripgrep/releases).
|
||||
|
||||
```
|
||||
$ curl -LO https://github.com/BurntSushi/ripgrep/releases/download/13.0.0/ripgrep_13.0.0_amd64.deb
|
||||
$ sudo dpkg -i ripgrep_13.0.0_amd64.deb
|
||||
$ curl -LO https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep_14.1.1-1_amd64.deb
|
||||
$ sudo dpkg -i ripgrep_14.1.1-1_amd64.deb
|
||||
```
|
||||
|
||||
If you run Debian stable, ripgrep is [officially maintained by
|
||||
@@ -358,9 +405,16 @@ same port as Haiku x86_64 using the x86 secondary architecture build:
|
||||
$ sudo pkgman install ripgrep_x86
|
||||
```
|
||||
|
||||
If you're a **Void Linux** user, then you can install ripgrep from the
|
||||
[official repository](https://voidlinux.org/packages/?arch=x86_64&q=ripgrep):
|
||||
|
||||
```
|
||||
$ sudo xbps-install -Syv ripgrep
|
||||
```
|
||||
|
||||
If you're a **Rust programmer**, ripgrep can be installed with `cargo`.
|
||||
|
||||
* Note that the minimum supported version of Rust for ripgrep is **1.70.0**,
|
||||
* Note that the minimum supported version of Rust for ripgrep is **1.88.0**,
|
||||
although ripgrep may work with older versions.
|
||||
* Note that the binary may be bigger than expected because it contains debug
|
||||
symbols. This is intentional. To remove debug symbols and therefore reduce
|
||||
@@ -383,7 +437,7 @@ $ cargo binstall ripgrep
|
||||
|
||||
ripgrep is written in Rust, so you'll need to grab a
|
||||
[Rust installation](https://www.rust-lang.org/) in order to compile it.
|
||||
ripgrep compiles with Rust 1.70.0 (stable) or newer. In general, ripgrep tracks
|
||||
ripgrep compiles with Rust 1.88.0 (stable) or newer. In general, ripgrep tracks
|
||||
the latest stable release of the Rust compiler.
|
||||
|
||||
To build ripgrep:
|
||||
@@ -396,18 +450,13 @@ $ ./target/release/rg --version
|
||||
0.1.3
|
||||
```
|
||||
|
||||
If you have a Rust nightly compiler and a recent Intel CPU, then you can enable
|
||||
additional optional SIMD acceleration like so:
|
||||
|
||||
```
|
||||
RUSTFLAGS="-C target-cpu=native" cargo build --release --features 'simd-accel'
|
||||
```
|
||||
|
||||
The `simd-accel` feature enables SIMD support in certain ripgrep dependencies
|
||||
(responsible for transcoding). They are not necessary to get SIMD optimizations
|
||||
for search; those are enabled automatically. Hopefully, some day, the
|
||||
`simd-accel` feature will similarly become unnecessary. **WARNING:** Currently,
|
||||
enabling this option can increase compilation times dramatically.
|
||||
**NOTE:** In the past, ripgrep supported a `simd-accel` Cargo feature when
|
||||
using a Rust nightly compiler. This only benefited UTF-16 transcoding.
|
||||
Since it required unstable features, this build mode was prone to breakage.
|
||||
Because of that, support for it has been removed. If you want SIMD
|
||||
optimizations for UTF-16 transcoding, then you'll have to petition the
|
||||
[`encoding_rs`](https://github.com/hsivonen/encoding_rs) project to use stable
|
||||
APIs.
|
||||
|
||||
Finally, optional PCRE2 support can be built with ripgrep by enabling the
|
||||
`pcre2` feature:
|
||||
@@ -416,9 +465,6 @@ Finally, optional PCRE2 support can be built with ripgrep by enabling the
|
||||
$ cargo build --release --features 'pcre2'
|
||||
```
|
||||
|
||||
(Tip: use `--features 'pcre2 simd-accel'` to also include compile time SIMD
|
||||
optimizations, which will only work with a nightly compiler.)
|
||||
|
||||
Enabling the PCRE2 feature works with a stable Rust compiler and will
|
||||
attempt to automatically find and link with your system's PCRE2 library via
|
||||
`pkg-config`. If one doesn't exist, then ripgrep will build PCRE2 from source
|
||||
|
27
build.rs
27
build.rs
@@ -22,7 +22,7 @@ fn set_windows_exe_options() {
|
||||
manifest.push(MANIFEST);
|
||||
let Some(manifest) = manifest.to_str() else { return };
|
||||
|
||||
println!("cargo:rerun-if-changed={}", MANIFEST);
|
||||
println!("cargo:rerun-if-changed={MANIFEST}");
|
||||
// Embed the Windows application manifest file.
|
||||
println!("cargo:rustc-link-arg-bin=rg=/MANIFEST:EMBED");
|
||||
println!("cargo:rustc-link-arg-bin=rg=/MANIFESTINPUT:{manifest}");
|
||||
@@ -37,10 +37,25 @@ fn set_git_revision_hash() {
|
||||
use std::process::Command;
|
||||
|
||||
let args = &["rev-parse", "--short=10", "HEAD"];
|
||||
let Ok(output) = Command::new("git").args(args).output() else { return };
|
||||
let rev = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if rev.is_empty() {
|
||||
return;
|
||||
let output = Command::new("git").args(args).output();
|
||||
match output {
|
||||
Ok(output) => {
|
||||
let rev =
|
||||
String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if rev.is_empty() {
|
||||
println!(
|
||||
"cargo:warning=output from `git rev-parse` is empty, \
|
||||
so skipping embedding of commit hash"
|
||||
);
|
||||
return;
|
||||
}
|
||||
println!("cargo:rustc-env=RIPGREP_BUILD_GIT_HASH={rev}");
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"cargo:warning=failed to run `git rev-parse`, \
|
||||
so skipping embedding of commit hash: {e}"
|
||||
);
|
||||
}
|
||||
}
|
||||
println!("cargo:rustc-env=RIPGREP_BUILD_GIT_HASH={}", rev);
|
||||
}
|
||||
|
@@ -1,23 +0,0 @@
|
||||
These are Docker images used for cross compilation in CI builds (or locally)
|
||||
via the [Cross](https://github.com/rust-embedded/cross) tool.
|
||||
|
||||
The Cross tool actually provides its own Docker images, and all Docker images
|
||||
in this directory are derived from one of them. We provide our own in order to
|
||||
customize the environment. For example, we need to install compression tools
|
||||
like `xz` so that tests for the `-z/--search-zip` flag are run.
|
||||
|
||||
If you make a change to a Docker image, then you can re-build it. `cd` into the
|
||||
directory containing the `Dockerfile` and run:
|
||||
|
||||
$ cd x86_64-unknown-linux-musl
|
||||
$ ./build
|
||||
|
||||
At this point, subsequent uses of `cross` will now use your built image since
|
||||
Docker prefers local images over remote images. In order to make these changes
|
||||
stick, they need to be pushed to Docker Hub:
|
||||
|
||||
$ docker push burntsushi/cross:x86_64-unknown-linux-musl
|
||||
|
||||
Of course, only I (BurntSushi) can push to that location. To make `cross` use
|
||||
a different location, then edit `Cross.toml` in the root of this repo to use
|
||||
a different image name for the desired target.
|
@@ -1,4 +0,0 @@
|
||||
FROM rustembedded/cross:aarch64-unknown-linux-gnu
|
||||
|
||||
COPY stage/ubuntu-install-packages /
|
||||
RUN /ubuntu-install-packages
|
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p stage
|
||||
cp ../../ubuntu-install-packages ./stage/
|
||||
docker build -t burntsushi/cross:aarch64-unknown-linux-gnu .
|
@@ -1,4 +0,0 @@
|
||||
FROM rustembedded/cross:i686-unknown-linux-gnu
|
||||
|
||||
COPY stage/ubuntu-install-packages /
|
||||
RUN /ubuntu-install-packages
|
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p stage
|
||||
cp ../../ubuntu-install-packages ./stage/
|
||||
docker build -t burntsushi/cross:i686-unknown-linux-gnu .
|
@@ -1,4 +0,0 @@
|
||||
FROM rustembedded/cross:powerpc64-unknown-linux-gnu
|
||||
|
||||
COPY stage/ubuntu-install-packages /
|
||||
RUN /ubuntu-install-packages
|
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p stage
|
||||
cp ../../ubuntu-install-packages ./stage/
|
||||
docker build -t burntsushi/cross:powerpc64-unknown-linux-gnu .
|
@@ -1,4 +0,0 @@
|
||||
FROM rustembedded/cross:s390x-unknown-linux-gnu
|
||||
|
||||
COPY stage/ubuntu-install-packages /
|
||||
RUN /ubuntu-install-packages
|
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p stage
|
||||
cp ../../ubuntu-install-packages ./stage/
|
||||
docker build -t burntsushi/cross:s390x-unknown-linux-gnu .
|
@@ -1,4 +0,0 @@
|
||||
FROM rustembedded/cross:x86_64-unknown-linux-musl
|
||||
|
||||
COPY stage/ubuntu-install-packages /
|
||||
RUN /ubuntu-install-packages
|
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p stage
|
||||
cp ../../ubuntu-install-packages ./stage/
|
||||
docker build -t burntsushi/cross:x86_64-unknown-linux-musl .
|
@@ -11,4 +11,4 @@ if ! command -V sudo; then
|
||||
fi
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
zsh xz-utils liblz4-tool musl-tools brotli zstd
|
||||
zsh xz-utils liblz4-tool musl-tools brotli zstd g++
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grep-cli"
|
||||
version = "0.1.10" #:version
|
||||
version = "0.1.11" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Utilities for search oriented command line applications.
|
||||
@@ -15,7 +15,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bstr = { version = "1.6.2", features = ["std"] }
|
||||
globset = { version = "0.4.14", path = "../globset" }
|
||||
globset = { version = "0.4.15", path = "../globset" }
|
||||
log = "0.4.20"
|
||||
termcolor = "1.3.0"
|
||||
|
||||
|
@@ -25,10 +25,10 @@ pub fn hostname() -> io::Result<OsString> {
|
||||
}
|
||||
#[cfg(not(any(windows, unix)))]
|
||||
{
|
||||
io::Error::new(
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"hostname could not be found on unsupported platform",
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -178,22 +178,71 @@ pub fn is_readable_stdin() -> bool {
|
||||
};
|
||||
|
||||
let stdin = std::io::stdin();
|
||||
let Ok(fd) = stdin.as_fd().try_clone_to_owned() else { return false };
|
||||
let fd = match stdin.as_fd().try_clone_to_owned() {
|
||||
Ok(fd) => fd,
|
||||
Err(err) => {
|
||||
log::debug!(
|
||||
"for heuristic stdin detection on Unix, \
|
||||
could not clone stdin file descriptor \
|
||||
(thus assuming stdin is not readable): {err}",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let file = File::from(fd);
|
||||
let Ok(md) = file.metadata() else { return false };
|
||||
let md = match file.metadata() {
|
||||
Ok(md) => md,
|
||||
Err(err) => {
|
||||
log::debug!(
|
||||
"for heuristic stdin detection on Unix, \
|
||||
could not get file metadata for stdin \
|
||||
(thus assuming stdin is not readable): {err}",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let ft = md.file_type();
|
||||
ft.is_file() || ft.is_fifo() || ft.is_socket()
|
||||
let is_file = ft.is_file();
|
||||
let is_fifo = ft.is_fifo();
|
||||
let is_socket = ft.is_socket();
|
||||
let is_readable = is_file || is_fifo || is_socket;
|
||||
log::debug!(
|
||||
"for heuristic stdin detection on Unix, \
|
||||
found that \
|
||||
is_file={is_file}, is_fifo={is_fifo} and is_socket={is_socket}, \
|
||||
and thus concluded that is_stdin_readable={is_readable}",
|
||||
);
|
||||
is_readable
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn imp() -> bool {
|
||||
winapi_util::file::typ(winapi_util::HandleRef::stdin())
|
||||
.map(|t| t.is_disk() || t.is_pipe())
|
||||
.unwrap_or(false)
|
||||
let stdin = winapi_util::HandleRef::stdin();
|
||||
let typ = match winapi_util::file::typ(stdin) {
|
||||
Ok(typ) => typ,
|
||||
Err(err) => {
|
||||
log::debug!(
|
||||
"for heuristic stdin detection on Windows, \
|
||||
could not get file type of stdin \
|
||||
(thus assuming stdin is not readable): {err}",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let is_disk = typ.is_disk();
|
||||
let is_pipe = typ.is_pipe();
|
||||
let is_readable = is_disk || is_pipe;
|
||||
log::debug!(
|
||||
"for heuristic stdin detection on Windows, \
|
||||
found that is_disk={is_disk} and is_pipe={is_pipe}, \
|
||||
and thus concluded that is_stdin_readable={is_readable}",
|
||||
);
|
||||
is_readable
|
||||
}
|
||||
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
fn imp() -> bool {
|
||||
log::debug!("on non-{{Unix,Windows}}, assuming stdin is not readable");
|
||||
false
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use std::io::{self, IsTerminal};
|
||||
|
||||
use termcolor::{self, HyperlinkSpec};
|
||||
use termcolor::HyperlinkSpec;
|
||||
|
||||
/// A writer that supports coloring with either line or block buffering.
|
||||
#[derive(Debug)]
|
||||
|
29
crates/core/flags/complete/encodings.sh
Normal file
29
crates/core/flags/complete/encodings.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
# This is impossible to read, but these encodings rarely if ever change, so
|
||||
# it probably does not matter. They are derived from the list given here:
|
||||
# https://encoding.spec.whatwg.org/#concept-encoding-get
|
||||
#
|
||||
# The globbing here works in both fish and zsh (though they expand it in
|
||||
# different orders). It may work in other shells too.
|
||||
|
||||
{{,us-}ascii,arabic,chinese,cyrillic,greek{,8},hebrew,korean}
|
||||
logical visual mac {,cs}macintosh x-mac-{cyrillic,roman,ukrainian}
|
||||
866 ibm{819,866} csibm866
|
||||
big5{,-hkscs} {cn-,cs}big5 x-x-big5
|
||||
cp{819,866,125{0,1,2,3,4,5,6,7,8}} x-cp125{0,1,2,3,4,5,6,7,8}
|
||||
csiso2022{jp,kr} csiso8859{6,8}{e,i}
|
||||
csisolatin{1,2,3,4,5,6,9} csisolatin{arabic,cyrillic,greek,hebrew}
|
||||
ecma-{114,118} asmo-708 elot_928 sun_eu_greek
|
||||
euc-{jp,kr} x-euc-jp cseuckr cseucpkdfmtjapanese
|
||||
{,x-}gbk csiso58gb231280 gb18030 {,cs}gb2312 gb_2312{,-80} hz-gb-2312
|
||||
iso-2022-{cn,cn-ext,jp,kr}
|
||||
iso8859{,-}{1,2,3,4,5,6,7,8,9,10,11,13,14,15}
|
||||
iso-8859-{1,2,3,4,5,6,7,8,9,10,11,{6,8}-{e,i},13,14,15,16} iso_8859-{1,2,3,4,5,6,7,8,9,15}
|
||||
iso_8859-{1,2,6,7}:1987 iso_8859-{3,4,5,8}:1988 iso_8859-9:1989
|
||||
iso-ir-{58,100,101,109,110,126,127,138,144,148,149,157}
|
||||
koi{,8,8-r,8-ru,8-u,8_r} cskoi8r
|
||||
ks_c_5601-{1987,1989} ksc{,_}5691 csksc56011987
|
||||
latin{1,2,3,4,5,6} l{1,2,3,4,5,6,9}
|
||||
shift{-,_}jis csshiftjis {,x-}sjis ms_kanji ms932
|
||||
utf{,-}8 utf-16{,be,le} unicode-1-1-utf-8
|
||||
windows-{31j,874,949,125{0,1,2,3,4,5,6,7,8}} dos-874 tis-620 ansi_x3.4-1968
|
||||
x-user-defined auto none
|
@@ -2,43 +2,71 @@
|
||||
Provides completions for ripgrep's CLI for the fish shell.
|
||||
*/
|
||||
|
||||
use crate::flags::defs::FLAGS;
|
||||
use crate::flags::{defs::FLAGS, CompletionType};
|
||||
|
||||
const TEMPLATE: &'static str =
|
||||
"complete -c rg -n '__fish_use_subcommand' !SHORT! !LONG! !DOC!\n";
|
||||
const TEMPLATE_CHOICES: &'static str =
|
||||
"complete -c rg -n '__fish_use_subcommand' !SHORT! !LONG! !DOC! -r -f -a '!CHOICES!'\n";
|
||||
const TEMPLATE: &'static str = "complete -c rg !SHORT! -l !LONG! -d '!DOC!'";
|
||||
const TEMPLATE_NEGATED: &'static str =
|
||||
"complete -c rg -l !NEGATED! -n '__rg_contains_opt !LONG! !SHORT!' -d '!DOC!'\n";
|
||||
|
||||
/// Generate completions for Fish.
|
||||
///
|
||||
/// Note that these completions are based on what was produced for ripgrep <=13
|
||||
/// using Clap 2.x. Improvements on this are welcome.
|
||||
/// Reference: <https://fishshell.com/docs/current/completions.html>
|
||||
pub(crate) fn generate() -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str(include_str!("prelude.fish"));
|
||||
out.push('\n');
|
||||
for flag in FLAGS.iter() {
|
||||
let short = match flag.name_short() {
|
||||
None => "".to_string(),
|
||||
Some(byte) => format!("-s {}", char::from(byte)),
|
||||
};
|
||||
let long = format!("-l '{}'", flag.name_long().replace("'", "\\'"));
|
||||
let doc = format!("-d '{}'", flag.doc_short().replace("'", "\\'"));
|
||||
let template = if flag.doc_choices().is_empty() {
|
||||
TEMPLATE.to_string()
|
||||
} else {
|
||||
TEMPLATE_CHOICES
|
||||
.replace("!CHOICES!", &flag.doc_choices().join(" "))
|
||||
};
|
||||
out.push_str(
|
||||
&template
|
||||
.replace("!SHORT!", &short)
|
||||
.replace("!LONG!", &long)
|
||||
.replace("!DOC!", &doc),
|
||||
);
|
||||
let long = flag.name_long();
|
||||
let doc = flag.doc_short().replace("'", "\\'");
|
||||
let mut completion = TEMPLATE
|
||||
.replace("!SHORT!", &short)
|
||||
.replace("!LONG!", &long)
|
||||
.replace("!DOC!", &doc);
|
||||
|
||||
match flag.completion_type() {
|
||||
CompletionType::Filename => {
|
||||
completion.push_str(" -r -F");
|
||||
}
|
||||
CompletionType::Executable => {
|
||||
completion.push_str(" -r -f -a '(__fish_complete_command)'");
|
||||
}
|
||||
CompletionType::Filetype => {
|
||||
completion.push_str(
|
||||
" -r -f -a '(rg --type-list | string replace : \\t)'",
|
||||
);
|
||||
}
|
||||
CompletionType::Encoding => {
|
||||
completion.push_str(" -r -f -a '");
|
||||
completion.push_str(super::ENCODINGS);
|
||||
completion.push_str("'");
|
||||
}
|
||||
CompletionType::Other if !flag.doc_choices().is_empty() => {
|
||||
completion.push_str(" -r -f -a '");
|
||||
completion.push_str(&flag.doc_choices().join(" "));
|
||||
completion.push_str("'");
|
||||
}
|
||||
CompletionType::Other if !flag.is_switch() => {
|
||||
completion.push_str(" -r -f");
|
||||
}
|
||||
CompletionType::Other => (),
|
||||
}
|
||||
|
||||
completion.push('\n');
|
||||
out.push_str(&completion);
|
||||
|
||||
if let Some(negated) = flag.name_negated() {
|
||||
let long = format!("-l '{}'", negated.replace("'", "\\'"));
|
||||
let short = match flag.name_short() {
|
||||
None => "".to_string(),
|
||||
Some(byte) => char::from(byte).to_string(),
|
||||
};
|
||||
out.push_str(
|
||||
&TEMPLATE
|
||||
.replace("!SHORT!", "")
|
||||
&TEMPLATE_NEGATED
|
||||
.replace("!NEGATED!", &negated)
|
||||
.replace("!SHORT!", &short)
|
||||
.replace("!LONG!", &long)
|
||||
.replace("!DOC!", &doc),
|
||||
);
|
||||
|
@@ -2,6 +2,8 @@
|
||||
Modules for generating completions for various shells.
|
||||
*/
|
||||
|
||||
static ENCODINGS: &'static str = include_str!("encodings.sh");
|
||||
|
||||
pub(super) mod bash;
|
||||
pub(super) mod fish;
|
||||
pub(super) mod powershell;
|
||||
|
@@ -72,7 +72,7 @@ pub(crate) fn generate() -> String {
|
||||
}
|
||||
|
||||
if let Some(negated) = flag.name_negated() {
|
||||
let dash_name = format!("--{}", negated);
|
||||
let dash_name = format!("--{negated}");
|
||||
flags.push_str("\n ");
|
||||
flags.push_str(
|
||||
&TEMPLATE_FLAG
|
||||
|
31
crates/core/flags/complete/prelude.fish
Normal file
31
crates/core/flags/complete/prelude.fish
Normal file
@@ -0,0 +1,31 @@
|
||||
# Usage: __rg_contains_opt LONG [SHORT]
|
||||
function __rg_contains_opt --description 'Specialized __fish_contains_opt'
|
||||
# Cache the config file because this function is called many times per
|
||||
# completion attempt.
|
||||
# The cache will persist for the entire shell session (even if the
|
||||
# variable or the file contents change).
|
||||
if not set -q __rg_config
|
||||
set -g __rg_config
|
||||
if set -qx RIPGREP_CONFIG_PATH
|
||||
set __rg_config (
|
||||
cat -- $RIPGREP_CONFIG_PATH 2>/dev/null \
|
||||
| string trim \
|
||||
| string match -rv '^$|^#'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
set -l commandline (commandline -cpo) (commandline -ct) $__rg_config
|
||||
|
||||
if contains -- "--$argv[1]" $commandline
|
||||
return 0
|
||||
end
|
||||
|
||||
if set -q argv[2]
|
||||
if string match -qr -- "^-[^-]*$argv[2]" $commandline
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
return 1
|
||||
end
|
@@ -363,10 +363,11 @@ _rg() {
|
||||
'column:specify coloring for column numbers'
|
||||
'line:specify coloring for line numbers'
|
||||
'match:specify coloring for match text'
|
||||
'highlight:specify coloring for matching lines'
|
||||
'path:specify coloring for file names'
|
||||
)
|
||||
descr='color/style type'
|
||||
elif [[ ${IPREFIX#--*=}$PREFIX == (column|line|match|path):[^:]# ]]; then
|
||||
elif [[ ${IPREFIX#--*=}$PREFIX == (column|line|match|highlight|path):[^:]# ]]; then
|
||||
suf=( -qS: )
|
||||
tmp=(
|
||||
'none:clear color/style for type'
|
||||
@@ -413,32 +414,8 @@ _rg_encodings() {
|
||||
local -a expl
|
||||
local -aU _encodings
|
||||
|
||||
# This is impossible to read, but these encodings rarely if ever change, so it
|
||||
# probably doesn't matter. They are derived from the list given here:
|
||||
# https://encoding.spec.whatwg.org/#concept-encoding-get
|
||||
_encodings=(
|
||||
{{,us-}ascii,arabic,chinese,cyrillic,greek{,8},hebrew,korean}
|
||||
logical visual mac {,cs}macintosh x-mac-{cyrillic,roman,ukrainian}
|
||||
866 ibm{819,866} csibm866
|
||||
big5{,-hkscs} {cn-,cs}big5 x-x-big5
|
||||
cp{819,866,125{0..8}} x-cp125{0..8}
|
||||
csiso2022{jp,kr} csiso8859{6,8}{e,i}
|
||||
csisolatin{{1..6},9} csisolatin{arabic,cyrillic,greek,hebrew}
|
||||
ecma-{114,118} asmo-708 elot_928 sun_eu_greek
|
||||
euc-{jp,kr} x-euc-jp cseuckr cseucpkdfmtjapanese
|
||||
{,x-}gbk csiso58gb231280 gb18030 {,cs}gb2312 gb_2312{,-80} hz-gb-2312
|
||||
iso-2022-{cn,cn-ext,jp,kr}
|
||||
iso8859{,-}{{1..11},13,14,15}
|
||||
iso-8859-{{1..11},{6,8}-{e,i},13,14,15,16} iso_8859-{{1..9},15}
|
||||
iso_8859-{1,2,6,7}:1987 iso_8859-{3,4,5,8}:1988 iso_8859-9:1989
|
||||
iso-ir-{58,100,101,109,110,126,127,138,144,148,149,157}
|
||||
koi{,8,8-r,8-ru,8-u,8_r} cskoi8r
|
||||
ks_c_5601-{1987,1989} ksc{,_}5691 csksc56011987
|
||||
latin{1..6} l{{1..6},9}
|
||||
shift{-,_}jis csshiftjis {,x-}sjis ms_kanji ms932
|
||||
utf{,-}8 utf-16{,be,le} unicode-1-1-utf-8
|
||||
windows-{31j,874,949,125{0..8}} dos-874 tis-620 ansi_x3.4-1968
|
||||
x-user-defined auto none
|
||||
!ENCODINGS!
|
||||
)
|
||||
|
||||
_wanted encodings expl encoding compadd -a "$@" - _encodings
|
||||
@@ -458,7 +435,15 @@ _rg_types() {
|
||||
fi
|
||||
}
|
||||
|
||||
_rg "$@"
|
||||
# Don't run the completion function when being sourced by itself.
|
||||
#
|
||||
# See https://github.com/BurntSushi/ripgrep/issues/2956
|
||||
# See https://github.com/BurntSushi/ripgrep/pull/2957
|
||||
if [[ $funcstack[1] == _rg ]] || (( ! $+functions[compdef] )); then
|
||||
_rg "$@"
|
||||
else
|
||||
compdef _rg rg
|
||||
fi
|
||||
|
||||
################################################################################
|
||||
# ZSH COMPLETION REFERENCE
|
||||
|
@@ -19,5 +19,5 @@ long as it meets criteria 3 and 4 above.
|
||||
|
||||
/// Generate completions for zsh.
|
||||
pub(crate) fn generate() -> String {
|
||||
include_str!("rg.zsh").to_string()
|
||||
include_str!("rg.zsh").replace("!ENCODINGS!", super::ENCODINGS.trim_end())
|
||||
}
|
||||
|
@@ -34,6 +34,8 @@ use crate::flags::{
|
||||
#[cfg(test)]
|
||||
use crate::flags::parse::parse_low_raw;
|
||||
|
||||
use super::CompletionType;
|
||||
|
||||
/// A list of all flags in ripgrep via implementations of `Flag`.
|
||||
///
|
||||
/// The order of these flags matter. It determines the order of the flags in
|
||||
@@ -749,7 +751,8 @@ the \flag{colors} flag to manually set all color styles to \fBnone\fP:
|
||||
\-\-colors 'path:none' \\
|
||||
\-\-colors 'line:none' \\
|
||||
\-\-colors 'column:none' \\
|
||||
\-\-colors 'match:none'
|
||||
\-\-colors 'match:none' \\
|
||||
\-\-colors 'highlight:none'
|
||||
.EE
|
||||
.sp
|
||||
"
|
||||
@@ -827,21 +830,21 @@ impl Flag for Colors {
|
||||
"Configure color settings and styles."
|
||||
}
|
||||
fn doc_long(&self) -> &'static str {
|
||||
r"
|
||||
r#"
|
||||
This flag specifies color settings for use in the output. This flag may be
|
||||
provided multiple times. Settings are applied iteratively. Pre-existing color
|
||||
labels are limited to one of eight choices: \fBred\fP, \fBblue\fP, \fBgreen\fP,
|
||||
\fBcyan\fP, \fBmagenta\fP, \fByellow\fP, \fBwhite\fP and \fBblack\fP. Styles
|
||||
are limited to \fBnobold\fP, \fBbold\fP, \fBnointense\fP, \fBintense\fP,
|
||||
\fBnounderline\fP or \fBunderline\fP.
|
||||
\fBnounderline\fP, \fBunderline\fP, \fBnoitalic\fP or \fBitalic\fP.
|
||||
.sp
|
||||
The format of the flag is
|
||||
\fB{\fP\fItype\fP\fB}:{\fP\fIattribute\fP\fB}:{\fP\fIvalue\fP\fB}\fP.
|
||||
\fItype\fP should be one of \fBpath\fP, \fBline\fP, \fBcolumn\fP or
|
||||
\fBmatch\fP. \fIattribute\fP can be \fBfg\fP, \fBbg\fP or \fBstyle\fP.
|
||||
\fIvalue\fP is either a color (for \fBfg\fP and \fBbg\fP) or a text style. A
|
||||
special format, \fB{\fP\fItype\fP\fB}:none\fP, will clear all color settings
|
||||
for \fItype\fP.
|
||||
\fItype\fP should be one of \fBpath\fP, \fBline\fP, \fBcolumn\fP,
|
||||
\fBhighlight\fP or \fBmatch\fP. \fIattribute\fP can be \fBfg\fP, \fBbg\fP or
|
||||
\fBstyle\fP. \fIvalue\fP is either a color (for \fBfg\fP and \fBbg\fP) or a
|
||||
text style. A special format, \fB{\fP\fItype\fP\fB}:none\fP, will clear all
|
||||
color settings for \fItype\fP.
|
||||
.sp
|
||||
For example, the following command will change the match color to magenta and
|
||||
the background color for line numbers to yellow:
|
||||
@@ -850,6 +853,17 @@ the background color for line numbers to yellow:
|
||||
rg \-\-colors 'match:fg:magenta' \-\-colors 'line:bg:yellow'
|
||||
.EE
|
||||
.sp
|
||||
Another example, the following command will "highlight" the non-matching text
|
||||
in matching lines:
|
||||
.sp
|
||||
.EX
|
||||
rg \-\-colors 'highlight:bg:yellow' \-\-colors 'highlight:fg:black'
|
||||
.EE
|
||||
.sp
|
||||
The "highlight" color type is particularly useful for contrasting matching
|
||||
lines with surrounding context printed by the \flag{before-context},
|
||||
\flag{after-context}, \flag{context} or \flag{passthru} flags.
|
||||
.sp
|
||||
Extended colors can be used for \fIvalue\fP when the tty supports ANSI color
|
||||
sequences. These are specified as either \fIx\fP (256-color) or
|
||||
.IB x , x , x
|
||||
@@ -872,7 +886,7 @@ or, equivalently,
|
||||
.sp
|
||||
Note that the \fBintense\fP and \fBnointense\fP styles will have no effect when
|
||||
used alongside these extended color codes.
|
||||
"
|
||||
"#
|
||||
}
|
||||
|
||||
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||
@@ -906,6 +920,24 @@ fn test_colors() {
|
||||
"line:bg:yellow".parse().unwrap()
|
||||
]
|
||||
);
|
||||
|
||||
let args = parse_low_raw(["--colors", "highlight:bg:240"]).unwrap();
|
||||
assert_eq!(args.colors, vec!["highlight:bg:240".parse().unwrap()]);
|
||||
|
||||
let args = parse_low_raw([
|
||||
"--colors",
|
||||
"match:fg:magenta",
|
||||
"--colors",
|
||||
"highlight:bg:blue",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
args.colors,
|
||||
vec![
|
||||
"match:fg:magenta".parse().unwrap(),
|
||||
"highlight:bg:blue".parse().unwrap()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// --column
|
||||
@@ -1582,6 +1614,9 @@ The encoding detection that ripgrep uses can be reverted to its automatic mode
|
||||
via the \flag-negate{encoding} flag.
|
||||
"
|
||||
}
|
||||
fn completion_type(&self) -> CompletionType {
|
||||
CompletionType::Encoding
|
||||
}
|
||||
|
||||
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||
let value = match v {
|
||||
@@ -1977,6 +2012,9 @@ When \flag{file} or \flag{regexp} is used, then ripgrep treats all positional
|
||||
arguments as files or directories to search.
|
||||
"
|
||||
}
|
||||
fn completion_type(&self) -> CompletionType {
|
||||
CompletionType::Filename
|
||||
}
|
||||
|
||||
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||
let path = PathBuf::from(v.unwrap_value());
|
||||
@@ -2619,7 +2657,7 @@ of printing the file path as a prefix for each matched line.
|
||||
This is the default mode when printing to a tty.
|
||||
.sp
|
||||
When \fBstdout\fP is not a tty, then ripgrep will default to the standard
|
||||
grep-like format. Once can force this format in Unix-like environments by
|
||||
grep-like format. One can force this format in Unix-like environments by
|
||||
piping the output of ripgrep to \fBcat\fP. For example, \fBrg\fP \fIfoo\fP \fB|
|
||||
cat\fP.
|
||||
"
|
||||
@@ -2738,12 +2776,17 @@ impl Flag for Hidden {
|
||||
Search hidden files and directories. By default, hidden files and directories
|
||||
are skipped. Note that if a hidden file or a directory is whitelisted in
|
||||
an ignore file, then it will be searched even if this flag isn't provided.
|
||||
Similarly if a hidden file or directory is given explicitly as an argumnet to
|
||||
Similarly if a hidden file or directory is given explicitly as an argument to
|
||||
ripgrep.
|
||||
.sp
|
||||
A file or directory is considered hidden if its base name starts with a dot
|
||||
character (\fB.\fP). On operating systems which support a "hidden" file
|
||||
attribute, like Windows, files with this attribute are also considered hidden.
|
||||
.sp
|
||||
Note that \flag{hidden} will include files and folders like \fB.git\fP
|
||||
regardless of \flag{no-ignore-vcs}. To exclude such paths when using
|
||||
\flag{hidden}, you must explicitly ignore them using another flag or ignore
|
||||
file.
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -2808,6 +2851,9 @@ to calling \fBgethostname\fP. On Windows, this corresponds to calling
|
||||
ripgrep uses your system's hostname for producing hyperlinks.
|
||||
"#
|
||||
}
|
||||
fn completion_type(&self) -> CompletionType {
|
||||
CompletionType::Executable
|
||||
}
|
||||
|
||||
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||
let path = PathBuf::from(v.unwrap_value());
|
||||
@@ -3141,6 +3187,9 @@ If you are looking for a way to include or exclude files and directories
|
||||
directly on the command line, then use \flag{glob} instead.
|
||||
"
|
||||
}
|
||||
fn completion_type(&self) -> CompletionType {
|
||||
CompletionType::Filename
|
||||
}
|
||||
|
||||
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||
let path = PathBuf::from(v.unwrap_value());
|
||||
@@ -4589,11 +4638,15 @@ impl Flag for NoIgnoreVcs {
|
||||
}
|
||||
fn doc_long(&self) -> &'static str {
|
||||
r"
|
||||
When given, filter rules from source control ignore files (e.g., \fB.gitignore\fP)
|
||||
are not respected. By default, ripgrep respects \fBgit\fP's ignore rules for
|
||||
automatic filtering. In some cases, it may not be desirable to respect the
|
||||
source control's ignore rules and instead only respect rules in \fB.ignore\fP
|
||||
or \fB.rgignore\fP.
|
||||
When given, filter rules from source control ignore files (e.g.,
|
||||
\fB.gitignore\fP) are not respected. By default, ripgrep respects \fBgit\fP's
|
||||
ignore rules for automatic filtering. In some cases, it may not be desirable
|
||||
to respect the source control's ignore rules and instead only respect rules in
|
||||
\fB.ignore\fP or \fB.rgignore\fP.
|
||||
.sp
|
||||
Note that this flag does not directly affect the filtering of source control
|
||||
files or folders that start with a dot (\fB.\fP), like \fB.git\fP. These are
|
||||
affected by \flag{hidden} and its related flags instead.
|
||||
.sp
|
||||
This flag implies \flag{no-ignore-parent} for source control ignore files as
|
||||
well.
|
||||
@@ -5410,6 +5463,9 @@ format, then \fBpzstd\fP is used to decompress the contents to stdout.
|
||||
This overrides the \flag{search-zip} flag.
|
||||
"#
|
||||
}
|
||||
fn completion_type(&self) -> CompletionType {
|
||||
CompletionType::Executable
|
||||
}
|
||||
|
||||
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||
let path = match v {
|
||||
@@ -6781,6 +6837,9 @@ any rules found in ignore files.
|
||||
To see the list of available file types, use the \flag{type-list} flag.
|
||||
"#
|
||||
}
|
||||
fn completion_type(&self) -> CompletionType {
|
||||
CompletionType::Filetype
|
||||
}
|
||||
|
||||
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||
args.type_changes.push(TypeChange::Select {
|
||||
@@ -7000,6 +7059,9 @@ will only search files that are unrecognized by its type definitions.
|
||||
To see the list of available file types, use the \flag{type-list} flag.
|
||||
"#
|
||||
}
|
||||
fn completion_type(&self) -> CompletionType {
|
||||
CompletionType::Filetype
|
||||
}
|
||||
|
||||
fn update(&self, v: FlagValue, args: &mut LowArgs) -> anyhow::Result<()> {
|
||||
args.type_changes.push(TypeChange::Negate {
|
||||
@@ -7238,7 +7300,7 @@ impl Flag for Vimgrep {
|
||||
Category::Output
|
||||
}
|
||||
fn doc_short(&self) -> &'static str {
|
||||
r"Print results im a vim compatible format."
|
||||
r"Print results in a vim compatible format."
|
||||
}
|
||||
fn doc_long(&self) -> &'static str {
|
||||
r"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.TH RG 1 2023-11-26 "!!VERSION!!" "User Commands"
|
||||
.TH RG 1 2024-09-08 "!!VERSION!!" "User Commands"
|
||||
.
|
||||
.
|
||||
.SH NAME
|
||||
@@ -43,10 +43,10 @@ configuration file. The file can specify one shell argument per line. Lines
|
||||
starting with \fB#\fP are ignored. For more details, see \fBCONFIGURATION
|
||||
FILES\fP below.
|
||||
.sp
|
||||
ripgrep will automatically detect if stdin exists and search stdin for a regex
|
||||
pattern, e.g. \fBls | rg foo\fP. In some environments, stdin may exist when
|
||||
it shouldn't. To turn off stdin detection, one can explicitly specify the
|
||||
directory to search, e.g. \fBrg foo ./\fP.
|
||||
ripgrep will automatically detect if stdin is a readable file and search stdin
|
||||
for a regex pattern, e.g. \fBls | rg foo\fP. In some environments, stdin may
|
||||
exist when it shouldn't. To turn off stdin detection, one can explicitly
|
||||
specify the directory to search, e.g. \fBrg foo ./\fP.
|
||||
.sp
|
||||
Like other tools such as \fBls\fP, ripgrep will alter its output depending on
|
||||
whether stdout is connected to a tty. By default, when printing a tty, ripgrep
|
||||
|
@@ -161,9 +161,6 @@ fn compile_cpu_features() -> Vec<String> {
|
||||
fn features() -> Vec<String> {
|
||||
let mut features = vec![];
|
||||
|
||||
let simd_accel = cfg!(feature = "simd-accel");
|
||||
features.push(format!("{sign}simd-accel", sign = sign(simd_accel)));
|
||||
|
||||
let pcre2 = cfg!(feature = "pcre2");
|
||||
features.push(format!("{sign}pcre2", sign = sign(pcre2)));
|
||||
|
||||
|
@@ -484,9 +484,9 @@ impl HiArgs {
|
||||
if self.crlf {
|
||||
builder.crlf(true);
|
||||
}
|
||||
// We don't need to set this in multiline mode since mulitline
|
||||
// We don't need to set this in multiline mode since multiline
|
||||
// matchers don't use optimizations related to line terminators.
|
||||
// Moreover, a mulitline regex used with --null-data should
|
||||
// Moreover, a multiline regex used with --null-data should
|
||||
// be allowed to match NUL bytes explicitly, which this would
|
||||
// otherwise forbid.
|
||||
if self.null_data {
|
||||
@@ -517,7 +517,7 @@ impl HiArgs {
|
||||
/// When this returns false, it is impossible for ripgrep to ever report
|
||||
/// a match.
|
||||
pub(crate) fn matches_possible(&self) -> bool {
|
||||
if self.patterns.patterns.is_empty() {
|
||||
if self.patterns.patterns.is_empty() && !self.invert_match {
|
||||
return false;
|
||||
}
|
||||
if self.max_count == Some(0) {
|
||||
@@ -562,7 +562,16 @@ impl HiArgs {
|
||||
wtr: W,
|
||||
) -> Printer<W> {
|
||||
let summary_kind = if self.quiet {
|
||||
SummaryKind::Quiet
|
||||
match search_mode {
|
||||
SearchMode::FilesWithMatches
|
||||
| SearchMode::Count
|
||||
| SearchMode::CountMatches
|
||||
| SearchMode::JSON
|
||||
| SearchMode::Standard => SummaryKind::QuietWithMatch,
|
||||
SearchMode::FilesWithoutMatch => {
|
||||
SummaryKind::QuietWithoutMatch
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match search_mode {
|
||||
SearchMode::FilesWithMatches => SummaryKind::PathWithMatch,
|
||||
@@ -589,6 +598,7 @@ impl HiArgs {
|
||||
.pretty(false)
|
||||
.max_matches(self.max_count)
|
||||
.always_begin_end(false)
|
||||
.replacement(self.replace.clone().map(|r| r.into()))
|
||||
.build(wtr)
|
||||
}
|
||||
|
||||
@@ -1080,9 +1090,18 @@ impl Paths {
|
||||
}
|
||||
paths.push(path);
|
||||
}
|
||||
log::debug!("number of paths given to search: {}", paths.len());
|
||||
if !paths.is_empty() {
|
||||
let is_one_file = paths.len() == 1
|
||||
&& (paths[0] == Path::new("-") || paths[0].is_file());
|
||||
// Note that we specifically use `!paths[0].is_dir()` here
|
||||
// instead of `paths[0].is_file()`. Namely, the latter can
|
||||
// return `false` even when the path is something resembling
|
||||
// a file. So instead, we just consider the path a file as
|
||||
// long as we know it isn't a directory.
|
||||
//
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/2736
|
||||
&& (paths[0] == Path::new("-") || !paths[0].is_dir());
|
||||
log::debug!("is_one_file? {is_one_file:?}");
|
||||
return Ok(Paths { paths, has_implicit_path: false, is_one_file });
|
||||
}
|
||||
// N.B. is_readable_stdin is a heuristic! Part of the issue is that a
|
||||
|
@@ -70,7 +70,7 @@ mod parse;
|
||||
/// value. Flags that accept multiple values are an unsupported abberation.
|
||||
trait Flag: Debug + Send + Sync + UnwindSafe + RefUnwindSafe + 'static {
|
||||
/// Returns true if this flag is a switch. When a flag is a switch, the
|
||||
/// CLI parser will look for a value after the flag is seen.
|
||||
/// CLI parser will not look for a value after the flag is seen.
|
||||
fn is_switch(&self) -> bool;
|
||||
|
||||
/// A short single byte name for this flag. This returns `None` by default,
|
||||
@@ -150,6 +150,10 @@ trait Flag: Debug + Send + Sync + UnwindSafe + RefUnwindSafe + 'static {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn completion_type(&self) -> CompletionType {
|
||||
CompletionType::Other
|
||||
}
|
||||
|
||||
/// Given the parsed value (which might just be a switch), this should
|
||||
/// update the state in `args` based on the value given for this flag.
|
||||
///
|
||||
@@ -228,6 +232,21 @@ impl Category {
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of argument a flag accepts, to be used for shell completions.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum CompletionType {
|
||||
/// No special category. is_switch() and doc_choices() may apply.
|
||||
Other,
|
||||
/// A path to a file.
|
||||
Filename,
|
||||
/// A command in $PATH.
|
||||
Executable,
|
||||
/// The name of a file type, as used by e.g. --type.
|
||||
Filetype,
|
||||
/// The name of an encoding_rs encoding, as used by --encoding.
|
||||
Encoding,
|
||||
}
|
||||
|
||||
/// Represents a value parsed from the command line.
|
||||
///
|
||||
/// This doesn't include the corresponding flag, but values come in one of
|
||||
|
@@ -323,7 +323,7 @@ enum FlagLookup<'a> {
|
||||
UnrecognizedLong(String),
|
||||
}
|
||||
|
||||
/// The info about a flag associated with a flag's ID in the the flag map.
|
||||
/// The info about a flag associated with a flag's ID in the flag map.
|
||||
#[derive(Debug)]
|
||||
struct FlagInfo {
|
||||
/// The flag object and its associated metadata.
|
||||
|
@@ -6,7 +6,7 @@ print to stderr. We therefore avoid bringing in extra dependencies just for
|
||||
this functionality.
|
||||
*/
|
||||
|
||||
use log::{self, Log};
|
||||
use log::Log;
|
||||
|
||||
/// The simplest possible logger that logs to stderr.
|
||||
///
|
||||
|
@@ -37,7 +37,7 @@ mod search;
|
||||
// i686.
|
||||
#[cfg(all(target_env = "musl", target_pointer_width = "64"))]
|
||||
#[global_allocator]
|
||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||
static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
||||
|
||||
/// Then, as it was, then again it will be.
|
||||
fn main() -> ExitCode {
|
||||
@@ -468,7 +468,7 @@ fn print_stats<W: Write>(
|
||||
{bytes_printed} bytes printed
|
||||
{bytes_searched} bytes searched
|
||||
{search_time:0.6} seconds spent searching
|
||||
{process_time:0.6} seconds
|
||||
{process_time:0.6} seconds total
|
||||
",
|
||||
matches = stats.matches(),
|
||||
lines = stats.matched_lines(),
|
||||
|
@@ -28,7 +28,7 @@ static ERRORED: AtomicBool = AtomicBool::new(false);
|
||||
///
|
||||
/// This locks stdout, not stderr, even though this prints to stderr. This
|
||||
/// avoids the appearance of interleaving output when stdout and stderr both
|
||||
/// correspond to a tty.)
|
||||
/// correspond to a tty.
|
||||
#[macro_export]
|
||||
macro_rules! eprintln_locked {
|
||||
($($tt:tt)*) => {{
|
||||
@@ -39,21 +39,29 @@ macro_rules! eprintln_locked {
|
||||
// lock stdout before printing to stderr. This avoids interleaving
|
||||
// lines within ripgrep because `search_parallel` uses `termcolor`,
|
||||
// which accesses the same stdout lock when writing lines.
|
||||
let stdout = std::io::stdout();
|
||||
let _handle = stdout.lock();
|
||||
let stdout = std::io::stdout().lock();
|
||||
let mut stderr = std::io::stderr().lock();
|
||||
// We specifically ignore any errors here. One plausible error we
|
||||
// can get in some cases is a broken pipe error. And when that
|
||||
// occurs, we should exit gracefully. Otherwise, just abort with
|
||||
// an error code because there isn't much else we can do.
|
||||
//
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/1966
|
||||
if let Err(err) = writeln!(std::io::stderr(), $($tt)*) {
|
||||
if let Err(err) = write!(stderr, "rg: ") {
|
||||
if err.kind() == std::io::ErrorKind::BrokenPipe {
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
if let Err(err) = writeln!(stderr, $($tt)*) {
|
||||
if err.kind() == std::io::ErrorKind::BrokenPipe {
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
drop(stdout);
|
||||
}
|
||||
}}
|
||||
}
|
||||
@@ -91,19 +99,19 @@ macro_rules! ignore_message {
|
||||
|
||||
/// Returns true if and only if messages should be shown.
|
||||
pub(crate) fn messages() -> bool {
|
||||
MESSAGES.load(Ordering::SeqCst)
|
||||
MESSAGES.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Set whether messages should be shown or not.
|
||||
///
|
||||
/// By default, they are not shown.
|
||||
pub(crate) fn set_messages(yes: bool) {
|
||||
MESSAGES.store(yes, Ordering::SeqCst)
|
||||
MESSAGES.store(yes, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Returns true if and only if "ignore" related messages should be shown.
|
||||
pub(crate) fn ignore_messages() -> bool {
|
||||
IGNORE_MESSAGES.load(Ordering::SeqCst)
|
||||
IGNORE_MESSAGES.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Set whether "ignore" related messages should be shown or not.
|
||||
@@ -114,12 +122,12 @@ pub(crate) fn ignore_messages() -> bool {
|
||||
/// `messages` is disabled, then "ignore" messages are never shown, regardless
|
||||
/// of this setting.
|
||||
pub(crate) fn set_ignore_messages(yes: bool) {
|
||||
IGNORE_MESSAGES.store(yes, Ordering::SeqCst)
|
||||
IGNORE_MESSAGES.store(yes, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Returns true if and only if ripgrep came across a non-fatal error.
|
||||
pub(crate) fn errored() -> bool {
|
||||
ERRORED.load(Ordering::SeqCst)
|
||||
ERRORED.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Indicate that ripgrep has come across a non-fatal error.
|
||||
@@ -127,5 +135,5 @@ pub(crate) fn errored() -> bool {
|
||||
/// Callers should not use this directly. Instead, it is called automatically
|
||||
/// via the `err_message` macro.
|
||||
pub(crate) fn set_errored() {
|
||||
ERRORED.store(true, Ordering::SeqCst);
|
||||
ERRORED.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
@@ -307,15 +307,14 @@ impl<W: WriteColor> SearchWorker<W> {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"preprocessor command could not start: '{:?}': {}",
|
||||
cmd, err,
|
||||
"preprocessor command could not start: '{cmd:?}': {err}",
|
||||
),
|
||||
)
|
||||
})?;
|
||||
let result = self.search_reader(path, &mut rdr).map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("preprocessor command failed: '{:?}': {}", cmd, err),
|
||||
format!("preprocessor command failed: '{cmd:?}': {err}"),
|
||||
)
|
||||
});
|
||||
let close_result = rdr.close();
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "globset"
|
||||
version = "0.4.14" #:version
|
||||
version = "0.4.16" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Cross platform single glob and glob set matching. Glob set matching is the
|
||||
@@ -21,6 +21,7 @@ bench = false
|
||||
|
||||
[dependencies]
|
||||
aho-corasick = "1.1.1"
|
||||
arbitrary = { version = "1.3.2", optional = true, features = ["derive"] }
|
||||
bstr = { version = "1.6.2", default-features = false, features = ["std"] }
|
||||
log = { version = "0.4.20", optional = true }
|
||||
serde = { version = "1.0.188", optional = true }
|
||||
@@ -41,6 +42,7 @@ serde_json = "1.0.107"
|
||||
|
||||
[features]
|
||||
default = ["log"]
|
||||
arbitrary = ["dep:arbitrary"]
|
||||
# DEPRECATED. It is a no-op. SIMD is done automatically through runtime
|
||||
# dispatch.
|
||||
simd-accel = []
|
||||
|
@@ -1,3 +1,4 @@
|
||||
use std::fmt::Write;
|
||||
use std::path::{is_separator, Path};
|
||||
|
||||
use regex_automata::meta::Regex;
|
||||
@@ -70,7 +71,8 @@ impl MatchStrategy {
|
||||
///
|
||||
/// It cannot be used directly to match file paths, but it can be converted
|
||||
/// to a regular expression string or a matcher.
|
||||
#[derive(Clone, Debug, Eq)]
|
||||
#[derive(Clone, Eq)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
pub struct Glob {
|
||||
glob: String,
|
||||
re: String,
|
||||
@@ -78,6 +80,12 @@ pub struct Glob {
|
||||
tokens: Tokens,
|
||||
}
|
||||
|
||||
impl AsRef<Glob> for Glob {
|
||||
fn as_ref(&self) -> &Glob {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Glob {
|
||||
fn eq(&self, other: &Glob) -> bool {
|
||||
self.glob == other.glob && self.opts == other.opts
|
||||
@@ -91,6 +99,21 @@ impl std::hash::Hash for Glob {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Glob {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if f.alternate() {
|
||||
f.debug_struct("Glob")
|
||||
.field("glob", &self.glob)
|
||||
.field("re", &self.re)
|
||||
.field("opts", &self.opts)
|
||||
.field("tokens", &self.tokens)
|
||||
.finish()
|
||||
} else {
|
||||
f.debug_tuple("Glob").field(&self.glob).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Glob {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.glob.fmt(f)
|
||||
@@ -193,6 +216,7 @@ pub struct GlobBuilder<'a> {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
struct GlobOptions {
|
||||
/// Whether to match case insensitively.
|
||||
case_insensitive: bool,
|
||||
@@ -219,6 +243,7 @@ impl GlobOptions {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
struct Tokens(Vec<Token>);
|
||||
|
||||
impl std::ops::Deref for Tokens {
|
||||
@@ -235,6 +260,7 @@ impl std::ops::DerefMut for Tokens {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
enum Token {
|
||||
Literal(char),
|
||||
Any,
|
||||
@@ -563,25 +589,26 @@ impl<'a> GlobBuilder<'a> {
|
||||
pub fn build(&self) -> Result<Glob, Error> {
|
||||
let mut p = Parser {
|
||||
glob: &self.glob,
|
||||
stack: vec![Tokens::default()],
|
||||
alternates_stack: Vec::new(),
|
||||
branches: vec![Tokens::default()],
|
||||
chars: self.glob.chars().peekable(),
|
||||
prev: None,
|
||||
cur: None,
|
||||
opts: &self.opts,
|
||||
};
|
||||
p.parse()?;
|
||||
if p.stack.is_empty() {
|
||||
Err(Error {
|
||||
glob: Some(self.glob.to_string()),
|
||||
kind: ErrorKind::UnopenedAlternates,
|
||||
})
|
||||
} else if p.stack.len() > 1 {
|
||||
if p.branches.is_empty() {
|
||||
// OK because of how the the branches/alternate_stack are managed.
|
||||
// If we end up here, then there *must* be a bug in the parser
|
||||
// somewhere.
|
||||
unreachable!()
|
||||
} else if p.branches.len() > 1 {
|
||||
Err(Error {
|
||||
glob: Some(self.glob.to_string()),
|
||||
kind: ErrorKind::UnclosedAlternates,
|
||||
})
|
||||
} else {
|
||||
let tokens = p.stack.pop().unwrap();
|
||||
let tokens = p.branches.pop().unwrap();
|
||||
Ok(Glob {
|
||||
glob: self.glob.to_string(),
|
||||
re: tokens.to_regex_with(&self.opts),
|
||||
@@ -732,7 +759,9 @@ impl Tokens {
|
||||
/// Convert a Unicode scalar value to an escaped string suitable for use as
|
||||
/// a literal in a non-Unicode regex.
|
||||
fn char_to_escaped_literal(c: char) -> String {
|
||||
bytes_to_escaped_literal(&c.to_string().into_bytes())
|
||||
let mut buf = [0; 4];
|
||||
let bytes = c.encode_utf8(&mut buf).as_bytes();
|
||||
bytes_to_escaped_literal(bytes)
|
||||
}
|
||||
|
||||
/// Converts an arbitrary sequence of bytes to a UTF-8 string. All non-ASCII
|
||||
@@ -741,11 +770,12 @@ fn bytes_to_escaped_literal(bs: &[u8]) -> String {
|
||||
let mut s = String::with_capacity(bs.len());
|
||||
for &b in bs {
|
||||
if b <= 0x7F {
|
||||
s.push_str(®ex_syntax::escape(
|
||||
regex_syntax::escape_into(
|
||||
char::from(b).encode_utf8(&mut [0; 4]),
|
||||
));
|
||||
&mut s,
|
||||
);
|
||||
} else {
|
||||
s.push_str(&format!("\\x{:02x}", b));
|
||||
write!(&mut s, "\\x{:02x}", b).unwrap();
|
||||
}
|
||||
}
|
||||
s
|
||||
@@ -753,7 +783,11 @@ fn bytes_to_escaped_literal(bs: &[u8]) -> String {
|
||||
|
||||
struct Parser<'a> {
|
||||
glob: &'a str,
|
||||
stack: Vec<Tokens>,
|
||||
// Marks the index in `stack` where the alternation started.
|
||||
alternates_stack: Vec<usize>,
|
||||
// The set of active alternation branches being parsed.
|
||||
// Tokens are added to the end of the last one.
|
||||
branches: Vec<Tokens>,
|
||||
chars: std::iter::Peekable<std::str::Chars<'a>>,
|
||||
prev: Option<char>,
|
||||
cur: Option<char>,
|
||||
@@ -782,36 +816,37 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
|
||||
fn push_alternate(&mut self) -> Result<(), Error> {
|
||||
if self.stack.len() > 1 {
|
||||
return Err(self.error(ErrorKind::NestedAlternates));
|
||||
}
|
||||
Ok(self.stack.push(Tokens::default()))
|
||||
self.alternates_stack.push(self.branches.len());
|
||||
self.branches.push(Tokens::default());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pop_alternate(&mut self) -> Result<(), Error> {
|
||||
let mut alts = vec![];
|
||||
while self.stack.len() >= 2 {
|
||||
alts.push(self.stack.pop().unwrap());
|
||||
}
|
||||
self.push_token(Token::Alternates(alts))
|
||||
let Some(start) = self.alternates_stack.pop() else {
|
||||
return Err(self.error(ErrorKind::UnopenedAlternates));
|
||||
};
|
||||
assert!(start <= self.branches.len());
|
||||
let alts = Token::Alternates(self.branches.drain(start..).collect());
|
||||
self.push_token(alts)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_token(&mut self, tok: Token) -> Result<(), Error> {
|
||||
if let Some(ref mut pat) = self.stack.last_mut() {
|
||||
if let Some(ref mut pat) = self.branches.last_mut() {
|
||||
return Ok(pat.push(tok));
|
||||
}
|
||||
Err(self.error(ErrorKind::UnopenedAlternates))
|
||||
}
|
||||
|
||||
fn pop_token(&mut self) -> Result<Token, Error> {
|
||||
if let Some(ref mut pat) = self.stack.last_mut() {
|
||||
if let Some(ref mut pat) = self.branches.last_mut() {
|
||||
return Ok(pat.pop().unwrap());
|
||||
}
|
||||
Err(self.error(ErrorKind::UnopenedAlternates))
|
||||
}
|
||||
|
||||
fn have_tokens(&self) -> Result<bool, Error> {
|
||||
match self.stack.last() {
|
||||
match self.branches.last() {
|
||||
None => Err(self.error(ErrorKind::UnopenedAlternates)),
|
||||
Some(ref pat) => Ok(!pat.is_empty()),
|
||||
}
|
||||
@@ -820,11 +855,11 @@ impl<'a> Parser<'a> {
|
||||
fn parse_comma(&mut self) -> Result<(), Error> {
|
||||
// If we aren't inside a group alternation, then don't
|
||||
// treat commas specially. Otherwise, we need to start
|
||||
// a new alternate.
|
||||
if self.stack.len() <= 1 {
|
||||
// a new alternate branch.
|
||||
if self.alternates_stack.is_empty() {
|
||||
self.push_token(Token::Literal(','))
|
||||
} else {
|
||||
Ok(self.stack.push(Tokens::default()))
|
||||
Ok(self.branches.push(Tokens::default()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -861,7 +896,7 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
|
||||
if !prev.map(is_separator).unwrap_or(false) {
|
||||
if self.stack.len() <= 1
|
||||
if self.branches.len() <= 1
|
||||
|| (prev != Some(',') && prev != Some('{'))
|
||||
{
|
||||
self.push_token(Token::ZeroOrMore)?;
|
||||
@@ -874,7 +909,7 @@ impl<'a> Parser<'a> {
|
||||
assert!(self.bump().is_none());
|
||||
true
|
||||
}
|
||||
Some(',') | Some('}') if self.stack.len() >= 2 => true,
|
||||
Some(',') | Some('}') if self.branches.len() >= 2 => true,
|
||||
Some(c) if is_separator(c) => {
|
||||
assert!(self.bump().map(is_separator).unwrap_or(false));
|
||||
false
|
||||
@@ -1202,6 +1237,10 @@ mod tests {
|
||||
syntaxerr!(err_unclosed4, "[!]", ErrorKind::UnclosedClass);
|
||||
syntaxerr!(err_range1, "[z-a]", ErrorKind::InvalidRange('z', 'a'));
|
||||
syntaxerr!(err_range2, "[z--]", ErrorKind::InvalidRange('z', '-'));
|
||||
syntaxerr!(err_alt1, "{a,b", ErrorKind::UnclosedAlternates);
|
||||
syntaxerr!(err_alt2, "{a,{b,c}", ErrorKind::UnclosedAlternates);
|
||||
syntaxerr!(err_alt3, "a,b}", ErrorKind::UnopenedAlternates);
|
||||
syntaxerr!(err_alt4, "{a,b}}", ErrorKind::UnopenedAlternates);
|
||||
|
||||
const CASEI: Options =
|
||||
Options { casei: Some(true), litsep: None, bsesc: None, ealtre: None };
|
||||
@@ -1222,6 +1261,8 @@ mod tests {
|
||||
ealtre: Some(true),
|
||||
};
|
||||
|
||||
toregex!(re_empty, "", "^$");
|
||||
|
||||
toregex!(re_casei, "a", "(?i)^a$", &CASEI);
|
||||
|
||||
toregex!(re_slash1, "?", r"^[^/]$", SLASHLIT);
|
||||
@@ -1261,7 +1302,9 @@ mod tests {
|
||||
toregex!(re32, "/a**", r"^/a.*.*$");
|
||||
toregex!(re33, "/**a", r"^/.*.*a$");
|
||||
toregex!(re34, "/a**b", r"^/a.*.*b$");
|
||||
toregex!(re35, "{a,b}", r"^(?:b|a)$");
|
||||
toregex!(re35, "{a,b}", r"^(?:a|b)$");
|
||||
toregex!(re36, "{a,{b,c}}", r"^(?:a|(?:b|c))$");
|
||||
toregex!(re37, "{{a,b},{c,d}}", r"^(?:(?:a|b)|(?:c|d))$");
|
||||
|
||||
matches!(match1, "a", "a");
|
||||
matches!(match2, "a*b", "a_b");
|
||||
@@ -1349,6 +1392,9 @@ mod tests {
|
||||
matches!(matchalt14, "foo{,.txt}", "foo.txt");
|
||||
nmatches!(matchalt15, "foo{,.txt}", "foo");
|
||||
matches!(matchalt16, "foo{,.txt}", "foo", EALTRE);
|
||||
matches!(matchalt17, "{a,b{c,d}}", "bc");
|
||||
matches!(matchalt18, "{a,b{c,d}}", "bd");
|
||||
matches!(matchalt19, "{a,b{c,d}}", "a");
|
||||
|
||||
matches!(matchslash1, "abc/def", "abc/def", SLASHLIT);
|
||||
#[cfg(unix)]
|
||||
|
@@ -94,6 +94,19 @@ Standard Unix-style glob syntax is supported:
|
||||
|
||||
A `GlobBuilder` can be used to prevent wildcards from matching path separators,
|
||||
or to enable case insensitive matching.
|
||||
|
||||
# Crate Features
|
||||
|
||||
This crate includes optional features that can be enabled if necessary.
|
||||
These features are not required but may be useful depending on the use case.
|
||||
|
||||
The following features are available:
|
||||
|
||||
* **arbitrary** -
|
||||
Enabling this feature introduces a public dependency on the
|
||||
[`arbitrary`](https://crates.io/crates/arbitrary)
|
||||
crate. Namely, it implements the `Arbitrary` trait from that crate for the
|
||||
[`Glob`] type. This feature is disabled by default.
|
||||
*/
|
||||
|
||||
#![deny(missing_docs)]
|
||||
@@ -150,6 +163,7 @@ pub struct Error {
|
||||
|
||||
/// The kind of error that can occur when parsing a glob pattern.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum ErrorKind {
|
||||
/// **DEPRECATED**.
|
||||
///
|
||||
@@ -169,20 +183,16 @@ pub enum ErrorKind {
|
||||
UnopenedAlternates,
|
||||
/// Occurs when a `{` is found without a matching `}`.
|
||||
UnclosedAlternates,
|
||||
/// Occurs when an alternating group is nested inside another alternating
|
||||
/// group, e.g., `{{a,b},{c,d}}`.
|
||||
/// **DEPRECATED**.
|
||||
///
|
||||
/// This error used to occur when an alternating group was nested inside
|
||||
/// another alternating group, e.g., `{{a,b},{c,d}}`. However, this is now
|
||||
/// supported and as such this error cannot occur.
|
||||
NestedAlternates,
|
||||
/// Occurs when an unescaped '\' is found at the end of a glob.
|
||||
DanglingEscape,
|
||||
/// An error associated with parsing or compiling a regex.
|
||||
Regex(String),
|
||||
/// Hints that destructuring should not be exhaustive.
|
||||
///
|
||||
/// This enum may grow additional variants, so this makes sure clients
|
||||
/// don't count on exhaustive matching. (Otherwise, adding a new variant
|
||||
/// could break existing code.)
|
||||
#[doc(hidden)]
|
||||
__Nonexhaustive,
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
@@ -226,7 +236,6 @@ impl ErrorKind {
|
||||
}
|
||||
ErrorKind::DanglingEscape => "dangling '\\'",
|
||||
ErrorKind::Regex(ref err) => err,
|
||||
ErrorKind::__Nonexhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,7 +264,6 @@ impl std::fmt::Display for ErrorKind {
|
||||
ErrorKind::InvalidRange(s, e) => {
|
||||
write!(f, "invalid range; '{}' > '{}'", s, e)
|
||||
}
|
||||
ErrorKind::__Nonexhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,7 +322,7 @@ impl GlobSet {
|
||||
|
||||
/// Create an empty `GlobSet`. An empty set matches nothing.
|
||||
#[inline]
|
||||
pub fn empty() -> GlobSet {
|
||||
pub const fn empty() -> GlobSet {
|
||||
GlobSet { len: 0, strats: vec![] }
|
||||
}
|
||||
|
||||
@@ -351,6 +359,43 @@ impl GlobSet {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if all globs in this set match the path given.
|
||||
///
|
||||
/// This will return true if the set of globs is empty, as in that case all
|
||||
/// `0` of the globs will match.
|
||||
///
|
||||
/// ```
|
||||
/// use globset::{Glob, GlobSetBuilder};
|
||||
///
|
||||
/// let mut builder = GlobSetBuilder::new();
|
||||
/// builder.add(Glob::new("src/*").unwrap());
|
||||
/// builder.add(Glob::new("**/*.rs").unwrap());
|
||||
/// let set = builder.build().unwrap();
|
||||
///
|
||||
/// assert!(set.matches_all("src/foo.rs"));
|
||||
/// assert!(!set.matches_all("src/bar.c"));
|
||||
/// assert!(!set.matches_all("test.rs"));
|
||||
/// ```
|
||||
pub fn matches_all<P: AsRef<Path>>(&self, path: P) -> bool {
|
||||
self.matches_all_candidate(&Candidate::new(path.as_ref()))
|
||||
}
|
||||
|
||||
/// Returns ture if all globs in this set match the path given.
|
||||
///
|
||||
/// This takes a Candidate as input, which can be used to amortize the cost
|
||||
/// of peparing a path for matching.
|
||||
///
|
||||
/// This will return true if the set of globs is empty, as in that case all
|
||||
/// `0` of the globs will match.
|
||||
pub fn matches_all_candidate(&self, path: &Candidate<'_>) -> bool {
|
||||
for strat in &self.strats {
|
||||
if !strat.is_match(path) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns the sequence number of every glob pattern that matches the
|
||||
/// given path.
|
||||
pub fn matches<P: AsRef<Path>>(&self, path: P) -> Vec<usize> {
|
||||
@@ -410,10 +455,20 @@ impl GlobSet {
|
||||
into.dedup();
|
||||
}
|
||||
|
||||
fn new(pats: &[Glob]) -> Result<GlobSet, Error> {
|
||||
if pats.is_empty() {
|
||||
return Ok(GlobSet { len: 0, strats: vec![] });
|
||||
/// Builds a new matcher from a collection of Glob patterns.
|
||||
///
|
||||
/// Once a matcher is built, no new patterns can be added to it.
|
||||
pub fn new<I, G>(globs: I) -> Result<GlobSet, Error>
|
||||
where
|
||||
I: IntoIterator<Item = G>,
|
||||
G: AsRef<Glob>,
|
||||
{
|
||||
let mut it = globs.into_iter().peekable();
|
||||
if it.peek().is_none() {
|
||||
return Ok(GlobSet::empty());
|
||||
}
|
||||
|
||||
let mut len = 0;
|
||||
let mut lits = LiteralStrategy::new();
|
||||
let mut base_lits = BasenameLiteralStrategy::new();
|
||||
let mut exts = ExtensionStrategy::new();
|
||||
@@ -421,7 +476,10 @@ impl GlobSet {
|
||||
let mut suffixes = MultiStrategyBuilder::new();
|
||||
let mut required_exts = RequiredExtensionStrategyBuilder::new();
|
||||
let mut regexes = MultiStrategyBuilder::new();
|
||||
for (i, p) in pats.iter().enumerate() {
|
||||
for (i, p) in it.enumerate() {
|
||||
len += 1;
|
||||
|
||||
let p = p.as_ref();
|
||||
match MatchStrategy::new(p) {
|
||||
MatchStrategy::Literal(lit) => {
|
||||
lits.add(i, lit);
|
||||
@@ -461,20 +519,33 @@ impl GlobSet {
|
||||
required_exts.0.len(),
|
||||
regexes.literals.len()
|
||||
);
|
||||
Ok(GlobSet {
|
||||
len: pats.len(),
|
||||
strats: vec![
|
||||
GlobSetMatchStrategy::Extension(exts),
|
||||
GlobSetMatchStrategy::BasenameLiteral(base_lits),
|
||||
GlobSetMatchStrategy::Literal(lits),
|
||||
GlobSetMatchStrategy::Suffix(suffixes.suffix()),
|
||||
GlobSetMatchStrategy::Prefix(prefixes.prefix()),
|
||||
GlobSetMatchStrategy::RequiredExtension(
|
||||
required_exts.build()?,
|
||||
),
|
||||
GlobSetMatchStrategy::Regex(regexes.regex_set()?),
|
||||
],
|
||||
})
|
||||
let mut strats = Vec::with_capacity(7);
|
||||
// Only add strategies that are populated
|
||||
if !exts.0.is_empty() {
|
||||
strats.push(GlobSetMatchStrategy::Extension(exts));
|
||||
}
|
||||
if !base_lits.0.is_empty() {
|
||||
strats.push(GlobSetMatchStrategy::BasenameLiteral(base_lits));
|
||||
}
|
||||
if !lits.0.is_empty() {
|
||||
strats.push(GlobSetMatchStrategy::Literal(lits));
|
||||
}
|
||||
if !suffixes.is_empty() {
|
||||
strats.push(GlobSetMatchStrategy::Suffix(suffixes.suffix()));
|
||||
}
|
||||
if !prefixes.is_empty() {
|
||||
strats.push(GlobSetMatchStrategy::Prefix(prefixes.prefix()));
|
||||
}
|
||||
if !required_exts.0.is_empty() {
|
||||
strats.push(GlobSetMatchStrategy::RequiredExtension(
|
||||
required_exts.build()?,
|
||||
));
|
||||
}
|
||||
if !regexes.is_empty() {
|
||||
strats.push(GlobSetMatchStrategy::Regex(regexes.regex_set()?));
|
||||
}
|
||||
|
||||
Ok(GlobSet { len, strats })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,7 +575,7 @@ impl GlobSetBuilder {
|
||||
///
|
||||
/// Once a matcher is built, no new patterns can be added to it.
|
||||
pub fn build(&self) -> Result<GlobSet, Error> {
|
||||
GlobSet::new(&self.pats)
|
||||
GlobSet::new(self.pats.iter())
|
||||
}
|
||||
|
||||
/// Add a new pattern to this set.
|
||||
@@ -540,7 +611,23 @@ impl<'a> std::fmt::Debug for Candidate<'a> {
|
||||
impl<'a> Candidate<'a> {
|
||||
/// Create a new candidate for matching from the given path.
|
||||
pub fn new<P: AsRef<Path> + ?Sized>(path: &'a P) -> Candidate<'a> {
|
||||
let path = normalize_path(Vec::from_path_lossy(path.as_ref()));
|
||||
Self::from_cow(Vec::from_path_lossy(path.as_ref()))
|
||||
}
|
||||
|
||||
/// Create a new candidate for matching from the given path as a sequence
|
||||
/// of bytes.
|
||||
///
|
||||
/// Generally speaking, this routine expects the bytes to be
|
||||
/// _conventionally_ UTF-8. It is legal for the byte sequence to contain
|
||||
/// invalid UTF-8. However, if the bytes are in some other encoding that
|
||||
/// isn't ASCII compatible (for example, UTF-16), then the results of
|
||||
/// matching are unspecified.
|
||||
pub fn from_bytes<P: AsRef<[u8]> + ?Sized>(path: &'a P) -> Candidate<'a> {
|
||||
Self::from_cow(Cow::Borrowed(path.as_ref()))
|
||||
}
|
||||
|
||||
fn from_cow(path: Cow<'a, [u8]>) -> Candidate<'a> {
|
||||
let path = normalize_path(path);
|
||||
let basename = file_name(&path).unwrap_or(Cow::Borrowed(B("")));
|
||||
let ext = file_name_ext(&basename).unwrap_or(Cow::Borrowed(B("")));
|
||||
Candidate { path, basename, ext }
|
||||
@@ -892,6 +979,10 @@ impl MultiStrategyBuilder {
|
||||
patset: Arc::new(Pool::new(create)),
|
||||
})
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.literals.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -928,13 +1019,26 @@ impl RequiredExtensionStrategyBuilder {
|
||||
///
|
||||
/// The escaping works by surrounding meta-characters with brackets. For
|
||||
/// example, `*` becomes `[*]`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use globset::escape;
|
||||
///
|
||||
/// assert_eq!(escape("foo*bar"), "foo[*]bar");
|
||||
/// assert_eq!(escape("foo?bar"), "foo[?]bar");
|
||||
/// assert_eq!(escape("foo[bar"), "foo[[]bar");
|
||||
/// assert_eq!(escape("foo]bar"), "foo[]]bar");
|
||||
/// assert_eq!(escape("foo{bar"), "foo[{]bar");
|
||||
/// assert_eq!(escape("foo}bar"), "foo[}]bar");
|
||||
/// ```
|
||||
pub fn escape(s: &str) -> String {
|
||||
let mut escaped = String::with_capacity(s.len());
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
// note that ! does not need escaping because it is only special
|
||||
// inside brackets
|
||||
'?' | '*' | '[' | ']' => {
|
||||
'?' | '*' | '[' | ']' | '{' | '}' => {
|
||||
escaped.push('[');
|
||||
escaped.push(c);
|
||||
escaped.push(']');
|
||||
@@ -979,6 +1083,7 @@ mod tests {
|
||||
let set = GlobSetBuilder::new().build().unwrap();
|
||||
assert!(!set.is_match(""));
|
||||
assert!(!set.is_match("a"));
|
||||
assert!(set.matches_all("a"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1019,4 +1124,16 @@ mod tests {
|
||||
let matches = set.matches("nada");
|
||||
assert_eq!(0, matches.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug() {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
builder.add(Glob::new("*foo*").unwrap());
|
||||
builder.add(Glob::new("*bar*").unwrap());
|
||||
builder.add(Glob::new("*quux*").unwrap());
|
||||
assert_eq!(
|
||||
format!("{builder:?}"),
|
||||
"GlobSetBuilder { pats: [Glob(\"*foo*\"), Glob(\"*bar*\"), Glob(\"*quux*\")] }",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -4,21 +4,25 @@ use bstr::{ByteSlice, ByteVec};
|
||||
|
||||
/// The final component of the path, if it is a normal file.
|
||||
///
|
||||
/// If the path terminates in `.`, `..`, or consists solely of a root of
|
||||
/// prefix, file_name will return None.
|
||||
/// If the path terminates in `..`, or consists solely of a root of prefix,
|
||||
/// file_name will return `None`.
|
||||
pub(crate) fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
|
||||
if path.last_byte().map_or(true, |b| b == b'.') {
|
||||
if path.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let last_slash = path.rfind_byte(b'/').map(|i| i + 1).unwrap_or(0);
|
||||
Some(match *path {
|
||||
let got = match *path {
|
||||
Cow::Borrowed(path) => Cow::Borrowed(&path[last_slash..]),
|
||||
Cow::Owned(ref path) => {
|
||||
let mut path = path.clone();
|
||||
path.drain_bytes(..last_slash);
|
||||
Cow::Owned(path)
|
||||
}
|
||||
})
|
||||
};
|
||||
if got == &b".."[..] {
|
||||
return None;
|
||||
}
|
||||
Some(got)
|
||||
}
|
||||
|
||||
/// Return a file extension given a path's file name.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grep"
|
||||
version = "0.3.1" #:version
|
||||
version = "0.3.2" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Fast line oriented regex searching as a library.
|
||||
@@ -14,20 +14,20 @@ license = "Unlicense OR MIT"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
grep-cli = { version = "0.1.10", path = "../cli" }
|
||||
grep-cli = { version = "0.1.11", path = "../cli" }
|
||||
grep-matcher = { version = "0.1.7", path = "../matcher" }
|
||||
grep-pcre2 = { version = "0.1.7", path = "../pcre2", optional = true }
|
||||
grep-printer = { version = "0.2.1", path = "../printer" }
|
||||
grep-regex = { version = "0.1.12", path = "../regex" }
|
||||
grep-searcher = { version = "0.1.13", path = "../searcher" }
|
||||
grep-pcre2 = { version = "0.1.8", path = "../pcre2", optional = true }
|
||||
grep-printer = { version = "0.2.2", path = "../printer" }
|
||||
grep-regex = { version = "0.1.13", path = "../regex" }
|
||||
grep-searcher = { version = "0.1.14", path = "../searcher" }
|
||||
|
||||
[dev-dependencies]
|
||||
termcolor = "1.0.4"
|
||||
walkdir = "2.2.7"
|
||||
|
||||
[features]
|
||||
simd-accel = ["grep-searcher/simd-accel"]
|
||||
pcre2 = ["grep-pcre2"]
|
||||
|
||||
# This feature is DEPRECATED. Runtime dispatch is used for SIMD now.
|
||||
# These features are DEPRECATED. Runtime dispatch is used for SIMD now.
|
||||
simd-accel = []
|
||||
avx-accel = []
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ignore"
|
||||
version = "0.4.21" #:version
|
||||
version = "0.4.23" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
A fast library for efficiently matching ignore files such as `.gitignore`
|
||||
@@ -20,7 +20,7 @@ bench = false
|
||||
|
||||
[dependencies]
|
||||
crossbeam-deque = "0.8.3"
|
||||
globset = { version = "0.4.14", path = "../globset" }
|
||||
globset = { version = "0.4.15", path = "../globset" }
|
||||
log = "0.4.20"
|
||||
memchr = "2.6.3"
|
||||
same-file = "1.0.6"
|
||||
@@ -36,7 +36,7 @@ version = "0.1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
bstr = { version = "1.6.2", default-features = false, features = ["std"] }
|
||||
crossbeam-channel = "0.5.8"
|
||||
crossbeam-channel = "0.5.15"
|
||||
|
||||
[features]
|
||||
# DEPRECATED. It is a no-op. SIMD is done automatically through runtime
|
||||
|
@@ -27,9 +27,10 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
(&["bat", "batch"], &["*.bat"]),
|
||||
(&["bazel"], &[
|
||||
"*.bazel", "*.bzl", "*.BUILD", "*.bazelrc", "BUILD", "MODULE.bazel",
|
||||
"WORKSPACE", "WORKSPACE.bazel",
|
||||
"WORKSPACE", "WORKSPACE.bazel", "WORKSPACE.bzlmod",
|
||||
]),
|
||||
(&["bitbake"], &["*.bb", "*.bbappend", "*.bbclass", "*.conf", "*.inc"]),
|
||||
(&["boxlang"], &["*.bx", "*.bxm", "*.bxs"]),
|
||||
(&["brotli"], &["*.br"]),
|
||||
(&["buildstream"], &["*.bst"]),
|
||||
(&["bzip2"], &["*.bz2", "*.tbz2"]),
|
||||
@@ -39,6 +40,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
(&["carp"], &["*.carp"]),
|
||||
(&["cbor"], &["*.cbor"]),
|
||||
(&["ceylon"], &["*.ceylon"]),
|
||||
(&["cfml"], &["*.cfc", "*.cfm"]),
|
||||
(&["clojure"], &["*.clj", "*.cljc", "*.cljs", "*.cljx"]),
|
||||
(&["cmake"], &["*.cmake", "CMakeLists.txt"]),
|
||||
(&["cmd"], &["*.bat", "*.cmd"]),
|
||||
@@ -62,7 +64,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
(&["cython"], &["*.pyx", "*.pxi", "*.pxd"]),
|
||||
(&["d"], &["*.d"]),
|
||||
(&["dart"], &["*.dart"]),
|
||||
(&["devicetree"], &["*.dts", "*.dtsi"]),
|
||||
(&["devicetree"], &["*.dts", "*.dtsi", "*.dtso"]),
|
||||
(&["dhall"], &["*.dhall"]),
|
||||
(&["diff"], &["*.patch", "*.diff"]),
|
||||
(&["dita"], &["*.dita", "*.ditamap", "*.ditaval"]),
|
||||
@@ -88,6 +90,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
(&["fsharp"], &["*.fs", "*.fsx", "*.fsi"]),
|
||||
(&["fut"], &["*.fut"]),
|
||||
(&["gap"], &["*.g", "*.gap", "*.gi", "*.gd", "*.tst"]),
|
||||
(&["gleam"], &["*.gleam"]),
|
||||
(&["gn"], &["*.gn", "*.gni"]),
|
||||
(&["go"], &["*.go"]),
|
||||
(&["gprbuild"], &["*.gpr"]),
|
||||
@@ -117,7 +120,9 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
(&["julia"], &["*.jl"]),
|
||||
(&["jupyter"], &["*.ipynb", "*.jpynb"]),
|
||||
(&["k"], &["*.k"]),
|
||||
(&["kconfig"], &["Kconfig", "Kconfig.*"]),
|
||||
(&["kotlin"], &["*.kt", "*.kts"]),
|
||||
(&["lean"], &["*.lean"]),
|
||||
(&["less"], &["*.less"]),
|
||||
(&["license"], &[
|
||||
// General
|
||||
@@ -148,6 +153,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
]),
|
||||
(&["lilypond"], &["*.ly", "*.ily"]),
|
||||
(&["lisp"], &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
|
||||
(&["llvm"], &["*.ll"]),
|
||||
(&["lock"], &["*.lock", "package-lock.json"]),
|
||||
(&["log"], &["*.log"]),
|
||||
(&["lua"], &["*.lua"]),
|
||||
@@ -158,6 +164,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
"[Gg][Nn][Uu]makefile", "[Mm]akefile",
|
||||
"[Gg][Nn][Uu]makefile.am", "[Mm]akefile.am",
|
||||
"[Gg][Nn][Uu]makefile.in", "[Mm]akefile.in",
|
||||
"Makefile.*",
|
||||
"*.mk", "*.mak"
|
||||
]),
|
||||
(&["mako"], &["*.mako", "*.mao"]),
|
||||
@@ -172,7 +179,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
"*.mdx",
|
||||
]),
|
||||
(&["matlab"], &["*.m"]),
|
||||
(&["meson"], &["meson.build", "meson_options.txt"]),
|
||||
(&["meson"], &["meson.build", "meson_options.txt", "meson.options"]),
|
||||
(&["minified"], &["*.min.html", "*.min.css", "*.min.js"]),
|
||||
(&["mint"], &["*.mint"]),
|
||||
(&["mk"], &["mkfile"]),
|
||||
@@ -180,7 +187,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
(&["motoko"], &["*.mo"]),
|
||||
(&["msbuild"], &[
|
||||
"*.csproj", "*.fsproj", "*.vcxproj", "*.proj", "*.props", "*.targets",
|
||||
"*.sln",
|
||||
"*.sln", "*.slnf"
|
||||
]),
|
||||
(&["nim"], &["*.nim", "*.nimf", "*.nimble", "*.nims"]),
|
||||
(&["nix"], &["*.nix"]),
|
||||
@@ -226,14 +233,16 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
// Idiomatic files
|
||||
"config.ru", "Gemfile", ".irbrc", "Rakefile",
|
||||
// Extensions
|
||||
"*.gemspec", "*.rb", "*.rbw"
|
||||
"*.gemspec", "*.rb", "*.rbw", "*.rake"
|
||||
]),
|
||||
(&["rust"], &["*.rs"]),
|
||||
(&["sass"], &["*.sass", "*.scss"]),
|
||||
(&["scala"], &["*.scala", "*.sbt"]),
|
||||
(&["scdoc"], &["*.scd", "*.scdoc"]),
|
||||
(&["seed7"], &["*.sd7", "*.s7i"]),
|
||||
(&["sh"], &[
|
||||
// Portable/misc. init files
|
||||
".login", ".logout", ".profile", "profile",
|
||||
".env", ".login", ".logout", ".profile", "profile",
|
||||
// bash-specific init files
|
||||
".bash_login", "bash_login",
|
||||
".bash_logout", "bash_logout",
|
||||
@@ -252,7 +261,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
".zprofile", "zprofile",
|
||||
".zshrc", "zshrc",
|
||||
// Extensions
|
||||
"*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh", "*.zsh",
|
||||
"*.bash", "*.csh", "*.env", "*.ksh", "*.sh", "*.tcsh", "*.zsh",
|
||||
]),
|
||||
(&["slim"], &["*.skim", "*.slim", "*.slime"]),
|
||||
(&["smarty"], &["*.tpl"]),
|
||||
@@ -264,6 +273,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
(&["sql"], &["*.sql", "*.psql"]),
|
||||
(&["stylus"], &["*.styl"]),
|
||||
(&["sv"], &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
|
||||
(&["svelte"], &["*.svelte", "*.svelte.ts"]),
|
||||
(&["svg"], &["*.svg"]),
|
||||
(&["swift"], &["*.swift"]),
|
||||
(&["swig"], &["*.def", "*.i"]),
|
||||
@@ -278,9 +288,8 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
(&["texinfo"], &["*.texi"]),
|
||||
(&["textile"], &["*.textile"]),
|
||||
(&["tf"], &[
|
||||
"*.tf", "*.auto.tfvars", "terraform.tfvars", "*.tf.json",
|
||||
"*.auto.tfvars.json", "terraform.tfvars.json", "*.terraformrc",
|
||||
"terraform.rc", "*.tfrc", "*.terraform.lock.hcl",
|
||||
"*.tf", "*.tf.json", "*.tfvars", "*.tfvars.json",
|
||||
"*.terraformrc", "terraform.rc", "*.tfrc", "*.terraform.lock.hcl",
|
||||
]),
|
||||
(&["thrift"], &["*.thrift"]),
|
||||
(&["toml"], &["*.toml", "Cargo.lock"]),
|
||||
@@ -288,6 +297,7 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
(&["twig"], &["*.twig"]),
|
||||
(&["txt"], &["*.txt"]),
|
||||
(&["typoscript"], &["*.typoscript", "*.ts"]),
|
||||
(&["typst"], &["*.typ"]),
|
||||
(&["usd"], &["*.usd", "*.usda", "*.usdc"]),
|
||||
(&["v"], &["*.v", "*.vsh"]),
|
||||
(&["vala"], &["*.vala"]),
|
||||
@@ -301,7 +311,9 @@ pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
|
||||
(&["vimscript"], &[
|
||||
"*.vim", ".vimrc", ".gvimrc", "vimrc", "gvimrc", "_vimrc", "_gvimrc",
|
||||
]),
|
||||
(&["vue"], &["*.vue"]),
|
||||
(&["webidl"], &["*.idl", "*.webidl", "*.widl"]),
|
||||
(&["wgsl"], &["*.wgsl"]),
|
||||
(&["wiki"], &["*.mediawiki", "*.wiki"]),
|
||||
(&["xml"], &[
|
||||
"*.xml", "*.xml.dist", "*.dtd", "*.xsl", "*.xslt", "*.xsd", "*.xjb",
|
||||
|
@@ -19,7 +19,7 @@ use std::{
|
||||
fs::{File, FileType},
|
||||
io::{self, BufRead},
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, RwLock},
|
||||
sync::{Arc, RwLock, Weak},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -34,11 +34,13 @@ use crate::{
|
||||
/// IgnoreMatch represents information about where a match came from when using
|
||||
/// the `Ignore` matcher.
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct IgnoreMatch<'a>(IgnoreMatchInner<'a>);
|
||||
|
||||
/// IgnoreMatchInner describes precisely where the match information came from.
|
||||
/// This is private to allow expansion to more matchers in the future.
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
enum IgnoreMatchInner<'a> {
|
||||
Override(overrides::Glob<'a>),
|
||||
Gitignore(&'a gitignore::Glob),
|
||||
@@ -99,7 +101,7 @@ struct IgnoreInner {
|
||||
/// Note that this is never used during matching, only when adding new
|
||||
/// parent directory matchers. This avoids needing to rebuild glob sets for
|
||||
/// parent directories if many paths are being searched.
|
||||
compiled: Arc<RwLock<HashMap<OsString, Ignore>>>,
|
||||
compiled: Arc<RwLock<HashMap<OsString, Weak<IgnoreInner>>>>,
|
||||
/// The path to the directory that this matcher was built from.
|
||||
dir: PathBuf,
|
||||
/// An override matcher (default is empty).
|
||||
@@ -198,9 +200,11 @@ impl Ignore {
|
||||
let mut ig = self.clone();
|
||||
for parent in parents.into_iter().rev() {
|
||||
let mut compiled = self.0.compiled.write().unwrap();
|
||||
if let Some(prebuilt) = compiled.get(parent.as_os_str()) {
|
||||
ig = prebuilt.clone();
|
||||
continue;
|
||||
if let Some(weak) = compiled.get(parent.as_os_str()) {
|
||||
if let Some(prebuilt) = weak.upgrade() {
|
||||
ig = Ignore(prebuilt);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let (mut igtmp, err) = ig.add_child_path(parent);
|
||||
errs.maybe_push(err);
|
||||
@@ -208,12 +212,16 @@ impl Ignore {
|
||||
igtmp.absolute_base = Some(absolute_base.clone());
|
||||
igtmp.has_git =
|
||||
if self.0.opts.require_git && self.0.opts.git_ignore {
|
||||
parent.join(".git").exists()
|
||||
parent.join(".git").exists() || parent.join(".jj").exists()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
ig = Ignore(Arc::new(igtmp));
|
||||
compiled.insert(parent.as_os_str().to_os_string(), ig.clone());
|
||||
let ig_arc = Arc::new(igtmp);
|
||||
ig = Ignore(ig_arc.clone());
|
||||
compiled.insert(
|
||||
parent.as_os_str().to_os_string(),
|
||||
Arc::downgrade(&ig_arc),
|
||||
);
|
||||
}
|
||||
(ig, errs.into_error_option())
|
||||
}
|
||||
@@ -243,7 +251,7 @@ impl Ignore {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let has_git = git_type.map(|_| true).unwrap_or(false);
|
||||
let has_git = git_type.is_some() || dir.join(".jj").exists();
|
||||
|
||||
let mut errs = PartialErrorBuilder::default();
|
||||
let custom_ig_matcher = if self.0.custom_ignore_filenames.is_empty() {
|
||||
@@ -282,6 +290,7 @@ impl Ignore {
|
||||
errs.maybe_push(err);
|
||||
m
|
||||
};
|
||||
|
||||
let gi_exclude_matcher = if !self.0.opts.git_exclude {
|
||||
Gitignore::empty()
|
||||
} else {
|
||||
@@ -453,21 +462,23 @@ impl Ignore {
|
||||
// off of `path`. Overall, this seems a little ham-fisted, but
|
||||
// it does fix a nasty bug. It should do fine until we overhaul
|
||||
// this crate.
|
||||
let dirpath = self.0.dir.as_path();
|
||||
let path_prefix = match strip_prefix("./", dirpath) {
|
||||
None => dirpath,
|
||||
Some(stripped_dot_slash) => stripped_dot_slash,
|
||||
};
|
||||
let path = match strip_prefix(path_prefix, path) {
|
||||
None => abs_parent_path.join(path),
|
||||
Some(p) => {
|
||||
let p = match strip_prefix("/", p) {
|
||||
None => p,
|
||||
Some(p) => p,
|
||||
};
|
||||
abs_parent_path.join(p)
|
||||
}
|
||||
};
|
||||
let path = abs_parent_path.join(
|
||||
self.parents()
|
||||
.take_while(|ig| !ig.0.is_absolute_parent)
|
||||
.last()
|
||||
.map_or(path, |ig| {
|
||||
strip_if_is_prefix(
|
||||
"/",
|
||||
strip_if_is_prefix(
|
||||
strip_if_is_prefix(
|
||||
"./",
|
||||
ig.0.dir.as_path(),
|
||||
),
|
||||
path,
|
||||
),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
for ig in
|
||||
self.parents().skip_while(|ig| !ig.0.is_absolute_parent)
|
||||
@@ -866,6 +877,15 @@ fn resolve_git_commondir(
|
||||
Ok(commondir_abs)
|
||||
}
|
||||
|
||||
/// Strips `prefix` from `path` if it's a prefix, otherwise returns `path`
|
||||
/// unchanged.
|
||||
fn strip_if_is_prefix<'a, P: AsRef<Path> + ?Sized>(
|
||||
prefix: &'a P,
|
||||
path: &'a Path,
|
||||
) -> &'a Path {
|
||||
strip_prefix(prefix, path).map_or(path, |p| p)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{io::Write, path::Path};
|
||||
@@ -935,6 +955,19 @@ mod tests {
|
||||
assert!(ig.matched("baz", false).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gitignore_with_jj() {
|
||||
let td = tmpdir();
|
||||
mkdirp(td.path().join(".jj"));
|
||||
wfile(td.path().join(".gitignore"), "foo\n!bar");
|
||||
|
||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert!(err.is_none());
|
||||
assert!(ig.matched("foo", false).is_ignore());
|
||||
assert!(ig.matched("bar", false).is_whitelist());
|
||||
assert!(ig.matched("baz", false).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gitignore_no_git() {
|
||||
let td = tmpdir();
|
||||
|
@@ -390,6 +390,7 @@ impl GitignoreBuilder {
|
||||
Err(err) => return Some(Error::Io(err).with_path(path)),
|
||||
Ok(file) => file,
|
||||
};
|
||||
log::debug!("opened gitignore file: {}", path.display());
|
||||
let rdr = BufReader::new(file);
|
||||
let mut errs = PartialErrorBuilder::default();
|
||||
for (i, line) in rdr.lines().enumerate() {
|
||||
@@ -401,6 +402,12 @@ impl GitignoreBuilder {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Match Git's handling of .gitignore files that begin with the Unicode BOM
|
||||
const UTF8_BOM: &str = "\u{feff}";
|
||||
let line =
|
||||
if i == 0 { line.trim_start_matches(UTF8_BOM) } else { &line };
|
||||
|
||||
if let Err(err) = self.add_line(Some(path.to_path_buf()), &line) {
|
||||
errs.push(err.tagged(path, lineno));
|
||||
}
|
||||
|
@@ -527,7 +527,7 @@ mod tests {
|
||||
|
||||
let tmpdir = env::temp_dir();
|
||||
for _ in 0..TRIES {
|
||||
let count = COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||
let count = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
let path = tmpdir.join("rust-ignore").join(count.to_string());
|
||||
if path.is_dir() {
|
||||
continue;
|
||||
|
@@ -23,9 +23,11 @@ use crate::{
|
||||
/// The lifetime `'a` refers to the lifetime of the matcher that produced
|
||||
/// this glob.
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Glob<'a>(GlobInner<'a>);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
enum GlobInner<'a> {
|
||||
/// No glob matched, but the file path should still be ignored.
|
||||
UnmatchedIgnore,
|
||||
|
@@ -11,7 +11,7 @@ use std::{
|
||||
use {
|
||||
crossbeam_deque::{Stealer, Worker as Deque},
|
||||
same_file::Handle,
|
||||
walkdir::{self, WalkDir},
|
||||
walkdir::WalkDir,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -591,7 +591,7 @@ impl WalkBuilder {
|
||||
///
|
||||
/// Note that this *doesn't* return something that implements `Iterator`.
|
||||
/// Instead, the returned value must be run with a closure. e.g.,
|
||||
/// `builder.build_parallel().run(|| |path| println!("{:?}", path))`.
|
||||
/// `builder.build_parallel().run(|| |path| { println!("{path:?}"); WalkState::Continue })`.
|
||||
pub fn build_parallel(&self) -> WalkParallel {
|
||||
WalkParallel {
|
||||
paths: self.paths.clone().into_iter(),
|
||||
@@ -798,6 +798,10 @@ impl WalkBuilder {
|
||||
///
|
||||
/// When disabled, git-related ignore rules are applied even when searching
|
||||
/// outside a git repository.
|
||||
///
|
||||
/// In particular, if this is `false` then `.gitignore` files will be read
|
||||
/// from parent directories above the git root directory containing `.git`,
|
||||
/// which is different from the git behavior.
|
||||
pub fn require_git(&mut self, yes: bool) -> &mut WalkBuilder {
|
||||
self.ig_builder.require_git(yes);
|
||||
self
|
||||
@@ -1305,7 +1309,7 @@ impl WalkParallel {
|
||||
|
||||
fn threads(&self) -> usize {
|
||||
if self.threads == 0 {
|
||||
2
|
||||
std::thread::available_parallelism().map_or(1, |n| n.get()).min(12)
|
||||
} else {
|
||||
self.threads
|
||||
}
|
||||
@@ -1420,8 +1424,11 @@ impl Stack {
|
||||
stealers: stealers.clone(),
|
||||
})
|
||||
.collect();
|
||||
// Distribute the initial messages.
|
||||
// Distribute the initial messages, reverse the order to cancel out
|
||||
// the other reversal caused by the inherent LIFO processing of the
|
||||
// per-thread stacks which are filled here.
|
||||
init.into_iter()
|
||||
.rev()
|
||||
.zip(stacks.iter().cycle())
|
||||
.for_each(|(m, s)| s.push(m));
|
||||
stacks
|
||||
|
2
crates/ignore/tests/gitignore_skip_bom.gitignore
Normal file
2
crates/ignore/tests/gitignore_skip_bom.gitignore
Normal file
@@ -0,0 +1,2 @@
|
||||
ignore/this/path
|
||||
# This file begins with a BOM (U+FEFF)
|
17
crates/ignore/tests/gitignore_skip_bom.rs
Normal file
17
crates/ignore/tests/gitignore_skip_bom.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use ignore::gitignore::GitignoreBuilder;
|
||||
|
||||
const IGNORE_FILE: &'static str = "tests/gitignore_skip_bom.gitignore";
|
||||
|
||||
/// Skip a Byte-Order Mark (BOM) at the beginning of the file, matching Git's
|
||||
/// behavior.
|
||||
///
|
||||
/// Ref: <https://github.com/BurntSushi/ripgrep/issues/2177>
|
||||
#[test]
|
||||
fn gitignore_skip_bom() {
|
||||
let mut builder = GitignoreBuilder::new("ROOT");
|
||||
let error = builder.add(IGNORE_FILE);
|
||||
assert!(error.is_none(), "failed to open gitignore file");
|
||||
let g = builder.build().unwrap();
|
||||
|
||||
assert!(g.matched("ignore/this/path", false).is_ignore());
|
||||
}
|
@@ -389,6 +389,15 @@ pub trait Captures {
|
||||
/// for the overall match.
|
||||
fn get(&self, i: usize) -> Option<Match>;
|
||||
|
||||
/// Return the overall match for the capture.
|
||||
///
|
||||
/// This returns the match for index `0`. That is it is equivalent to
|
||||
/// `get(0).unwrap()`
|
||||
#[inline]
|
||||
fn as_match(&self) -> Match {
|
||||
self.get(0).unwrap()
|
||||
}
|
||||
|
||||
/// Returns true if and only if these captures are empty. This occurs
|
||||
/// when `len` is `0`.
|
||||
///
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grep-pcre2"
|
||||
version = "0.1.7" #:version
|
||||
version = "0.1.8" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Use PCRE2 with the 'grep' crate.
|
||||
|
@@ -55,7 +55,12 @@ impl RegexMatcherBuilder {
|
||||
format!("(?:{})", p.as_ref())
|
||||
});
|
||||
}
|
||||
let mut singlepat = pats.join("|");
|
||||
let mut singlepat = if patterns.is_empty() {
|
||||
// A way to spell a pattern that can never match anything.
|
||||
r"[^\S\s]".to_string()
|
||||
} else {
|
||||
pats.join("|")
|
||||
};
|
||||
if self.case_smart && !has_uppercase_literal(&singlepat) {
|
||||
builder.caseless(true);
|
||||
}
|
||||
@@ -428,7 +433,7 @@ fn has_uppercase_literal(pattern: &str) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use grep_matcher::{LineMatchKind, Matcher};
|
||||
use grep_matcher::LineMatchKind;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grep-printer"
|
||||
version = "0.2.1" #:version
|
||||
version = "0.2.2" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
An implementation of the grep crate's Sink trait that provides standard
|
||||
@@ -21,14 +21,14 @@ serde = ["dep:serde", "dep:serde_json"]
|
||||
[dependencies]
|
||||
bstr = "1.6.2"
|
||||
grep-matcher = { version = "0.1.7", path = "../matcher" }
|
||||
grep-searcher = { version = "0.1.13", path = "../searcher" }
|
||||
grep-searcher = { version = "0.1.14", path = "../searcher" }
|
||||
log = "0.4.5"
|
||||
termcolor = "1.3.0"
|
||||
serde = { version = "1.0.193", optional = true }
|
||||
serde_json = { version = "1.0.107", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
grep-regex = { version = "0.1.12", path = "../regex" }
|
||||
grep-regex = { version = "0.1.13", path = "../regex" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
# We want to document all features.
|
||||
|
@@ -51,13 +51,13 @@ impl std::fmt::Display for ColorError {
|
||||
ColorError::UnrecognizedOutType(ref name) => write!(
|
||||
f,
|
||||
"unrecognized output type '{}'. Choose from: \
|
||||
path, line, column, match.",
|
||||
path, line, column, match, highlight.",
|
||||
name,
|
||||
),
|
||||
ColorError::UnrecognizedSpecType(ref name) => write!(
|
||||
f,
|
||||
"unrecognized spec type '{}'. Choose from: \
|
||||
fg, bg, style, none.",
|
||||
fg, bg, style, none.",
|
||||
name,
|
||||
),
|
||||
ColorError::UnrecognizedColor(_, ref msg) => write!(f, "{}", msg),
|
||||
@@ -65,13 +65,13 @@ impl std::fmt::Display for ColorError {
|
||||
f,
|
||||
"unrecognized style attribute '{}'. Choose from: \
|
||||
nobold, bold, nointense, intense, nounderline, \
|
||||
underline.",
|
||||
underline, noitalic, italic.",
|
||||
name,
|
||||
),
|
||||
ColorError::InvalidFormat(ref original) => write!(
|
||||
f,
|
||||
"invalid color spec format: '{}'. Valid format \
|
||||
is '(path|line|column|match):(fg|bg|style):(value)'.",
|
||||
"invalid color spec format: '{}'. Valid format is \
|
||||
'(path|line|column|match|highlight):(fg|bg|style):(value)'.",
|
||||
original,
|
||||
),
|
||||
}
|
||||
@@ -90,6 +90,7 @@ pub struct ColorSpecs {
|
||||
line: ColorSpec,
|
||||
column: ColorSpec,
|
||||
matched: ColorSpec,
|
||||
highlight: ColorSpec,
|
||||
}
|
||||
|
||||
/// A single color specification provided by the user.
|
||||
@@ -99,7 +100,7 @@ pub struct ColorSpecs {
|
||||
/// The format of a `Spec` is a triple: `{type}:{attribute}:{value}`. Each
|
||||
/// component is defined as follows:
|
||||
///
|
||||
/// * `{type}` can be one of `path`, `line`, `column` or `match`.
|
||||
/// * `{type}` can be one of `path`, `line`, `column`, `match` or `highlight`.
|
||||
/// * `{attribute}` can be one of `fg`, `bg` or `style`. `{attribute}` may also
|
||||
/// be the special value `none`, in which case, `{value}` can be omitted.
|
||||
/// * `{value}` is either a color name (for `fg`/`bg`) or a style instruction.
|
||||
@@ -121,7 +122,7 @@ pub struct ColorSpecs {
|
||||
/// `0x`.
|
||||
///
|
||||
/// Valid style instructions are `nobold`, `bold`, `intense`, `nointense`,
|
||||
/// `underline`, `nounderline`.
|
||||
/// `underline`, `nounderline`, `italic`, `noitalic`.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
@@ -181,6 +182,7 @@ enum OutType {
|
||||
Line,
|
||||
Column,
|
||||
Match,
|
||||
Highlight,
|
||||
}
|
||||
|
||||
/// The specification type.
|
||||
@@ -201,6 +203,8 @@ enum Style {
|
||||
NoIntense,
|
||||
Underline,
|
||||
NoUnderline,
|
||||
Italic,
|
||||
NoItalic,
|
||||
}
|
||||
|
||||
impl ColorSpecs {
|
||||
@@ -214,6 +218,7 @@ impl ColorSpecs {
|
||||
OutType::Line => spec.merge_into(&mut merged.line),
|
||||
OutType::Column => spec.merge_into(&mut merged.column),
|
||||
OutType::Match => spec.merge_into(&mut merged.matched),
|
||||
OutType::Highlight => spec.merge_into(&mut merged.highlight),
|
||||
}
|
||||
}
|
||||
merged
|
||||
@@ -247,6 +252,12 @@ impl ColorSpecs {
|
||||
pub fn matched(&self) -> &ColorSpec {
|
||||
&self.matched
|
||||
}
|
||||
|
||||
/// Return the color specification for coloring entire line if there is a
|
||||
/// matched text.
|
||||
pub fn highlight(&self) -> &ColorSpec {
|
||||
&self.highlight
|
||||
}
|
||||
}
|
||||
|
||||
impl UserColorSpec {
|
||||
@@ -286,6 +297,12 @@ impl SpecValue {
|
||||
Style::NoUnderline => {
|
||||
cspec.set_underline(false);
|
||||
}
|
||||
Style::Italic => {
|
||||
cspec.set_italic(true);
|
||||
}
|
||||
Style::NoItalic => {
|
||||
cspec.set_italic(false);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -340,6 +357,7 @@ impl std::str::FromStr for OutType {
|
||||
"line" => Ok(OutType::Line),
|
||||
"column" => Ok(OutType::Column),
|
||||
"match" => Ok(OutType::Match),
|
||||
"highlight" => Ok(OutType::Highlight),
|
||||
_ => Err(ColorError::UnrecognizedOutType(s.to_string())),
|
||||
}
|
||||
}
|
||||
@@ -370,6 +388,8 @@ impl std::str::FromStr for Style {
|
||||
"nointense" => Ok(Style::NoIntense),
|
||||
"underline" => Ok(Style::Underline),
|
||||
"nounderline" => Ok(Style::NoUnderline),
|
||||
"italic" => Ok(Style::Italic),
|
||||
"noitalic" => Ok(Style::NoItalic),
|
||||
_ => Err(ColorError::UnrecognizedStyle(s.to_string())),
|
||||
}
|
||||
}
|
||||
|
@@ -702,16 +702,20 @@ impl HyperlinkPath {
|
||||
/// Returns a hyperlink path from an OS path.
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn from_path(original_path: &Path) -> Option<HyperlinkPath> {
|
||||
// On Windows, Path::canonicalize returns the result of
|
||||
// GetFinalPathNameByHandleW with VOLUME_NAME_DOS,
|
||||
// which produces paths such as the following:
|
||||
// On Windows, we use `std::path::absolute` instead of `Path::canonicalize`
|
||||
// as it can be much faster since it does not touch the file system.
|
||||
// It wraps the [`GetFullPathNameW`][1] API, except for verbatim paths
|
||||
// (those which start with `\\?\`, see [the documentation][2] for details).
|
||||
//
|
||||
// Here, we strip any verbatim path prefixes since we cannot use them
|
||||
// in hyperlinks anyway. This can only happen if the user explicitly
|
||||
// supplies a verbatim path as input, which already needs to be absolute:
|
||||
//
|
||||
// \\?\C:\dir\file.txt (local path)
|
||||
// \\?\UNC\server\dir\file.txt (network share)
|
||||
//
|
||||
// The \\?\ prefix comes from VOLUME_NAME_DOS and is constant.
|
||||
// It is followed either by the drive letter, or by UNC\
|
||||
// (universal naming convention), which denotes a network share.
|
||||
// The `\\?\` prefix is constant for verbatim paths, and can be followed
|
||||
// by `UNC\` (universal naming convention), which denotes a network share.
|
||||
//
|
||||
// Given that the default URL format on Windows is file://{path}
|
||||
// we need to return the following from this function:
|
||||
@@ -750,18 +754,19 @@ impl HyperlinkPath {
|
||||
//
|
||||
// It doesn't parse any other number of slashes in "file//server" as a
|
||||
// network path.
|
||||
//
|
||||
// [1]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew
|
||||
// [2]: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
||||
|
||||
const WIN32_NAMESPACE_PREFIX: &str = r"\\?\";
|
||||
const UNC_PREFIX: &str = r"UNC\";
|
||||
|
||||
// As for Unix, we canonicalize the path to make sure we have an
|
||||
// absolute path.
|
||||
let path = match original_path.canonicalize() {
|
||||
let path = match std::path::absolute(original_path) {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
log::debug!(
|
||||
"hyperlink creation for {:?} failed, error occurred \
|
||||
during path canonicalization: {}",
|
||||
during conversion to absolute path: {}",
|
||||
original_path,
|
||||
err,
|
||||
);
|
||||
@@ -784,24 +789,20 @@ impl HyperlinkPath {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
// As the comment above says, we expect all canonicalized paths to
|
||||
// begin with a \\?\. If it doesn't, then something weird is happening
|
||||
// and we should just give up.
|
||||
if !string.starts_with(WIN32_NAMESPACE_PREFIX) {
|
||||
log::debug!(
|
||||
"hyperlink creation for {:?} failed, canonicalization \
|
||||
returned {:?}, which does not start with \\\\?\\",
|
||||
original_path,
|
||||
path,
|
||||
);
|
||||
return None;
|
||||
}
|
||||
string = &string[WIN32_NAMESPACE_PREFIX.len()..];
|
||||
|
||||
// And as above, drop the UNC prefix too, but keep the leading slash.
|
||||
if string.starts_with(UNC_PREFIX) {
|
||||
string = &string[(UNC_PREFIX.len() - 1)..];
|
||||
// Strip verbatim path prefixes (see the comment above for details).
|
||||
if string.starts_with(WIN32_NAMESPACE_PREFIX) {
|
||||
string = &string[WIN32_NAMESPACE_PREFIX.len()..];
|
||||
|
||||
// Drop the UNC prefix if there is one, but keep the leading slash.
|
||||
if string.starts_with(UNC_PREFIX) {
|
||||
string = &string[(UNC_PREFIX.len() - 1)..];
|
||||
}
|
||||
} else if string.starts_with(r"\\") || string.starts_with(r"//") {
|
||||
// Drop one of the two leading slashes of network paths, it will be added back.
|
||||
string = &string[1..];
|
||||
}
|
||||
|
||||
// Finally, add a leading slash. In the local file case, this turns
|
||||
// C:\foo\bar into /C:\foo\bar (and then percent encoding turns it into
|
||||
// /C:/foo/bar). In the network share case, this turns \share\foo\bar
|
||||
@@ -811,6 +812,13 @@ impl HyperlinkPath {
|
||||
Some(HyperlinkPath::encode(with_slash.as_bytes()))
|
||||
}
|
||||
|
||||
/// For other platforms (not windows, not unix), return None and log a debug message.
|
||||
#[cfg(not(any(windows, unix)))]
|
||||
pub(crate) fn from_path(original_path: &Path) -> Option<HyperlinkPath> {
|
||||
log::debug!("hyperlinks are not supported on this platform");
|
||||
None
|
||||
}
|
||||
|
||||
/// Percent-encodes a path.
|
||||
///
|
||||
/// The alphanumeric ASCII characters and "-", ".", "_", "~" are unreserved
|
||||
@@ -999,4 +1007,33 @@ mod tests {
|
||||
err(InvalidVariable("bar{{".to_string())),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn convert_to_hyperlink_path() {
|
||||
let convert = |path| {
|
||||
String::from_utf8(
|
||||
HyperlinkPath::from_path(Path::new(path)).unwrap().0,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(convert(r"C:\dir\file.txt"), "/C:/dir/file.txt");
|
||||
assert_eq!(
|
||||
convert(r"C:\foo\bar\..\other\baz.txt"),
|
||||
"/C:/foo/other/baz.txt"
|
||||
);
|
||||
|
||||
assert_eq!(convert(r"\\server\dir\file.txt"), "//server/dir/file.txt");
|
||||
assert_eq!(
|
||||
convert(r"\\server\dir\foo\..\other\file.txt"),
|
||||
"//server/dir/other/file.txt"
|
||||
);
|
||||
|
||||
assert_eq!(convert(r"\\?\C:\dir\file.txt"), "/C:/dir/file.txt");
|
||||
assert_eq!(
|
||||
convert(r"\\?\UNC\server\dir\file.txt"),
|
||||
"//server/dir/file.txt"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
@@ -13,7 +14,8 @@ use {
|
||||
};
|
||||
|
||||
use crate::{
|
||||
counter::CounterWriter, jsont, stats::Stats, util::find_iter_at_in_context,
|
||||
counter::CounterWriter, jsont, stats::Stats,
|
||||
util::find_iter_at_in_context, util::Replacer,
|
||||
};
|
||||
|
||||
/// The configuration for the JSON printer.
|
||||
@@ -26,11 +28,17 @@ struct Config {
|
||||
pretty: bool,
|
||||
max_matches: Option<u64>,
|
||||
always_begin_end: bool,
|
||||
replacement: Arc<Option<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config { pretty: false, max_matches: None, always_begin_end: false }
|
||||
Config {
|
||||
pretty: false,
|
||||
max_matches: None,
|
||||
always_begin_end: false,
|
||||
replacement: Arc::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +106,24 @@ impl JSONBuilder {
|
||||
self.config.always_begin_end = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the bytes that will be used to replace each occurrence of a match
|
||||
/// found.
|
||||
///
|
||||
/// The replacement bytes given may include references to capturing groups,
|
||||
/// which may either be in index form (e.g., `$2`) or can reference named
|
||||
/// capturing groups if present in the original pattern (e.g., `$foo`).
|
||||
///
|
||||
/// For documentation on the full format, please see the `Capture` trait's
|
||||
/// `interpolate` method in the
|
||||
/// [grep-printer](https://docs.rs/grep-printer) crate.
|
||||
pub fn replacement(
|
||||
&mut self,
|
||||
replacement: Option<Vec<u8>>,
|
||||
) -> &mut JSONBuilder {
|
||||
self.config.replacement = Arc::new(replacement);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The JSON printer, which emits results in a JSON lines format.
|
||||
@@ -256,7 +282,8 @@ impl JSONBuilder {
|
||||
/// encoded, then the byte offsets correspond to the data after base64
|
||||
/// decoding.) The `submatch` objects are guaranteed to be sorted by their
|
||||
/// starting offsets. Note that it is possible for this array to be empty,
|
||||
/// for example, when searching reports inverted matches.
|
||||
/// for example, when searching reports inverted matches. If the configuration
|
||||
/// specifies a replacement, the resulting replacement text is also present.
|
||||
///
|
||||
/// #### Message: **context**
|
||||
///
|
||||
@@ -286,7 +313,9 @@ impl JSONBuilder {
|
||||
/// decoding.) The `submatch` objects are guaranteed to be sorted by
|
||||
/// their starting offsets. Note that it is possible for this array to be
|
||||
/// non-empty, for example, when searching reports inverted matches such that
|
||||
/// the original matcher could match things in the contextual lines.
|
||||
/// the original matcher could match things in the contextual lines. If the
|
||||
/// configuration specifies a replacemement, the resulting replacement text
|
||||
/// is also present.
|
||||
///
|
||||
/// #### Object: **submatch**
|
||||
///
|
||||
@@ -308,6 +337,10 @@ impl JSONBuilder {
|
||||
/// the `lines` field in the
|
||||
/// [`match`](#message-match) or [`context`](#message-context)
|
||||
/// messages.
|
||||
/// * **replacement** (optional) - An
|
||||
/// [arbitrary data object](#object-arbitrary-data) corresponding to the
|
||||
/// replacement text for this submatch, if the configuration specifies
|
||||
/// a replacement.
|
||||
///
|
||||
/// #### Object: **stats**
|
||||
///
|
||||
@@ -447,6 +480,23 @@ impl JSONBuilder {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// and here's what a match type item would looks like if a replacement text
|
||||
/// of 'Moriarity' was given as a parameter:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "type": "match",
|
||||
/// "data": {
|
||||
/// "path": {"text": "/home/andrew/sherlock"},
|
||||
/// "lines": {"text": "For the Doctor Watsons of this world, as opposed to the Sherlock\n"},
|
||||
/// "line_number": 1,
|
||||
/// "absolute_offset": 0,
|
||||
/// "submatches": [
|
||||
/// {"match": {"text": "Watson"}, "replacement": {"text": "Moriarity"}, "start": 15, "end": 21}
|
||||
/// ]
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct JSON<W> {
|
||||
config: Config,
|
||||
@@ -471,6 +521,7 @@ impl<W: io::Write> JSON<W> {
|
||||
) -> JSONSink<'static, 's, M, W> {
|
||||
JSONSink {
|
||||
matcher,
|
||||
replacer: Replacer::new(),
|
||||
json: self,
|
||||
path: None,
|
||||
start_time: Instant::now(),
|
||||
@@ -497,6 +548,7 @@ impl<W: io::Write> JSON<W> {
|
||||
{
|
||||
JSONSink {
|
||||
matcher,
|
||||
replacer: Replacer::new(),
|
||||
json: self,
|
||||
path: Some(path.as_ref()),
|
||||
start_time: Instant::now(),
|
||||
@@ -559,6 +611,7 @@ impl<W> JSON<W> {
|
||||
#[derive(Debug)]
|
||||
pub struct JSONSink<'p, 's, M: Matcher, W> {
|
||||
matcher: M,
|
||||
replacer: Replacer<M>,
|
||||
json: &'s mut JSON<W>,
|
||||
path: Option<&'p Path>,
|
||||
start_time: Instant,
|
||||
@@ -643,6 +696,31 @@ impl<'p, 's, M: Matcher, W: io::Write> JSONSink<'p, 's, M, W> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If the configuration specifies a replacement, then this executes the
|
||||
/// replacement, lazily allocating memory if necessary.
|
||||
///
|
||||
/// To access the result of a replacement, use `replacer.replacement()`.
|
||||
fn replace(
|
||||
&mut self,
|
||||
searcher: &Searcher,
|
||||
bytes: &[u8],
|
||||
range: std::ops::Range<usize>,
|
||||
) -> io::Result<()> {
|
||||
self.replacer.clear();
|
||||
if self.json.config.replacement.is_some() {
|
||||
let replacement =
|
||||
(*self.json.config.replacement).as_ref().map(|r| &*r).unwrap();
|
||||
self.replacer.replace_all(
|
||||
searcher,
|
||||
&self.matcher,
|
||||
bytes,
|
||||
range,
|
||||
replacement,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns true if this printer should quit.
|
||||
///
|
||||
/// This implements the logic for handling quitting after seeing a certain
|
||||
@@ -711,10 +789,15 @@ impl<'p, 's, M: Matcher, W: io::Write> Sink for JSONSink<'p, 's, M, W> {
|
||||
mat.buffer(),
|
||||
mat.bytes_range_in_buffer(),
|
||||
)?;
|
||||
self.replace(searcher, mat.buffer(), mat.bytes_range_in_buffer())?;
|
||||
self.stats.add_matches(self.json.matches.len() as u64);
|
||||
self.stats.add_matched_lines(mat.lines().count() as u64);
|
||||
|
||||
let submatches = SubMatches::new(mat.bytes(), &self.json.matches);
|
||||
let submatches = SubMatches::new(
|
||||
mat.bytes(),
|
||||
&self.json.matches,
|
||||
self.replacer.replacement(),
|
||||
);
|
||||
let msg = jsont::Message::Match(jsont::Match {
|
||||
path: self.path,
|
||||
lines: mat.bytes(),
|
||||
@@ -740,7 +823,12 @@ impl<'p, 's, M: Matcher, W: io::Write> Sink for JSONSink<'p, 's, M, W> {
|
||||
}
|
||||
let submatches = if searcher.invert_match() {
|
||||
self.record_matches(searcher, ctx.bytes(), 0..ctx.bytes().len())?;
|
||||
SubMatches::new(ctx.bytes(), &self.json.matches)
|
||||
self.replace(searcher, ctx.bytes(), 0..ctx.bytes().len())?;
|
||||
SubMatches::new(
|
||||
ctx.bytes(),
|
||||
&self.json.matches,
|
||||
self.replacer.replacement(),
|
||||
)
|
||||
} else {
|
||||
SubMatches::empty()
|
||||
};
|
||||
@@ -831,19 +919,27 @@ enum SubMatches<'a> {
|
||||
impl<'a> SubMatches<'a> {
|
||||
/// Create a new set of match ranges from a set of matches and the
|
||||
/// corresponding bytes that those matches apply to.
|
||||
fn new(bytes: &'a [u8], matches: &[Match]) -> SubMatches<'a> {
|
||||
fn new(
|
||||
bytes: &'a [u8],
|
||||
matches: &[Match],
|
||||
replacement: Option<(&'a [u8], &'a [Match])>,
|
||||
) -> SubMatches<'a> {
|
||||
if matches.len() == 1 {
|
||||
let mat = matches[0];
|
||||
SubMatches::Small([jsont::SubMatch {
|
||||
m: &bytes[mat],
|
||||
replacement: replacement
|
||||
.map(|(rbuf, rmatches)| &rbuf[rmatches[0]]),
|
||||
start: mat.start(),
|
||||
end: mat.end(),
|
||||
}])
|
||||
} else {
|
||||
let mut match_ranges = vec![];
|
||||
for &mat in matches {
|
||||
for (i, &mat) in matches.iter().enumerate() {
|
||||
match_ranges.push(jsont::SubMatch {
|
||||
m: &bytes[mat],
|
||||
replacement: replacement
|
||||
.map(|(rbuf, rmatches)| &rbuf[rmatches[i]]),
|
||||
start: mat.start(),
|
||||
end: mat.end(),
|
||||
});
|
||||
|
@@ -135,6 +135,7 @@ impl<'a> serde::Serialize for Context<'a> {
|
||||
|
||||
pub(crate) struct SubMatch<'a> {
|
||||
pub(crate) m: &'a [u8],
|
||||
pub(crate) replacement: Option<&'a [u8]>,
|
||||
pub(crate) start: usize,
|
||||
pub(crate) end: usize,
|
||||
}
|
||||
@@ -148,6 +149,9 @@ impl<'a> serde::Serialize for SubMatch<'a> {
|
||||
|
||||
let mut state = s.serialize_struct("SubMatch", 3)?;
|
||||
state.serialize_field("match", &Data::from_bytes(self.m))?;
|
||||
if let Some(r) = self.replacement {
|
||||
state.serialize_field("replacement", &Data::from_bytes(r))?;
|
||||
}
|
||||
state.serialize_field("start", &self.start)?;
|
||||
state.serialize_field("end", &self.end)?;
|
||||
state.end()
|
||||
|
@@ -762,10 +762,8 @@ impl<'p, 's, M: Matcher, W: WriteColor> StandardSink<'p, 's, M, W> {
|
||||
) -> io::Result<()> {
|
||||
self.replacer.clear();
|
||||
if self.standard.config.replacement.is_some() {
|
||||
let replacement = (*self.standard.config.replacement)
|
||||
.as_ref()
|
||||
.map(|r| &*r)
|
||||
.unwrap();
|
||||
let replacement =
|
||||
(*self.standard.config.replacement).as_ref().unwrap();
|
||||
self.replacer.replace_all(
|
||||
searcher,
|
||||
&self.matcher,
|
||||
@@ -1322,6 +1320,7 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
||||
self.write(&bytes[line])?;
|
||||
return Ok(());
|
||||
}
|
||||
self.start_line_highlight()?;
|
||||
while !line.is_empty() {
|
||||
if matches[*match_index].end() <= line.start() {
|
||||
if *match_index + 1 < matches.len() {
|
||||
@@ -1348,6 +1347,7 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
||||
}
|
||||
}
|
||||
self.end_color_match()?;
|
||||
self.end_line_highlight()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1549,11 +1549,37 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
||||
if !self.in_color_match.get() {
|
||||
return Ok(());
|
||||
}
|
||||
self.wtr().borrow_mut().reset()?;
|
||||
if self.highlight_on() {
|
||||
self.wtr()
|
||||
.borrow_mut()
|
||||
.set_color(self.config().colors.highlight())?;
|
||||
} else {
|
||||
self.wtr().borrow_mut().reset()?;
|
||||
}
|
||||
self.in_color_match.set(false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn highlight_on(&self) -> bool {
|
||||
!self.config().colors.highlight().is_none() && !self.is_context()
|
||||
}
|
||||
|
||||
fn start_line_highlight(&self) -> io::Result<()> {
|
||||
if self.highlight_on() {
|
||||
self.wtr()
|
||||
.borrow_mut()
|
||||
.set_color(self.config().colors.highlight())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end_line_highlight(&self) -> io::Result<()> {
|
||||
if self.highlight_on() {
|
||||
self.wtr().borrow_mut().reset()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&self, buf: &[u8]) -> io::Result<()> {
|
||||
self.wtr().borrow_mut().write_all(buf)
|
||||
}
|
||||
|
@@ -87,7 +87,13 @@ pub enum SummaryKind {
|
||||
///
|
||||
/// Note that if `stats` is enabled, then searching continues in order to
|
||||
/// compute statistics.
|
||||
Quiet,
|
||||
QuietWithMatch,
|
||||
/// Don't show any output and the stop the search once a non-matching file
|
||||
/// is found.
|
||||
///
|
||||
/// Note that if `stats` is enabled, then searching continues in order to
|
||||
/// compute statistics.
|
||||
QuietWithoutMatch,
|
||||
}
|
||||
|
||||
impl SummaryKind {
|
||||
@@ -101,7 +107,7 @@ impl SummaryKind {
|
||||
|
||||
match *self {
|
||||
PathWithMatch | PathWithoutMatch => true,
|
||||
Count | CountMatches | Quiet => false,
|
||||
Count | CountMatches | QuietWithMatch | QuietWithoutMatch => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +118,8 @@ impl SummaryKind {
|
||||
|
||||
match *self {
|
||||
CountMatches => true,
|
||||
Count | PathWithMatch | PathWithoutMatch | Quiet => false,
|
||||
Count | PathWithMatch | PathWithoutMatch | QuietWithMatch
|
||||
| QuietWithoutMatch => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,8 +129,10 @@ impl SummaryKind {
|
||||
use self::SummaryKind::*;
|
||||
|
||||
match *self {
|
||||
PathWithMatch | Quiet => true,
|
||||
Count | CountMatches | PathWithoutMatch => false,
|
||||
PathWithMatch | QuietWithMatch => true,
|
||||
Count | CountMatches | PathWithoutMatch | QuietWithoutMatch => {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,9 +255,9 @@ impl SummaryBuilder {
|
||||
///
|
||||
/// When this is enabled, this printer may need to do extra work in order
|
||||
/// to compute certain statistics, which could cause the search to take
|
||||
/// longer. For example, in `Quiet` mode, a search can quit after finding
|
||||
/// the first match, but if `stats` is enabled, then the search will
|
||||
/// continue after the first match in order to compute statistics.
|
||||
/// longer. For example, in `QuietWithMatch` mode, a search can quit after
|
||||
/// finding the first match, but if `stats` is enabled, then the search
|
||||
/// will continue after the first match in order to compute statistics.
|
||||
///
|
||||
/// For a complete description of available statistics, see [`Stats`].
|
||||
///
|
||||
@@ -505,7 +514,9 @@ impl<'p, 's, M: Matcher, W: WriteColor> SummarySink<'p, 's, M, W> {
|
||||
/// search.
|
||||
pub fn has_match(&self) -> bool {
|
||||
match self.summary.config.kind {
|
||||
SummaryKind::PathWithoutMatch => self.match_count == 0,
|
||||
SummaryKind::PathWithoutMatch | SummaryKind::QuietWithoutMatch => {
|
||||
self.match_count == 0
|
||||
}
|
||||
_ => self.match_count > 0,
|
||||
}
|
||||
}
|
||||
@@ -749,14 +760,14 @@ impl<'p, 's, M: Matcher, W: WriteColor> Sink for SummarySink<'p, 's, M, W> {
|
||||
// don't quit and therefore search the entire contents of the file.
|
||||
//
|
||||
// There is an unfortunate inconsistency here. Namely, when using
|
||||
// Quiet or PathWithMatch, then the printer can quit after the first
|
||||
// match seen, which could be long before seeing binary data. This
|
||||
// means that using PathWithMatch can print a path where as using
|
||||
// QuietWithMatch or PathWithMatch, then the printer can quit after the
|
||||
// first match seen, which could be long before seeing binary data.
|
||||
// This means that using PathWithMatch can print a path where as using
|
||||
// Count might not print it at all because of binary data.
|
||||
//
|
||||
// It's not possible to fix this without also potentially significantly
|
||||
// impacting the performance of Quiet or PathWithMatch, so we accept
|
||||
// the bug.
|
||||
// impacting the performance of QuietWithMatch or PathWithMatch, so we
|
||||
// accept the bug.
|
||||
if self.binary_byte_offset.is_some()
|
||||
&& searcher.binary_detection().quit_byte().is_some()
|
||||
{
|
||||
@@ -798,7 +809,7 @@ impl<'p, 's, M: Matcher, W: WriteColor> Sink for SummarySink<'p, 's, M, W> {
|
||||
self.write_path_line(searcher)?;
|
||||
}
|
||||
}
|
||||
SummaryKind::Quiet => {}
|
||||
SummaryKind::QuietWithMatch | SummaryKind::QuietWithoutMatch => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1122,7 +1133,7 @@ and exhibited clearly, with a label attached.
|
||||
fn quiet() {
|
||||
let matcher = RegexMatcher::new(r"Watson|Sherlock").unwrap();
|
||||
let mut printer = SummaryBuilder::new()
|
||||
.kind(SummaryKind::Quiet)
|
||||
.kind(SummaryKind::QuietWithMatch)
|
||||
.build_no_color(vec![]);
|
||||
let match_count = {
|
||||
let mut sink = printer.sink_with_path(&matcher, "sherlock");
|
||||
@@ -1144,7 +1155,7 @@ and exhibited clearly, with a label attached.
|
||||
fn quiet_with_stats() {
|
||||
let matcher = RegexMatcher::new(r"Watson|Sherlock").unwrap();
|
||||
let mut printer = SummaryBuilder::new()
|
||||
.kind(SummaryKind::Quiet)
|
||||
.kind(SummaryKind::QuietWithMatch)
|
||||
.stats(true)
|
||||
.build_no_color(vec![]);
|
||||
let match_count = {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grep-regex"
|
||||
version = "0.1.12" #:version
|
||||
version = "0.1.13" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Use Rust's regex library with the 'grep' crate.
|
||||
|
@@ -233,7 +233,7 @@ impl ConfiguredHIR {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Return a reference to the underyling HIR.
|
||||
/// Return a reference to the underlying HIR.
|
||||
pub(crate) fn hir(&self) -> &Hir {
|
||||
&self.hir
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ use crate::{config::ConfiguredHIR, error::Error};
|
||||
/// that are in turn used to build a simpler regex that is more amenable to
|
||||
/// optimization.
|
||||
///
|
||||
/// The main idea underyling the validity of this technique is the fact
|
||||
/// The main idea underlying the validity of this technique is the fact
|
||||
/// that ripgrep searches individuals lines and not across lines. (Unless
|
||||
/// -U/--multiline is enabled.) Namely, we can pluck literals out of the regex,
|
||||
/// search for them, find the bounds of the line in which that literal occurs
|
||||
@@ -430,6 +430,7 @@ impl Extractor {
|
||||
}
|
||||
seq1.union(seq2);
|
||||
assert!(seq1.len().map_or(true, |x| x <= self.limit_total));
|
||||
seq1.prefix = seq1.prefix && seq2.prefix;
|
||||
seq1
|
||||
}
|
||||
|
||||
@@ -586,10 +587,15 @@ impl TSeq {
|
||||
lits.iter().any(is_poisonous)
|
||||
}
|
||||
|
||||
/// Compare the two sequences and return the one that is believed to be best
|
||||
/// according to a hodge podge of heuristics.
|
||||
/// Compare the two sequences and return the one that is believed to be
|
||||
/// best according to a hodge podge of heuristics.
|
||||
fn choose(self, other: TSeq) -> TSeq {
|
||||
let (seq1, seq2) = (self, other);
|
||||
let (mut seq1, mut seq2) = (self, other);
|
||||
// Whichever one we pick, by virtue of picking one, we choose
|
||||
// to not take the other. So we must consider the result inexact.
|
||||
seq1.make_inexact();
|
||||
seq2.make_inexact();
|
||||
|
||||
if !seq1.is_finite() {
|
||||
return seq2;
|
||||
} else if !seq2.is_finite() {
|
||||
@@ -681,7 +687,7 @@ mod tests {
|
||||
assert_eq!(e(r"foo"), seq([E("foo")]));
|
||||
assert_eq!(e(r"[a-z]foo[a-z]"), seq([I("foo")]));
|
||||
assert_eq!(e(r"[a-z](foo)(bar)[a-z]"), seq([I("foobar")]));
|
||||
assert_eq!(e(r"[a-z]([a-z]foo)(bar[a-z])[a-z]"), seq([I("foobar")]));
|
||||
assert_eq!(e(r"[a-z]([a-z]foo)(bar[a-z])[a-z]"), seq([I("foo")]));
|
||||
assert_eq!(e(r"[a-z]([a-z]foo)([a-z]foo)[a-z]"), seq([I("foo")]));
|
||||
assert_eq!(e(r"(\d{1,3}\.){3}\d{1,3}"), seq([I(".")]));
|
||||
assert_eq!(e(r"[a-z]([a-z]foo){3}[a-z]"), seq([I("foo")]));
|
||||
@@ -689,7 +695,7 @@ mod tests {
|
||||
assert_eq!(e(r"[a-z]([a-z]foo[a-z]){3}[a-z]"), seq([I("foo")]));
|
||||
assert_eq!(
|
||||
e(r"[a-z]([a-z]foo){3}(bar[a-z]){3}[a-z]"),
|
||||
seq([I("foobar")])
|
||||
seq([I("foo")])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -935,14 +941,14 @@ mod tests {
|
||||
assert_eq!(Seq::infinite(), e(r"[A-Z]+"));
|
||||
assert_eq!(seq([I("1")]), e(r"1[A-Z]"));
|
||||
assert_eq!(seq([I("1")]), e(r"1[A-Z]2"));
|
||||
assert_eq!(seq([E("123")]), e(r"[A-Z]+123"));
|
||||
assert_eq!(seq([I("123")]), e(r"[A-Z]+123"));
|
||||
assert_eq!(seq([I("123")]), e(r"[A-Z]+123[A-Z]+"));
|
||||
assert_eq!(Seq::infinite(), e(r"1|[A-Z]|3"));
|
||||
assert_eq!(seq([E("1"), I("2"), E("3")]), e(r"1|2[A-Z]|3"),);
|
||||
assert_eq!(seq([E("1"), I("2"), E("3")]), e(r"1|[A-Z]2[A-Z]|3"),);
|
||||
assert_eq!(seq([E("1"), E("2"), E("3")]), e(r"1|[A-Z]2|3"),);
|
||||
assert_eq!(seq([E("1"), I("2"), E("3")]), e(r"1|[A-Z]2|3"),);
|
||||
assert_eq!(seq([E("1"), I("2"), E("4")]), e(r"1|2[A-Z]3|4"),);
|
||||
assert_eq!(seq([E("2")]), e(r"(?:|1)[A-Z]2"));
|
||||
assert_eq!(seq([I("2")]), e(r"(?:|1)[A-Z]2"));
|
||||
assert_eq!(inexact([I("a")]), e(r"a.z"));
|
||||
}
|
||||
|
||||
@@ -1005,4 +1011,11 @@ mod tests {
|
||||
let s = e(r"foobarfoo|foo| |foofoo");
|
||||
assert_eq!(Seq::infinite(), s);
|
||||
}
|
||||
|
||||
// Regression test for: https://github.com/BurntSushi/ripgrep/issues/2884
|
||||
#[test]
|
||||
fn case_insensitive_alternation() {
|
||||
let s = e(r"(?i:e.x|ex)");
|
||||
assert_eq!(s, seq([I("X"), I("x")]));
|
||||
}
|
||||
}
|
||||
|
@@ -552,8 +552,6 @@ impl RegexCaptures {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use grep_matcher::{LineMatchKind, Matcher};
|
||||
|
||||
use super::*;
|
||||
|
||||
// Test that enabling word matches does the right thing and demonstrate
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grep-searcher"
|
||||
version = "0.1.13" #:version
|
||||
version = "0.1.14" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Fast line oriented regex searching as a library.
|
||||
@@ -23,11 +23,10 @@ memchr = "2.6.3"
|
||||
memmap = { package = "memmap2", version = "0.9.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
grep-regex = { version = "0.1.12", path = "../regex" }
|
||||
grep-regex = { version = "0.1.13", path = "../regex" }
|
||||
regex = "1.9.5"
|
||||
|
||||
[features]
|
||||
simd-accel = ["encoding_rs/simd-accel"]
|
||||
|
||||
# This feature is DEPRECATED. Runtime dispatch is used for SIMD now.
|
||||
# These features are DEPRECATED. Runtime dispatch is used for SIMD now.
|
||||
simd-accel = []
|
||||
avx-accel = []
|
||||
|
@@ -538,6 +538,11 @@ fn replace_bytes(
|
||||
while let Some(i) = bytes.find_byte(src) {
|
||||
bytes[i] = replacement;
|
||||
bytes = &mut bytes[i + 1..];
|
||||
|
||||
// To search for adjacent `src` bytes we use a different strategy.
|
||||
// Since binary data tends to have long runs of NUL terminators,
|
||||
// it is faster to compare one-byte-at-a-time than to stop and start
|
||||
// memchr (through `find_byte`) for every byte in a sequence.
|
||||
while bytes.get(0) == Some(&src) {
|
||||
bytes[0] = replacement;
|
||||
bytes = &mut bytes[1..];
|
||||
@@ -548,7 +553,7 @@ fn replace_bytes(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bstr::{ByteSlice, ByteVec};
|
||||
use bstr::ByteVec;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -577,6 +582,9 @@ and exhibited clearly, with a label attached.\
|
||||
|
||||
#[test]
|
||||
fn replace() {
|
||||
assert_eq!(replace_str("", b'b', b'z'), (s(""), None));
|
||||
assert_eq!(replace_str("a", b'a', b'a'), (s("a"), None));
|
||||
assert_eq!(replace_str("a", b'b', b'z'), (s("a"), None));
|
||||
assert_eq!(replace_str("abc", b'b', b'z'), (s("azc"), Some(1)));
|
||||
assert_eq!(replace_str("abb", b'b', b'z'), (s("azz"), Some(1)));
|
||||
assert_eq!(replace_str("aba", b'a', b'z'), (s("zbz"), Some(0)));
|
||||
|
@@ -198,8 +198,6 @@ fn preceding_by_pos(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use grep_matcher::Match;
|
||||
|
||||
use super::*;
|
||||
|
||||
const SHERLOCK: &'static str = "\
|
||||
|
@@ -37,7 +37,11 @@ where
|
||||
|
||||
pub(crate) fn run(mut self) -> Result<(), S::Error> {
|
||||
if self.core.begin()? {
|
||||
while self.fill()? && self.core.match_by_line(self.rdr.buffer())? {
|
||||
while self.fill()? {
|
||||
if !self.core.match_by_line(self.rdr.buffer())? {
|
||||
self.consume_remaining();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.core.finish(
|
||||
@@ -46,6 +50,11 @@ where
|
||||
)
|
||||
}
|
||||
|
||||
fn consume_remaining(&mut self) {
|
||||
let consumed = self.core.pos();
|
||||
self.rdr.consume(consumed);
|
||||
}
|
||||
|
||||
fn fill(&mut self) -> Result<bool, S::Error> {
|
||||
assert!(self.rdr.buffer()[self.core.pos()..].is_empty());
|
||||
|
||||
|
@@ -7,7 +7,6 @@ use std::{
|
||||
};
|
||||
|
||||
use {
|
||||
encoding_rs,
|
||||
encoding_rs_io::DecodeReaderBytesBuilder,
|
||||
grep_matcher::{LineTerminator, Match, Matcher},
|
||||
};
|
||||
@@ -1005,6 +1004,7 @@ fn slice_has_bom(slice: &[u8]) -> bool {
|
||||
None => return false,
|
||||
Some((enc, _)) => enc,
|
||||
};
|
||||
log::trace!("found byte-order mark (BOM) for encoding {enc:?}");
|
||||
[encoding_rs::UTF_16LE, encoding_rs::UTF_16BE, encoding_rs::UTF_8]
|
||||
.contains(&enc)
|
||||
}
|
||||
|
@@ -725,8 +725,6 @@ impl TesterConfig {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use grep_matcher::{Match, Matcher};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn m(start: usize, end: usize) -> Match {
|
||||
|
317
doc/rg.1.txt.tpl
317
doc/rg.1.txt.tpl
@@ -1,317 +0,0 @@
|
||||
rg(1)
|
||||
=====
|
||||
|
||||
Name
|
||||
----
|
||||
rg - recursively search the current directory for lines matching a pattern
|
||||
|
||||
|
||||
Synopsis
|
||||
--------
|
||||
*rg* [_OPTIONS_] _PATTERN_ [_PATH_...]
|
||||
|
||||
*rg* [_OPTIONS_] *-e* _PATTERN_... [_PATH_...]
|
||||
|
||||
*rg* [_OPTIONS_] *-f* _PATTERNFILE_... [_PATH_...]
|
||||
|
||||
*rg* [_OPTIONS_] *--files* [_PATH_...]
|
||||
|
||||
*rg* [_OPTIONS_] *--type-list*
|
||||
|
||||
*command* | *rg* [_OPTIONS_] _PATTERN_
|
||||
|
||||
*rg* [_OPTIONS_] *--help*
|
||||
|
||||
*rg* [_OPTIONS_] *--version*
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
ripgrep (rg) recursively searches the current directory for a regex pattern.
|
||||
By default, ripgrep will respect your .gitignore and automatically skip hidden
|
||||
files/directories and binary files.
|
||||
|
||||
ripgrep's default regex engine uses finite automata and guarantees linear
|
||||
time searching. Because of this, features like backreferences and arbitrary
|
||||
look-around are not supported. However, if ripgrep is built with PCRE2, then
|
||||
the *--pcre2* flag can be used to enable backreferences and look-around.
|
||||
|
||||
ripgrep supports configuration files. Set *RIPGREP_CONFIG_PATH* to a
|
||||
configuration file. The file can specify one shell argument per line. Lines
|
||||
starting with *#* are ignored. For more details, see the man page or the
|
||||
*README*.
|
||||
|
||||
ripgrep will automatically detect if stdin exists and search stdin for a regex
|
||||
pattern, e.g. *ls | rg foo*. In some environments, stdin may exist when it
|
||||
shouldn't. To turn off stdin detection explicitly specify the directory to
|
||||
search, e.g. *rg foo ./*.
|
||||
|
||||
Tip: to disable all smart filtering and make ripgrep behave a bit more like
|
||||
classical grep, use *rg -uuu*.
|
||||
|
||||
|
||||
REGEX SYNTAX
|
||||
------------
|
||||
ripgrep uses Rust's regex engine by default, which documents its syntax:
|
||||
https://docs.rs/regex/*/regex/#syntax
|
||||
|
||||
ripgrep uses byte-oriented regexes, which has some additional documentation:
|
||||
https://docs.rs/regex/*/regex/bytes/index.html#syntax
|
||||
|
||||
To a first approximation, ripgrep uses Perl-like regexes without look-around or
|
||||
backreferences. This makes them very similar to the "extended" (ERE) regular
|
||||
expressions supported by *egrep*, but with a few additional features like
|
||||
Unicode character classes.
|
||||
|
||||
If you're using ripgrep with the *--pcre2* flag, then please consult
|
||||
https://www.pcre.org or the PCRE2 man pages for documentation on the supported
|
||||
syntax.
|
||||
|
||||
|
||||
POSITIONAL ARGUMENTS
|
||||
--------------------
|
||||
_PATTERN_::
|
||||
A regular expression used for searching. To match a pattern beginning with a
|
||||
dash, use the -e/--regexp option.
|
||||
|
||||
_PATH_::
|
||||
A file or directory to search. Directories are searched recursively. File
|
||||
paths specified explicitly on the command line override glob and ignore
|
||||
rules.
|
||||
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
Note that many options can be disabled via flags. In some cases, those flags
|
||||
are not listed in a first class way below. For example, the *--column*
|
||||
flag (listed below) enables column numbers in ripgrep's output, but the
|
||||
*--no-column* flag (not listed below) disables them. The reverse can also
|
||||
exist. For example, the *--no-ignore* flag (listed below) disables ripgrep's
|
||||
*gitignore* logic, but the *--ignore* flag (not listed below) enables it. These
|
||||
flags are useful for overriding a ripgrep configuration file on the command
|
||||
line. Each flag's documentation notes whether an inverted flag exists. In all
|
||||
cases, the flag specified last takes precedence.
|
||||
|
||||
{OPTIONS}
|
||||
|
||||
|
||||
EXIT STATUS
|
||||
-----------
|
||||
If ripgrep finds a match, then the exit status of the program is 0. If no match
|
||||
could be found, then the exit status is 1. If an error occurred, then the exit
|
||||
status is always 2 unless ripgrep was run with the *--quiet* flag and a match
|
||||
was found. In summary:
|
||||
|
||||
* `0` exit status occurs only when at least one match was found, and if
|
||||
no error occurred, unless *--quiet* was given.
|
||||
* `1` exit status occurs only when no match was found and no error occurred.
|
||||
* `2` exit status occurs when an error occurred. This is true for both
|
||||
catastrophic errors (e.g., a regex syntax error) and for soft errors (e.g.,
|
||||
unable to read a file).
|
||||
|
||||
|
||||
AUTOMATIC FILTERING
|
||||
-------------------
|
||||
TL;DR - To disable automatic filtering, use 'rg -uuu'.
|
||||
|
||||
One of ripgrep's most important features is its automatic smart filtering.
|
||||
It is the most apparent differentiating feature between ripgrep and other tools
|
||||
like 'grep'. As such, its behavior may be surprising to users that aren't
|
||||
expecting it.
|
||||
|
||||
ripgrep does four types of filtering automatically:
|
||||
|
||||
1. Files and directories that match ignore rules are not searched.
|
||||
2. Hidden files and directories are not searched.
|
||||
3. Binary files (files with a 'NUL' byte) are not searched.
|
||||
4. Symbolic links are not followed.
|
||||
|
||||
The first type of filtering is the most sophisticated. ripgrep will attempt to
|
||||
respect your gitignore rules as faithfully as possible. In particular, this
|
||||
includes the following:
|
||||
|
||||
* Any global rules, e.g., in '$HOME/.config/git/ignore'.
|
||||
* Any rules in '.gitignore'.
|
||||
* Any local rules, e.g., in '.git/info/exclude'.
|
||||
|
||||
In some cases, ripgrep and git will not always be in sync in terms of which
|
||||
files are ignored. For example, a file that is ignored via '.gitignore' but is
|
||||
tracked by git would not be searched by ripgrep even though git tracks it. This
|
||||
is unlikely to ever be fixed. Instead, you should either make sure your exclude
|
||||
rules match the files you track precisely, or otherwise use 'git grep' for
|
||||
search.
|
||||
|
||||
Additional ignore rules can be provided outside of a git context:
|
||||
|
||||
* Any rules in '.ignore'.
|
||||
* Any rules in '.rgignore'.
|
||||
* Any rules in files specified with the '--ignore-file' flag.
|
||||
|
||||
The precedence of ignore rules is as follows, with later items overriding
|
||||
earlier items:
|
||||
|
||||
* Files given by '--ignore-file'.
|
||||
* Global gitignore rules, e.g., from '$HOME/.config/git/ignore'.
|
||||
* Local rules from '.git/info/exclude'.
|
||||
* Rules from '.gitignore'.
|
||||
* Rules from '.ignore'.
|
||||
* Rules from '.rgignore'.
|
||||
|
||||
So for example, if 'foo' were in a '.gitignore' and '!foo' were in an
|
||||
'.rgignore', then 'foo' would not be ignored since '.rgignore' takes precedence
|
||||
over '.gitignore'.
|
||||
|
||||
Each of the types of filtering can be configured via command line flags:
|
||||
|
||||
* There are several flags starting with '--no-ignore' that toggle which,
|
||||
if any, ignore rules are respected. '--no-ignore' by itself will disable
|
||||
all of them.
|
||||
* '-./--hidden' will force ripgrep to search hidden files and directories.
|
||||
* '--binary' will force ripgrep to search binary files.
|
||||
* '-L/--follow' will force ripgrep to follow symlinks.
|
||||
|
||||
As a special short hand, the `-u` flag can be specified up to three times. Each
|
||||
additional time incrementally decreases filtering:
|
||||
|
||||
* '-u' is equivalent to '--no-ignore'.
|
||||
* '-uu' is equivalent to '--no-ignore --hidden'.
|
||||
* '-uuu' is equivalent to '--no-ignore --hidden --binary'.
|
||||
|
||||
In particular, 'rg -uuu' should search the same exact content as 'grep -r'.
|
||||
|
||||
|
||||
CONFIGURATION FILES
|
||||
-------------------
|
||||
ripgrep supports reading configuration files that change ripgrep's default
|
||||
behavior. The format of the configuration file is an "rc" style and is very
|
||||
simple. It is defined by two rules:
|
||||
|
||||
1. Every line is a shell argument, after trimming whitespace.
|
||||
2. Lines starting with *#* (optionally preceded by any amount of
|
||||
whitespace) are ignored.
|
||||
|
||||
ripgrep will look for a single configuration file if and only if the
|
||||
*RIPGREP_CONFIG_PATH* environment variable is set and is non-empty. ripgrep
|
||||
will parse shell arguments from this file on startup and will behave as if
|
||||
the arguments in this file were prepended to any explicit arguments given to
|
||||
ripgrep on the command line. Note though that the 'rg' command you run must
|
||||
still be valid. That is, it must always contain at least one pattern at the
|
||||
command line, even if the configuration file uses the '-e/--regexp' flag.
|
||||
|
||||
For example, if your ripgreprc file contained a single line:
|
||||
|
||||
--smart-case
|
||||
|
||||
then the following command
|
||||
|
||||
RIPGREP_CONFIG_PATH=wherever/.ripgreprc rg foo
|
||||
|
||||
would behave identically to the following command
|
||||
|
||||
rg --smart-case foo
|
||||
|
||||
another example is adding types
|
||||
|
||||
--type-add
|
||||
web:*.{html,css,js}*
|
||||
|
||||
would behave identically to the following command
|
||||
|
||||
rg --type-add 'web:*.{html,css,js}*' foo
|
||||
|
||||
same with using globs
|
||||
|
||||
--glob=!.git
|
||||
|
||||
or
|
||||
|
||||
--glob
|
||||
!.git
|
||||
|
||||
would behave identically to the following command
|
||||
|
||||
rg --glob '!.git' foo
|
||||
|
||||
The bottom line is that every shell argument needs to be on its own line. So
|
||||
for example, a config file containing
|
||||
|
||||
-j 4
|
||||
|
||||
is probably not doing what you intend. Instead, you want
|
||||
|
||||
-j
|
||||
4
|
||||
|
||||
ripgrep also provides a flag, *--no-config*, that when present will suppress
|
||||
any and all support for configuration. This includes any future support
|
||||
for auto-loading configuration files from pre-determined paths.
|
||||
|
||||
Conflicts between configuration files and explicit arguments are handled
|
||||
exactly like conflicts in the same command line invocation. That is,
|
||||
this command:
|
||||
|
||||
RIPGREP_CONFIG_PATH=wherever/.ripgreprc rg foo --case-sensitive
|
||||
|
||||
is exactly equivalent to
|
||||
|
||||
rg --smart-case foo --case-sensitive
|
||||
|
||||
in which case, the *--case-sensitive* flag would override the *--smart-case*
|
||||
flag.
|
||||
|
||||
|
||||
SHELL COMPLETION
|
||||
----------------
|
||||
Shell completion files are included in the release tarball for Bash, Fish, Zsh
|
||||
and PowerShell.
|
||||
|
||||
For *bash*, move *rg.bash* to *$XDG_CONFIG_HOME/bash_completion*
|
||||
or */etc/bash_completion.d/*.
|
||||
|
||||
For *fish*, move *rg.fish* to *$HOME/.config/fish/completions*.
|
||||
|
||||
For *zsh*, move *_rg* to one of your *$fpath* directories.
|
||||
|
||||
|
||||
CAVEATS
|
||||
-------
|
||||
ripgrep may abort unexpectedly when using default settings if it searches a
|
||||
file that is simultaneously truncated. This behavior can be avoided by passing
|
||||
the *--no-mmap* flag which will forcefully disable the use of memory maps in
|
||||
all cases.
|
||||
|
||||
ripgrep may use a large amount of memory depending on a few factors. Firstly,
|
||||
if ripgrep uses parallelism for search (the default), then the entire output
|
||||
for each individual file is buffered into memory in order to prevent
|
||||
interleaving matches in the output. To avoid this, you can disable parallelism
|
||||
with the *-j1* flag. Secondly, ripgrep always needs to have at least a single
|
||||
line in memory in order to execute a search. A file with a very long line can
|
||||
thus cause ripgrep to use a lot of memory. Generally, this only occurs when
|
||||
searching binary data with the *-a* flag enabled. (When the *-a* flag isn't
|
||||
enabled, ripgrep will replace all NUL bytes with line terminators, which
|
||||
typically prevents exorbitant memory usage.) Thirdly, when ripgrep searches
|
||||
a large file using a memory map, the process will report its resident memory
|
||||
usage as the size of the file. However, this does not mean ripgrep actually
|
||||
needed to use that much memory; the operating system will generally handle this
|
||||
for you.
|
||||
|
||||
|
||||
VERSION
|
||||
-------
|
||||
{VERSION}
|
||||
|
||||
|
||||
HOMEPAGE
|
||||
--------
|
||||
https://github.com/BurntSushi/ripgrep
|
||||
|
||||
Please report bugs and feature requests in the issue tracker. Please do your
|
||||
best to provide a reproducible test case for bugs. This should include the
|
||||
corpus being searched, the *rg* command, the actual output and the expected
|
||||
output. Please also include the output of running the same *rg* command but
|
||||
with the *--debug* flag.
|
||||
|
||||
|
||||
AUTHORS
|
||||
-------
|
||||
Andrew Gallant <jamslam@gmail.com>
|
4
fuzz/.gitignore
vendored
Normal file
4
fuzz/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
coverage
|
188
fuzz/Cargo.lock
generated
Normal file
188
fuzz/Cargo.lock
generated
Normal file
@@ -0,0 +1,188 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuzz"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"libfuzzer-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.16"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"arbitrary",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.195"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.195"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
25
fuzz/Cargo.toml
Normal file
25
fuzz/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "fuzz"
|
||||
version = "0.0.1"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
globset = { path = "../crates/globset", features = ["arbitrary"] }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_glob"
|
||||
path = "fuzz_targets/fuzz_glob.rs"
|
||||
test = false
|
||||
doc = false
|
52
fuzz/README.md
Normal file
52
fuzz/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Fuzz Testing
|
||||
|
||||
## Introduction
|
||||
|
||||
Fuzz testing produces pseudo-random / arbitrary data that is used to find
|
||||
stability issues within a code base. While Rust provides a strong type system,
|
||||
this does not guarantee that an object will convert properly from one struct
|
||||
to another. It is the responsibility of the developer to ensure that a struct
|
||||
is converted properly. Fuzz testing will generate input within the domain of
|
||||
each property. This arbitrary data can then be used to convert from ObjectA
|
||||
to ObjectB and then back. This type of testing will help catch bugs that the
|
||||
type system is not able to see.
|
||||
|
||||
## Installation
|
||||
|
||||
This crate relies on the `cargo-fuzz` component. To install this component,
|
||||
run the following from the `fuzz` directory:
|
||||
|
||||
```bash
|
||||
cargo install cargo-fuzz
|
||||
```
|
||||
|
||||
## Listing Targets
|
||||
|
||||
Once installed, fuzz targets can be listed by running the following command:
|
||||
|
||||
```bash
|
||||
cargo fuzz list
|
||||
```
|
||||
|
||||
This command will print out a list of all targets that can be tested.
|
||||
|
||||
## Running Fuzz Tests
|
||||
|
||||
To run a fuzz test, the target must be specified:
|
||||
|
||||
```bash
|
||||
cargo fuzz run <target>
|
||||
```
|
||||
|
||||
Note that the above will run the fuzz test indefinitely. Use the
|
||||
`-max_total_time=<num seconds>` flag to specify how many seconds the test
|
||||
should run for:
|
||||
|
||||
```bash
|
||||
cargo fuzz run <target> -- -max_total_time=5
|
||||
```
|
||||
|
||||
The above command will run the fuzz test for five seconds. If the test
|
||||
completes without error it will show how many tests were run successfully.
|
||||
The test will abort and return a non-zero error code if it is able to produce
|
||||
an error. The arbitrary input will be displayed in the event of a failure.
|
22
fuzz/fuzz_targets/fuzz_glob.rs
Normal file
22
fuzz/fuzz_targets/fuzz_glob.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
#![no_main]
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use globset::Glob;
|
||||
|
||||
libfuzzer_sys::fuzz_target!(|glob_str: &str| {
|
||||
let Ok(glob) = Glob::new(glob_str) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(glob2) = Glob::from_str(glob_str) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Verify that a `Glob` constructed with `new` is the same as a `Glob`` constructed
|
||||
// with `from_str`.
|
||||
assert_eq!(glob, glob2);
|
||||
|
||||
// Verify that `Glob::glob` produces the same string as the original.
|
||||
assert_eq!(glob.glob(), glob_str);
|
||||
});
|
@@ -1,14 +1,14 @@
|
||||
class RipgrepBin < Formula
|
||||
version '14.0.2'
|
||||
version '14.1.1'
|
||||
desc "Recursively search directories for a regex pattern."
|
||||
homepage "https://github.com/BurntSushi/ripgrep"
|
||||
|
||||
if OS.mac?
|
||||
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-apple-darwin.tar.gz"
|
||||
sha256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
sha256 "fc87e78f7cb3fea12d69072e7ef3b21509754717b746368fd40d88963630e2b3"
|
||||
elsif OS.linux?
|
||||
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-unknown-linux-musl.tar.gz"
|
||||
sha256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
sha256 "4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e"
|
||||
end
|
||||
|
||||
conflicts_with "ripgrep"
|
||||
|
122
tests/binary.rs
122
tests/binary.rs
@@ -4,8 +4,8 @@ use crate::util::{Dir, TestCommand};
|
||||
// handling of binary files. There's quite a bit of discussion on this in this
|
||||
// bug report: https://github.com/BurntSushi/ripgrep/issues/306
|
||||
|
||||
// Our haystack is the first 500 lines of Gutenberg's copy of "A Study in
|
||||
// Scarlet," with a NUL byte at line 1898: `abcdef\x00`.
|
||||
// Our haystack is the first 2,133 lines of Gutenberg's copy of "A Study in
|
||||
// Scarlet," with a NUL byte at line 1870: `abcdef\x00`.
|
||||
//
|
||||
// The position and size of the haystack is, unfortunately, significant. In
|
||||
// particular, the NUL byte is specifically inserted at some point *after* the
|
||||
@@ -21,10 +21,124 @@ use crate::util::{Dir, TestCommand};
|
||||
// detection with memory maps is a bit different. Namely, NUL bytes are only
|
||||
// searched for in the first few KB of the file and in a match. Normally, NUL
|
||||
// bytes are searched for everywhere.
|
||||
//
|
||||
// TODO: Add tests for binary file detection when using memory maps.
|
||||
const HAY: &'static [u8] = include_bytes!("./data/sherlock-nul.txt");
|
||||
|
||||
// Tests for binary file detection when using memory maps.
|
||||
// As noted in the original comments, with memory maps binary detection
|
||||
// works differently - NUL bytes are only searched for in the first few KB
|
||||
// of the file and in matches.
|
||||
|
||||
// Test that matches in a binary file with memory maps work as expected
|
||||
// with implicit file search (via glob pattern).
|
||||
rgtest!(mmap_match_implicit, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&["--mmap", "-n", "Project Gutenberg EBook", "-g", "hay"]);
|
||||
|
||||
// With mmap, we get a match and a warning about binary content
|
||||
let expected = "\
|
||||
hay:1:The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Test with an explicit file argument when using memory maps.
|
||||
rgtest!(mmap_match_explicit, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&["--mmap", "-n", "Project Gutenberg EBook", "hay"]);
|
||||
|
||||
let expected = "\
|
||||
1:The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Test specifically with a pattern that matches near the NUL byte which should
|
||||
// trigger binary detection with memory maps.
|
||||
rgtest!(mmap_match_near_nul, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
// Pattern that matches around line 1898 where the NUL byte is.
|
||||
// Note: Using direct file path instead of glob.
|
||||
cmd.args(&["--mmap", "-n", "abcdef", "hay"]);
|
||||
|
||||
let expected = "\
|
||||
binary file matches (found \"\\0\" byte around offset 77041)
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Test with --count option to ensure full file scanning works with mmap.
|
||||
rgtest!(mmap_match_count, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&["--mmap", "-c", "Project Gutenberg EBook|Heaven", "hay"]);
|
||||
|
||||
// With mmap, since we're counting all matches and might not
|
||||
// encounter the NUL byte during initial detection, the count
|
||||
// should still be reported.
|
||||
eqnice!("2\n", cmd.stdout());
|
||||
});
|
||||
|
||||
// Test binary detection with mmap when pattern would match before and after NUL
|
||||
// byte.
|
||||
rgtest!(mmap_match_multiple, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
// Use explicit file path.
|
||||
cmd.args(&["--mmap", "-n", "Project Gutenberg EBook|Heaven", "hay"]);
|
||||
|
||||
// With explicit file and memory maps, matches before and after NUL byte
|
||||
// are shown.
|
||||
let expected = "\
|
||||
1:The Project Gutenberg EBook of A Study In Scarlet, by Arthur Conan Doyle
|
||||
1871:\"No. Heaven knows what the objects of his studies are. But here we
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Test that --binary flag can have odd results when searching with a memory
|
||||
// map.
|
||||
rgtest!(mmap_binary_flag, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
// Use glob pattern.
|
||||
cmd.args(&["--mmap", "-n", "--binary", "Heaven", "-g", "hay"]);
|
||||
|
||||
let expected = "\
|
||||
hay:1871:\"No. Heaven knows what the objects of his studies are. But here we
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Test that using -a/--text flag works as expected with mmap.
|
||||
rgtest!(mmap_text_flag, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
cmd.args(&["--mmap", "-n", "--text", "Heaven", "-g", "hay"]);
|
||||
|
||||
// With --text flag, binary detection should be disabled.
|
||||
let expected = "\
|
||||
hay:1871:\"No. Heaven knows what the objects of his studies are. But here we
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// Test pattern that matches before and after the NUL byte with memory maps.
|
||||
rgtest!(mmap_after_nul_match, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_bytes("hay", HAY);
|
||||
// Use explicit file path.
|
||||
cmd.args(&["--mmap", "-n", "medical student", "hay"]);
|
||||
|
||||
// With explicit file and memory maps, all matches are shown
|
||||
let expected = "\
|
||||
176:\"A medical student, I suppose?\" said I.
|
||||
409:\"A medical student, I suppose?\" said I.
|
||||
642:\"A medical student, I suppose?\" said I.
|
||||
875:\"A medical student, I suppose?\" said I.
|
||||
1108:\"A medical student, I suppose?\" said I.
|
||||
1341:\"A medical student, I suppose?\" said I.
|
||||
1574:\"A medical student, I suppose?\" said I.
|
||||
1807:\"A medical student, I suppose?\" said I.
|
||||
1867:\"And yet you say he is not a medical student?\"
|
||||
";
|
||||
eqnice!(expected, cmd.stdout());
|
||||
});
|
||||
|
||||
// This tests that ripgrep prints a warning message if it finds and prints a
|
||||
// match in a binary file before detecting that it is a binary file. The point
|
||||
// here is to notify that user that the search of the file is only partially
|
||||
|
@@ -972,10 +972,10 @@ rgtest!(f1404_nothing_searched_warning, |dir: Dir, mut cmd: TestCommand| {
|
||||
cmd.assert_err();
|
||||
|
||||
// Test that we actually get an error message that we expect.
|
||||
let output = cmd.cmd().output().unwrap();
|
||||
let output = cmd.raw_output();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let expected = "\
|
||||
No files were searched, which means ripgrep probably applied \
|
||||
rg: No files were searched, which means ripgrep probably applied \
|
||||
a filter you didn't expect.\n\
|
||||
Running with --debug will show why files are being skipped.\n\
|
||||
";
|
||||
@@ -995,7 +995,7 @@ rgtest!(f1404_nothing_searched_ignored, |dir: Dir, mut cmd: TestCommand| {
|
||||
|
||||
// But since --no-messages is given, there should not be any error message
|
||||
// printed.
|
||||
let output = cmd.cmd().output().unwrap();
|
||||
let output = cmd.raw_output();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let expected = "";
|
||||
eqnice!(expected, stderr);
|
||||
|
@@ -21,45 +21,47 @@ impl Message {
|
||||
fn unwrap_begin(&self) -> Begin {
|
||||
match *self {
|
||||
Message::Begin(ref x) => x.clone(),
|
||||
ref x => panic!("expected Message::Begin but got {:?}", x),
|
||||
ref x => panic!("expected Message::Begin but got {x:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_end(&self) -> End {
|
||||
match *self {
|
||||
Message::End(ref x) => x.clone(),
|
||||
ref x => panic!("expected Message::End but got {:?}", x),
|
||||
ref x => panic!("expected Message::End but got {x:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_match(&self) -> Match {
|
||||
match *self {
|
||||
Message::Match(ref x) => x.clone(),
|
||||
ref x => panic!("expected Message::Match but got {:?}", x),
|
||||
ref x => panic!("expected Message::Match but got {x:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_context(&self) -> Context {
|
||||
match *self {
|
||||
Message::Context(ref x) => x.clone(),
|
||||
ref x => panic!("expected Message::Context but got {:?}", x),
|
||||
ref x => panic!("expected Message::Context but got {x:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_summary(&self) -> Summary {
|
||||
match *self {
|
||||
Message::Summary(ref x) => x.clone(),
|
||||
ref x => panic!("expected Message::Summary but got {:?}", x),
|
||||
ref x => panic!("expected Message::Summary but got {x:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Begin {
|
||||
path: Option<Data>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct End {
|
||||
path: Option<Data>,
|
||||
binary_offset: Option<u64>,
|
||||
@@ -67,12 +69,14 @@ struct End {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Summary {
|
||||
elapsed_total: Duration,
|
||||
stats: Stats,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Match {
|
||||
path: Option<Data>,
|
||||
lines: Data,
|
||||
@@ -82,6 +86,7 @@ struct Match {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Context {
|
||||
path: Option<Data>,
|
||||
lines: Data,
|
||||
@@ -91,9 +96,11 @@ struct Context {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct SubMatch {
|
||||
#[serde(rename = "match")]
|
||||
m: Data,
|
||||
replacement: Option<Data>,
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
@@ -117,6 +124,7 @@ impl Data {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Stats {
|
||||
elapsed: Duration,
|
||||
searches: u64,
|
||||
@@ -128,6 +136,7 @@ struct Stats {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Duration {
|
||||
#[serde(flatten)]
|
||||
duration: time::Duration,
|
||||
@@ -178,6 +187,7 @@ rgtest!(basic, |dir: Dir, mut cmd: TestCommand| {
|
||||
absolute_offset: 129,
|
||||
submatches: vec![SubMatch {
|
||||
m: Data::text("Sherlock Holmes"),
|
||||
replacement: None,
|
||||
start: 48,
|
||||
end: 63,
|
||||
},],
|
||||
@@ -189,6 +199,57 @@ rgtest!(basic, |dir: Dir, mut cmd: TestCommand| {
|
||||
assert_eq!(msgs[4].unwrap_summary().stats.bytes_printed, 494);
|
||||
});
|
||||
|
||||
rgtest!(replacement, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("sherlock", SHERLOCK);
|
||||
cmd.arg("--json")
|
||||
.arg("-B1")
|
||||
.arg("Sherlock Holmes")
|
||||
.args(["-r", "John Watson"])
|
||||
.arg("sherlock");
|
||||
|
||||
let msgs = json_decode(&cmd.stdout());
|
||||
|
||||
assert_eq!(
|
||||
msgs[0].unwrap_begin(),
|
||||
Begin { path: Some(Data::text("sherlock")) }
|
||||
);
|
||||
assert_eq!(
|
||||
msgs[1].unwrap_context(),
|
||||
Context {
|
||||
path: Some(Data::text("sherlock")),
|
||||
lines: Data::text(
|
||||
"Holmeses, success in the province of \
|
||||
detective work must always\n",
|
||||
),
|
||||
line_number: Some(2),
|
||||
absolute_offset: 65,
|
||||
submatches: vec![],
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
msgs[2].unwrap_match(),
|
||||
Match {
|
||||
path: Some(Data::text("sherlock")),
|
||||
lines: Data::text(
|
||||
"be, to a very large extent, the result of luck. \
|
||||
Sherlock Holmes\n",
|
||||
),
|
||||
line_number: Some(3),
|
||||
absolute_offset: 129,
|
||||
submatches: vec![SubMatch {
|
||||
m: Data::text("Sherlock Holmes"),
|
||||
replacement: Some(Data::text("John Watson")),
|
||||
start: 48,
|
||||
end: 63,
|
||||
},],
|
||||
}
|
||||
);
|
||||
assert_eq!(msgs[3].unwrap_end().path, Some(Data::text("sherlock")));
|
||||
assert_eq!(msgs[3].unwrap_end().binary_offset, None);
|
||||
assert_eq!(msgs[4].unwrap_summary().stats.searches_with_match, 1);
|
||||
assert_eq!(msgs[4].unwrap_summary().stats.bytes_printed, 531);
|
||||
});
|
||||
|
||||
rgtest!(quiet_stats, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("sherlock", SHERLOCK);
|
||||
cmd.arg("--json")
|
||||
@@ -244,6 +305,7 @@ rgtest!(notutf8, |dir: Dir, mut cmd: TestCommand| {
|
||||
absolute_offset: 0,
|
||||
submatches: vec![SubMatch {
|
||||
m: Data::bytes("/w=="),
|
||||
replacement: None,
|
||||
start: 4,
|
||||
end: 5,
|
||||
},],
|
||||
@@ -285,6 +347,7 @@ rgtest!(notutf8_file, |dir: Dir, mut cmd: TestCommand| {
|
||||
absolute_offset: 0,
|
||||
submatches: vec![SubMatch {
|
||||
m: Data::bytes("/w=="),
|
||||
replacement: None,
|
||||
start: 4,
|
||||
end: 5,
|
||||
},],
|
||||
@@ -305,7 +368,12 @@ rgtest!(crlf, |dir: Dir, mut cmd: TestCommand| {
|
||||
|
||||
assert_eq!(
|
||||
msgs[1].unwrap_match().submatches[0].clone(),
|
||||
SubMatch { m: Data::text("Sherlock"), start: 56, end: 64 },
|
||||
SubMatch {
|
||||
m: Data::text("Sherlock"),
|
||||
replacement: None,
|
||||
start: 56,
|
||||
end: 64
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
|
@@ -411,7 +411,7 @@ rgtest!(include_zero, |dir: Dir, mut cmd: TestCommand| {
|
||||
cmd.args(&["--count", "--include-zero", "nada"]);
|
||||
cmd.assert_err();
|
||||
|
||||
let output = cmd.cmd().output().unwrap();
|
||||
let output = cmd.raw_output();
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let expected = "sherlock:0\n";
|
||||
|
||||
@@ -423,7 +423,7 @@ rgtest!(include_zero_override, |dir: Dir, mut cmd: TestCommand| {
|
||||
cmd.args(&["--count", "--include-zero", "--no-include-zero", "nada"]);
|
||||
cmd.assert_err();
|
||||
|
||||
let output = cmd.cmd().output().unwrap();
|
||||
let output = cmd.raw_output();
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(stdout.is_empty());
|
||||
});
|
||||
@@ -1092,16 +1092,22 @@ rgtest!(type_list, |_: Dir, mut cmd: TestCommand| {
|
||||
// This order is important when sorting them by system time-stamps.
|
||||
fn sort_setup(dir: Dir) {
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
// As reported in https://github.com/BurntSushi/ripgrep/issues/3071
|
||||
// this test fails if sufficient delay is not given on Windows/Aarch64.
|
||||
let delay = if cfg!(all(windows, target_arch = "aarch64")) {
|
||||
Duration::from_millis(1000)
|
||||
} else {
|
||||
Duration::from_millis(100)
|
||||
};
|
||||
let sub_dir = dir.path().join("dir");
|
||||
dir.create("a", "test");
|
||||
sleep(Duration::from_millis(100));
|
||||
sleep(delay);
|
||||
dir.create_dir(&sub_dir);
|
||||
sleep(Duration::from_millis(100));
|
||||
sleep(delay);
|
||||
dir.create(sub_dir.join("c"), "test");
|
||||
sleep(Duration::from_millis(100));
|
||||
sleep(delay);
|
||||
dir.create("b", "test");
|
||||
sleep(Duration::from_millis(100));
|
||||
sleep(delay);
|
||||
dir.create(sub_dir.join("d"), "test");
|
||||
}
|
||||
|
||||
|
@@ -399,12 +399,12 @@ rgtest!(r428_unrecognized_style, |dir: Dir, mut cmd: TestCommand| {
|
||||
cmd.arg("--colors=match:style:").arg("Sherlock");
|
||||
cmd.assert_err();
|
||||
|
||||
let output = cmd.cmd().output().unwrap();
|
||||
let output = cmd.raw_output();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let expected = "\
|
||||
error parsing flag --colors: \
|
||||
rg: error parsing flag --colors: \
|
||||
unrecognized style attribute ''. Choose from: nobold, bold, nointense, \
|
||||
intense, nounderline, underline.
|
||||
intense, nounderline, underline, noitalic, italic.
|
||||
";
|
||||
eqnice!(expected, stderr);
|
||||
});
|
||||
@@ -569,6 +569,197 @@ rgtest!(r807, |dir: Dir, mut cmd: TestCommand| {
|
||||
eqnice!(".a/c/file:test\n", cmd.arg("--hidden").arg("test").stdout());
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/pull/2711
|
||||
//
|
||||
// Note that this isn't a regression test. In particular, this didn't fail
|
||||
// with ripgrep 14.1.1. I couldn't figure out how to turn what the OP gave me
|
||||
// into a failing test.
|
||||
rgtest!(r2711, |dir: Dir, _cmd: TestCommand| {
|
||||
dir.create_dir("a/b");
|
||||
dir.create("a/.ignore", ".foo");
|
||||
dir.create("a/b/.foo", "");
|
||||
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!("a/.ignore\n", cmd.arg("--hidden").arg("--files").stdout());
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!(
|
||||
"./a/.ignore\n",
|
||||
cmd.arg("--hidden").arg("--files").arg("./").stdout()
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!(
|
||||
"a/.ignore\n",
|
||||
cmd.arg("--hidden").arg("--files").arg("a").stdout()
|
||||
);
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.arg("--hidden").arg("--files").arg("a/b").assert_err();
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!(
|
||||
"./a/.ignore\n",
|
||||
cmd.arg("--hidden").arg("--files").arg("./a").stdout()
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.current_dir(dir.path().join("a"));
|
||||
eqnice!(".ignore\n", cmd.arg("--hidden").arg("--files").stdout());
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.current_dir(dir.path().join("a").join("b"));
|
||||
cmd.arg("--hidden").arg("--files").assert_err();
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.current_dir(dir.path().join("./a"));
|
||||
eqnice!(".ignore\n", cmd.arg("--hidden").arg("--files").stdout());
|
||||
}
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/829
|
||||
rgtest!(r829_original, |dir: Dir, _cmd: TestCommand| {
|
||||
dir.create_dir("a/b");
|
||||
dir.create(".ignore", "/a/b");
|
||||
dir.create("a/b/test.txt", "Sample text");
|
||||
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.args(&["Sample"]).assert_err();
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.args(&["Sample", "a"]).assert_err();
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.current_dir(dir.path().join("a"));
|
||||
cmd.args(&["Sample"]).assert_err();
|
||||
}
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/2731
|
||||
rgtest!(r829_2731, |dir: Dir, _cmd: TestCommand| {
|
||||
dir.create_dir("some_dir/build");
|
||||
dir.create("some_dir/build/foo", "string");
|
||||
dir.create(".ignore", "build/\n!/some_dir/build/");
|
||||
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!("some_dir/build/foo\n", cmd.arg("-l").arg("string").stdout());
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!(
|
||||
"some_dir/build/foo\n",
|
||||
cmd.arg("-l").arg("string").arg("some_dir").stdout()
|
||||
);
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!(
|
||||
"./some_dir/build/foo\n",
|
||||
cmd.arg("-l").arg("string").arg("./some_dir").stdout()
|
||||
);
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!(
|
||||
"some_dir/build/foo\n",
|
||||
cmd.arg("-l").arg("string").arg("some_dir/build").stdout()
|
||||
);
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!(
|
||||
"./some_dir/build/foo\n",
|
||||
cmd.arg("-l").arg("string").arg("./some_dir/build").stdout()
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/2747
|
||||
rgtest!(r829_2747, |dir: Dir, _cmd: TestCommand| {
|
||||
dir.create_dir("a/c/b");
|
||||
dir.create_dir("a/src/f/b");
|
||||
dir.create("a/c/b/foo", "");
|
||||
dir.create("a/src/f/b/foo", "");
|
||||
dir.create(".ignore", "/a/*/b");
|
||||
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!("a/src/f/b/foo\n", cmd.arg("--files").stdout());
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!("a/src/f/b/foo\n", cmd.arg("--files").arg("a/src").stdout());
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.current_dir(dir.path().join("a/src"));
|
||||
eqnice!("f/b/foo\n", cmd.arg("--files").stdout());
|
||||
}
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/2778
|
||||
rgtest!(r829_2778, |dir: Dir, _cmd: TestCommand| {
|
||||
dir.create_dir("parent/subdir");
|
||||
dir.create(".ignore", "/parent/*.txt");
|
||||
dir.create("parent/ignore-me.txt", "");
|
||||
dir.create("parent/subdir/dont-ignore-me.txt", "");
|
||||
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!(
|
||||
"parent/subdir/dont-ignore-me.txt\n",
|
||||
cmd.arg("--files").stdout()
|
||||
);
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.current_dir(dir.path().join("parent"));
|
||||
eqnice!("subdir/dont-ignore-me.txt\n", cmd.arg("--files").stdout());
|
||||
}
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/2836
|
||||
rgtest!(r829_2836, |dir: Dir, _cmd: TestCommand| {
|
||||
dir.create_dir("testdir/sub/sub2");
|
||||
dir.create(".ignore", "/testdir/sub/sub2/\n");
|
||||
dir.create("testdir/sub/sub2/foo", "");
|
||||
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.arg("--files").assert_err();
|
||||
}
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.current_dir(dir.path().join("testdir"));
|
||||
cmd.arg("--files").assert_err();
|
||||
}
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/pull/2933
|
||||
rgtest!(r829_2933, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create_dir("testdir/sub/sub2");
|
||||
dir.create(".ignore", "/testdir/sub/sub2/");
|
||||
dir.create("testdir/sub/sub2/testfile", "needle");
|
||||
|
||||
let args = &["--files-with-matches", "needle"];
|
||||
cmd.current_dir(dir.path().join("testdir"));
|
||||
cmd.args(args).assert_err();
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/900
|
||||
rgtest!(r900, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("sherlock", SHERLOCK);
|
||||
@@ -764,6 +955,43 @@ rgtest!(r1319, |dir: Dir, mut cmd: TestCommand| {
|
||||
);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/1332
|
||||
rgtest!(r1334_invert_empty_patterns, |dir: Dir, _cmd: TestCommand| {
|
||||
dir.create("zero-patterns", "");
|
||||
dir.create("one-pattern", "\n");
|
||||
dir.create("haystack", "one\ntwo\nthree\n");
|
||||
|
||||
// zero patterns matches nothing
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.arg("-f").arg("zero-patterns").arg("haystack").assert_err();
|
||||
}
|
||||
// one pattern that matches empty string matches everything
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!(
|
||||
"one\ntwo\nthree\n",
|
||||
cmd.arg("-f").arg("one-pattern").arg("haystack").stdout()
|
||||
);
|
||||
}
|
||||
|
||||
// inverting zero patterns matches everything
|
||||
// (This is the regression. ripgrep used to match nothing because of an
|
||||
// incorrect optimization.)
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
eqnice!(
|
||||
"one\ntwo\nthree\n",
|
||||
cmd.arg("-vf").arg("zero-patterns").arg("haystack").stdout()
|
||||
);
|
||||
}
|
||||
// inverting one pattern that matches empty string matches nothing
|
||||
{
|
||||
let mut cmd = dir.command();
|
||||
cmd.arg("-vf").arg("one-pattern").arg("haystack").assert_err();
|
||||
}
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/1334
|
||||
rgtest!(r1334_crazy_literals, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("patterns", &"1.208.0.0/12\n".repeat(40));
|
||||
@@ -965,6 +1193,15 @@ rgtest!(f1757, |dir: Dir, _: TestCommand| {
|
||||
eqnice!("rust/source.rs\n", dir.command().args(args).stdout());
|
||||
let args = &["--files-with-matches", "needle", "./rust"];
|
||||
eqnice!("./rust/source.rs\n", dir.command().args(args).stdout());
|
||||
|
||||
dir.create_dir("rust1/target/onemore");
|
||||
dir.create(".ignore", "rust1/target/onemore");
|
||||
dir.create("rust1/source.rs", "needle");
|
||||
dir.create("rust1/target/onemore/rustdoc-output.html", "needle");
|
||||
let args = &["--files-with-matches", "needle", "rust1"];
|
||||
eqnice!("rust1/source.rs\n", dir.command().args(args).stdout());
|
||||
let args = &["--files-with-matches", "needle", "./rust1"];
|
||||
eqnice!("./rust1/source.rs\n", dir.command().args(args).stdout());
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/1765
|
||||
@@ -1217,3 +1454,66 @@ rgtest!(r2658_null_data_line_regexp, |dir: Dir, mut cmd: TestCommand| {
|
||||
let got = cmd.args(&["--null-data", "--line-regexp", r"bar"]).stdout();
|
||||
eqnice!("haystack:bar\0", got);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/pull/2944
|
||||
rgtest!(r2944_incorrect_bytes_searched, |dir: Dir, mut cmd: TestCommand| {
|
||||
dir.create("haystack", "foo1\nfoo2\nfoo3\nfoo4\nfoo5\n");
|
||||
let got = cmd.args(&["--stats", "-m2", "foo", "."]).stdout();
|
||||
assert!(got.contains("10 bytes searched\n"));
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/2990
|
||||
rgtest!(r2990_trip_over_trailing_dot, |dir: Dir, _cmd: TestCommand| {
|
||||
dir.create_dir("asdf");
|
||||
dir.create_dir("asdf.");
|
||||
dir.create("asdf/foo", "");
|
||||
dir.create("asdf./foo", "");
|
||||
|
||||
let got = dir.command().args(&["--files", "-g", "!asdf/"]).stdout();
|
||||
eqnice!("asdf./foo\n", got);
|
||||
|
||||
// This used to ignore the glob given and included `asdf./foo` in output.
|
||||
let got = dir.command().args(&["--files", "-g", "!asdf./"]).stdout();
|
||||
eqnice!("asdf/foo\n", got);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/3108
|
||||
rgtest!(r3108_files_without_match_quiet_exit, |dir: Dir, _: TestCommand| {
|
||||
dir.create("yes-match", "abc");
|
||||
dir.create("non-match", "xyz");
|
||||
|
||||
dir.command().args(&["-q", "abc", "non-match"]).assert_exit_code(1);
|
||||
dir.command().args(&["-q", "abc", "yes-match"]).assert_exit_code(0);
|
||||
dir.command()
|
||||
.args(&["--files-with-matches", "-q", "abc", "non-match"])
|
||||
.assert_exit_code(1);
|
||||
dir.command()
|
||||
.args(&["--files-with-matches", "-q", "abc", "yes-match"])
|
||||
.assert_exit_code(0);
|
||||
|
||||
dir.command()
|
||||
.args(&["--files-without-match", "abc", "non-match"])
|
||||
.assert_exit_code(0);
|
||||
dir.command()
|
||||
.args(&["--files-without-match", "abc", "yes-match"])
|
||||
.assert_exit_code(1);
|
||||
|
||||
let got = dir
|
||||
.command()
|
||||
.args(&["--files-without-match", "abc", "non-match"])
|
||||
.stdout();
|
||||
eqnice!("non-match\n", got);
|
||||
|
||||
dir.command()
|
||||
.args(&["--files-without-match", "-q", "abc", "non-match"])
|
||||
.assert_exit_code(0);
|
||||
dir.command()
|
||||
.args(&["--files-without-match", "-q", "abc", "yes-match"])
|
||||
.assert_exit_code(1);
|
||||
|
||||
let got = dir
|
||||
.command()
|
||||
.args(&["--files-without-match", "-q", "abc", "non-match"])
|
||||
.stdout();
|
||||
eqnice!("", got);
|
||||
});
|
||||
|
@@ -9,6 +9,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use bstr::ByteSlice;
|
||||
|
||||
static TEST_DIR: &'static str = "ripgrep-tests";
|
||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
@@ -66,14 +68,14 @@ impl Dir {
|
||||
/// does not need to be distinct for each invocation, but should correspond
|
||||
/// to a logical grouping of tests.
|
||||
pub fn new(name: &str) -> Dir {
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
let root = env::current_exe()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.expect("executable's directory")
|
||||
.to_path_buf();
|
||||
let dir =
|
||||
env::temp_dir().join(TEST_DIR).join(name).join(&format!("{}", id));
|
||||
env::temp_dir().join(TEST_DIR).join(name).join(&format!("{id}"));
|
||||
if dir.exists() {
|
||||
nice_err(&dir, fs::remove_dir_all(&dir));
|
||||
}
|
||||
@@ -282,16 +284,7 @@ impl TestCommand {
|
||||
/// Runs and captures the stdout of the given command.
|
||||
pub fn stdout(&mut self) -> String {
|
||||
let o = self.output();
|
||||
let stdout = String::from_utf8_lossy(&o.stdout);
|
||||
match stdout.parse() {
|
||||
Ok(t) => t,
|
||||
Err(err) => {
|
||||
panic!(
|
||||
"could not convert from string: {:?}\n\n{}",
|
||||
err, stdout
|
||||
);
|
||||
}
|
||||
}
|
||||
String::from_utf8_lossy(&o.stdout).into_owned()
|
||||
}
|
||||
|
||||
/// Pipe `input` to a command, and collect the output.
|
||||
@@ -311,27 +304,26 @@ impl TestCommand {
|
||||
let output = self.expect_success(child.wait_with_output().unwrap());
|
||||
worker.join().unwrap().unwrap();
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
match stdout.parse() {
|
||||
Ok(t) => t,
|
||||
Err(err) => {
|
||||
panic!(
|
||||
"could not convert from string: {:?}\n\n{}",
|
||||
err, stdout
|
||||
);
|
||||
}
|
||||
}
|
||||
String::from_utf8_lossy(&output.stdout).into_owned()
|
||||
}
|
||||
|
||||
/// Gets the output of a command. If the command failed, then this panics.
|
||||
pub fn output(&mut self) -> process::Output {
|
||||
let output = self.cmd.output().unwrap();
|
||||
let output = self.raw_output();
|
||||
self.expect_success(output)
|
||||
}
|
||||
|
||||
/// Gets the raw output of a command after filtering nonsense like jemalloc
|
||||
/// error messages from stderr.
|
||||
pub fn raw_output(&mut self) -> process::Output {
|
||||
let mut output = self.cmd.output().unwrap();
|
||||
output.stderr = strip_jemalloc_nonsense(&output.stderr);
|
||||
output
|
||||
}
|
||||
|
||||
/// Runs the command and asserts that it resulted in an error exit code.
|
||||
pub fn assert_err(&mut self) {
|
||||
let o = self.cmd.output().unwrap();
|
||||
let o = self.raw_output();
|
||||
if o.status.success() {
|
||||
panic!(
|
||||
"\n\n===== {:?} =====\n\
|
||||
@@ -479,7 +471,7 @@ fn dir_list<P: AsRef<Path>>(dir: P) -> Vec<String> {
|
||||
/// So... we just manually handle these cases. So fucking fun.
|
||||
fn cross_runner() -> Option<String> {
|
||||
let runner = std::env::var("CROSS_RUNNER").ok()?;
|
||||
if runner.is_empty() {
|
||||
if runner.is_empty() || runner == "empty" {
|
||||
return None;
|
||||
}
|
||||
if cfg!(target_arch = "powerpc64") {
|
||||
@@ -500,3 +492,17 @@ fn cross_runner() -> Option<String> {
|
||||
pub fn is_cross() -> bool {
|
||||
std::env::var("CROSS_RUNNER").ok().map_or(false, |v| !v.is_empty())
|
||||
}
|
||||
|
||||
/// Strips absolutely fucked `<jemalloc>:` lines from the output.
|
||||
///
|
||||
/// In theory this only happens under qemu, which is where our tests run under
|
||||
/// `cross`. But is messes with our tests, because... they don't expect the
|
||||
/// allocator to fucking write to stderr. I mean, what the fuck? Who prints a
|
||||
/// warning message with absolutely no instruction for what to do with it or
|
||||
/// how to disable it. Absolutely fucking bonkers.
|
||||
fn strip_jemalloc_nonsense(data: &[u8]) -> Vec<u8> {
|
||||
let lines = data
|
||||
.lines_with_terminator()
|
||||
.filter(|line| !line.starts_with_str("<jemalloc>:"));
|
||||
bstr::concat(lines)
|
||||
}
|
||||
|
Reference in New Issue
Block a user