mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-08-24 00:23:47 -07:00
Compare commits
140 Commits
grep-match
...
grep-searc
Author | SHA1 | Date | |
---|---|---|---|
|
fc3cf41247 | ||
|
a4868b8835 | ||
|
f99b991117 | ||
|
de0bc78982 | ||
|
147e96914c | ||
|
0abc40c23c | ||
|
f768796e4f | ||
|
da0c0c4705 | ||
|
05411b2b32 | ||
|
cc93db3b18 | ||
|
049354b766 | ||
|
386dd2806d | ||
|
5fe9a954e6 | ||
|
f158a42a71 | ||
|
5724391d39 | ||
|
0df71240ff | ||
|
f3164f2615 | ||
|
31d3e24130 | ||
|
bf842dbc7f | ||
|
6d5dba85bd | ||
|
afb89bcdad | ||
|
332dc56372 | ||
|
12a6ca45f9 | ||
|
9d703110cf | ||
|
e99b6bda0e | ||
|
276e2c9b9a | ||
|
9a9f54d44c | ||
|
47833b9ce7 | ||
|
44a9e37737 | ||
|
8fd05cacee | ||
|
4691d11034 | ||
|
519a6b68af | ||
|
9c940b45f4 | ||
|
0a167021c3 | ||
|
aeaa5fc1b1 | ||
|
7048a06c31 | ||
|
23be3cf850 | ||
|
b48bbf527d | ||
|
8eabe47b57 | ||
|
ff712bfd9d | ||
|
a7f2d48234 | ||
|
57500ad013 | ||
|
0b04553aff | ||
|
1ae121122f | ||
|
688003e51c | ||
|
718a00f6f2 | ||
|
7cbc535d70 | ||
|
7a6a40bae1 | ||
|
1e9ee2cc85 | ||
|
968491f8e9 | ||
|
63b0f31a22 | ||
|
7ecee299a5 | ||
|
dd396ff34e | ||
|
fb0a82f3c3 | ||
|
dbc8ca9cc1 | ||
|
c3db8db93d | ||
|
17ef4c40f3 | ||
|
a9e0477ea8 | ||
|
b3c5773266 | ||
|
118b950085 | ||
|
b45b2f58ea | ||
|
662a9bc73d | ||
|
401add0a99 | ||
|
f81b72721b | ||
|
1d4fccaadc | ||
|
09e464e674 | ||
|
31adff6f3c | ||
|
b41e596327 | ||
|
fb62266620 | ||
|
acf226c39d | ||
|
8299625e48 | ||
|
db256c87eb | ||
|
ba533f390e | ||
|
ba503eb677 | ||
|
f72c2dfd90 | ||
|
c0aa58b4f7 | ||
|
184ee4c328 | ||
|
e82fbf2c46 | ||
|
eb18da0450 | ||
|
0f7494216f | ||
|
442a278635 | ||
|
7ebed3ace6 | ||
|
8a7db1a918 | ||
|
ce80d794c0 | ||
|
c5d467a2ab | ||
|
a62cd553c2 | ||
|
ce5188335b | ||
|
b7a456ae83 | ||
|
d14f0b37d6 | ||
|
3ddc3c040f | ||
|
eeaa42ecaf | ||
|
3797a2a5cb | ||
|
0e2f8f7b47 | ||
|
3dd4b77dfb | ||
|
3b5cdea862 | ||
|
54b3e9eb10 | ||
|
56e8864426 | ||
|
b8f619d16e | ||
|
83dff33326 | ||
|
003c3695f4 | ||
|
10777c150d | ||
|
827179250b | ||
|
fd22cd520b | ||
|
241bc8f8fc | ||
|
b6e30124e0 | ||
|
4846d63539 | ||
|
13c47530a6 | ||
|
328f4369e6 | ||
|
04518e32e7 | ||
|
f2eaf5b977 | ||
|
3edeeca6e9 | ||
|
c41b353009 | ||
|
d18839f3dc | ||
|
8f978a3cf7 | ||
|
87b745454d | ||
|
e5bb750995 | ||
|
d599f0b3c7 | ||
|
40e310a9f9 | ||
|
510f15f4da | ||
|
f9ce7a84a8 | ||
|
1b6089674e | ||
|
05a0389555 | ||
|
16353bad6e | ||
|
fe442de091 | ||
|
1bb8b7170f | ||
|
55ed698a98 | ||
|
f1e025873f | ||
|
033ad2b8e4 | ||
|
098a8ee843 | ||
|
2f3dbf5fee | ||
|
5c80e4adb6 | ||
|
fcd1853031 | ||
|
74a89be641 | ||
|
5b1ce8bdc2 | ||
|
1529ce3341 | ||
|
95a4f15916 | ||
|
0eef05142a | ||
|
edd6eb4e06 | ||
|
7ac9782970 | ||
|
180054d7dc |
@@ -62,13 +62,13 @@ matrix:
|
|||||||
# Minimum Rust supported channel. We enable these to make sure ripgrep
|
# Minimum Rust supported channel. We enable these to make sure ripgrep
|
||||||
# continues to work on the advertised minimum Rust version.
|
# continues to work on the advertised minimum Rust version.
|
||||||
- os: linux
|
- os: linux
|
||||||
rust: 1.23.0
|
rust: 1.32.0
|
||||||
env: TARGET=x86_64-unknown-linux-gnu
|
env: TARGET=x86_64-unknown-linux-gnu
|
||||||
- os: linux
|
- os: linux
|
||||||
rust: 1.23.0
|
rust: 1.32.0
|
||||||
env: TARGET=x86_64-unknown-linux-musl
|
env: TARGET=x86_64-unknown-linux-musl
|
||||||
- os: linux
|
- os: linux
|
||||||
rust: 1.23.0
|
rust: 1.32.0
|
||||||
env: TARGET=arm-unknown-linux-gnueabihf GCC_VERSION=4.8
|
env: TARGET=arm-unknown-linux-gnueabihf GCC_VERSION=4.8
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
@@ -93,7 +93,7 @@ deploy:
|
|||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
on:
|
on:
|
||||||
condition: $TRAVIS_RUST_VERSION = nightly
|
condition: $TRAVIS_RUST_VERSION = nightly
|
||||||
branch: master
|
branch: master # i guess we do need this after all?
|
||||||
tags: true
|
tags: true
|
||||||
api_key:
|
api_key:
|
||||||
secure: "IbSnsbGkxSydR/sozOf1/SRvHplzwRUHzcTjM7BKnr7GccL86gRPUrsrvD103KjQUGWIc1TnK1YTq5M0Onswg/ORDjqa1JEJPkPdPnVh9ipbF7M2De/7IlB4X4qXLKoApn8+bx2x/mfYXu4G+G1/2QdbaKK2yfXZKyjz0YFx+6CNrVCT2Nk8q7aHvOOzAL58vsG8iPDpupuhxlMDDn/UhyOWVInmPPQ0iJR1ZUJN8xJwXvKvBbfp3AhaBiAzkhXHNLgBR8QC5noWWMXnuVDMY3k4f3ic0V+p/qGUCN/nhptuceLxKFicMCYObSZeUzE5RAI0/OBW7l3z2iCoc+TbAnn+JrX/ObJCfzgAOXAU3tLaBFMiqQPGFKjKg1ltSYXomOFP/F7zALjpvFp4lYTBajRR+O3dqaxA9UQuRjw27vOeUpMcga4ZzL4VXFHzrxZKBHN//XIGjYAVhJ1NSSeGpeJV5/+jYzzWKfwSagRxQyVCzMooYFFXzn8Yxdm3PJlmp3GaAogNkdB9qKcrEvRINCelalzALPi0hD/HUDi8DD2PNTCLLMo6VSYtvc685Zbe+KgNzDV1YyTrRCUW6JotrS0r2ULLwnsh40hSB//nNv3XmwNmC/CmW5QAnIGj8cBMF4S2t6ohADIndojdAfNiptmaZOIT6owK7bWMgPMyopo="
|
secure: "IbSnsbGkxSydR/sozOf1/SRvHplzwRUHzcTjM7BKnr7GccL86gRPUrsrvD103KjQUGWIc1TnK1YTq5M0Onswg/ORDjqa1JEJPkPdPnVh9ipbF7M2De/7IlB4X4qXLKoApn8+bx2x/mfYXu4G+G1/2QdbaKK2yfXZKyjz0YFx+6CNrVCT2Nk8q7aHvOOzAL58vsG8iPDpupuhxlMDDn/UhyOWVInmPPQ0iJR1ZUJN8xJwXvKvBbfp3AhaBiAzkhXHNLgBR8QC5noWWMXnuVDMY3k4f3ic0V+p/qGUCN/nhptuceLxKFicMCYObSZeUzE5RAI0/OBW7l3z2iCoc+TbAnn+JrX/ObJCfzgAOXAU3tLaBFMiqQPGFKjKg1ltSYXomOFP/F7zALjpvFp4lYTBajRR+O3dqaxA9UQuRjw27vOeUpMcga4ZzL4VXFHzrxZKBHN//XIGjYAVhJ1NSSeGpeJV5/+jYzzWKfwSagRxQyVCzMooYFFXzn8Yxdm3PJlmp3GaAogNkdB9qKcrEvRINCelalzALPi0hD/HUDi8DD2PNTCLLMo6VSYtvc685Zbe+KgNzDV1YyTrRCUW6JotrS0r2ULLwnsh40hSB//nNv3XmwNmC/CmW5QAnIGj8cBMF4S2t6ohADIndojdAfNiptmaZOIT6owK7bWMgPMyopo="
|
||||||
|
98
CHANGELOG.md
98
CHANGELOG.md
@@ -1,5 +1,67 @@
|
|||||||
0.10.0 (TBD)
|
0.11.0 (TBD)
|
||||||
============
|
============
|
||||||
|
TODO.
|
||||||
|
|
||||||
|
**BREAKING CHANGES**:
|
||||||
|
|
||||||
|
* ripgrep has tweaked its exit status codes to be more like GNU grep's. Namely,
|
||||||
|
if a non-fatal error occurs during a search, then ripgrep will now always
|
||||||
|
emit a `2` exit status code, regardless of whether a match is found or not.
|
||||||
|
Previously, ripgrep would only emit a `2` exit status code for a catastrophic
|
||||||
|
error (e.g., regex syntax error). One exception to this is if ripgrep is run
|
||||||
|
with `-q/--quiet`. In that case, if an error occurs and a match is found,
|
||||||
|
then ripgrep will exit with a `0` exit status code.
|
||||||
|
* The `avx-accel` feature of ripgrep has been removed since it is no longer
|
||||||
|
necessary. All uses of AVX in ripgrep are now enabled automatically via
|
||||||
|
runtime CPU feature detection. The `simd-accel` feature does remain
|
||||||
|
available, however, it does increase compilation times substantially at the
|
||||||
|
moment.
|
||||||
|
|
||||||
|
Feature enhancements:
|
||||||
|
|
||||||
|
* [FEATURE #1099](https://github.com/BurntSushi/ripgrep/pull/1099):
|
||||||
|
Add support for Brotli and Zstd to the `-z/--search-zip` flag.
|
||||||
|
* [FEATURE #1138](https://github.com/BurntSushi/ripgrep/pull/1138):
|
||||||
|
Add `--no-ignore-dot` flag for ignoring `.ignore` files.
|
||||||
|
* [FEATURE #1159](https://github.com/BurntSushi/ripgrep/pull/1159):
|
||||||
|
ripgrep's exit status logic should now match GNU grep. See updated man page.
|
||||||
|
* [FEATURE #1170](https://github.com/BurntSushi/ripgrep/pull/1170):
|
||||||
|
Add `--ignore-file-case-insensitive` for case insensitive .ignore globs.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* [BUG #373](https://github.com/BurntSushi/ripgrep/issues/373),
|
||||||
|
[BUG #1098](https://github.com/BurntSushi/ripgrep/issues/1098):
|
||||||
|
`**` is now accepted as valid syntax anywhere in a glob.
|
||||||
|
* [BUG #916](https://github.com/BurntSushi/ripgrep/issues/916):
|
||||||
|
ripgrep no longer hangs when searching `/proc` with a zombie process present.
|
||||||
|
* [BUG #1091](https://github.com/BurntSushi/ripgrep/issues/1091):
|
||||||
|
Add note about inverted flags to the man page.
|
||||||
|
* [BUG #1095](https://github.com/BurntSushi/ripgrep/issues/1095):
|
||||||
|
Fix corner cases involving the `--crlf` flag.
|
||||||
|
* [BUG #1103](https://github.com/BurntSushi/ripgrep/issues/1103):
|
||||||
|
Clarify what `--encoding auto` does.
|
||||||
|
* [BUG #1106](https://github.com/BurntSushi/ripgrep/issues/1106):
|
||||||
|
`--files-with-matches` and `--files-without-match` work with one file.
|
||||||
|
* [BUG #1093](https://github.com/BurntSushi/ripgrep/pull/1093):
|
||||||
|
Fix handling of literal slashes in gitignore patterns.
|
||||||
|
* [BUG #1121](https://github.com/BurntSushi/ripgrep/issues/1121):
|
||||||
|
Fix bug that was triggering Windows antimalware when using the --files flag.
|
||||||
|
* [BUG #1125](https://github.com/BurntSushi/ripgrep/issues/1125),
|
||||||
|
[BUG #1159](https://github.com/BurntSushi/ripgrep/issues/1159):
|
||||||
|
ripgrep shouldn't panic for `rg -h | rg` and should emit correct exit status.
|
||||||
|
* [BUG #1154](https://github.com/BurntSushi/ripgrep/issues/1154):
|
||||||
|
Windows files with "hidden" attribute are now treated as hidden.
|
||||||
|
* [BUG #1173](https://github.com/BurntSushi/ripgrep/issues/1173):
|
||||||
|
Fix handling of `**` patterns in gitignore files.
|
||||||
|
* [BUG #1174](https://github.com/BurntSushi/ripgrep/issues/1174):
|
||||||
|
Fix handling of repeated `**` patterns in gitignore files.
|
||||||
|
* [BUG #1176](https://github.com/BurntSushi/ripgrep/issues/1176):
|
||||||
|
Fix bug where `-F`/`-x` weren't applied to patterns given via `-f`.
|
||||||
|
|
||||||
|
|
||||||
|
0.10.0 (2018-09-07)
|
||||||
|
===================
|
||||||
This is a new minor version release of ripgrep that contains some major new
|
This is a new minor version release of ripgrep that contains some major new
|
||||||
features, a huge number of bug fixes, and is the first release based on
|
features, a huge number of bug fixes, and is the first release based on
|
||||||
libripgrep. The entirety of ripgrep's core search and printing code has been
|
libripgrep. The entirety of ripgrep's core search and printing code has been
|
||||||
@@ -10,24 +72,32 @@ format.
|
|||||||
|
|
||||||
**BREAKING CHANGES**:
|
**BREAKING CHANGES**:
|
||||||
|
|
||||||
|
* The minimum version required to compile Rust has now changed to track the
|
||||||
|
latest stable version of Rust. Patch releases will continue to compile with
|
||||||
|
the same version of Rust as the previous patch release, but new minor
|
||||||
|
versions will use the current stable version of the Rust compile as its
|
||||||
|
minimum supported version.
|
||||||
* The match semantics of `-w/--word-regexp` have changed slightly. They used
|
* The match semantics of `-w/--word-regexp` have changed slightly. They used
|
||||||
to be `\b(?:<your pattern>)\b`, but now it's
|
to be `\b(?:<your pattern>)\b`, but now it's
|
||||||
`(?:^|\W)(?:<your pattern>)(?:$|\W)`.
|
`(?:^|\W)(?:<your pattern>)(?:$|\W)`. This matches the behavior of GNU grep
|
||||||
See [#389](https://github.com/BurntSushi/ripgrep/issues/389) for more
|
and is believed to be closer to the intended semantics of the flag. See
|
||||||
details.
|
[#389](https://github.com/BurntSushi/ripgrep/issues/389) for more details.
|
||||||
|
|
||||||
Feature enhancements:
|
Feature enhancements:
|
||||||
|
|
||||||
* [FEATURE #162](https://github.com/BurntSushi/ripgrep/issues/162):
|
* [FEATURE #162](https://github.com/BurntSushi/ripgrep/issues/162):
|
||||||
libripgrep is now a thing, composed of the following crates:
|
libripgrep is now a thing. The primary crate is
|
||||||
`grep`, `grep-matcher`, `grep-pcre2`, `grep-printer`, `grep-regex` and
|
[`grep`](https://docs.rs/grep).
|
||||||
`grep-searcher`.
|
|
||||||
* [FEATURE #176](https://github.com/BurntSushi/ripgrep/issues/176):
|
* [FEATURE #176](https://github.com/BurntSushi/ripgrep/issues/176):
|
||||||
Add `-U/--multiline` flag that permits matching over multiple lines.
|
Add `-U/--multiline` flag that permits matching over multiple lines.
|
||||||
* [FEATURE #188](https://github.com/BurntSushi/ripgrep/issues/188):
|
* [FEATURE #188](https://github.com/BurntSushi/ripgrep/issues/188):
|
||||||
Add `-P/--pcre2` flag that gives support for look-around and backreferences.
|
Add `-P/--pcre2` flag that gives support for look-around and backreferences.
|
||||||
* [FEATURE #244](https://github.com/BurntSushi/ripgrep/issues/244):
|
* [FEATURE #244](https://github.com/BurntSushi/ripgrep/issues/244):
|
||||||
Add `--json` flag that prints results in a JSON Lines format.
|
Add `--json` flag that prints results in a JSON Lines format.
|
||||||
|
* [FEATURE #321](https://github.com/BurntSushi/ripgrep/issues/321):
|
||||||
|
Add `--one-file-system` flag to skip directories on different file systems.
|
||||||
|
* [FEATURE #404](https://github.com/BurntSushi/ripgrep/issues/404):
|
||||||
|
Add `--sort` and `--sortr` flag for more sorting. Deprecate `--sort-files`.
|
||||||
* [FEATURE #416](https://github.com/BurntSushi/ripgrep/issues/416):
|
* [FEATURE #416](https://github.com/BurntSushi/ripgrep/issues/416):
|
||||||
Add `--crlf` flag to permit `$` to work with carriage returns on Windows.
|
Add `--crlf` flag to permit `$` to work with carriage returns on Windows.
|
||||||
* [FEATURE #917](https://github.com/BurntSushi/ripgrep/issues/917):
|
* [FEATURE #917](https://github.com/BurntSushi/ripgrep/issues/917):
|
||||||
@@ -36,6 +106,10 @@ Feature enhancements:
|
|||||||
Add `--null-data` flag, which makes ripgrep use NUL as a line terminator.
|
Add `--null-data` flag, which makes ripgrep use NUL as a line terminator.
|
||||||
* [FEATURE #997](https://github.com/BurntSushi/ripgrep/issues/997):
|
* [FEATURE #997](https://github.com/BurntSushi/ripgrep/issues/997):
|
||||||
The `--passthru` flag now works with the `--replace` flag.
|
The `--passthru` flag now works with the `--replace` flag.
|
||||||
|
* [FEATURE #1038-1](https://github.com/BurntSushi/ripgrep/issues/1038):
|
||||||
|
Add `--line-buffered` and `--block-buffered` for forcing a buffer strategy.
|
||||||
|
* [FEATURE #1038-2](https://github.com/BurntSushi/ripgrep/issues/1038):
|
||||||
|
Add `--pre-glob` for filtering files through the `--pre` flag.
|
||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
|
|
||||||
@@ -53,14 +127,22 @@ Bug fixes:
|
|||||||
Matching empty lines now works correctly in several corner cases.
|
Matching empty lines now works correctly in several corner cases.
|
||||||
* [BUG #764](https://github.com/BurntSushi/ripgrep/issues/764):
|
* [BUG #764](https://github.com/BurntSushi/ripgrep/issues/764):
|
||||||
Color escape sequences now coalesce, which reduces output size.
|
Color escape sequences now coalesce, which reduces output size.
|
||||||
|
* [BUG #842](https://github.com/BurntSushi/ripgrep/issues/842):
|
||||||
|
Add man page to binary Debian package.
|
||||||
* [BUG #922](https://github.com/BurntSushi/ripgrep/issues/922):
|
* [BUG #922](https://github.com/BurntSushi/ripgrep/issues/922):
|
||||||
ripgrep is now more robust with respect to memory maps failing.
|
ripgrep is now more robust with respect to memory maps failing.
|
||||||
* [BUG #937](https://github.com/BurntSushi/ripgrep/issues/937):
|
* [BUG #937](https://github.com/BurntSushi/ripgrep/issues/937):
|
||||||
Color escape sequences are no longer emitted for empty matches.
|
Color escape sequences are no longer emitted for empty matches.
|
||||||
* [BUG #940](https://github.com/BurntSushi/ripgrep/issues/940):
|
* [BUG #940](https://github.com/BurntSushi/ripgrep/issues/940):
|
||||||
Context from the `--passthru` flag should not impact process exit status.
|
Context from the `--passthru` flag should not impact process exit status.
|
||||||
|
* [BUG #984](https://github.com/BurntSushi/ripgrep/issues/984):
|
||||||
|
Fixes bug in `ignore` crate where first path was always treated as a symlink.
|
||||||
|
* [BUG #990](https://github.com/BurntSushi/ripgrep/issues/990):
|
||||||
|
Read stderr asynchronously when running a process.
|
||||||
* [BUG #1013](https://github.com/BurntSushi/ripgrep/issues/1013):
|
* [BUG #1013](https://github.com/BurntSushi/ripgrep/issues/1013):
|
||||||
Add compile time and runtime CPU features to `--version` output.
|
Add compile time and runtime CPU features to `--version` output.
|
||||||
|
* [BUG #1028](https://github.com/BurntSushi/ripgrep/pull/1028):
|
||||||
|
Don't complete bare pattern after `-f` in zsh.
|
||||||
|
|
||||||
|
|
||||||
0.9.0 (2018-08-03)
|
0.9.0 (2018-08-03)
|
||||||
@@ -96,7 +178,7 @@ multi-line search support and a JSON output format.
|
|||||||
|
|
||||||
Feature enhancements:
|
Feature enhancements:
|
||||||
|
|
||||||
* Added or improved file type filtering for Android, Bazel, Fuschia, Haskell,
|
* Added or improved file type filtering for Android, Bazel, Fuchsia, Haskell,
|
||||||
Java and Puppet.
|
Java and Puppet.
|
||||||
* [FEATURE #411](https://github.com/BurntSushi/ripgrep/issues/411):
|
* [FEATURE #411](https://github.com/BurntSushi/ripgrep/issues/411):
|
||||||
Add a `--stats` flag, which emits aggregate statistics after search results.
|
Add a `--stats` flag, which emits aggregate statistics after search results.
|
||||||
|
647
Cargo.lock
generated
647
Cargo.lock
generated
@@ -1,17 +1,11 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.6.6"
|
version = "0.6.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ansi_term"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -19,46 +13,47 @@ name = "atty"
|
|||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.9.2"
|
version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecount"
|
name = "bytecount"
|
||||||
version = "0.3.2"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
|
||||||
"simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.2.4"
|
version = "1.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.18"
|
version = "1.0.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -66,34 +61,53 @@ name = "clap"
|
|||||||
version = "2.32.0"
|
version = "2.32.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam"
|
name = "cloudabi"
|
||||||
version = "0.3.2"
|
version = "0.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.6"
|
version = "0.8.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs_io"
|
name = "encoding_rs_io"
|
||||||
version = "0.1.2"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"encoding_rs 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"encoding_rs 0.8.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -102,17 +116,8 @@ version = "1.0.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fuchsia-zircon"
|
name = "fuchsia-cprng"
|
||||||
version = "0.3.3"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fuchsia-zircon-sys"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -122,164 +127,183 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep"
|
name = "grep"
|
||||||
version = "0.2.0"
|
version = "0.2.3"
|
||||||
|
dependencies = [
|
||||||
|
"grep-cli 0.1.1",
|
||||||
|
"grep-matcher 0.1.1",
|
||||||
|
"grep-pcre2 0.1.2",
|
||||||
|
"grep-printer 0.1.1",
|
||||||
|
"grep-regex 0.1.1",
|
||||||
|
"grep-searcher 0.1.2",
|
||||||
|
"termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grep-cli"
|
||||||
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grep-matcher 0.1.0",
|
"globset 0.4.2",
|
||||||
"grep-pcre2 0.1.0",
|
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grep-printer 0.1.0",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grep-regex 0.1.0",
|
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grep-searcher 0.1.0",
|
"same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"termcolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-matcher"
|
name = "grep-matcher"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-pcre2"
|
name = "grep-pcre2"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grep-matcher 0.1.0",
|
"grep-matcher 0.1.1",
|
||||||
"pcre2 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"pcre2 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-printer"
|
name = "grep-printer"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grep-matcher 0.1.0",
|
"grep-matcher 0.1.1",
|
||||||
"grep-regex 0.1.0",
|
"grep-regex 0.1.1",
|
||||||
"grep-searcher 0.1.0",
|
"grep-searcher 0.1.2",
|
||||||
"serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_json 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"termcolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-regex"
|
name = "grep-regex"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grep-matcher 0.1.0",
|
"grep-matcher 0.1.1",
|
||||||
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-searcher"
|
name = "grep-searcher"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytecount 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bytecount 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"encoding_rs 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"encoding_rs 0.8.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"encoding_rs_io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"encoding_rs_io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grep-matcher 0.1.0",
|
"grep-matcher 0.1.1",
|
||||||
"grep-regex 0.1.0",
|
"grep-regex 0.1.1",
|
||||||
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ignore"
|
name = "ignore"
|
||||||
version = "0.4.3"
|
version = "0.4.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"globset 0.4.1",
|
"globset 0.4.2",
|
||||||
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
|
||||||
"version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.43"
|
version = "0.2.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.4"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.0.1"
|
version = "2.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memmap"
|
name = "memmap"
|
||||||
version = "0.6.2"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.8.0"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packed_simd"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pcre2"
|
name = "pcre2"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"pcre2-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"pcre2-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -288,19 +312,19 @@ name = "pcre2-sys"
|
|||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
"pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.13"
|
version = "0.3.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "0.4.13"
|
version = "0.4.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -308,25 +332,119 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "0.6.6"
|
version = "0.6.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
"proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.4.3"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_jitter 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_os 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_hc"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_isaac"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_jitter"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_os"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"fuchsia-cprng 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_pcg"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_xorshift"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rdrand"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.1.40"
|
version = "0.1.51"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -334,27 +452,27 @@ name = "redox_termios"
|
|||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.0.2"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.2"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -362,77 +480,92 @@ name = "remove_dir_all"
|
|||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ripgrep"
|
name = "ripgrep"
|
||||||
version = "0.9.0"
|
version = "0.10.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"globset 0.4.1",
|
"grep 0.2.3",
|
||||||
"grep 0.2.0",
|
"ignore 0.4.6",
|
||||||
"ignore 0.4.3",
|
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_json 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)",
|
"termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
]
|
||||||
"termcolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
[[package]]
|
||||||
|
name = "rustc_version"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "0.2.4"
|
version = "0.2.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "safemem"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.2"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver-parser"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.71"
|
version = "1.0.85"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.71"
|
version = "1.0.85"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
"proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"quote 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"syn 0.14.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.26"
|
version = "1.0.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ryu 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd"
|
name = "smallvec"
|
||||||
version = "0.2.2"
|
version = "0.6.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
@@ -441,29 +574,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "0.14.8"
|
version = "0.15.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
"proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"quote 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempdir"
|
name = "tempfile"
|
||||||
version = "0.3.7"
|
version = "3.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "1.0.1"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wincolor 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -471,8 +608,8 @@ name = "termion"
|
|||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
"redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -489,12 +626,12 @@ name = "thread_local"
|
|||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ucd-util"
|
name = "ucd-util"
|
||||||
version = "0.1.1"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -508,27 +645,36 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8-ranges"
|
name = "unreachable"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8-ranges"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "void"
|
||||||
version = "0.1.4"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.2.0"
|
version = "2.2.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.5"
|
version = "0.3.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -540,6 +686,14 @@ name = "winapi-i686-pc-windows-gnu"
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -547,69 +701,86 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wincolor"
|
name = "wincolor"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
"checksum aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c1c6d463cbe7ed28720b5b489e7c083eeb8f90d08be2a0d6bb9e1ffea9ce1afa"
|
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
|
||||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
|
||||||
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
||||||
"checksum base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "85415d2594767338a74a30c1d370b2f3262ec1b4ed2d7bba5b3faf4de40467d9"
|
"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
|
||||||
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
|
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||||
"checksum bytecount 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f861d9ce359f56dbcb6e0c2a1cb84e52ad732cadb57b806adeb3c7668caccbd8"
|
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
|
||||||
"checksum byteorder 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8389c509ec62b9fe8eca58c502a0acaf017737355615243496cde4994f8fa4f9"
|
"checksum bytecount 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c219c0335c21a8bd79587ce5aee9f64aff1d0bd7a2cca7a58a815f9780cd3b0d"
|
||||||
"checksum cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "2119ea4867bd2b8ed3aecab467709720b2d55b1bcfe09f772fd68066eaf15275"
|
"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
|
||||||
"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3"
|
"checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749"
|
||||||
|
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||||
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
|
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
|
||||||
"checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19"
|
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||||
"checksum encoding_rs 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2a91912d6f37c6a8fef8a2316a862542d036f13c923ad518b5aca7bcaac7544c"
|
"checksum crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0ed1a4de2235cabda8558ff5840bffb97fcb64c97827f354a451307df5f72b"
|
||||||
"checksum encoding_rs_io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f222ff554d6e172f3569a2d7d0fd8061d54215984ef67b24ce031c1fcbf2c9b3"
|
"checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c"
|
||||||
|
"checksum encoding_rs 0.8.16 (registry+https://github.com/rust-lang/crates.io-index)" = "0535f350c60aac0b87ccf28319abc749391e912192255b0c00a2c12c6917bd73"
|
||||||
|
"checksum encoding_rs_io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6c89a56158243c7cde22fde70e452a40dded9d9d9100f71273df19af9be4d034"
|
||||||
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||||
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
"checksum fuchsia-cprng 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81f7f8eb465745ea9b02e2704612a9946a59fa40572086c6fd49d6ddcf30bf31"
|
||||||
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
|
||||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||||
"checksum itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5adb58558dcd1d786b5f0bd15f3226ee23486e24b7b58304b60f64dc68e62606"
|
"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
|
||||||
"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7"
|
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
|
||||||
"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d"
|
"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047"
|
||||||
"checksum log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cba860f648db8e6f269df990180c2217f333472b4a6e901e97446858487971e2"
|
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
|
||||||
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
"checksum memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e1dd4eaac298c32ce07eb6ed9242eda7d82955b9170b7d6db59b2e02cc63fcb8"
|
||||||
"checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff"
|
"checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
|
||||||
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
|
"checksum num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238"
|
||||||
"checksum pcre2 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c16ec0e30c17f938a2da8ff970ad9a4100166d0538898dcc035b55c393cab54"
|
"checksum packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a85ea9fc0d4ac0deb6fe7911d38786b32fc11119afd9e9d38b84ff691ce64220"
|
||||||
|
"checksum pcre2 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae0a2682105ec5ca0ee5910bbc7e926386d348a05166348f74007942983c319"
|
||||||
"checksum pcre2-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a9027f9474e4e13d3b965538aafcaebe48c803488ad76b3c97ef061a8324695f"
|
"checksum pcre2-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a9027f9474e4e13d3b965538aafcaebe48c803488ad76b3c97ef061a8324695f"
|
||||||
"checksum pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "104630aa1c83213cbc76db0703630fcb0421dac3585063be4ce9a8a2feeaa745"
|
"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"
|
||||||
"checksum proc-macro2 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "ee5697238f0d893c7f0ecc59c0999f18d2af85e424de441178bcacc9f9e6cf67"
|
"checksum proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)" = "38fddd23d98b2144d197c0eca5705632d4fe2667d14a6be5df8934f8d74f1978"
|
||||||
"checksum quote 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ed7d650913520df631972f21e104a4fa2f9c82a14afc65d17b388a2e29731e7c"
|
"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
|
||||||
"checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd"
|
"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
|
||||||
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
|
"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
|
||||||
|
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||||
|
"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
|
||||||
|
"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
|
||||||
|
"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
|
||||||
|
"checksum rand_jitter 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "29fe7b8bc348249f3b1bbb9ab8baa6fa3419196ecfbf213408bca1b2494710de"
|
||||||
|
"checksum rand_os 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b7c690732391ae0abafced5015ffb53656abfaec61b342290e5eb56b286a679d"
|
||||||
|
"checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05"
|
||||||
|
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
|
||||||
|
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||||
|
"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85"
|
||||||
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||||
"checksum regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5bbbea44c5490a1e84357ff28b7d518b4619a159fed5d25f6c1de2d19cc42814"
|
"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f"
|
||||||
"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d"
|
"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861"
|
||||||
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
|
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
|
||||||
"checksum ryu 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0568787116e13c652377b6846f5931454a363a8fdf8ae50463ee40935b278b"
|
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||||
"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
|
"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
|
||||||
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
|
"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
|
||||||
"checksum serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)" = "6dfad05c8854584e5f72fb859385ecdfa03af69c3fd0572f0da2d4c95f060bdb"
|
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||||
"checksum serde_derive 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)" = "b719c6d5e9f73fbc37892246d5852333f040caa617b8873c6aced84bcb28e7bb"
|
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
"checksum serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "44dd2cfde475037451fa99b7e5df77aa3cfd1536575fa8e7a538ab36dcde49ae"
|
"checksum serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)" = "534b8b91a95e0f71bca3ed5824752d558da048d4248c91af873b63bd60519752"
|
||||||
"checksum simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3686dd9418ebcc3a26a0c0ae56deab0681e53fe899af91f5bbcee667ebffb1"
|
"checksum serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)" = "a915306b0f1ac5607797697148c223bedeaa36bcc2e28a01441cd638cc6567b4"
|
||||||
|
"checksum serde_json 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "4b90a9fbe1211e57d3e1c15670f1cb00802988fb23a1a4aad7a2b63544f1920e"
|
||||||
|
"checksum smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "88aea073965ab29f6edb5493faf96ad662fb18aa9eeb186a3b7057951605ed15"
|
||||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||||
"checksum syn 0.14.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b7bfcbb0c068d0f642a0ffbd5c604965a360a61f99e8add013cef23a838614f3"
|
"checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9"
|
||||||
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
"checksum tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7e91405c14320e5c79b3d148e1c86f40749a36e490642202a31689cb1a3452b2"
|
||||||
"checksum termcolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "722426c4a0539da2c4ffd9b419d90ad540b4cff4a053be9069c908d4d07e2836"
|
"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f"
|
||||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
||||||
"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
|
"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
|
||||||
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
||||||
"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
|
"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
|
||||||
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
||||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||||
"checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051"
|
"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
|
||||||
"checksum walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1b768ba943161a9226ccd59b26bcd901e5d60e6061f4fcad3034784e0c7372b"
|
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
|
"checksum walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1"
|
||||||
|
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
|
||||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
|
||||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
"checksum wincolor 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9dc3aa9dcda98b5a16150c54619c1ead22e3d3a5d458778ae914be760aa981a"
|
"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
|
||||||
|
69
Cargo.toml
69
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ripgrep"
|
name = "ripgrep"
|
||||||
version = "0.9.0" #:version
|
version = "0.10.0" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
ripgrep is a line-oriented search tool that recursively searches your current
|
ripgrep is a line-oriented search tool that recursively searches your current
|
||||||
@@ -17,6 +17,7 @@ license = "Unlicense OR MIT"
|
|||||||
exclude = ["HomebrewFormula"]
|
exclude = ["HomebrewFormula"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
autotests = false
|
autotests = false
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
travis-ci = { repository = "BurntSushi/ripgrep" }
|
travis-ci = { repository = "BurntSushi/ripgrep" }
|
||||||
@@ -35,6 +36,7 @@ path = "tests/tests.rs"
|
|||||||
members = [
|
members = [
|
||||||
"globset",
|
"globset",
|
||||||
"grep",
|
"grep",
|
||||||
|
"grep-cli",
|
||||||
"grep-matcher",
|
"grep-matcher",
|
||||||
"grep-pcre2",
|
"grep-pcre2",
|
||||||
"grep-printer",
|
"grep-printer",
|
||||||
@@ -44,43 +46,62 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
atty = "0.2.11"
|
grep = { version = "0.2.3", path = "grep" }
|
||||||
globset = { version = "0.4.0", path = "globset" }
|
ignore = { version = "0.4.4", path = "ignore" }
|
||||||
grep = { version = "0.2.0", path = "grep" }
|
lazy_static = "1.1.0"
|
||||||
ignore = { version = "0.4.0", path = "ignore" }
|
log = "0.4.5"
|
||||||
lazy_static = "1"
|
num_cpus = "1.8.0"
|
||||||
log = "0.4"
|
regex = "1.0.5"
|
||||||
num_cpus = "1"
|
serde_json = "1.0.23"
|
||||||
regex = "1"
|
termcolor = "1.0.3"
|
||||||
same-file = "1"
|
|
||||||
serde_json = "1"
|
|
||||||
termcolor = "1"
|
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "2.29.4"
|
version = "2.32.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["suggestions", "color"]
|
features = ["suggestions"]
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.winapi]
|
|
||||||
version = "0.3"
|
|
||||||
features = ["std", "fileapi", "winnt"]
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
lazy_static = "1"
|
lazy_static = "1.1.0"
|
||||||
|
|
||||||
[build-dependencies.clap]
|
[build-dependencies.clap]
|
||||||
version = "2.29.4"
|
version = "2.32.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["suggestions", "color"]
|
features = ["suggestions"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde = "1"
|
serde = "1.0.77"
|
||||||
serde_derive = "1"
|
serde_derive = "1.0.77"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
avx-accel = ["grep/avx-accel"]
|
|
||||||
simd-accel = ["grep/simd-accel"]
|
simd-accel = ["grep/simd-accel"]
|
||||||
pcre2 = ["grep/pcre2"]
|
pcre2 = ["grep/pcre2"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 1
|
debug = 1
|
||||||
|
|
||||||
|
[package.metadata.deb]
|
||||||
|
features = ["pcre2"]
|
||||||
|
section = "utils"
|
||||||
|
assets = [
|
||||||
|
["target/release/rg", "usr/bin/", "755"],
|
||||||
|
["COPYING", "usr/share/doc/ripgrep/", "644"],
|
||||||
|
["LICENSE-MIT", "usr/share/doc/ripgrep/", "644"],
|
||||||
|
["UNLICENSE", "usr/share/doc/ripgrep/", "644"],
|
||||||
|
["CHANGELOG.md", "usr/share/doc/ripgrep/CHANGELOG", "644"],
|
||||||
|
["README.md", "usr/share/doc/ripgrep/README", "644"],
|
||||||
|
["FAQ.md", "usr/share/doc/ripgrep/FAQ", "644"],
|
||||||
|
# The man page is automatically generated by ripgrep's build process, so
|
||||||
|
# this file isn't actually commited. Instead, to create a dpkg, either
|
||||||
|
# create a deployment/deb directory and copy the man page to it, or use the
|
||||||
|
# 'ci/build_deb.sh' script.
|
||||||
|
["deployment/deb/rg.1", "usr/share/man/man1/rg.1", "644"],
|
||||||
|
# Similarly for shell completions.
|
||||||
|
["deployment/deb/rg.bash", "usr/share/bash-completion/completions/rg", "644"],
|
||||||
|
["deployment/deb/rg.fish", "usr/share/fish/completions/rg.fish", "644"],
|
||||||
|
["deployment/deb/_rg", "usr/share/zsh/vendor-completions/", "644"],
|
||||||
|
]
|
||||||
|
extended-description = """\
|
||||||
|
ripgrep (rg) recursively searches your current directory for a regex pattern.
|
||||||
|
By default, ripgrep will respect your .gitignore and automatically skip hidden
|
||||||
|
files/directories and binary files.
|
||||||
|
"""
|
||||||
|
25
FAQ.md
25
FAQ.md
@@ -118,7 +118,7 @@ from run to run of ripgrep.
|
|||||||
The only way to make the order of results consistent is to ask ripgrep to
|
The only way to make the order of results consistent is to ask ripgrep to
|
||||||
sort the output. Currently, this will disable all parallelism. (On smaller
|
sort the output. Currently, this will disable all parallelism. (On smaller
|
||||||
repositories, you might not notice much of a performance difference!) You
|
repositories, you might not notice much of a performance difference!) You
|
||||||
can achieve this with the `--sort-files` flag.
|
can achieve this with the `--sort path` flag.
|
||||||
|
|
||||||
There is more discussion on this topic here:
|
There is more discussion on this topic here:
|
||||||
https://github.com/BurntSushi/ripgrep/issues/152
|
https://github.com/BurntSushi/ripgrep/issues/152
|
||||||
@@ -136,10 +136,10 @@ How do I search compressed files?
|
|||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
ripgrep's `-z/--search-zip` flag will cause it to search compressed files
|
ripgrep's `-z/--search-zip` flag will cause it to search compressed files
|
||||||
automatically. Currently, this supports gzip, bzip2, lzma, lz4 and xz only and
|
automatically. Currently, this supports gzip, bzip2, xz, lzma, lz4, Brotli and
|
||||||
requires the corresponding `gzip`, `bzip2` and `xz` binaries to be installed on
|
Zstd. Each of these requires requires the corresponding `gzip`, `bzip2`, `xz`,
|
||||||
your system. (That is, ripgrep does decompression by shelling out to another
|
`lz4`, `brotli` and `zstd` binaries to be installed on your system. (That is,
|
||||||
process.)
|
ripgrep does decompression by shelling out to another process.)
|
||||||
|
|
||||||
ripgrep currently does not search archive formats, so `*.tar.gz` files, for
|
ripgrep currently does not search archive formats, so `*.tar.gz` files, for
|
||||||
example, are skipped.
|
example, are skipped.
|
||||||
@@ -149,9 +149,8 @@ example, are skipped.
|
|||||||
How do I search over multiple lines?
|
How do I search over multiple lines?
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
This isn't currently possible. ripgrep is fundamentally a line-oriented search
|
The `-U/--multiline` flag enables ripgrep to report results that span over
|
||||||
tool. With that said,
|
multiple lines.
|
||||||
[multiline search is a planned opt-in feature](https://github.com/BurntSushi/ripgrep/issues/176).
|
|
||||||
|
|
||||||
|
|
||||||
<h3 name="fancy">
|
<h3 name="fancy">
|
||||||
@@ -635,7 +634,7 @@ real 0m1.714s
|
|||||||
user 0m1.669s
|
user 0m1.669s
|
||||||
sys 0m0.044s
|
sys 0m0.044s
|
||||||
|
|
||||||
[andrew@Cheetah 2016] time rg -P '^\w{42}$' subtitles2016-sample --no-pcre2-unicode
|
$ time rg -P '^\w{42}$' subtitles2016-sample --no-pcre2-unicode
|
||||||
21225780:EverymajordevelopmentinthehistoryofAmerica
|
21225780:EverymajordevelopmentinthehistoryofAmerica
|
||||||
|
|
||||||
real 0m1.997s
|
real 0m1.997s
|
||||||
@@ -675,14 +674,18 @@ no longer needs to do any kind of UTF-8 checks. This allows the file to get
|
|||||||
memory mapped and passed right through PCRE2's JIT at impressive speeds. (As
|
memory mapped and passed right through PCRE2's JIT at impressive speeds. (As
|
||||||
a brief and interesting historical note, the configuration of "memory map +
|
a brief and interesting historical note, the configuration of "memory map +
|
||||||
multiline + no-Unicode" is exactly the configuration used by The Silver
|
multiline + no-Unicode" is exactly the configuration used by The Silver
|
||||||
Searcher. This analysis perhaps sheds some reasoning as to why it converged on
|
Searcher. This analysis perhaps sheds some reasoning as to why that
|
||||||
that specific setting!)
|
configuration is useful!)
|
||||||
|
|
||||||
In summary, if you want PCRE2 to go as fast as possible and you don't care
|
In summary, if you want PCRE2 to go as fast as possible and you don't care
|
||||||
about Unicode and you don't care about matches possibly spanning across
|
about Unicode and you don't care about matches possibly spanning across
|
||||||
multiple lines, then enable multiline mode with `-U` and disable PCRE2's
|
multiple lines, then enable multiline mode with `-U` and disable PCRE2's
|
||||||
Unicode support with the `--no-pcre2-unicode` flag.
|
Unicode support with the `--no-pcre2-unicode` flag.
|
||||||
|
|
||||||
|
Caveat emptor: This author is not a PCRE2 expert, so there may be APIs that can
|
||||||
|
improve performance that the author missed. Similarly, there may be alternative
|
||||||
|
designs for a searching tool that are more amenable to how PCRE2 works.
|
||||||
|
|
||||||
|
|
||||||
<h3 name="rg-other-cmd">
|
<h3 name="rg-other-cmd">
|
||||||
When I run <code>rg</code>, why does it execute some other command?
|
When I run <code>rg</code>, why does it execute some other command?
|
||||||
|
18
GUIDE.md
18
GUIDE.md
@@ -227,7 +227,7 @@ with the following contents:
|
|||||||
```
|
```
|
||||||
|
|
||||||
ripgrep treats `.ignore` files with higher precedence than `.gitignore` files
|
ripgrep treats `.ignore` files with higher precedence than `.gitignore` files
|
||||||
(and treats `.rgignore` files with higher precdence than `.ignore` files).
|
(and treats `.rgignore` files with higher precedence than `.ignore` files).
|
||||||
This means ripgrep will see the `!log/` whitelist rule first and search that
|
This means ripgrep will see the `!log/` whitelist rule first and search that
|
||||||
directory.
|
directory.
|
||||||
|
|
||||||
@@ -235,6 +235,11 @@ Like `.gitignore`, a `.ignore` file can be placed in any directory. Its rules
|
|||||||
will be processed with respect to the directory it resides in, just like
|
will be processed with respect to the directory it resides in, just like
|
||||||
`.gitignore`.
|
`.gitignore`.
|
||||||
|
|
||||||
|
To process `.gitignore` and `.ignore` files case insensitively, use the flag
|
||||||
|
`--ignore-file-case-insensitive`. This is especially useful on case insensitive
|
||||||
|
file systems like those on Windows and macOS. Note though that this can come
|
||||||
|
with a significant performance penalty, and is therefore disabled by default.
|
||||||
|
|
||||||
For a more in depth description of how glob patterns in a `.gitignore` file
|
For a more in depth description of how glob patterns in a `.gitignore` file
|
||||||
are interpreted, please see `man gitignore`.
|
are interpreted, please see `man gitignore`.
|
||||||
|
|
||||||
@@ -580,7 +585,7 @@ override it.
|
|||||||
|
|
||||||
If you're confused about what configuration file ripgrep is reading arguments
|
If you're confused about what configuration file ripgrep is reading arguments
|
||||||
from, then running ripgrep with the `--debug` flag should help clarify things.
|
from, then running ripgrep with the `--debug` flag should help clarify things.
|
||||||
The debug output should note what config file is being loaded and the arugments
|
The debug output should note what config file is being loaded and the arguments
|
||||||
that have been read from the configuration.
|
that have been read from the configuration.
|
||||||
|
|
||||||
Finally, if you want to make absolutely sure that ripgrep *isn't* reading a
|
Finally, if you want to make absolutely sure that ripgrep *isn't* reading a
|
||||||
@@ -604,7 +609,8 @@ topic, but we can try to summarize its relevancy to ripgrep:
|
|||||||
the most popular encodings likely consist of ASCII, latin1 or UTF-8. As
|
the most popular encodings likely consist of ASCII, latin1 or UTF-8. As
|
||||||
a special exception, UTF-16 is prevalent in Windows environments
|
a special exception, UTF-16 is prevalent in Windows environments
|
||||||
|
|
||||||
In light of the above, here is how ripgrep behaves:
|
In light of the above, here is how ripgrep behaves when `--encoding auto` is
|
||||||
|
given, which is the default:
|
||||||
|
|
||||||
* All input is assumed to be ASCII compatible (which means every byte that
|
* All input is assumed to be ASCII compatible (which means every byte that
|
||||||
corresponds to an ASCII codepoint actually is an ASCII codepoint). This
|
corresponds to an ASCII codepoint actually is an ASCII codepoint). This
|
||||||
@@ -675,10 +681,10 @@ used options that will likely impact how you use ripgrep on a regular basis.
|
|||||||
* `--files`: Print the files that ripgrep *would* search, but don't actually
|
* `--files`: Print the files that ripgrep *would* search, but don't actually
|
||||||
search them.
|
search them.
|
||||||
* `-a/--text`: Search binary files as if they were plain text.
|
* `-a/--text`: Search binary files as if they were plain text.
|
||||||
* `-z/--search-zip`: Search compressed files (gzip, bzip2, lzma, xz). This is
|
* `-z/--search-zip`: Search compressed files (gzip, bzip2, lzma, xz, lz4,
|
||||||
disabled by default.
|
brotli, zstd). This is disabled by default.
|
||||||
* `-C/--context`: Show the lines surrounding a match.
|
* `-C/--context`: Show the lines surrounding a match.
|
||||||
* `--sort-files`: Force ripgrep to sort its output by file name. (This disables
|
* `--sort path`: Force ripgrep to sort its output by file name. (This disables
|
||||||
parallelism, so it might be slower.)
|
parallelism, so it might be slower.)
|
||||||
* `-L/--follow`: Follow symbolic links while recursively searching.
|
* `-L/--follow`: Follow symbolic links while recursively searching.
|
||||||
* `-M/--max-columns`: Limit the length of lines printed by ripgrep.
|
* `-M/--max-columns`: Limit the length of lines printed by ripgrep.
|
||||||
|
85
README.md
85
README.md
@@ -23,7 +23,7 @@ Please see the [CHANGELOG](CHANGELOG.md) for a release history.
|
|||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
* [User Guide](GUIDE.md)
|
* [User Guide](GUIDE.md)
|
||||||
* [Frequently Asked Questions](FAQ.md)
|
* [Frequently Asked Questions](FAQ.md)
|
||||||
* [Regex syntax](https://docs.rs/regex/0.2.5/regex/#syntax)
|
* [Regex syntax](https://docs.rs/regex/1/regex/#syntax)
|
||||||
* [Configuration files](GUIDE.md#configuration-file)
|
* [Configuration files](GUIDE.md#configuration-file)
|
||||||
* [Shell completions](FAQ.md#complete)
|
* [Shell completions](FAQ.md#complete)
|
||||||
* [Building](#building)
|
* [Building](#building)
|
||||||
@@ -103,6 +103,10 @@ increases the times to `2.640s` for ripgrep and `10.277s` for GNU grep.
|
|||||||
of search results, searching multiple patterns, highlighting matches with
|
of search results, searching multiple patterns, highlighting matches with
|
||||||
color and full Unicode support. Unlike GNU grep, ripgrep stays fast while
|
color and full Unicode support. Unlike GNU grep, ripgrep stays fast while
|
||||||
supporting Unicode (which is always on).
|
supporting Unicode (which is always on).
|
||||||
|
* ripgrep has optional support for switching its regex engine to use PCRE2.
|
||||||
|
Among other things, this makes it possible to use look-around and
|
||||||
|
backreferences in your patterns, which are not supported in ripgrep's default
|
||||||
|
regex engine. PCRE2 support is enabled with `-P`.
|
||||||
* ripgrep supports searching files in text encodings other than UTF-8, such
|
* ripgrep supports searching files in text encodings other than UTF-8, such
|
||||||
as UTF-16, latin-1, GBK, EUC-JP, Shift_JIS and more. (Some support for
|
as UTF-16, latin-1, GBK, EUC-JP, Shift_JIS and more. (Some support for
|
||||||
automatically detecting UTF-16 is provided. Other text encodings must be
|
automatically detecting UTF-16 is provided. Other text encodings must be
|
||||||
@@ -114,7 +118,7 @@ increases the times to `2.640s` for ripgrep and `10.277s` for GNU grep.
|
|||||||
detection and so on.
|
detection and so on.
|
||||||
|
|
||||||
In other words, use ripgrep if you like speed, filtering by default, fewer
|
In other words, use ripgrep if you like speed, filtering by default, fewer
|
||||||
bugs, and Unicode support.
|
bugs and Unicode support.
|
||||||
|
|
||||||
|
|
||||||
### Why shouldn't I use ripgrep?
|
### Why shouldn't I use ripgrep?
|
||||||
@@ -131,8 +135,8 @@ or more of the following:
|
|||||||
* You need a portable and ubiquitous tool. While ripgrep works on Windows,
|
* You need a portable and ubiquitous tool. While ripgrep works on Windows,
|
||||||
macOS and Linux, it is not ubiquitous and it does not conform to any
|
macOS and Linux, it is not ubiquitous and it does not conform to any
|
||||||
standard such as POSIX. The best tool for this job is good old grep.
|
standard such as POSIX. The best tool for this job is good old grep.
|
||||||
* There still exists some other minor feature (or bug) found in another tool
|
* There still exists some other feature (or bug) not listed in this README that
|
||||||
that isn't in ripgrep.
|
you rely on that's in another tool that isn't in ripgrep.
|
||||||
* There is a performance edge case where ripgrep doesn't do well where another
|
* There is a performance edge case where ripgrep doesn't do well where another
|
||||||
tool does do well. (Please file a bug report!)
|
tool does do well. (Please file a bug report!)
|
||||||
* ripgrep isn't possible to install on your machine or isn't available for your
|
* ripgrep isn't possible to install on your machine or isn't available for your
|
||||||
@@ -159,7 +163,7 @@ Summarizing, ripgrep is fast because:
|
|||||||
latter is better for large directories. ripgrep chooses the best searching
|
latter is better for large directories. ripgrep chooses the best searching
|
||||||
strategy for you automatically.
|
strategy for you automatically.
|
||||||
* Applies your ignore patterns in `.gitignore` files using a
|
* Applies your ignore patterns in `.gitignore` files using a
|
||||||
[`RegexSet`](https://docs.rs/regex/1.0.0/regex/struct.RegexSet.html).
|
[`RegexSet`](https://docs.rs/regex/1/regex/struct.RegexSet.html).
|
||||||
That means a single file path can be matched against multiple glob patterns
|
That means a single file path can be matched against multiple glob patterns
|
||||||
simultaneously.
|
simultaneously.
|
||||||
* It uses a lock-free parallel recursive directory iterator, courtesy of
|
* It uses a lock-free parallel recursive directory iterator, courtesy of
|
||||||
@@ -244,21 +248,22 @@ If you're a **Gentoo** user, you can install ripgrep from the
|
|||||||
$ emerge sys-apps/ripgrep
|
$ emerge sys-apps/ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Fedora 27+** user, you can install ripgrep from official
|
If you're a **Fedora** user, you can install ripgrep from official
|
||||||
repositories.
|
repositories.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sudo dnf install ripgrep
|
$ sudo dnf install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Fedora 24+** user, you can install ripgrep from
|
If you're an **openSUSE Leap 15.0** user, you can install ripgrep from the
|
||||||
[copr](https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/):
|
[utilities repo](https://build.opensuse.org/package/show/utilities/ripgrep):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sudo dnf copr enable carlwgeorge/ripgrep
|
$ sudo zypper ar https://download.opensuse.org/repositories/utilities/openSUSE_Leap_15.0/utilities.repo
|
||||||
$ sudo dnf install ripgrep
|
$ sudo zypper install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
If you're an **openSUSE Tumbleweed** user, you can install ripgrep from the
|
If you're an **openSUSE Tumbleweed** user, you can install ripgrep from the
|
||||||
[official repo](http://software.opensuse.org/package/ripgrep):
|
[official repo](http://software.opensuse.org/package/ripgrep):
|
||||||
|
|
||||||
@@ -284,12 +289,11 @@ $ # (Or using the attribute name, which is also ripgrep.)
|
|||||||
|
|
||||||
If you're a **Debian** user (or a user of a Debian derivative like **Ubuntu**),
|
If you're a **Debian** user (or a user of a Debian derivative like **Ubuntu**),
|
||||||
then ripgrep can be installed using a binary `.deb` file provided in each
|
then ripgrep can be installed using a binary `.deb` file provided in each
|
||||||
[ripgrep release](https://github.com/BurntSushi/ripgrep/releases). Note that
|
[ripgrep release](https://github.com/BurntSushi/ripgrep/releases).
|
||||||
ripgrep is not in the official Debian or Ubuntu repositories.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ curl -LO https://github.com/BurntSushi/ripgrep/releases/download/0.9.0/ripgrep_0.9.0_amd64.deb
|
$ curl -LO https://github.com/BurntSushi/ripgrep/releases/download/0.10.0/ripgrep_0.10.0_amd64.deb
|
||||||
$ sudo dpkg -i ripgrep_0.9.0_amd64.deb
|
$ sudo dpkg -i ripgrep_0.10.0_amd64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
If you run Debian Buster (currently Debian testing) or Debian sid, ripgrep is
|
If you run Debian Buster (currently Debian testing) or Debian sid, ripgrep is
|
||||||
@@ -298,6 +302,14 @@ If you run Debian Buster (currently Debian testing) or Debian sid, ripgrep is
|
|||||||
$ sudo apt-get install ripgrep
|
$ sudo apt-get install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you're an **Ubuntu Cosmic (18.10)** (or newer) user, ripgrep is
|
||||||
|
[available](https://launchpad.net/ubuntu/+source/rust-ripgrep) using the same
|
||||||
|
packaging as Debian:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo apt-get install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
(N.B. Various snaps for ripgrep on Ubuntu are also available, but none of them
|
(N.B. Various snaps for ripgrep on Ubuntu are also available, but none of them
|
||||||
seem to work right and generate a number of very strange bug reports that I
|
seem to work right and generate a number of very strange bug reports that I
|
||||||
don't know how to fix and don't have the time to fix. Therefore, it is no
|
don't know how to fix and don't have the time to fix. Therefore, it is no
|
||||||
@@ -326,7 +338,7 @@ If you're a **NetBSD** user, then you can install ripgrep from
|
|||||||
|
|
||||||
If you're a **Rust programmer**, ripgrep can be installed with `cargo`.
|
If you're a **Rust programmer**, ripgrep can be installed with `cargo`.
|
||||||
|
|
||||||
* Note that the minimum supported version of Rust for ripgrep is **1.23.0**,
|
* Note that the minimum supported version of Rust for ripgrep is **1.32.0**,
|
||||||
although ripgrep may work with older versions.
|
although ripgrep may work with older versions.
|
||||||
* Note that the binary may be bigger than expected because it contains debug
|
* Note that the binary may be bigger than expected because it contains debug
|
||||||
symbols. This is intentional. To remove debug symbols and therefore reduce
|
symbols. This is intentional. To remove debug symbols and therefore reduce
|
||||||
@@ -347,7 +359,10 @@ ripgrep isn't currently in any other package repositories.
|
|||||||
|
|
||||||
ripgrep is written in Rust, so you'll need to grab a
|
ripgrep is written in Rust, so you'll need to grab a
|
||||||
[Rust installation](https://www.rust-lang.org/) in order to compile it.
|
[Rust installation](https://www.rust-lang.org/) in order to compile it.
|
||||||
ripgrep compiles with Rust 1.23.0 (stable) or newer. Building is easy:
|
ripgrep compiles with Rust 1.32.0 (stable) or newer. In general, ripgrep tracks
|
||||||
|
the latest stable release of the Rust compiler.
|
||||||
|
|
||||||
|
To build ripgrep:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/BurntSushi/ripgrep
|
$ git clone https://github.com/BurntSushi/ripgrep
|
||||||
@@ -361,18 +376,14 @@ If you have a Rust nightly compiler and a recent Intel CPU, then you can enable
|
|||||||
additional optional SIMD acceleration like so:
|
additional optional SIMD acceleration like so:
|
||||||
|
|
||||||
```
|
```
|
||||||
RUSTFLAGS="-C target-cpu=native" cargo build --release --features 'simd-accel avx-accel'
|
RUSTFLAGS="-C target-cpu=native" cargo build --release --features 'simd-accel'
|
||||||
```
|
```
|
||||||
|
|
||||||
If your machine doesn't support AVX instructions, then simply remove
|
The `simd-accel` feature enables SIMD support in certain ripgrep dependencies
|
||||||
`avx-accel` from the features list. Similarly for SIMD (which corresponds
|
(responsible for transcoding). They are not necessary to get SIMD optimizations
|
||||||
roughly to SSE instructions).
|
for search; those are enabled automatically. Hopefully, some day, the
|
||||||
|
`simd-accel` feature will similarly become unnecessary. **WARNING:** Currently,
|
||||||
The `simd-accel` and `avx-accel` features enable SIMD support in certain
|
enabling this option can increase compilation times dramatically.
|
||||||
ripgrep dependencies (responsible for counting lines and transcoding). They
|
|
||||||
are not necessary to get SIMD optimizations for search; those are enabled
|
|
||||||
automatically. Hopefully, some day, the `simd-accel` and `avx-accel` features
|
|
||||||
will similarly become unnecessary.
|
|
||||||
|
|
||||||
Finally, optional PCRE2 support can be built with ripgrep by enabling the
|
Finally, optional PCRE2 support can be built with ripgrep by enabling the
|
||||||
`pcre2` feature:
|
`pcre2` feature:
|
||||||
@@ -381,15 +392,16 @@ Finally, optional PCRE2 support can be built with ripgrep by enabling the
|
|||||||
$ cargo build --release --features 'pcre2'
|
$ cargo build --release --features 'pcre2'
|
||||||
```
|
```
|
||||||
|
|
||||||
(Tip: use `--features 'pcre2 simd-accel avx-accel'` to also include compile
|
(Tip: use `--features 'pcre2 simd-accel'` to also include compile time SIMD
|
||||||
time SIMD optimizations.)
|
optimizations, which will only work with a nightly compiler.)
|
||||||
|
|
||||||
Enabling the PCRE2 feature will attempt to automatically find and link with
|
Enabling the PCRE2 feature works with a stable Rust compiler and will
|
||||||
your system's PCRE2 library via `pkg-config`. If one doesn't exist, then
|
attempt to automatically find and link with your system's PCRE2 library via
|
||||||
ripgrep will build PCRE2 from source using your system's C compiler and then
|
`pkg-config`. If one doesn't exist, then ripgrep will build PCRE2 from source
|
||||||
statically link it into the final executable. Static linking can be forced even
|
using your system's C compiler and then statically link it into the final
|
||||||
when there is an available PCRE2 system library by either building ripgrep with
|
executable. Static linking can be forced even when there is an available PCRE2
|
||||||
the MUSL target or by setting `PCRE2_SYS_STATIC=1`.
|
system library by either building ripgrep with the MUSL target or by setting
|
||||||
|
`PCRE2_SYS_STATIC=1`.
|
||||||
|
|
||||||
ripgrep can be built with the MUSL target on Linux by first installing the MUSL
|
ripgrep can be built with the MUSL target on Linux by first installing the MUSL
|
||||||
library on your system (consult your friendly neighborhood package manager).
|
library on your system (consult your friendly neighborhood package manager).
|
||||||
@@ -401,7 +413,10 @@ $ rustup target add x86_64-unknown-linux-musl
|
|||||||
$ cargo build --release --target x86_64-unknown-linux-musl
|
$ cargo build --release --target x86_64-unknown-linux-musl
|
||||||
```
|
```
|
||||||
|
|
||||||
Applying the `--features` flag from above works as expected.
|
Applying the `--features` flag from above works as expected. If you want to
|
||||||
|
build a static executable with MUSL and with PCRE2, then you will need to have
|
||||||
|
`musl-gcc` installed, which might be in a separate package from the actual
|
||||||
|
MUSL library, depending on your Linux distribution.
|
||||||
|
|
||||||
|
|
||||||
### Running tests
|
### Running tests
|
||||||
|
12
appveyor.yml
12
appveyor.yml
@@ -45,11 +45,10 @@ install:
|
|||||||
- rustc -V
|
- rustc -V
|
||||||
- cargo -V
|
- cargo -V
|
||||||
|
|
||||||
# ???
|
# Hack to work around a harmless warning in Appveyor builds?
|
||||||
build: false
|
build: false
|
||||||
|
|
||||||
# Equivalent to Travis' `script` phase
|
# Equivalent to Travis' `script` phase
|
||||||
# TODO modify this phase as you see fit
|
|
||||||
test_script:
|
test_script:
|
||||||
- cargo test --verbose --all --features pcre2
|
- cargo test --verbose --all --features pcre2
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ before_deploy:
|
|||||||
- copy target\release\rg.exe staging
|
- copy target\release\rg.exe staging
|
||||||
- ps: copy target\release\build\ripgrep-*\out\_rg.ps1 staging
|
- ps: copy target\release\build\ripgrep-*\out\_rg.ps1 staging
|
||||||
- cd staging
|
- cd staging
|
||||||
# release zipfile will look like 'rust-everywhere-v1.2.3-x86_64-pc-windows-msvc'
|
# release zipfile will look like 'ripgrep-1.2.3-x86_64-pc-windows-msvc'
|
||||||
- 7z a ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip *
|
- 7z a ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip *
|
||||||
- appveyor PushArtifact ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip
|
- appveyor PushArtifact ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip
|
||||||
|
|
||||||
@@ -73,9 +72,6 @@ deploy:
|
|||||||
provider: GitHub
|
provider: GitHub
|
||||||
# deploy when a new tag is pushed and only on the stable channel
|
# deploy when a new tag is pushed and only on the stable channel
|
||||||
on:
|
on:
|
||||||
# channel to use to produce the release artifacts
|
|
||||||
# NOTE make sure you only release *once* per target
|
|
||||||
# TODO you may want to pick a different channel
|
|
||||||
CHANNEL: stable
|
CHANNEL: stable
|
||||||
appveyor_repo_tag: true
|
appveyor_repo_tag: true
|
||||||
|
|
||||||
@@ -83,7 +79,3 @@ branches:
|
|||||||
only:
|
only:
|
||||||
- /\d+\.\d+\.\d+/
|
- /\d+\.\d+\.\d+/
|
||||||
- master
|
- master
|
||||||
# - appveyor
|
|
||||||
# - /\d+\.\d+\.\d+/
|
|
||||||
# except:
|
|
||||||
# - master
|
|
||||||
|
89
build.rs
89
build.rs
@@ -1,10 +1,4 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsString;
|
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -19,22 +13,6 @@ use app::{RGArg, RGArgKind};
|
|||||||
mod app;
|
mod app;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// If our version of Rust has runtime SIMD detection, then set a cfg so
|
|
||||||
// we know we can test for it. We use this when generating ripgrep's
|
|
||||||
// --version output.
|
|
||||||
let version = rustc_version();
|
|
||||||
let parsed = match Version::parse(&version) {
|
|
||||||
Ok(parsed) => parsed,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("failed to parse `rustc --version`: {}", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let minimum = Version { major: 1, minor: 27, patch: 0 };
|
|
||||||
if version.contains("nightly") || parsed >= minimum {
|
|
||||||
println!("cargo:rustc-cfg=ripgrep_runtime_cpu");
|
|
||||||
}
|
|
||||||
|
|
||||||
// OUT_DIR is set by Cargo and it's where any additional build artifacts
|
// OUT_DIR is set by Cargo and it's where any additional build artifacts
|
||||||
// are written.
|
// are written.
|
||||||
let outdir = match env::var_os("OUT_DIR") {
|
let outdir = match env::var_os("OUT_DIR") {
|
||||||
@@ -185,7 +163,12 @@ fn formatted_arg(arg: &RGArg) -> io::Result<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn formatted_doc_txt(arg: &RGArg) -> io::Result<String> {
|
fn formatted_doc_txt(arg: &RGArg) -> io::Result<String> {
|
||||||
let paragraphs: Vec<&str> = arg.doc_long.split("\n\n").collect();
|
let paragraphs: Vec<String> = arg.doc_long
|
||||||
|
.replace("{", "{")
|
||||||
|
.replace("}", r"}")
|
||||||
|
.split("\n\n")
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect();
|
||||||
if paragraphs.is_empty() {
|
if paragraphs.is_empty() {
|
||||||
return Err(ioerr(format!("missing docs for --{}", arg.name)));
|
return Err(ioerr(format!("missing docs for --{}", arg.name)));
|
||||||
}
|
}
|
||||||
@@ -199,63 +182,3 @@ fn formatted_doc_txt(arg: &RGArg) -> io::Result<String> {
|
|||||||
fn ioerr(msg: String) -> io::Error {
|
fn ioerr(msg: String) -> io::Error {
|
||||||
io::Error::new(io::ErrorKind::Other, msg)
|
io::Error::new(io::ErrorKind::Other, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rustc_version() -> String {
|
|
||||||
let rustc = env::var_os("RUSTC").unwrap_or(OsString::from("rustc"));
|
|
||||||
let output = process::Command::new(&rustc)
|
|
||||||
.arg("--version")
|
|
||||||
.output()
|
|
||||||
.unwrap()
|
|
||||||
.stdout;
|
|
||||||
String::from_utf8(output).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
|
||||||
struct Version {
|
|
||||||
major: u32,
|
|
||||||
minor: u32,
|
|
||||||
patch: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Version {
|
|
||||||
fn parse(mut s: &str) -> Result<Version, String> {
|
|
||||||
if !s.starts_with("rustc ") {
|
|
||||||
return Err(format!("unrecognized version string: {}", s));
|
|
||||||
}
|
|
||||||
s = &s["rustc ".len()..];
|
|
||||||
|
|
||||||
let parts: Vec<&str> = s.split(".").collect();
|
|
||||||
if parts.len() < 3 {
|
|
||||||
return Err(format!("not enough version parts: {:?}", parts));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut num = String::new();
|
|
||||||
for c in parts[0].chars() {
|
|
||||||
if !c.is_digit(10) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
num.push(c);
|
|
||||||
}
|
|
||||||
let major = num.parse::<u32>().map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
num.clear();
|
|
||||||
for c in parts[1].chars() {
|
|
||||||
if !c.is_digit(10) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
num.push(c);
|
|
||||||
}
|
|
||||||
let minor = num.parse::<u32>().map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
num.clear();
|
|
||||||
for c in parts[2].chars() {
|
|
||||||
if !c.is_digit(10) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
num.push(c);
|
|
||||||
}
|
|
||||||
let patch = num.parse::<u32>().map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
Ok(Version { major, minor, patch })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -11,7 +11,9 @@ mk_artifacts() {
|
|||||||
if is_arm; then
|
if is_arm; then
|
||||||
cargo build --target "$TARGET" --release
|
cargo build --target "$TARGET" --release
|
||||||
else
|
else
|
||||||
cargo build --target "$TARGET" --release --features 'pcre2'
|
# Technically, MUSL builds will force PCRE2 to get statically compiled,
|
||||||
|
# but we also want PCRE2 statically build for macOS binaries.
|
||||||
|
PCRE2_SYS_STATIC=1 cargo build --target "$TARGET" --release --features 'pcre2'
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
43
ci/build_deb.sh
Executable file
43
ci/build_deb.sh
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# This script builds a binary dpkg for Debian based distros. It does not
|
||||||
|
# currently run in CI, and is instead run manually and the resulting dpkg is
|
||||||
|
# uploaded to GitHub via the web UI.
|
||||||
|
#
|
||||||
|
# Note that this requires 'cargo deb', which can be installed with
|
||||||
|
# 'cargo install cargo-deb'.
|
||||||
|
#
|
||||||
|
# This should be run from the root of the ripgrep repo.
|
||||||
|
|
||||||
|
if ! command -V cargo-deb > /dev/null 2>&1; then
|
||||||
|
echo "cargo-deb command missing" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 'cargo deb' does not seem to provide a way to specify an asset that is
|
||||||
|
# created at build time, such as ripgrep's man page. To work around this,
|
||||||
|
# we force a debug build, copy out the man page (and shell completions)
|
||||||
|
# produced from that build, put it into a predictable location and then build
|
||||||
|
# the deb, which knows where to look.
|
||||||
|
|
||||||
|
DEPLOY_DIR=deployment/deb
|
||||||
|
mkdir -p "$DEPLOY_DIR"
|
||||||
|
cargo build
|
||||||
|
|
||||||
|
# Find and copy man page.
|
||||||
|
manpage="$(find ./target/debug -name rg.1 -print0 | xargs -0 ls -t | head -n1)"
|
||||||
|
cp "$manpage" "$DEPLOY_DIR/"
|
||||||
|
|
||||||
|
# Do the same for shell completions.
|
||||||
|
compbash="$(find ./target/debug -name rg.bash -print0 | xargs -0 ls -t | head -n1)"
|
||||||
|
cp "$compbash" "$DEPLOY_DIR/"
|
||||||
|
compfish="$(find ./target/debug -name rg.fish -print0 | xargs -0 ls -t | head -n1)"
|
||||||
|
cp "$compfish" "$DEPLOY_DIR/"
|
||||||
|
compzsh="complete/_rg"
|
||||||
|
cp "$compzsh" "$DEPLOY_DIR/"
|
||||||
|
|
||||||
|
# Since we're distributing the dpkg, we don't know whether the user will have
|
||||||
|
# PCRE2 installed, so just do a static build.
|
||||||
|
PCRE2_SYS_STATIC=1 cargo deb
|
50
complete/_rg
50
complete/_rg
@@ -44,6 +44,12 @@ _rg() {
|
|||||||
'(: * -)'{-h,--help}'[display help information]'
|
'(: * -)'{-h,--help}'[display help information]'
|
||||||
'(: * -)'{-V,--version}'[display version information]'
|
'(: * -)'{-V,--version}'[display version information]'
|
||||||
|
|
||||||
|
+ '(buffered)' # buffering options
|
||||||
|
'--line-buffered[force line buffering]'
|
||||||
|
$no"--no-line-buffered[don't force line buffering]"
|
||||||
|
'--block-buffered[force block buffering]'
|
||||||
|
$no"--no-block-buffered[don't force block buffering]"
|
||||||
|
|
||||||
+ '(case)' # Case-sensitivity options
|
+ '(case)' # Case-sensitivity options
|
||||||
{-i,--ignore-case}'[search case-insensitively]'
|
{-i,--ignore-case}'[search case-insensitively]'
|
||||||
{-s,--case-sensitive}'[search case-sensitively]'
|
{-s,--case-sensitive}'[search case-sensitively]'
|
||||||
@@ -71,7 +77,7 @@ _rg() {
|
|||||||
$no'--no-encoding[use default text encoding]'
|
$no'--no-encoding[use default text encoding]'
|
||||||
|
|
||||||
+ file # File-input options
|
+ file # File-input options
|
||||||
'*'{-f+,--file=}'[specify file containing patterns to search for]: :_files'
|
'(1)*'{-f+,--file=}'[specify file containing patterns to search for]: :_files'
|
||||||
|
|
||||||
+ '(file-match)' # Files with/without match options
|
+ '(file-match)' # Files with/without match options
|
||||||
'(stats)'{-l,--files-with-matches}'[only show names of files with matches]'
|
'(stats)'{-l,--files-with-matches}'[only show names of files with matches]'
|
||||||
@@ -81,6 +87,10 @@ _rg() {
|
|||||||
{-H,--with-filename}'[show file name for matches]'
|
{-H,--with-filename}'[show file name for matches]'
|
||||||
"--no-filename[don't show file name for matches]"
|
"--no-filename[don't show file name for matches]"
|
||||||
|
|
||||||
|
+ '(file-system)' # File system options
|
||||||
|
"--one-file-system[don't descend into directories on other file systems]"
|
||||||
|
$no'--no-one-file-system[descend into directories on other file systems]'
|
||||||
|
|
||||||
+ '(fixed)' # Fixed-string options
|
+ '(fixed)' # Fixed-string options
|
||||||
{-F,--fixed-strings}'[treat pattern as literal string instead of regular expression]'
|
{-F,--fixed-strings}'[treat pattern as literal string instead of regular expression]'
|
||||||
$no"--no-fixed-strings[don't treat pattern as literal string]"
|
$no"--no-fixed-strings[don't treat pattern as literal string]"
|
||||||
@@ -102,8 +112,12 @@ _rg() {
|
|||||||
$no"--no-hidden[don't search hidden files and directories]"
|
$no"--no-hidden[don't search hidden files and directories]"
|
||||||
|
|
||||||
+ '(ignore)' # Ignore-file options
|
+ '(ignore)' # Ignore-file options
|
||||||
"(--no-ignore-global --no-ignore-parent --no-ignore-vcs)--no-ignore[don't respect ignore files]"
|
"(--no-ignore-global --no-ignore-parent --no-ignore-vcs --no-ignore-dot)--no-ignore[don't respect ignore files]"
|
||||||
$no'(--ignore-global --ignore-parent --ignore-vcs)--ignore[respect ignore files]'
|
$no'(--ignore-global --ignore-parent --ignore-vcs --ignore-dot)--ignore[respect ignore files]'
|
||||||
|
|
||||||
|
+ '(ignore-file-case-insensitive)' # Ignore-file case sensitivity options
|
||||||
|
'--ignore-file-case-insensitive[process ignore files case insensitively]'
|
||||||
|
$no'--no-ignore-file-case-insensitive[process ignore files case sensitively]'
|
||||||
|
|
||||||
+ '(ignore-global)' # Global ignore-file options
|
+ '(ignore-global)' # Global ignore-file options
|
||||||
"--no-ignore-global[don't respect global ignore files]"
|
"--no-ignore-global[don't respect global ignore files]"
|
||||||
@@ -117,6 +131,10 @@ _rg() {
|
|||||||
"--no-ignore-vcs[don't respect version control ignore files]"
|
"--no-ignore-vcs[don't respect version control ignore files]"
|
||||||
$no'--ignore-vcs[respect version control ignore files]'
|
$no'--ignore-vcs[respect version control ignore files]'
|
||||||
|
|
||||||
|
+ '(ignore-dot)' # .ignore-file options
|
||||||
|
"--no-ignore-dot[don't respect .ignore files]"
|
||||||
|
$no'--ignore-dot[respect .ignore files]'
|
||||||
|
|
||||||
+ '(json)' # JSON options
|
+ '(json)' # JSON options
|
||||||
'--json[output results in JSON Lines format]'
|
'--json[output results in JSON Lines format]'
|
||||||
$no"--no-json[don't output results in JSON Lines format]"
|
$no"--no-json[don't output results in JSON Lines format]"
|
||||||
@@ -166,13 +184,16 @@ _rg() {
|
|||||||
$no'(pcre2-unicode)--no-pcre2[disable matching with PCRE2]'
|
$no'(pcre2-unicode)--no-pcre2[disable matching with PCRE2]'
|
||||||
|
|
||||||
+ '(pcre2-unicode)' # PCRE2 Unicode options
|
+ '(pcre2-unicode)' # PCRE2 Unicode options
|
||||||
$no'(--no-pcre2-unicode)--pcre2-unicode[enable PCRE2 Unicode mode (with -P)]'
|
$no'(--no-pcre2 --no-pcre2-unicode)--pcre2-unicode[enable PCRE2 Unicode mode (with -P)]'
|
||||||
'(--no-pcre2-unicode)--no-pcre2-unicode[disable PCRE2 Unicode mode (with -P)]'
|
'(--no-pcre2 --pcre2-unicode)--no-pcre2-unicode[disable PCRE2 Unicode mode (with -P)]'
|
||||||
|
|
||||||
+ '(pre)' # Preprocessing options
|
+ '(pre)' # Preprocessing options
|
||||||
'(-z --search-zip)--pre=[specify preprocessor utility]:preprocessor utility:_command_names -e'
|
'(-z --search-zip)--pre=[specify preprocessor utility]:preprocessor utility:_command_names -e'
|
||||||
$no'--no-pre[disable preprocessor utility]'
|
$no'--no-pre[disable preprocessor utility]'
|
||||||
|
|
||||||
|
+ pre-glob # Preprocessing glob options
|
||||||
|
'*--pre-glob[include/exclude files for preprocessing with --pre]'
|
||||||
|
|
||||||
+ '(pretty-vimgrep)' # Pretty/vimgrep display options
|
+ '(pretty-vimgrep)' # Pretty/vimgrep display options
|
||||||
'(heading)'{-p,--pretty}'[alias for --color=always --heading -n]'
|
'(heading)'{-p,--pretty}'[alias for --color=always --heading -n]'
|
||||||
'(heading passthru)--vimgrep[show results in vim-compatible format]'
|
'(heading passthru)--vimgrep[show results in vim-compatible format]'
|
||||||
@@ -184,8 +205,21 @@ _rg() {
|
|||||||
{-r+,--replace=}'[specify string used to replace matches]:replace string'
|
{-r+,--replace=}'[specify string used to replace matches]:replace string'
|
||||||
|
|
||||||
+ '(sort)' # File-sorting options
|
+ '(sort)' # File-sorting options
|
||||||
'(threads)--sort-files[sort results by file path (disables parallelism)]'
|
'(threads)--sort=[sort results in ascending order (disables parallelism)]:sort method:((
|
||||||
$no"--no-sort-files[don't sort results by file path]"
|
none\:"no sorting"
|
||||||
|
path\:"sort by file path"
|
||||||
|
modified\:"sort by last modified time"
|
||||||
|
accessed\:"sort by last accessed time"
|
||||||
|
created\:"sort by creation time"
|
||||||
|
))'
|
||||||
|
'(threads)--sortr=[sort results in descending order (disables parallelism)]:sort method:((
|
||||||
|
none\:"no sorting"
|
||||||
|
path\:"sort by file path"
|
||||||
|
modified\:"sort by last modified time"
|
||||||
|
accessed\:"sort by last accessed time"
|
||||||
|
created\:"sort by creation time"
|
||||||
|
))'
|
||||||
|
'!(threads)--sort-files[sort results by file path (disables parallelism)]'
|
||||||
|
|
||||||
+ '(stats)' # Statistics options
|
+ '(stats)' # Statistics options
|
||||||
'(--files file-match)--stats[show search statistics]'
|
'(--files file-match)--stats[show search statistics]'
|
||||||
@@ -196,7 +230,7 @@ _rg() {
|
|||||||
$no"(--null-data)--no-text[don't search binary files as if they were text]"
|
$no"(--null-data)--no-text[don't search binary files as if they were text]"
|
||||||
|
|
||||||
+ '(threads)' # Thread-count options
|
+ '(threads)' # Thread-count options
|
||||||
'(--sort-files)'{-j+,--threads=}'[specify approximate number of threads to use]:number of threads'
|
'(sort)'{-j+,--threads=}'[specify approximate number of threads to use]:number of threads'
|
||||||
|
|
||||||
+ '(trim)' # Trim options
|
+ '(trim)' # Trim options
|
||||||
'--trim[trim any ASCII whitespace prefix from each line]'
|
'--trim[trim any ASCII whitespace prefix from each line]'
|
||||||
|
@@ -28,27 +28,37 @@ Synopsis
|
|||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
ripgrep (rg) recursively searches your current directory for a regex pattern.
|
ripgrep (rg) recursively searches your current directory for a regex pattern.
|
||||||
By default, ripgrep will respect your `.gitignore` and automatically skip
|
By default, ripgrep will respect your .gitignore and automatically skip hidden
|
||||||
hidden files/directories and binary files.
|
files/directories and binary files.
|
||||||
|
|
||||||
ripgrep's regex engine uses finite automata and guarantees linear time
|
ripgrep's default regex engine uses finite automata and guarantees linear
|
||||||
searching. Because of this, features like backreferences and arbitrary
|
time searching. Because of this, features like backreferences and arbitrary
|
||||||
lookaround are not supported.
|
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*.
|
||||||
|
|
||||||
|
|
||||||
REGEX SYNTAX
|
REGEX SYNTAX
|
||||||
------------
|
------------
|
||||||
ripgrep uses Rust's regex engine, which documents its syntax:
|
ripgrep uses Rust's regex engine by default, which documents its syntax:
|
||||||
https://docs.rs/regex/0.2.5/regex/#syntax
|
https://docs.rs/regex/*/regex/#syntax
|
||||||
|
|
||||||
ripgrep uses byte-oriented regexes, which has some additional documentation:
|
ripgrep uses byte-oriented regexes, which has some additional documentation:
|
||||||
https://docs.rs/regex/0.2.5/regex/bytes/index.html#syntax
|
https://docs.rs/regex/*/regex/bytes/index.html#syntax
|
||||||
|
|
||||||
To a first approximation, ripgrep uses Perl-like regexes without look-around or
|
To a first approximation, ripgrep uses Perl-like regexes without look-around or
|
||||||
backreferences. This makes them very similar to the "extended" (ERE) regular
|
backreferences. This makes them very similar to the "extended" (ERE) regular
|
||||||
expressions supported by `egrep`, but with a few additional features like
|
expressions supported by *egrep*, but with a few additional features like
|
||||||
Unicode character classes.
|
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
|
POSITIONAL ARGUMENTS
|
||||||
--------------------
|
--------------------
|
||||||
@@ -58,18 +68,37 @@ _PATTERN_::
|
|||||||
|
|
||||||
_PATH_::
|
_PATH_::
|
||||||
A file or directory to search. Directories are searched recursively. Paths
|
A file or directory to search. Directories are searched recursively. Paths
|
||||||
specified expicitly on the command line override glob and ignore rules.
|
specified explicitly on the command line override glob and ignore rules.
|
||||||
|
|
||||||
|
|
||||||
OPTIONS
|
OPTIONS
|
||||||
-------
|
-------
|
||||||
|
Note that for many options, there exist flags to disable them. 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}
|
{OPTIONS}
|
||||||
|
|
||||||
|
|
||||||
EXIT STATUS
|
EXIT STATUS
|
||||||
-----------
|
-----------
|
||||||
If ripgrep finds a match, then the exit status of the program is 0. If no match
|
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 non-zero.
|
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).
|
||||||
|
|
||||||
|
|
||||||
CONFIGURATION FILES
|
CONFIGURATION FILES
|
||||||
@@ -79,11 +108,11 @@ behavior. The format of the configuration file is an "rc" style and is very
|
|||||||
simple. It is defined by two rules:
|
simple. It is defined by two rules:
|
||||||
|
|
||||||
1. Every line is a shell argument, after trimming ASCII whitespace.
|
1. Every line is a shell argument, after trimming ASCII whitespace.
|
||||||
2. Lines starting with _#_ (optionally preceded by any amount of
|
2. Lines starting with *#* (optionally preceded by any amount of
|
||||||
ASCII whitespace) are ignored.
|
ASCII whitespace) are ignored.
|
||||||
|
|
||||||
ripgrep will look for a single configuration file if and only if the
|
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_CONFIG_PATH* environment variable is set and is non-empty.
|
||||||
ripgrep will parse shell arguments from this file on startup and will
|
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
|
behave as if the arguments in this file were prepended to any explicit
|
||||||
arguments given to ripgrep on the command line.
|
arguments given to ripgrep on the command line.
|
||||||
@@ -145,20 +174,20 @@ SHELL COMPLETION
|
|||||||
Shell completion files are included in the release tarball for Bash, Fish, Zsh
|
Shell completion files are included in the release tarball for Bash, Fish, Zsh
|
||||||
and PowerShell.
|
and PowerShell.
|
||||||
|
|
||||||
For *bash*, move `rg.bash` to `$XDG_CONFIG_HOME/bash_completion`
|
For *bash*, move *rg.bash* to *$XDG_CONFIG_HOME/bash_completion*
|
||||||
or `/etc/bash_completion.d/`.
|
or */etc/bash_completion.d/*.
|
||||||
|
|
||||||
For *fish*, move `rg.fish` to `$HOME/.config/fish/completions`.
|
For *fish*, move *rg.fish* to *$HOME/.config/fish/completions*.
|
||||||
|
|
||||||
For *zsh*, move `_rg` to one of your `$fpath` directories.
|
For *zsh*, move *_rg* to one of your *$fpath* directories.
|
||||||
|
|
||||||
|
|
||||||
CAVEATS
|
CAVEATS
|
||||||
-------
|
-------
|
||||||
ripgrep may abort unexpectedly when using default settings if it searches a
|
ripgrep may abort unexpectedly when using default settings if it searches a
|
||||||
file that is simultaneously truncated. This behavior can be avoided by passing
|
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
|
the *--no-mmap* flag which will forcefully disable the use of memory maps in
|
||||||
cases.
|
all cases.
|
||||||
|
|
||||||
|
|
||||||
VERSION
|
VERSION
|
||||||
@@ -170,7 +199,11 @@ HOMEPAGE
|
|||||||
--------
|
--------
|
||||||
https://github.com/BurntSushi/ripgrep
|
https://github.com/BurntSushi/ripgrep
|
||||||
|
|
||||||
Please report bugs and feature requests in the issue tracker.
|
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
|
AUTHORS
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.4.1" #:version
|
version = "0.4.2" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
Cross platform single glob and glob set matching. Glob set matching is the
|
Cross platform single glob and glob set matching. Glob set matching is the
|
||||||
@@ -19,14 +19,14 @@ name = "globset"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aho-corasick = "0.6.0"
|
aho-corasick = "0.6.8"
|
||||||
fnv = "1.0"
|
fnv = "1.0.6"
|
||||||
log = "0.4"
|
log = "0.4.5"
|
||||||
memchr = "2"
|
memchr = "2.1.0"
|
||||||
regex = "1"
|
regex = "1.1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
glob = "0.2"
|
glob = "0.2.11"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
simd-accel = []
|
simd-accel = []
|
||||||
|
@@ -837,40 +837,66 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
fn parse_star(&mut self) -> Result<(), Error> {
|
fn parse_star(&mut self) -> Result<(), Error> {
|
||||||
let prev = self.prev;
|
let prev = self.prev;
|
||||||
if self.chars.peek() != Some(&'*') {
|
if self.peek() != Some('*') {
|
||||||
self.push_token(Token::ZeroOrMore)?;
|
self.push_token(Token::ZeroOrMore)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
assert!(self.bump() == Some('*'));
|
assert!(self.bump() == Some('*'));
|
||||||
if !self.have_tokens()? {
|
if !self.have_tokens()? {
|
||||||
self.push_token(Token::RecursivePrefix)?;
|
if !self.peek().map_or(true, is_separator) {
|
||||||
let next = self.bump();
|
self.push_token(Token::ZeroOrMore)?;
|
||||||
if !next.map(is_separator).unwrap_or(true) {
|
self.push_token(Token::ZeroOrMore)?;
|
||||||
return Err(self.error(ErrorKind::InvalidRecursive));
|
} else {
|
||||||
|
self.push_token(Token::RecursivePrefix)?;
|
||||||
|
assert!(self.bump().map_or(true, is_separator));
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
self.pop_token()?;
|
|
||||||
if !prev.map(is_separator).unwrap_or(false) {
|
if !prev.map(is_separator).unwrap_or(false) {
|
||||||
if self.stack.len() <= 1
|
if self.stack.len() <= 1
|
||||||
|| (prev != Some(',') && prev != Some('{')) {
|
|| (prev != Some(',') && prev != Some('{'))
|
||||||
return Err(self.error(ErrorKind::InvalidRecursive));
|
{
|
||||||
|
self.push_token(Token::ZeroOrMore)?;
|
||||||
|
self.push_token(Token::ZeroOrMore)?;
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match self.chars.peek() {
|
let is_suffix =
|
||||||
None => {
|
match self.peek() {
|
||||||
assert!(self.bump().is_none());
|
None => {
|
||||||
self.push_token(Token::RecursiveSuffix)
|
assert!(self.bump().is_none());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Some(',') | Some('}') if self.stack.len() >= 2 => {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Some(c) if is_separator(c) => {
|
||||||
|
assert!(self.bump().map(is_separator).unwrap_or(false));
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.push_token(Token::ZeroOrMore)?;
|
||||||
|
self.push_token(Token::ZeroOrMore)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match self.pop_token()? {
|
||||||
|
Token::RecursivePrefix => {
|
||||||
|
self.push_token(Token::RecursivePrefix)?;
|
||||||
}
|
}
|
||||||
Some(&',') | Some(&'}') if self.stack.len() >= 2 => {
|
Token::RecursiveSuffix => {
|
||||||
self.push_token(Token::RecursiveSuffix)
|
self.push_token(Token::RecursiveSuffix)?;
|
||||||
}
|
}
|
||||||
Some(&c) if is_separator(c) => {
|
_ => {
|
||||||
assert!(self.bump().map(is_separator).unwrap_or(false));
|
if is_suffix {
|
||||||
self.push_token(Token::RecursiveZeroOrMore)
|
self.push_token(Token::RecursiveSuffix)?;
|
||||||
|
} else {
|
||||||
|
self.push_token(Token::RecursiveZeroOrMore)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => Err(self.error(ErrorKind::InvalidRecursive)),
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_class(&mut self) -> Result<(), Error> {
|
fn parse_class(&mut self) -> Result<(), Error> {
|
||||||
@@ -959,6 +985,10 @@ impl<'a> Parser<'a> {
|
|||||||
self.cur = self.chars.next();
|
self.cur = self.chars.next();
|
||||||
self.cur
|
self.cur
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn peek(&mut self) -> Option<char> {
|
||||||
|
self.chars.peek().map(|&ch| ch)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -1144,13 +1174,6 @@ mod tests {
|
|||||||
syntax!(cls20, "[^a]", vec![classn('a', 'a')]);
|
syntax!(cls20, "[^a]", vec![classn('a', 'a')]);
|
||||||
syntax!(cls21, "[^a-z]", vec![classn('a', 'z')]);
|
syntax!(cls21, "[^a-z]", vec![classn('a', 'z')]);
|
||||||
|
|
||||||
syntaxerr!(err_rseq1, "a**", ErrorKind::InvalidRecursive);
|
|
||||||
syntaxerr!(err_rseq2, "**a", ErrorKind::InvalidRecursive);
|
|
||||||
syntaxerr!(err_rseq3, "a**b", ErrorKind::InvalidRecursive);
|
|
||||||
syntaxerr!(err_rseq4, "***", ErrorKind::InvalidRecursive);
|
|
||||||
syntaxerr!(err_rseq5, "/a**", ErrorKind::InvalidRecursive);
|
|
||||||
syntaxerr!(err_rseq6, "/**a", ErrorKind::InvalidRecursive);
|
|
||||||
syntaxerr!(err_rseq7, "/a**b", ErrorKind::InvalidRecursive);
|
|
||||||
syntaxerr!(err_unclosed1, "[", ErrorKind::UnclosedClass);
|
syntaxerr!(err_unclosed1, "[", ErrorKind::UnclosedClass);
|
||||||
syntaxerr!(err_unclosed2, "[]", ErrorKind::UnclosedClass);
|
syntaxerr!(err_unclosed2, "[]", ErrorKind::UnclosedClass);
|
||||||
syntaxerr!(err_unclosed3, "[!", ErrorKind::UnclosedClass);
|
syntaxerr!(err_unclosed3, "[!", ErrorKind::UnclosedClass);
|
||||||
@@ -1194,8 +1217,30 @@ mod tests {
|
|||||||
toregex!(re8, "[*]", r"^[\*]$");
|
toregex!(re8, "[*]", r"^[\*]$");
|
||||||
toregex!(re9, "[+]", r"^[\+]$");
|
toregex!(re9, "[+]", r"^[\+]$");
|
||||||
toregex!(re10, "+", r"^\+$");
|
toregex!(re10, "+", r"^\+$");
|
||||||
toregex!(re11, "**", r"^.*$");
|
toregex!(re11, "☃", r"^\xe2\x98\x83$");
|
||||||
toregex!(re12, "☃", r"^\xe2\x98\x83$");
|
toregex!(re12, "**", r"^.*$");
|
||||||
|
toregex!(re13, "**/", r"^.*$");
|
||||||
|
toregex!(re14, "**/*", r"^(?:/?|.*/).*$");
|
||||||
|
toregex!(re15, "**/**", r"^.*$");
|
||||||
|
toregex!(re16, "**/**/*", r"^(?:/?|.*/).*$");
|
||||||
|
toregex!(re17, "**/**/**", r"^.*$");
|
||||||
|
toregex!(re18, "**/**/**/*", r"^(?:/?|.*/).*$");
|
||||||
|
toregex!(re19, "a/**", r"^a(?:/?|/.*)$");
|
||||||
|
toregex!(re20, "a/**/**", r"^a(?:/?|/.*)$");
|
||||||
|
toregex!(re21, "a/**/**/**", r"^a(?:/?|/.*)$");
|
||||||
|
toregex!(re22, "a/**/b", r"^a(?:/|/.*/)b$");
|
||||||
|
toregex!(re23, "a/**/**/b", r"^a(?:/|/.*/)b$");
|
||||||
|
toregex!(re24, "a/**/**/**/b", r"^a(?:/|/.*/)b$");
|
||||||
|
toregex!(re25, "**/b", r"^(?:/?|.*/)b$");
|
||||||
|
toregex!(re26, "**/**/b", r"^(?:/?|.*/)b$");
|
||||||
|
toregex!(re27, "**/**/**/b", r"^(?:/?|.*/)b$");
|
||||||
|
toregex!(re28, "a**", r"^a.*.*$");
|
||||||
|
toregex!(re29, "**a", r"^.*.*a$");
|
||||||
|
toregex!(re30, "a**b", r"^a.*.*b$");
|
||||||
|
toregex!(re31, "***", r"^.*.*.*$");
|
||||||
|
toregex!(re32, "/a**", r"^/a.*.*$");
|
||||||
|
toregex!(re33, "/**a", r"^/.*.*a$");
|
||||||
|
toregex!(re34, "/a**b", r"^/a.*.*b$");
|
||||||
|
|
||||||
matches!(match1, "a", "a");
|
matches!(match1, "a", "a");
|
||||||
matches!(match2, "a*b", "a_b");
|
matches!(match2, "a*b", "a_b");
|
||||||
|
@@ -143,8 +143,13 @@ pub struct Error {
|
|||||||
/// The kind of error that can occur when parsing a glob pattern.
|
/// The kind of error that can occur when parsing a glob pattern.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum ErrorKind {
|
pub enum ErrorKind {
|
||||||
/// Occurs when a use of `**` is invalid. Namely, `**` can only appear
|
/// **DEPRECATED**.
|
||||||
/// adjacent to a path separator, or the beginning/end of a glob.
|
///
|
||||||
|
/// This error used to occur for consistency with git's glob specification,
|
||||||
|
/// but the specification now accepts all uses of `**`. When `**` does not
|
||||||
|
/// appear adjacent to a path separator or at the beginning/end of a glob,
|
||||||
|
/// it is now treated as two consecutive `*` patterns. As such, this error
|
||||||
|
/// is no longer used.
|
||||||
InvalidRecursive,
|
InvalidRecursive,
|
||||||
/// Occurs when a character class (e.g., `[abc]`) is not closed.
|
/// Occurs when a character class (e.g., `[abc]`) is not closed.
|
||||||
UnclosedClass,
|
UnclosedClass,
|
||||||
|
25
grep-cli/Cargo.toml
Normal file
25
grep-cli/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "grep-cli"
|
||||||
|
version = "0.1.1" #:version
|
||||||
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
|
description = """
|
||||||
|
Utilities for search oriented command line applications.
|
||||||
|
"""
|
||||||
|
documentation = "https://docs.rs/grep-cli"
|
||||||
|
homepage = "https://github.com/BurntSushi/ripgrep"
|
||||||
|
repository = "https://github.com/BurntSushi/ripgrep"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["regex", "grep", "cli", "utility", "util"]
|
||||||
|
license = "Unlicense/MIT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
atty = "0.2.11"
|
||||||
|
globset = { version = "0.4.2", path = "../globset" }
|
||||||
|
lazy_static = "1.1.0"
|
||||||
|
log = "0.4.5"
|
||||||
|
regex = "1.1"
|
||||||
|
same-file = "1.0.4"
|
||||||
|
termcolor = "1.0.4"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies.winapi-util]
|
||||||
|
version = "0.1.1"
|
21
grep-cli/LICENSE-MIT
Normal file
21
grep-cli/LICENSE-MIT
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Andrew Gallant
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
38
grep-cli/README.md
Normal file
38
grep-cli/README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
grep-cli
|
||||||
|
--------
|
||||||
|
A utility library that provides common routines desired in search oriented
|
||||||
|
command line applications. This includes, but is not limited to, parsing hex
|
||||||
|
escapes, detecting whether stdin is readable and more. To the extent possible,
|
||||||
|
this crate strives for compatibility across Windows, macOS and Linux.
|
||||||
|
|
||||||
|
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||||
|
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||||
|
[](https://crates.io/crates/grep-cli)
|
||||||
|
|
||||||
|
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||||
|
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
[https://docs.rs/grep-cli](https://docs.rs/grep-cli)
|
||||||
|
|
||||||
|
**NOTE:** You probably don't want to use this crate directly. Instead, you
|
||||||
|
should prefer the facade defined in the
|
||||||
|
[`grep`](https://docs.rs/grep)
|
||||||
|
crate.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Add this to your `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
grep-cli = "0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
and this to your crate root:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate grep_cli;
|
||||||
|
```
|
24
grep-cli/UNLICENSE
Normal file
24
grep-cli/UNLICENSE
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org/>
|
382
grep-cli/src/decompress.rs
Normal file
382
grep-cli/src/decompress.rs
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
|
||||||
|
use process::{CommandError, CommandReader, CommandReaderBuilder};
|
||||||
|
|
||||||
|
/// A builder for a matcher that determines which files get decompressed.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct DecompressionMatcherBuilder {
|
||||||
|
/// The commands for each matching glob.
|
||||||
|
commands: Vec<DecompressionCommand>,
|
||||||
|
/// Whether to include the default matching rules.
|
||||||
|
defaults: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A representation of a single command for decompressing data
|
||||||
|
/// out-of-proccess.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct DecompressionCommand {
|
||||||
|
/// The glob that matches this command.
|
||||||
|
glob: String,
|
||||||
|
/// The command or binary name.
|
||||||
|
bin: OsString,
|
||||||
|
/// The arguments to invoke with the command.
|
||||||
|
args: Vec<OsString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DecompressionMatcherBuilder {
|
||||||
|
fn default() -> DecompressionMatcherBuilder {
|
||||||
|
DecompressionMatcherBuilder::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecompressionMatcherBuilder {
|
||||||
|
/// Create a new builder for configuring a decompression matcher.
|
||||||
|
pub fn new() -> DecompressionMatcherBuilder {
|
||||||
|
DecompressionMatcherBuilder {
|
||||||
|
commands: vec![],
|
||||||
|
defaults: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a matcher for determining how to decompress files.
|
||||||
|
///
|
||||||
|
/// If there was a problem compiling the matcher, then an error is
|
||||||
|
/// returned.
|
||||||
|
pub fn build(&self) -> Result<DecompressionMatcher, CommandError> {
|
||||||
|
let defaults =
|
||||||
|
if !self.defaults {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
default_decompression_commands()
|
||||||
|
};
|
||||||
|
let mut glob_builder = GlobSetBuilder::new();
|
||||||
|
let mut commands = vec![];
|
||||||
|
for decomp_cmd in defaults.iter().chain(&self.commands) {
|
||||||
|
let glob = Glob::new(&decomp_cmd.glob).map_err(|err| {
|
||||||
|
CommandError::io(io::Error::new(io::ErrorKind::Other, err))
|
||||||
|
})?;
|
||||||
|
glob_builder.add(glob);
|
||||||
|
commands.push(decomp_cmd.clone());
|
||||||
|
}
|
||||||
|
let globs = glob_builder.build().map_err(|err| {
|
||||||
|
CommandError::io(io::Error::new(io::ErrorKind::Other, err))
|
||||||
|
})?;
|
||||||
|
Ok(DecompressionMatcher { globs, commands })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When enabled, the default matching rules will be compiled into this
|
||||||
|
/// matcher before any other associations. When disabled, only the
|
||||||
|
/// rules explicitly given to this builder will be used.
|
||||||
|
///
|
||||||
|
/// This is enabled by default.
|
||||||
|
pub fn defaults(&mut self, yes: bool) -> &mut DecompressionMatcherBuilder {
|
||||||
|
self.defaults = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Associates a glob with a command to decompress files matching the glob.
|
||||||
|
///
|
||||||
|
/// If multiple globs match the same file, then the most recently added
|
||||||
|
/// glob takes precedence.
|
||||||
|
///
|
||||||
|
/// The syntax for the glob is documented in the
|
||||||
|
/// [`globset` crate](https://docs.rs/globset/#syntax).
|
||||||
|
pub fn associate<P, I, A>(
|
||||||
|
&mut self,
|
||||||
|
glob: &str,
|
||||||
|
program: P,
|
||||||
|
args: I,
|
||||||
|
) -> &mut DecompressionMatcherBuilder
|
||||||
|
where P: AsRef<OsStr>,
|
||||||
|
I: IntoIterator<Item=A>,
|
||||||
|
A: AsRef<OsStr>,
|
||||||
|
{
|
||||||
|
|
||||||
|
let glob = glob.to_string();
|
||||||
|
let bin = program.as_ref().to_os_string();
|
||||||
|
let args = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| a.as_ref().to_os_string())
|
||||||
|
.collect();
|
||||||
|
self.commands.push(DecompressionCommand { glob, bin, args });
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A matcher for determining how to decompress files.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct DecompressionMatcher {
|
||||||
|
/// The set of globs to match. Each glob has a corresponding entry in
|
||||||
|
/// `commands`. When a glob matches, the corresponding command should be
|
||||||
|
/// used to perform out-of-process decompression.
|
||||||
|
globs: GlobSet,
|
||||||
|
/// The commands for each matching glob.
|
||||||
|
commands: Vec<DecompressionCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DecompressionMatcher {
|
||||||
|
fn default() -> DecompressionMatcher {
|
||||||
|
DecompressionMatcher::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecompressionMatcher {
|
||||||
|
/// Create a new matcher with default rules.
|
||||||
|
///
|
||||||
|
/// To add more matching rules, build a matcher with
|
||||||
|
/// [`DecompressionMatcherBuilder`](struct.DecompressionMatcherBuilder.html).
|
||||||
|
pub fn new() -> DecompressionMatcher {
|
||||||
|
DecompressionMatcherBuilder::new()
|
||||||
|
.build()
|
||||||
|
.expect("built-in matching rules should always compile")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a pre-built command based on the given file path that can
|
||||||
|
/// decompress its contents. If no such decompressor is known, then this
|
||||||
|
/// returns `None`.
|
||||||
|
///
|
||||||
|
/// If there are multiple possible commands matching the given path, then
|
||||||
|
/// the command added last takes precedence.
|
||||||
|
pub fn command<P: AsRef<Path>>(&self, path: P) -> Option<Command> {
|
||||||
|
for i in self.globs.matches(path).into_iter().rev() {
|
||||||
|
let decomp_cmd = &self.commands[i];
|
||||||
|
let mut cmd = Command::new(&decomp_cmd.bin);
|
||||||
|
cmd.args(&decomp_cmd.args);
|
||||||
|
return Some(cmd);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if the given file path has at least one
|
||||||
|
/// matching command to perform decompression on.
|
||||||
|
pub fn has_command<P: AsRef<Path>>(&self, path: P) -> bool {
|
||||||
|
self.globs.is_match(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures and builds a streaming reader for decompressing data.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct DecompressionReaderBuilder {
|
||||||
|
matcher: DecompressionMatcher,
|
||||||
|
command_builder: CommandReaderBuilder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecompressionReaderBuilder {
|
||||||
|
/// Create a new builder with the default configuration.
|
||||||
|
pub fn new() -> DecompressionReaderBuilder {
|
||||||
|
DecompressionReaderBuilder::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a new streaming reader for decompressing data.
|
||||||
|
///
|
||||||
|
/// If decompression is done out-of-process and if there was a problem
|
||||||
|
/// spawning the process, then its error is logged at the debug level and a
|
||||||
|
/// passthru reader is returned that does no decompression. This behavior
|
||||||
|
/// typically occurs when the given file path matches a decompression
|
||||||
|
/// command, but is executing in an environment where the decompression
|
||||||
|
/// command is not available.
|
||||||
|
///
|
||||||
|
/// If the given file path could not be matched with a decompression
|
||||||
|
/// strategy, then a passthru reader is returned that does no
|
||||||
|
/// decompression.
|
||||||
|
pub fn build<P: AsRef<Path>>(
|
||||||
|
&self,
|
||||||
|
path: P,
|
||||||
|
) -> Result<DecompressionReader, CommandError> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let mut cmd = match self.matcher.command(path) {
|
||||||
|
None => return DecompressionReader::new_passthru(path),
|
||||||
|
Some(cmd) => cmd,
|
||||||
|
};
|
||||||
|
cmd.arg(path);
|
||||||
|
|
||||||
|
match self.command_builder.build(&mut cmd) {
|
||||||
|
Ok(cmd_reader) => Ok(DecompressionReader { rdr: Ok(cmd_reader) }),
|
||||||
|
Err(err) => {
|
||||||
|
debug!(
|
||||||
|
"{}: error spawning command '{:?}': {} \
|
||||||
|
(falling back to uncompressed reader)",
|
||||||
|
path.display(),
|
||||||
|
cmd,
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
DecompressionReader::new_passthru(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the matcher to use to look up the decompression command for each
|
||||||
|
/// file path.
|
||||||
|
///
|
||||||
|
/// A set of sensible rules is enabled by default. Setting this will
|
||||||
|
/// completely replace the current rules.
|
||||||
|
pub fn matcher(
|
||||||
|
&mut self,
|
||||||
|
matcher: DecompressionMatcher,
|
||||||
|
) -> &mut DecompressionReaderBuilder {
|
||||||
|
self.matcher = matcher;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the underlying matcher currently used by this builder.
|
||||||
|
pub fn get_matcher(&self) -> &DecompressionMatcher {
|
||||||
|
&self.matcher
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When enabled, the reader will asynchronously read the contents of the
|
||||||
|
/// command's stderr output. When disabled, stderr is only read after the
|
||||||
|
/// stdout stream has been exhausted (or if the process quits with an error
|
||||||
|
/// code).
|
||||||
|
///
|
||||||
|
/// Note that when enabled, this may require launching an additional
|
||||||
|
/// thread in order to read stderr. This is done so that the process being
|
||||||
|
/// executed is never blocked from writing to stdout or stderr. If this is
|
||||||
|
/// disabled, then it is possible for the process to fill up the stderr
|
||||||
|
/// buffer and deadlock.
|
||||||
|
///
|
||||||
|
/// This is enabled by default.
|
||||||
|
pub fn async_stderr(
|
||||||
|
&mut self,
|
||||||
|
yes: bool,
|
||||||
|
) -> &mut DecompressionReaderBuilder {
|
||||||
|
self.command_builder.async_stderr(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A streaming reader for decompressing the contents of a file.
|
||||||
|
///
|
||||||
|
/// The purpose of this reader is to provide a seamless way to decompress the
|
||||||
|
/// contents of file using existing tools in the current environment. This is
|
||||||
|
/// meant to be an alternative to using decompression libraries in favor of the
|
||||||
|
/// simplicity and portability of using external commands such as `gzip` and
|
||||||
|
/// `xz`. This does impose the overhead of spawning a process, so other means
|
||||||
|
/// for performing decompression should be sought if this overhead isn't
|
||||||
|
/// acceptable.
|
||||||
|
///
|
||||||
|
/// A decompression reader comes with a default set of matching rules that are
|
||||||
|
/// meant to associate file paths with the corresponding command to use to
|
||||||
|
/// decompress them. For example, a glob like `*.gz` matches gzip compressed
|
||||||
|
/// files with the command `gzip -d -c`. If a file path does not match any
|
||||||
|
/// existing rules, or if it matches a rule whose command does not exist in the
|
||||||
|
/// current environment, then the decompression reader passes through the
|
||||||
|
/// contents of the underlying file without doing any decompression.
|
||||||
|
///
|
||||||
|
/// The default matching rules are probably good enough for most cases, and if
|
||||||
|
/// they require revision, pull requests are welcome. In cases where they must
|
||||||
|
/// be changed or extended, they can be customized through the use of
|
||||||
|
/// [`DecompressionMatcherBuilder`](struct.DecompressionMatcherBuilder.html)
|
||||||
|
/// and
|
||||||
|
/// [`DecompressionReaderBuilder`](struct.DecompressionReaderBuilder.html).
|
||||||
|
///
|
||||||
|
/// By default, this reader will asynchronously read the processes' stderr.
|
||||||
|
/// This prevents subtle deadlocking bugs for noisy processes that write a lot
|
||||||
|
/// to stderr. Currently, the entire contents of stderr is read on to the heap.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This example shows how to read the decompressed contents of a file without
|
||||||
|
/// needing to explicitly choose the decompression command to run.
|
||||||
|
///
|
||||||
|
/// Note that if you need to decompress multiple files, it is better to use
|
||||||
|
/// `DecompressionReaderBuilder`, which will amortize the cost of compiling the
|
||||||
|
/// matcher.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use std::io::Read;
|
||||||
|
/// use std::process::Command;
|
||||||
|
/// use grep_cli::DecompressionReader;
|
||||||
|
///
|
||||||
|
/// # fn example() -> Result<(), Box<::std::error::Error>> {
|
||||||
|
/// let mut rdr = DecompressionReader::new("/usr/share/man/man1/ls.1.gz")?;
|
||||||
|
/// let mut contents = vec![];
|
||||||
|
/// rdr.read_to_end(&mut contents)?;
|
||||||
|
/// # Ok(()) }
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DecompressionReader {
|
||||||
|
rdr: Result<CommandReader, File>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecompressionReader {
|
||||||
|
/// Build a new streaming reader for decompressing data.
|
||||||
|
///
|
||||||
|
/// If decompression is done out-of-process and if there was a problem
|
||||||
|
/// spawning the process, then its error is returned.
|
||||||
|
///
|
||||||
|
/// If the given file path could not be matched with a decompression
|
||||||
|
/// strategy, then a passthru reader is returned that does no
|
||||||
|
/// decompression.
|
||||||
|
///
|
||||||
|
/// This uses the default matching rules for determining how to decompress
|
||||||
|
/// the given file. To change those matching rules, use
|
||||||
|
/// [`DecompressionReaderBuilder`](struct.DecompressionReaderBuilder.html)
|
||||||
|
/// and
|
||||||
|
/// [`DecompressionMatcherBuilder`](struct.DecompressionMatcherBuilder.html).
|
||||||
|
///
|
||||||
|
/// When creating readers for many paths. it is better to use the builder
|
||||||
|
/// since it will amortize the cost of constructing the matcher.
|
||||||
|
pub fn new<P: AsRef<Path>>(
|
||||||
|
path: P,
|
||||||
|
) -> Result<DecompressionReader, CommandError> {
|
||||||
|
DecompressionReaderBuilder::new().build(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new "passthru" decompression reader that reads from the file
|
||||||
|
/// corresponding to the given path without doing decompression and without
|
||||||
|
/// executing another process.
|
||||||
|
fn new_passthru(path: &Path) -> Result<DecompressionReader, CommandError> {
|
||||||
|
let file = File::open(path)?;
|
||||||
|
Ok(DecompressionReader { rdr: Err(file) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Read for DecompressionReader {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match self.rdr {
|
||||||
|
Ok(ref mut rdr) => rdr.read(buf),
|
||||||
|
Err(ref mut rdr) => rdr.read(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_decompression_commands() -> Vec<DecompressionCommand> {
|
||||||
|
const ARGS_GZIP: &[&str] = &["gzip", "-d", "-c"];
|
||||||
|
const ARGS_BZIP: &[&str] = &["bzip2", "-d", "-c"];
|
||||||
|
const ARGS_XZ: &[&str] = &["xz", "-d", "-c"];
|
||||||
|
const ARGS_LZ4: &[&str] = &["lz4", "-d", "-c"];
|
||||||
|
const ARGS_LZMA: &[&str] = &["xz", "--format=lzma", "-d", "-c"];
|
||||||
|
const ARGS_BROTLI: &[&str] = &["brotli", "-d", "-c"];
|
||||||
|
const ARGS_ZSTD: &[&str] = &["zstd", "-q", "-d", "-c"];
|
||||||
|
|
||||||
|
fn cmd(glob: &str, args: &[&str]) -> DecompressionCommand {
|
||||||
|
DecompressionCommand {
|
||||||
|
glob: glob.to_string(),
|
||||||
|
bin: OsStr::new(&args[0]).to_os_string(),
|
||||||
|
args: args
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.map(|s| OsStr::new(s).to_os_string())
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vec![
|
||||||
|
cmd("*.gz", ARGS_GZIP),
|
||||||
|
cmd("*.tgz", ARGS_GZIP),
|
||||||
|
cmd("*.bz2", ARGS_BZIP),
|
||||||
|
cmd("*.tbz2", ARGS_BZIP),
|
||||||
|
cmd("*.xz", ARGS_XZ),
|
||||||
|
cmd("*.txz", ARGS_XZ),
|
||||||
|
cmd("*.lz4", ARGS_LZ4),
|
||||||
|
cmd("*.lzma", ARGS_LZMA),
|
||||||
|
cmd("*.br", ARGS_BROTLI),
|
||||||
|
cmd("*.zst", ARGS_ZSTD),
|
||||||
|
cmd("*.zstd", ARGS_ZSTD),
|
||||||
|
]
|
||||||
|
}
|
315
grep-cli/src/escape.rs
Normal file
315
grep-cli/src/escape.rs
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
/// A single state in the state machine used by `unescape`.
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
|
enum State {
|
||||||
|
/// The state after seeing a `\`.
|
||||||
|
Escape,
|
||||||
|
/// The state after seeing a `\x`.
|
||||||
|
HexFirst,
|
||||||
|
/// The state after seeing a `\x[0-9A-Fa-f]`.
|
||||||
|
HexSecond(char),
|
||||||
|
/// Default state.
|
||||||
|
Literal,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Escapes arbitrary bytes into a human readable string.
|
||||||
|
///
|
||||||
|
/// This converts `\t`, `\r` and `\n` into their escaped forms. It also
|
||||||
|
/// converts the non-printable subset of ASCII in addition to invalid UTF-8
|
||||||
|
/// bytes to hexadecimal escape sequences. Everything else is left as is.
|
||||||
|
///
|
||||||
|
/// The dual of this routine is [`unescape`](fn.unescape.html).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This example shows how to convert a byte string that contains a `\n` and
|
||||||
|
/// invalid UTF-8 bytes into a `String`.
|
||||||
|
///
|
||||||
|
/// Pay special attention to the use of raw strings. That is, `r"\n"` is
|
||||||
|
/// equivalent to `"\\n"`.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use grep_cli::escape;
|
||||||
|
///
|
||||||
|
/// assert_eq!(r"foo\nbar\xFFbaz", escape(b"foo\nbar\xFFbaz"));
|
||||||
|
/// ```
|
||||||
|
pub fn escape(mut bytes: &[u8]) -> String {
|
||||||
|
let mut escaped = String::new();
|
||||||
|
while let Some(result) = decode_utf8(bytes) {
|
||||||
|
match result {
|
||||||
|
Ok(cp) => {
|
||||||
|
escape_char(cp, &mut escaped);
|
||||||
|
bytes = &bytes[cp.len_utf8()..];
|
||||||
|
}
|
||||||
|
Err(byte) => {
|
||||||
|
escape_byte(byte, &mut escaped);
|
||||||
|
bytes = &bytes[1..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
escaped
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Escapes an OS string into a human readable string.
|
||||||
|
///
|
||||||
|
/// This is like [`escape`](fn.escape.html), but accepts an OS string.
|
||||||
|
pub fn escape_os(string: &OsStr) -> String {
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn imp(string: &OsStr) -> String {
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
|
escape(string.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn imp(string: &OsStr) -> String {
|
||||||
|
escape(string.to_string_lossy().as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
imp(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unescapes a string.
|
||||||
|
///
|
||||||
|
/// It supports a limited set of escape sequences:
|
||||||
|
///
|
||||||
|
/// * `\t`, `\r` and `\n` are mapped to their corresponding ASCII bytes.
|
||||||
|
/// * `\xZZ` hexadecimal escapes are mapped to their byte.
|
||||||
|
///
|
||||||
|
/// Everything else is left as is, including non-hexadecimal escapes like
|
||||||
|
/// `\xGG`.
|
||||||
|
///
|
||||||
|
/// This is useful when it is desirable for a command line argument to be
|
||||||
|
/// capable of specifying arbitrary bytes or otherwise make it easier to
|
||||||
|
/// specify non-printable characters.
|
||||||
|
///
|
||||||
|
/// The dual of this routine is [`escape`](fn.escape.html).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This example shows how to convert an escaped string (which is valid UTF-8)
|
||||||
|
/// into a corresponding sequence of bytes. Each escape sequence is mapped to
|
||||||
|
/// its bytes, which may include invalid UTF-8.
|
||||||
|
///
|
||||||
|
/// Pay special attention to the use of raw strings. That is, `r"\n"` is
|
||||||
|
/// equivalent to `"\\n"`.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use grep_cli::unescape;
|
||||||
|
///
|
||||||
|
/// assert_eq!(&b"foo\nbar\xFFbaz"[..], &*unescape(r"foo\nbar\xFFbaz"));
|
||||||
|
/// ```
|
||||||
|
pub fn unescape(s: &str) -> Vec<u8> {
|
||||||
|
use self::State::*;
|
||||||
|
|
||||||
|
let mut bytes = vec![];
|
||||||
|
let mut state = Literal;
|
||||||
|
for c in s.chars() {
|
||||||
|
match state {
|
||||||
|
Escape => {
|
||||||
|
match c {
|
||||||
|
'\\' => { bytes.push(b'\\'); state = Literal; }
|
||||||
|
'n' => { bytes.push(b'\n'); state = Literal; }
|
||||||
|
'r' => { bytes.push(b'\r'); state = Literal; }
|
||||||
|
't' => { bytes.push(b'\t'); state = Literal; }
|
||||||
|
'x' => { state = HexFirst; }
|
||||||
|
c => {
|
||||||
|
bytes.extend(format!(r"\{}", c).into_bytes());
|
||||||
|
state = Literal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HexFirst => {
|
||||||
|
match c {
|
||||||
|
'0'...'9' | 'A'...'F' | 'a'...'f' => {
|
||||||
|
state = HexSecond(c);
|
||||||
|
}
|
||||||
|
c => {
|
||||||
|
bytes.extend(format!(r"\x{}", c).into_bytes());
|
||||||
|
state = Literal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HexSecond(first) => {
|
||||||
|
match c {
|
||||||
|
'0'...'9' | 'A'...'F' | 'a'...'f' => {
|
||||||
|
let ordinal = format!("{}{}", first, c);
|
||||||
|
let byte = u8::from_str_radix(&ordinal, 16).unwrap();
|
||||||
|
bytes.push(byte);
|
||||||
|
state = Literal;
|
||||||
|
}
|
||||||
|
c => {
|
||||||
|
let original = format!(r"\x{}{}", first, c);
|
||||||
|
bytes.extend(original.into_bytes());
|
||||||
|
state = Literal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Literal => {
|
||||||
|
match c {
|
||||||
|
'\\' => { state = Escape; }
|
||||||
|
c => { bytes.extend(c.to_string().as_bytes()); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match state {
|
||||||
|
Escape => bytes.push(b'\\'),
|
||||||
|
HexFirst => bytes.extend(b"\\x"),
|
||||||
|
HexSecond(c) => bytes.extend(format!("\\x{}", c).into_bytes()),
|
||||||
|
Literal => {}
|
||||||
|
}
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unescapes an OS string.
|
||||||
|
///
|
||||||
|
/// This is like [`unescape`](fn.unescape.html), but accepts an OS string.
|
||||||
|
///
|
||||||
|
/// Note that this first lossily decodes the given OS string as UTF-8. That
|
||||||
|
/// is, an escaped string (the thing given) should be valid UTF-8.
|
||||||
|
pub fn unescape_os(string: &OsStr) -> Vec<u8> {
|
||||||
|
unescape(&string.to_string_lossy())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the given codepoint to the given string, escaping it if necessary.
|
||||||
|
fn escape_char(cp: char, into: &mut String) {
|
||||||
|
if cp.is_ascii() {
|
||||||
|
escape_byte(cp as u8, into);
|
||||||
|
} else {
|
||||||
|
into.push(cp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the given byte to the given string, escaping it if necessary.
|
||||||
|
fn escape_byte(byte: u8, into: &mut String) {
|
||||||
|
match byte {
|
||||||
|
0x21...0x5B | 0x5D...0x7D => into.push(byte as char),
|
||||||
|
b'\n' => into.push_str(r"\n"),
|
||||||
|
b'\r' => into.push_str(r"\r"),
|
||||||
|
b'\t' => into.push_str(r"\t"),
|
||||||
|
b'\\' => into.push_str(r"\\"),
|
||||||
|
_ => into.push_str(&format!(r"\x{:02X}", byte)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes the next UTF-8 encoded codepoint from the given byte slice.
|
||||||
|
///
|
||||||
|
/// If no valid encoding of a codepoint exists at the beginning of the given
|
||||||
|
/// byte slice, then the first byte is returned instead.
|
||||||
|
///
|
||||||
|
/// This returns `None` if and only if `bytes` is empty.
|
||||||
|
fn decode_utf8(bytes: &[u8]) -> Option<Result<char, u8>> {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let len = match utf8_len(bytes[0]) {
|
||||||
|
None => return Some(Err(bytes[0])),
|
||||||
|
Some(len) if len > bytes.len() => return Some(Err(bytes[0])),
|
||||||
|
Some(len) => len,
|
||||||
|
};
|
||||||
|
match str::from_utf8(&bytes[..len]) {
|
||||||
|
Ok(s) => Some(Ok(s.chars().next().unwrap())),
|
||||||
|
Err(_) => Some(Err(bytes[0])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a UTF-8 leading byte, this returns the total number of code units
|
||||||
|
/// in the following encoded codepoint.
|
||||||
|
///
|
||||||
|
/// If the given byte is not a valid UTF-8 leading byte, then this returns
|
||||||
|
/// `None`.
|
||||||
|
fn utf8_len(byte: u8) -> Option<usize> {
|
||||||
|
if byte <= 0x7F {
|
||||||
|
Some(1)
|
||||||
|
} else if byte <= 0b110_11111 {
|
||||||
|
Some(2)
|
||||||
|
} else if byte <= 0b1110_1111 {
|
||||||
|
Some(3)
|
||||||
|
} else if byte <= 0b1111_0111 {
|
||||||
|
Some(4)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{escape, unescape};
|
||||||
|
|
||||||
|
fn b(bytes: &'static [u8]) -> Vec<u8> {
|
||||||
|
bytes.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty() {
|
||||||
|
assert_eq!(b(b""), unescape(r""));
|
||||||
|
assert_eq!(r"", escape(b""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn backslash() {
|
||||||
|
assert_eq!(b(b"\\"), unescape(r"\\"));
|
||||||
|
assert_eq!(r"\\", escape(b"\\"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nul() {
|
||||||
|
assert_eq!(b(b"\x00"), unescape(r"\x00"));
|
||||||
|
assert_eq!(r"\x00", escape(b"\x00"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nl() {
|
||||||
|
assert_eq!(b(b"\n"), unescape(r"\n"));
|
||||||
|
assert_eq!(r"\n", escape(b"\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tab() {
|
||||||
|
assert_eq!(b(b"\t"), unescape(r"\t"));
|
||||||
|
assert_eq!(r"\t", escape(b"\t"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn carriage() {
|
||||||
|
assert_eq!(b(b"\r"), unescape(r"\r"));
|
||||||
|
assert_eq!(r"\r", escape(b"\r"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nothing_simple() {
|
||||||
|
assert_eq!(b(b"\\a"), unescape(r"\a"));
|
||||||
|
assert_eq!(b(b"\\a"), unescape(r"\\a"));
|
||||||
|
assert_eq!(r"\\a", escape(b"\\a"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nothing_hex0() {
|
||||||
|
assert_eq!(b(b"\\x"), unescape(r"\x"));
|
||||||
|
assert_eq!(b(b"\\x"), unescape(r"\\x"));
|
||||||
|
assert_eq!(r"\\x", escape(b"\\x"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nothing_hex1() {
|
||||||
|
assert_eq!(b(b"\\xz"), unescape(r"\xz"));
|
||||||
|
assert_eq!(b(b"\\xz"), unescape(r"\\xz"));
|
||||||
|
assert_eq!(r"\\xz", escape(b"\\xz"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nothing_hex2() {
|
||||||
|
assert_eq!(b(b"\\xzz"), unescape(r"\xzz"));
|
||||||
|
assert_eq!(b(b"\\xzz"), unescape(r"\\xzz"));
|
||||||
|
assert_eq!(r"\\xzz", escape(b"\\xzz"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_utf8() {
|
||||||
|
assert_eq!(r"\xFF", escape(b"\xFF"));
|
||||||
|
assert_eq!(r"a\xFFb", escape(b"a\xFFb"));
|
||||||
|
}
|
||||||
|
}
|
171
grep-cli/src/human.rs
Normal file
171
grep-cli/src/human.rs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
/// An error that occurs when parsing a human readable size description.
|
||||||
|
///
|
||||||
|
/// This error provides a end user friendly message describing why the
|
||||||
|
/// description coudln't be parsed and what the expected format is.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct ParseSizeError {
|
||||||
|
original: String,
|
||||||
|
kind: ParseSizeErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
enum ParseSizeErrorKind {
|
||||||
|
InvalidFormat,
|
||||||
|
InvalidInt(ParseIntError),
|
||||||
|
Overflow,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParseSizeError {
|
||||||
|
fn format(original: &str) -> ParseSizeError {
|
||||||
|
ParseSizeError {
|
||||||
|
original: original.to_string(),
|
||||||
|
kind: ParseSizeErrorKind::InvalidFormat,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn int(original: &str, err: ParseIntError) -> ParseSizeError {
|
||||||
|
ParseSizeError {
|
||||||
|
original: original.to_string(),
|
||||||
|
kind: ParseSizeErrorKind::InvalidInt(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overflow(original: &str) -> ParseSizeError {
|
||||||
|
ParseSizeError {
|
||||||
|
original: original.to_string(),
|
||||||
|
kind: ParseSizeErrorKind::Overflow,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for ParseSizeError {
|
||||||
|
fn description(&self) -> &str { "invalid size" }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ParseSizeError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::ParseSizeErrorKind::*;
|
||||||
|
|
||||||
|
match self.kind {
|
||||||
|
InvalidFormat => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"invalid format for size '{}', which should be a sequence \
|
||||||
|
of digits followed by an optional 'K', 'M' or 'G' \
|
||||||
|
suffix",
|
||||||
|
self.original
|
||||||
|
)
|
||||||
|
}
|
||||||
|
InvalidInt(ref err) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"invalid integer found in size '{}': {}",
|
||||||
|
self.original,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Overflow => {
|
||||||
|
write!(f, "size too big in '{}'", self.original)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseSizeError> for io::Error {
|
||||||
|
fn from(size_err: ParseSizeError) -> io::Error {
|
||||||
|
io::Error::new(io::ErrorKind::Other, size_err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a human readable size like `2M` into a corresponding number of bytes.
|
||||||
|
///
|
||||||
|
/// Supported size suffixes are `K` (for kilobyte), `M` (for megabyte) and `G`
|
||||||
|
/// (for gigabyte). If a size suffix is missing, then the size is interpreted
|
||||||
|
/// as bytes. If the size is too big to fit into a `u64`, then this returns an
|
||||||
|
/// error.
|
||||||
|
///
|
||||||
|
/// Additional suffixes may be added over time.
|
||||||
|
pub fn parse_human_readable_size(size: &str) -> Result<u64, ParseSizeError> {
|
||||||
|
lazy_static! {
|
||||||
|
// Normally I'd just parse something this simple by hand to avoid the
|
||||||
|
// regex dep, but we bring regex in any way for glob matching, so might
|
||||||
|
// as well use it.
|
||||||
|
static ref RE: Regex = Regex::new(r"^([0-9]+)([KMG])?$").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let caps = match RE.captures(size) {
|
||||||
|
Some(caps) => caps,
|
||||||
|
None => return Err(ParseSizeError::format(size)),
|
||||||
|
};
|
||||||
|
let value: u64 = caps[1].parse().map_err(|err| {
|
||||||
|
ParseSizeError::int(size, err)
|
||||||
|
})?;
|
||||||
|
let suffix = match caps.get(2) {
|
||||||
|
None => return Ok(value),
|
||||||
|
Some(cap) => cap.as_str(),
|
||||||
|
};
|
||||||
|
let bytes = match suffix {
|
||||||
|
"K" => value.checked_mul(1<<10),
|
||||||
|
"M" => value.checked_mul(1<<20),
|
||||||
|
"G" => value.checked_mul(1<<30),
|
||||||
|
// Because if the regex matches this group, it must be [KMG].
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
bytes.ok_or_else(|| ParseSizeError::overflow(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn suffix_none() {
|
||||||
|
let x = parse_human_readable_size("123").unwrap();
|
||||||
|
assert_eq!(123, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn suffix_k() {
|
||||||
|
let x = parse_human_readable_size("123K").unwrap();
|
||||||
|
assert_eq!(123 * (1<<10), x);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn suffix_m() {
|
||||||
|
let x = parse_human_readable_size("123M").unwrap();
|
||||||
|
assert_eq!(123 * (1<<20), x);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn suffix_g() {
|
||||||
|
let x = parse_human_readable_size("123G").unwrap();
|
||||||
|
assert_eq!(123 * (1<<30), x);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_empty() {
|
||||||
|
assert!(parse_human_readable_size("").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_non_digit() {
|
||||||
|
assert!(parse_human_readable_size("a").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_overflow() {
|
||||||
|
assert!(parse_human_readable_size("9999999999999999G").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_suffix() {
|
||||||
|
assert!(parse_human_readable_size("123T").is_err());
|
||||||
|
}
|
||||||
|
}
|
251
grep-cli/src/lib.rs
Normal file
251
grep-cli/src/lib.rs
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
/*!
|
||||||
|
This crate provides common routines used in command line applications, with a
|
||||||
|
focus on routines useful for search oriented applications. As a utility
|
||||||
|
library, there is no central type or function. However, a key focus of this
|
||||||
|
crate is to improve failure modes and provide user friendly error messages
|
||||||
|
when things go wrong.
|
||||||
|
|
||||||
|
To the best extent possible, everything in this crate works on Windows, macOS
|
||||||
|
and Linux.
|
||||||
|
|
||||||
|
|
||||||
|
# Standard I/O
|
||||||
|
|
||||||
|
The
|
||||||
|
[`is_readable_stdin`](fn.is_readable_stdin.html),
|
||||||
|
[`is_tty_stderr`](fn.is_tty_stderr.html),
|
||||||
|
[`is_tty_stdin`](fn.is_tty_stdin.html)
|
||||||
|
and
|
||||||
|
[`is_tty_stdout`](fn.is_tty_stdout.html)
|
||||||
|
routines query aspects of standard I/O. `is_readable_stdin` determines whether
|
||||||
|
stdin can be usefully read from, while the `tty` methods determine whether a
|
||||||
|
tty is attached to stdin/stdout/stderr.
|
||||||
|
|
||||||
|
`is_readable_stdin` is useful when writing an application that changes behavior
|
||||||
|
based on whether the application was invoked with data on stdin. For example,
|
||||||
|
`rg foo` might recursively search the current working directory for
|
||||||
|
occurrences of `foo`, but `rg foo < file` might only search the contents of
|
||||||
|
`file`.
|
||||||
|
|
||||||
|
The `tty` methods are useful for similar reasons. Namely, commands like `ls`
|
||||||
|
will change their output depending on whether they are printing to a terminal
|
||||||
|
or not. For example, `ls` shows a file on each line when stdout is redirected
|
||||||
|
to a file or a pipe, but condenses the output to show possibly many files on
|
||||||
|
each line when stdout is connected to a tty.
|
||||||
|
|
||||||
|
|
||||||
|
# Coloring and buffering
|
||||||
|
|
||||||
|
The
|
||||||
|
[`stdout`](fn.stdout.html),
|
||||||
|
[`stdout_buffered_block`](fn.stdout_buffered_block.html)
|
||||||
|
and
|
||||||
|
[`stdout_buffered_line`](fn.stdout_buffered_line.html)
|
||||||
|
routines are alternative constructors for
|
||||||
|
[`StandardStream`](struct.StandardStream.html).
|
||||||
|
A `StandardStream` implements `termcolor::WriteColor`, which provides a way
|
||||||
|
to emit colors to terminals. Its key use is the encapsulation of buffering
|
||||||
|
style. Namely, `stdout` will return a line buffered `StandardStream` if and
|
||||||
|
only if stdout is connected to a tty, and will otherwise return a block
|
||||||
|
buffered `StandardStream`. Line buffering is important for use with a tty
|
||||||
|
because it typically decreases the latency at which the end user sees output.
|
||||||
|
Block buffering is used otherwise because it is faster, and redirecting stdout
|
||||||
|
to a file typically doesn't benefit from the decreased latency that line
|
||||||
|
buffering provides.
|
||||||
|
|
||||||
|
The `stdout_buffered_block` and `stdout_buffered_line` can be used to
|
||||||
|
explicitly set the buffering strategy regardless of whether stdout is connected
|
||||||
|
to a tty or not.
|
||||||
|
|
||||||
|
|
||||||
|
# Escaping
|
||||||
|
|
||||||
|
The
|
||||||
|
[`escape`](fn.escape.html),
|
||||||
|
[`escape_os`](fn.escape_os.html),
|
||||||
|
[`unescape`](fn.unescape.html)
|
||||||
|
and
|
||||||
|
[`unescape_os`](fn.unescape_os.html)
|
||||||
|
routines provide a user friendly way of dealing with UTF-8 encoded strings that
|
||||||
|
can express arbitrary bytes. For example, you might want to accept a string
|
||||||
|
containing arbitrary bytes as a command line argument, but most interactive
|
||||||
|
shells make such strings difficult to type. Instead, we can ask users to use
|
||||||
|
escape sequences.
|
||||||
|
|
||||||
|
For example, `a\xFFz` is itself a valid UTF-8 string corresponding to the
|
||||||
|
following bytes:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
[b'a', b'\\', b'x', b'F', b'F', b'z']
|
||||||
|
```
|
||||||
|
|
||||||
|
However, we can
|
||||||
|
interpret `\xFF` as an escape sequence with the `unescape`/`unescape_os`
|
||||||
|
routines, which will yield
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
[b'a', b'\xFF', b'z']
|
||||||
|
```
|
||||||
|
|
||||||
|
instead. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
use grep_cli::unescape;
|
||||||
|
|
||||||
|
// Note the use of a raw string!
|
||||||
|
assert_eq!(vec![b'a', b'\xFF', b'z'], unescape(r"a\xFFz"));
|
||||||
|
```
|
||||||
|
|
||||||
|
The `escape`/`escape_os` routines provide the reverse transformation, which
|
||||||
|
makes it easy to show user friendly error messages involving arbitrary bytes.
|
||||||
|
|
||||||
|
|
||||||
|
# Building patterns
|
||||||
|
|
||||||
|
Typically, regular expression patterns must be valid UTF-8. However, command
|
||||||
|
line arguments aren't guaranteed to be valid UTF-8. Unfortunately, the
|
||||||
|
standard library's UTF-8 conversion functions from `OsStr`s do not provide
|
||||||
|
good error messages. However, the
|
||||||
|
[`pattern_from_bytes`](fn.pattern_from_bytes.html)
|
||||||
|
and
|
||||||
|
[`pattern_from_os`](fn.pattern_from_os.html)
|
||||||
|
do, including reporting exactly where the first invalid UTF-8 byte is seen.
|
||||||
|
|
||||||
|
Additionally, it can be useful to read patterns from a file while reporting
|
||||||
|
good error messages that include line numbers. The
|
||||||
|
[`patterns_from_path`](fn.patterns_from_path.html),
|
||||||
|
[`patterns_from_reader`](fn.patterns_from_reader.html)
|
||||||
|
and
|
||||||
|
[`patterns_from_stdin`](fn.patterns_from_stdin.html)
|
||||||
|
routines do just that. If any pattern is found that is invalid UTF-8, then the
|
||||||
|
error includes the file path (if available) along with the line number and the
|
||||||
|
byte offset at which the first invalid UTF-8 byte was observed.
|
||||||
|
|
||||||
|
|
||||||
|
# Read process output
|
||||||
|
|
||||||
|
Sometimes a command line application needs to execute other processes and read
|
||||||
|
its stdout in a streaming fashion. The
|
||||||
|
[`CommandReader`](struct.CommandReader.html)
|
||||||
|
provides this functionality with an explicit goal of improving failure modes.
|
||||||
|
In particular, if the process exits with an error code, then stderr is read
|
||||||
|
and converted into a normal Rust error to show to end users. This makes the
|
||||||
|
underlying failure modes explicit and gives more information to end users for
|
||||||
|
debugging the problem.
|
||||||
|
|
||||||
|
As a special case,
|
||||||
|
[`DecompressionReader`](struct.DecompressionReader.html)
|
||||||
|
provides a way to decompress arbitrary files by matching their file extensions
|
||||||
|
up with corresponding decompression programs (such as `gzip` and `xz`). This
|
||||||
|
is useful as a means of performing simplistic decompression in a portable
|
||||||
|
manner without binding to specific compression libraries. This does come with
|
||||||
|
some overhead though, so if you need to decompress lots of small files, this
|
||||||
|
may not be an appropriate convenience to use.
|
||||||
|
|
||||||
|
Each reader has a corresponding builder for additional configuration, such as
|
||||||
|
whether to read stderr asynchronously in order to avoid deadlock (which is
|
||||||
|
enabled by default).
|
||||||
|
|
||||||
|
|
||||||
|
# Miscellaneous parsing
|
||||||
|
|
||||||
|
The
|
||||||
|
[`parse_human_readable_size`](fn.parse_human_readable_size.html)
|
||||||
|
routine parses strings like `2M` and converts them to the corresponding number
|
||||||
|
of bytes (`2 * 1<<20` in this case). If an invalid size is found, then a good
|
||||||
|
error message is crafted that typically tells the user how to fix the problem.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
extern crate atty;
|
||||||
|
extern crate globset;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
extern crate regex;
|
||||||
|
extern crate same_file;
|
||||||
|
extern crate termcolor;
|
||||||
|
#[cfg(windows)]
|
||||||
|
extern crate winapi_util;
|
||||||
|
|
||||||
|
mod decompress;
|
||||||
|
mod escape;
|
||||||
|
mod human;
|
||||||
|
mod pattern;
|
||||||
|
mod process;
|
||||||
|
mod wtr;
|
||||||
|
|
||||||
|
pub use decompress::{
|
||||||
|
DecompressionMatcher, DecompressionMatcherBuilder,
|
||||||
|
DecompressionReader, DecompressionReaderBuilder,
|
||||||
|
};
|
||||||
|
pub use escape::{escape, escape_os, unescape, unescape_os};
|
||||||
|
pub use human::{ParseSizeError, parse_human_readable_size};
|
||||||
|
pub use pattern::{
|
||||||
|
InvalidPatternError,
|
||||||
|
pattern_from_os, pattern_from_bytes,
|
||||||
|
patterns_from_path, patterns_from_reader, patterns_from_stdin,
|
||||||
|
};
|
||||||
|
pub use process::{CommandError, CommandReader, CommandReaderBuilder};
|
||||||
|
pub use wtr::{
|
||||||
|
StandardStream,
|
||||||
|
stdout, stdout_buffered_line, stdout_buffered_block,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Returns true if and only if stdin is believed to be readable.
|
||||||
|
///
|
||||||
|
/// When stdin is readable, command line programs may choose to behave
|
||||||
|
/// differently than when stdin is not readable. For example, `command foo`
|
||||||
|
/// might search the current directory for occurrences of `foo` where as
|
||||||
|
/// `command foo < some-file` or `cat some-file | command foo` might instead
|
||||||
|
/// only search stdin for occurrences of `foo`.
|
||||||
|
pub fn is_readable_stdin() -> bool {
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn imp() -> bool {
|
||||||
|
use std::os::unix::fs::FileTypeExt;
|
||||||
|
use same_file::Handle;
|
||||||
|
|
||||||
|
let ft = match Handle::stdin().and_then(|h| h.as_file().metadata()) {
|
||||||
|
Err(_) => return false,
|
||||||
|
Ok(md) => md.file_type(),
|
||||||
|
};
|
||||||
|
ft.is_file() || ft.is_fifo()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn imp() -> bool {
|
||||||
|
use winapi_util as winutil;
|
||||||
|
|
||||||
|
winutil::file::typ(winutil::HandleRef::stdin())
|
||||||
|
.map(|t| t.is_disk() || t.is_pipe())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
!is_tty_stdin() && imp()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if stdin is believed to be connectted to a tty
|
||||||
|
/// or a console.
|
||||||
|
pub fn is_tty_stdin() -> bool {
|
||||||
|
atty::is(atty::Stream::Stdin)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if stdout is believed to be connectted to a tty
|
||||||
|
/// or a console.
|
||||||
|
///
|
||||||
|
/// This is useful for when you want your command line program to produce
|
||||||
|
/// different output depending on whether it's printing directly to a user's
|
||||||
|
/// terminal or whether it's being redirected somewhere else. For example,
|
||||||
|
/// implementations of `ls` will often show one item per line when stdout is
|
||||||
|
/// redirected, but will condensed output when printing to a tty.
|
||||||
|
pub fn is_tty_stdout() -> bool {
|
||||||
|
atty::is(atty::Stream::Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if stderr is believed to be connectted to a tty
|
||||||
|
/// or a console.
|
||||||
|
pub fn is_tty_stderr() -> bool {
|
||||||
|
atty::is(atty::Stream::Stderr)
|
||||||
|
}
|
205
grep-cli/src/pattern.rs
Normal file
205
grep-cli/src/pattern.rs
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
use std::error;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fmt;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, BufRead};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
use escape::{escape, escape_os};
|
||||||
|
|
||||||
|
/// An error that occurs when a pattern could not be converted to valid UTF-8.
|
||||||
|
///
|
||||||
|
/// The purpose of this error is to give a more targeted failure mode for
|
||||||
|
/// patterns written by end users that are not valid UTF-8.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct InvalidPatternError {
|
||||||
|
original: String,
|
||||||
|
valid_up_to: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InvalidPatternError {
|
||||||
|
/// Returns the index in the given string up to which valid UTF-8 was
|
||||||
|
/// verified.
|
||||||
|
pub fn valid_up_to(&self) -> usize {
|
||||||
|
self.valid_up_to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for InvalidPatternError {
|
||||||
|
fn description(&self) -> &str { "invalid pattern" }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for InvalidPatternError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"found invalid UTF-8 in pattern at byte offset {} \
|
||||||
|
(use hex escape sequences to match arbitrary bytes \
|
||||||
|
in a pattern, e.g., \\xFF): '{}'",
|
||||||
|
self.valid_up_to,
|
||||||
|
self.original,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InvalidPatternError> for io::Error {
|
||||||
|
fn from(paterr: InvalidPatternError) -> io::Error {
|
||||||
|
io::Error::new(io::ErrorKind::Other, paterr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert an OS string into a regular expression pattern.
|
||||||
|
///
|
||||||
|
/// This conversion fails if the given pattern is not valid UTF-8, in which
|
||||||
|
/// case, a targeted error with more information about where the invalid UTF-8
|
||||||
|
/// occurs is given. The error also suggests the use of hex escape sequences,
|
||||||
|
/// which are supported by many regex engines.
|
||||||
|
pub fn pattern_from_os(pattern: &OsStr) -> Result<&str, InvalidPatternError> {
|
||||||
|
pattern.to_str().ok_or_else(|| {
|
||||||
|
let valid_up_to = pattern
|
||||||
|
.to_string_lossy()
|
||||||
|
.find('\u{FFFD}')
|
||||||
|
.expect("a Unicode replacement codepoint for invalid UTF-8");
|
||||||
|
InvalidPatternError {
|
||||||
|
original: escape_os(pattern),
|
||||||
|
valid_up_to: valid_up_to,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert arbitrary bytes into a regular expression pattern.
|
||||||
|
///
|
||||||
|
/// This conversion fails if the given pattern is not valid UTF-8, in which
|
||||||
|
/// case, a targeted error with more information about where the invalid UTF-8
|
||||||
|
/// occurs is given. The error also suggests the use of hex escape sequences,
|
||||||
|
/// which are supported by many regex engines.
|
||||||
|
pub fn pattern_from_bytes(
|
||||||
|
pattern: &[u8],
|
||||||
|
) -> Result<&str, InvalidPatternError> {
|
||||||
|
str::from_utf8(pattern).map_err(|err| {
|
||||||
|
InvalidPatternError {
|
||||||
|
original: escape(pattern),
|
||||||
|
valid_up_to: err.valid_up_to(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read patterns from a file path, one per line.
|
||||||
|
///
|
||||||
|
/// If there was a problem reading or if any of the patterns contain invalid
|
||||||
|
/// UTF-8, then an error is returned. If there was a problem with a specific
|
||||||
|
/// pattern, then the error message will include the line number and the file
|
||||||
|
/// path.
|
||||||
|
pub fn patterns_from_path<P: AsRef<Path>>(path: P) -> io::Result<Vec<String>> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let file = File::open(path).map_err(|err| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("{}: {}", path.display(), err),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
patterns_from_reader(file).map_err(|err| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("{}:{}", path.display(), err),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read patterns from stdin, one per line.
|
||||||
|
///
|
||||||
|
/// If there was a problem reading or if any of the patterns contain invalid
|
||||||
|
/// UTF-8, then an error is returned. If there was a problem with a specific
|
||||||
|
/// pattern, then the error message will include the line number and the fact
|
||||||
|
/// that it came from stdin.
|
||||||
|
pub fn patterns_from_stdin() -> io::Result<Vec<String>> {
|
||||||
|
let stdin = io::stdin();
|
||||||
|
let locked = stdin.lock();
|
||||||
|
patterns_from_reader(locked).map_err(|err| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("<stdin>:{}", err),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read patterns from any reader, one per line.
|
||||||
|
///
|
||||||
|
/// If there was a problem reading or if any of the patterns contain invalid
|
||||||
|
/// UTF-8, then an error is returned. If there was a problem with a specific
|
||||||
|
/// pattern, then the error message will include the line number.
|
||||||
|
///
|
||||||
|
/// Note that this routine uses its own internal buffer, so the caller should
|
||||||
|
/// not provide their own buffered reader if possible.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This shows how to parse patterns, one per line.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use grep_cli::patterns_from_reader;
|
||||||
|
///
|
||||||
|
/// # fn example() -> Result<(), Box<::std::error::Error>> {
|
||||||
|
/// let patterns = "\
|
||||||
|
/// foo
|
||||||
|
/// bar\\s+foo
|
||||||
|
/// [a-z]{3}
|
||||||
|
/// ";
|
||||||
|
///
|
||||||
|
/// assert_eq!(patterns_from_reader(patterns.as_bytes())?, vec![
|
||||||
|
/// r"foo",
|
||||||
|
/// r"bar\s+foo",
|
||||||
|
/// r"[a-z]{3}",
|
||||||
|
/// ]);
|
||||||
|
/// # Ok(()) }
|
||||||
|
/// ```
|
||||||
|
pub fn patterns_from_reader<R: io::Read>(rdr: R) -> io::Result<Vec<String>> {
|
||||||
|
let mut patterns = vec![];
|
||||||
|
let mut bufrdr = io::BufReader::new(rdr);
|
||||||
|
let mut line = vec![];
|
||||||
|
let mut line_number = 0;
|
||||||
|
while {
|
||||||
|
line.clear();
|
||||||
|
line_number += 1;
|
||||||
|
bufrdr.read_until(b'\n', &mut line)? > 0
|
||||||
|
} {
|
||||||
|
line.pop().unwrap(); // remove trailing '\n'
|
||||||
|
if line.last() == Some(&b'\r') {
|
||||||
|
line.pop().unwrap();
|
||||||
|
}
|
||||||
|
match pattern_from_bytes(&line) {
|
||||||
|
Ok(pattern) => patterns.push(pattern.to_string()),
|
||||||
|
Err(err) => {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("{}: {}", line_number, err),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(patterns)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes() {
|
||||||
|
let pat = b"abc\xFFxyz";
|
||||||
|
let err = pattern_from_bytes(pat).unwrap_err();
|
||||||
|
assert_eq!(3, err.valid_up_to());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn os() {
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
|
let pat = OsStr::from_bytes(b"abc\xFFxyz");
|
||||||
|
let err = pattern_from_os(pat).unwrap_err();
|
||||||
|
assert_eq!(3, err.valid_up_to());
|
||||||
|
}
|
||||||
|
}
|
267
grep-cli/src/process.rs
Normal file
267
grep-cli/src/process.rs
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
use std::iter;
|
||||||
|
use std::process;
|
||||||
|
use std::thread::{self, JoinHandle};
|
||||||
|
|
||||||
|
/// An error that can occur while running a command and reading its output.
|
||||||
|
///
|
||||||
|
/// This error can be seamlessly converted to an `io::Error` via a `From`
|
||||||
|
/// implementation.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CommandError {
|
||||||
|
kind: CommandErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum CommandErrorKind {
|
||||||
|
Io(io::Error),
|
||||||
|
Stderr(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandError {
|
||||||
|
/// Create an error from an I/O error.
|
||||||
|
pub(crate) fn io(ioerr: io::Error) -> CommandError {
|
||||||
|
CommandError { kind: CommandErrorKind::Io(ioerr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an error from the contents of stderr (which may be empty).
|
||||||
|
pub(crate) fn stderr(bytes: Vec<u8>) -> CommandError {
|
||||||
|
CommandError { kind: CommandErrorKind::Stderr(bytes) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for CommandError {
|
||||||
|
fn description(&self) -> &str { "command error" }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CommandError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self.kind {
|
||||||
|
CommandErrorKind::Io(ref e) => e.fmt(f),
|
||||||
|
CommandErrorKind::Stderr(ref bytes) => {
|
||||||
|
let msg = String::from_utf8_lossy(bytes);
|
||||||
|
if msg.trim().is_empty() {
|
||||||
|
write!(f, "<stderr is empty>")
|
||||||
|
} else {
|
||||||
|
let div = iter::repeat('-').take(79).collect::<String>();
|
||||||
|
write!(f, "\n{div}\n{msg}\n{div}", div=div, msg=msg.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for CommandError {
|
||||||
|
fn from(ioerr: io::Error) -> CommandError {
|
||||||
|
CommandError { kind: CommandErrorKind::Io(ioerr) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CommandError> for io::Error {
|
||||||
|
fn from(cmderr: CommandError) -> io::Error {
|
||||||
|
match cmderr.kind {
|
||||||
|
CommandErrorKind::Io(ioerr) => ioerr,
|
||||||
|
CommandErrorKind::Stderr(_) => {
|
||||||
|
io::Error::new(io::ErrorKind::Other, cmderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures and builds a streaming reader for process output.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct CommandReaderBuilder {
|
||||||
|
async_stderr: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandReaderBuilder {
|
||||||
|
/// Create a new builder with the default configuration.
|
||||||
|
pub fn new() -> CommandReaderBuilder {
|
||||||
|
CommandReaderBuilder::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a new streaming reader for the given command's output.
|
||||||
|
///
|
||||||
|
/// The caller should set everything that's required on the given command
|
||||||
|
/// before building a reader, such as its arguments, environment and
|
||||||
|
/// current working directory. Settings such as the stdout and stderr (but
|
||||||
|
/// not stdin) pipes will be overridden so that they can be controlled by
|
||||||
|
/// the reader.
|
||||||
|
///
|
||||||
|
/// If there was a problem spawning the given command, then its error is
|
||||||
|
/// returned.
|
||||||
|
pub fn build(
|
||||||
|
&self,
|
||||||
|
command: &mut process::Command,
|
||||||
|
) -> Result<CommandReader, CommandError> {
|
||||||
|
let mut child = command
|
||||||
|
.stdout(process::Stdio::piped())
|
||||||
|
.stderr(process::Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
let stdout = child.stdout.take().unwrap();
|
||||||
|
let stderr =
|
||||||
|
if self.async_stderr {
|
||||||
|
StderrReader::async(child.stderr.take().unwrap())
|
||||||
|
} else {
|
||||||
|
StderrReader::sync(child.stderr.take().unwrap())
|
||||||
|
};
|
||||||
|
Ok(CommandReader {
|
||||||
|
child: child,
|
||||||
|
stdout: stdout,
|
||||||
|
stderr: stderr,
|
||||||
|
done: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When enabled, the reader will asynchronously read the contents of the
|
||||||
|
/// command's stderr output. When disabled, stderr is only read after the
|
||||||
|
/// stdout stream has been exhausted (or if the process quits with an error
|
||||||
|
/// code).
|
||||||
|
///
|
||||||
|
/// Note that when enabled, this may require launching an additional
|
||||||
|
/// thread in order to read stderr. This is done so that the process being
|
||||||
|
/// executed is never blocked from writing to stdout or stderr. If this is
|
||||||
|
/// disabled, then it is possible for the process to fill up the stderr
|
||||||
|
/// buffer and deadlock.
|
||||||
|
///
|
||||||
|
/// This is enabled by default.
|
||||||
|
pub fn async_stderr(&mut self, yes: bool) -> &mut CommandReaderBuilder {
|
||||||
|
self.async_stderr = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A streaming reader for a command's output.
|
||||||
|
///
|
||||||
|
/// The purpose of this reader is to provide an easy way to execute processes
|
||||||
|
/// whose stdout is read in a streaming way while also making the processes'
|
||||||
|
/// stderr available when the process fails with an exit code. This makes it
|
||||||
|
/// possible to execute processes while surfacing the underlying failure mode
|
||||||
|
/// in the case of an error.
|
||||||
|
///
|
||||||
|
/// Moreover, by default, this reader will asynchronously read the processes'
|
||||||
|
/// stderr. This prevents subtle deadlocking bugs for noisy processes that
|
||||||
|
/// write a lot to stderr. Currently, the entire contents of stderr is read
|
||||||
|
/// on to the heap.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This example shows how to invoke `gzip` to decompress the contents of a
|
||||||
|
/// file. If the `gzip` command reports a failing exit status, then its stderr
|
||||||
|
/// is returned as an error.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use std::io::Read;
|
||||||
|
/// use std::process::Command;
|
||||||
|
/// use grep_cli::CommandReader;
|
||||||
|
///
|
||||||
|
/// # fn example() -> Result<(), Box<::std::error::Error>> {
|
||||||
|
/// let mut cmd = Command::new("gzip");
|
||||||
|
/// cmd.arg("-d").arg("-c").arg("/usr/share/man/man1/ls.1.gz");
|
||||||
|
///
|
||||||
|
/// let mut rdr = CommandReader::new(&mut cmd)?;
|
||||||
|
/// let mut contents = vec![];
|
||||||
|
/// rdr.read_to_end(&mut contents)?;
|
||||||
|
/// # Ok(()) }
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CommandReader {
|
||||||
|
child: process::Child,
|
||||||
|
stdout: process::ChildStdout,
|
||||||
|
stderr: StderrReader,
|
||||||
|
done: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandReader {
|
||||||
|
/// Create a new streaming reader for the given command using the default
|
||||||
|
/// configuration.
|
||||||
|
///
|
||||||
|
/// The caller should set everything that's required on the given command
|
||||||
|
/// before building a reader, such as its arguments, environment and
|
||||||
|
/// current working directory. Settings such as the stdout and stderr (but
|
||||||
|
/// not stdin) pipes will be overridden so that they can be controlled by
|
||||||
|
/// the reader.
|
||||||
|
///
|
||||||
|
/// If there was a problem spawning the given command, then its error is
|
||||||
|
/// returned.
|
||||||
|
///
|
||||||
|
/// If the caller requires additional configuration for the reader
|
||||||
|
/// returned, then use
|
||||||
|
/// [`CommandReaderBuilder`](struct.CommandReaderBuilder.html).
|
||||||
|
pub fn new(
|
||||||
|
cmd: &mut process::Command,
|
||||||
|
) -> Result<CommandReader, CommandError> {
|
||||||
|
CommandReaderBuilder::new().build(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Read for CommandReader {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
if self.done {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
let nread = self.stdout.read(buf)?;
|
||||||
|
if nread == 0 {
|
||||||
|
self.done = true;
|
||||||
|
// Reap the child now that we're done reading. If the command
|
||||||
|
// failed, report stderr as an error.
|
||||||
|
if !self.child.wait()?.success() {
|
||||||
|
return Err(io::Error::from(self.stderr.read_to_end()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(nread)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A reader that encapsulates the asynchronous or synchronous reading of
|
||||||
|
/// stderr.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum StderrReader {
|
||||||
|
Async(Option<JoinHandle<CommandError>>),
|
||||||
|
Sync(process::ChildStderr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StderrReader {
|
||||||
|
/// Create a reader for stderr that reads contents asynchronously.
|
||||||
|
fn async(mut stderr: process::ChildStderr) -> StderrReader {
|
||||||
|
let handle = thread::spawn(move || {
|
||||||
|
stderr_to_command_error(&mut stderr)
|
||||||
|
});
|
||||||
|
StderrReader::Async(Some(handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a reader for stderr that reads contents synchronously.
|
||||||
|
fn sync(stderr: process::ChildStderr) -> StderrReader {
|
||||||
|
StderrReader::Sync(stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes all of stderr on to the heap and returns it as an error.
|
||||||
|
///
|
||||||
|
/// If there was a problem reading stderr itself, then this returns an I/O
|
||||||
|
/// command error.
|
||||||
|
fn read_to_end(&mut self) -> CommandError {
|
||||||
|
match *self {
|
||||||
|
StderrReader::Async(ref mut handle) => {
|
||||||
|
let handle = handle
|
||||||
|
.take()
|
||||||
|
.expect("read_to_end cannot be called more than once");
|
||||||
|
handle
|
||||||
|
.join()
|
||||||
|
.expect("stderr reading thread does not panic")
|
||||||
|
}
|
||||||
|
StderrReader::Sync(ref mut stderr) => {
|
||||||
|
stderr_to_command_error(stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stderr_to_command_error(stderr: &mut process::ChildStderr) -> CommandError {
|
||||||
|
let mut bytes = vec![];
|
||||||
|
match stderr.read_to_end(&mut bytes) {
|
||||||
|
Ok(_) => CommandError::stderr(bytes),
|
||||||
|
Err(err) => CommandError::io(err),
|
||||||
|
}
|
||||||
|
}
|
133
grep-cli/src/wtr.rs
Normal file
133
grep-cli/src/wtr.rs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
use std::io;
|
||||||
|
|
||||||
|
use termcolor;
|
||||||
|
|
||||||
|
use is_tty_stdout;
|
||||||
|
|
||||||
|
/// A writer that supports coloring with either line or block buffering.
|
||||||
|
pub struct StandardStream(StandardStreamKind);
|
||||||
|
|
||||||
|
/// Returns a possibly buffered writer to stdout for the given color choice.
|
||||||
|
///
|
||||||
|
/// The writer returned is either line buffered or block buffered. The decision
|
||||||
|
/// between these two is made automatically based on whether a tty is attached
|
||||||
|
/// to stdout or not. If a tty is attached, then line buffering is used.
|
||||||
|
/// Otherwise, block buffering is used. In general, block buffering is more
|
||||||
|
/// efficient, but may increase the time it takes for the end user to see the
|
||||||
|
/// first bits of output.
|
||||||
|
///
|
||||||
|
/// If you need more fine grained control over the buffering mode, then use one
|
||||||
|
/// of `stdout_buffered_line` or `stdout_buffered_block`.
|
||||||
|
///
|
||||||
|
/// The color choice given is passed along to the underlying writer. To
|
||||||
|
/// completely disable colors in all cases, use `ColorChoice::Never`.
|
||||||
|
pub fn stdout(color_choice: termcolor::ColorChoice) -> StandardStream {
|
||||||
|
if is_tty_stdout() {
|
||||||
|
stdout_buffered_line(color_choice)
|
||||||
|
} else {
|
||||||
|
stdout_buffered_block(color_choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a line buffered writer to stdout for the given color choice.
|
||||||
|
///
|
||||||
|
/// This writer is useful when printing results directly to a tty such that
|
||||||
|
/// users see output as soon as it's written. The downside of this approach
|
||||||
|
/// is that it can be slower, especially when there is a lot of output.
|
||||||
|
///
|
||||||
|
/// You might consider using
|
||||||
|
/// [`stdout`](fn.stdout.html)
|
||||||
|
/// instead, which chooses the buffering strategy automatically based on
|
||||||
|
/// whether stdout is connected to a tty.
|
||||||
|
pub fn stdout_buffered_line(
|
||||||
|
color_choice: termcolor::ColorChoice,
|
||||||
|
) -> StandardStream {
|
||||||
|
let out = termcolor::StandardStream::stdout(color_choice);
|
||||||
|
StandardStream(StandardStreamKind::LineBuffered(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a block buffered writer to stdout for the given color choice.
|
||||||
|
///
|
||||||
|
/// This writer is useful when printing results to a file since it amortizes
|
||||||
|
/// the cost of writing data. The downside of this approach is that it can
|
||||||
|
/// increase the latency of display output when writing to a tty.
|
||||||
|
///
|
||||||
|
/// You might consider using
|
||||||
|
/// [`stdout`](fn.stdout.html)
|
||||||
|
/// instead, which chooses the buffering strategy automatically based on
|
||||||
|
/// whether stdout is connected to a tty.
|
||||||
|
pub fn stdout_buffered_block(
|
||||||
|
color_choice: termcolor::ColorChoice,
|
||||||
|
) -> StandardStream {
|
||||||
|
let out = termcolor::BufferedStandardStream::stdout(color_choice);
|
||||||
|
StandardStream(StandardStreamKind::BlockBuffered(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StandardStreamKind {
|
||||||
|
LineBuffered(termcolor::StandardStream),
|
||||||
|
BlockBuffered(termcolor::BufferedStandardStream),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Write for StandardStream {
|
||||||
|
#[inline]
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
use self::StandardStreamKind::*;
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
LineBuffered(ref mut w) => w.write(buf),
|
||||||
|
BlockBuffered(ref mut w) => w.write(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
use self::StandardStreamKind::*;
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
LineBuffered(ref mut w) => w.flush(),
|
||||||
|
BlockBuffered(ref mut w) => w.flush(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl termcolor::WriteColor for StandardStream {
|
||||||
|
#[inline]
|
||||||
|
fn supports_color(&self) -> bool {
|
||||||
|
use self::StandardStreamKind::*;
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
LineBuffered(ref w) => w.supports_color(),
|
||||||
|
BlockBuffered(ref w) => w.supports_color(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_color(&mut self, spec: &termcolor::ColorSpec) -> io::Result<()> {
|
||||||
|
use self::StandardStreamKind::*;
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
LineBuffered(ref mut w) => w.set_color(spec),
|
||||||
|
BlockBuffered(ref mut w) => w.set_color(spec),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn reset(&mut self) -> io::Result<()> {
|
||||||
|
use self::StandardStreamKind::*;
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
LineBuffered(ref mut w) => w.reset(),
|
||||||
|
BlockBuffered(ref mut w) => w.reset(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_synchronous(&self) -> bool {
|
||||||
|
use self::StandardStreamKind::*;
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
LineBuffered(ref w) => w.is_synchronous(),
|
||||||
|
BlockBuffered(ref w) => w.is_synchronous(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-matcher"
|
name = "grep-matcher"
|
||||||
version = "0.1.0" #:version
|
version = "0.1.1" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
A trait for regular expressions, with a focus on line oriented search.
|
A trait for regular expressions, with a focus on line oriented search.
|
||||||
@@ -14,10 +14,10 @@ license = "Unlicense/MIT"
|
|||||||
autotests = false
|
autotests = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memchr = "2"
|
memchr = "2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
regex = "1"
|
regex = "1.1"
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "integration"
|
name = "integration"
|
||||||
|
@@ -266,6 +266,16 @@ impl LineTerminator {
|
|||||||
LineTerminatorImp::CRLF => &[b'\r', b'\n'],
|
LineTerminatorImp::CRLF => &[b'\r', b'\n'],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if the given slice ends with this line
|
||||||
|
/// terminator.
|
||||||
|
///
|
||||||
|
/// If this line terminator is `CRLF`, then this only checks whether the
|
||||||
|
/// last byte is `\n`.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_suffix(&self, slice: &[u8]) -> bool {
|
||||||
|
slice.last().map_or(false, |&b| b == self.as_byte())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LineTerminator {
|
impl Default for LineTerminator {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-pcre2"
|
name = "grep-pcre2"
|
||||||
version = "0.1.0" #:version
|
version = "0.1.2" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
Use PCRE2 with the 'grep' crate.
|
Use PCRE2 with the 'grep' crate.
|
||||||
@@ -13,5 +13,5 @@ keywords = ["regex", "grep", "pcre", "backreference", "look"]
|
|||||||
license = "Unlicense/MIT"
|
license = "Unlicense/MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
grep-matcher = { version = "0.1.0", path = "../grep-matcher" }
|
grep-matcher = { version = "0.1.1", path = "../grep-matcher" }
|
||||||
pcre2 = "0.1"
|
pcre2 = "0.1.1"
|
||||||
|
@@ -199,16 +199,34 @@ impl RegexMatcherBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable PCRE2's JIT.
|
/// Enable PCRE2's JIT and return an error if it's not available.
|
||||||
///
|
///
|
||||||
/// This generally speeds up matching quite a bit. The downside is that it
|
/// This generally speeds up matching quite a bit. The downside is that it
|
||||||
/// can increase the time it takes to compile a pattern.
|
/// can increase the time it takes to compile a pattern.
|
||||||
///
|
///
|
||||||
/// This is disabled by default.
|
/// If the JIT isn't available or if JIT compilation returns an error, then
|
||||||
|
/// regex compilation will fail with the corresponding error.
|
||||||
|
///
|
||||||
|
/// This is disabled by default, and always overrides `jit_if_available`.
|
||||||
pub fn jit(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
pub fn jit(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
self.builder.jit(yes);
|
self.builder.jit(yes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enable PCRE2's JIT if it's available.
|
||||||
|
///
|
||||||
|
/// This generally speeds up matching quite a bit. The downside is that it
|
||||||
|
/// can increase the time it takes to compile a pattern.
|
||||||
|
///
|
||||||
|
/// If the JIT isn't available or if JIT compilation returns an error,
|
||||||
|
/// then a debug message with the error will be emitted and the regex will
|
||||||
|
/// otherwise silently fall back to non-JIT matching.
|
||||||
|
///
|
||||||
|
/// This is disabled by default, and always overrides `jit`.
|
||||||
|
pub fn jit_if_available(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
|
self.builder.jit_if_available(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An implementation of the `Matcher` trait using PCRE2.
|
/// An implementation of the `Matcher` trait using PCRE2.
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-printer"
|
name = "grep-printer"
|
||||||
version = "0.1.0" #:version
|
version = "0.1.1" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
An implementation of the grep crate's Sink trait that provides standard
|
An implementation of the grep crate's Sink trait that provides standard
|
||||||
@@ -18,13 +18,13 @@ default = ["serde1"]
|
|||||||
serde1 = ["base64", "serde", "serde_derive", "serde_json"]
|
serde1 = ["base64", "serde", "serde_derive", "serde_json"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = { version = "0.9", optional = true }
|
base64 = { version = "0.10.0", optional = true }
|
||||||
grep-matcher = { version = "0.1.0", path = "../grep-matcher" }
|
grep-matcher = { version = "0.1.1", path = "../grep-matcher" }
|
||||||
grep-searcher = { version = "0.1.0", path = "../grep-searcher" }
|
grep-searcher = { version = "0.1.1", path = "../grep-searcher" }
|
||||||
termcolor = "1"
|
termcolor = "1.0.4"
|
||||||
serde = { version = "1", optional = true }
|
serde = { version = "1.0.77", optional = true }
|
||||||
serde_derive = { version = "1", optional = true }
|
serde_derive = { version = "1.0.77", optional = true }
|
||||||
serde_json = { version = "1", optional = true }
|
serde_json = { version = "1.0.27", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
grep-regex = { version = "0.1.0", path = "../grep-regex" }
|
grep-regex = { version = "0.1.1", path = "../grep-regex" }
|
||||||
|
@@ -4,6 +4,25 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use termcolor::{Color, ColorSpec, ParseColorError};
|
use termcolor::{Color, ColorSpec, ParseColorError};
|
||||||
|
|
||||||
|
/// Returns a default set of color specifications.
|
||||||
|
///
|
||||||
|
/// This may change over time, but the color choices are meant to be fairly
|
||||||
|
/// conservative that work across terminal themes.
|
||||||
|
///
|
||||||
|
/// Additional color specifications can be added to the list returned. More
|
||||||
|
/// recently added specifications override previously added specifications.
|
||||||
|
pub fn default_color_specs() -> Vec<UserColorSpec> {
|
||||||
|
vec![
|
||||||
|
#[cfg(unix)]
|
||||||
|
"path:fg:magenta".parse().unwrap(),
|
||||||
|
#[cfg(windows)]
|
||||||
|
"path:fg:cyan".parse().unwrap(),
|
||||||
|
"line:fg:green".parse().unwrap(),
|
||||||
|
"match:fg:red".parse().unwrap(),
|
||||||
|
"match:style:bold".parse().unwrap(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
/// An error that can occur when parsing color specifications.
|
/// An error that can occur when parsing color specifications.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum ColorError {
|
pub enum ColorError {
|
||||||
@@ -227,6 +246,15 @@ impl ColorSpecs {
|
|||||||
merged
|
merged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a default set of specifications that have color.
|
||||||
|
///
|
||||||
|
/// This is distinct from `ColorSpecs`'s `Default` implementation in that
|
||||||
|
/// this provides a set of default color choices, where as the `Default`
|
||||||
|
/// implementation provides no color choices.
|
||||||
|
pub fn default_with_color() -> ColorSpecs {
|
||||||
|
ColorSpecs::new(&default_color_specs())
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the color specification for coloring file paths.
|
/// Return the color specification for coloring file paths.
|
||||||
pub fn path(&self) -> &ColorSpec {
|
pub fn path(&self) -> &ColorSpec {
|
||||||
&self.path
|
&self.path
|
||||||
|
@@ -817,7 +817,8 @@ impl<'a> SubMatches<'a> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use grep_regex::RegexMatcher;
|
use grep_regex::{RegexMatcher, RegexMatcherBuilder};
|
||||||
|
use grep_matcher::LineTerminator;
|
||||||
use grep_searcher::SearcherBuilder;
|
use grep_searcher::SearcherBuilder;
|
||||||
|
|
||||||
use super::{JSON, JSONBuilder};
|
use super::{JSON, JSONBuilder};
|
||||||
@@ -918,4 +919,45 @@ and exhibited clearly, with a label attached.\
|
|||||||
assert_eq!(got.lines().count(), 2);
|
assert_eq!(got.lines().count(), 2);
|
||||||
assert!(got.contains("begin") && got.contains("end"));
|
assert!(got.contains("begin") && got.contains("end"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_crlf() {
|
||||||
|
let haystack = "test\r\n".as_bytes();
|
||||||
|
|
||||||
|
let matcher = RegexMatcherBuilder::new()
|
||||||
|
.build("test")
|
||||||
|
.unwrap();
|
||||||
|
let mut printer = JSONBuilder::new()
|
||||||
|
.build(vec![]);
|
||||||
|
SearcherBuilder::new()
|
||||||
|
.build()
|
||||||
|
.search_reader(&matcher, haystack, printer.sink(&matcher))
|
||||||
|
.unwrap();
|
||||||
|
let got = printer_contents(&mut printer);
|
||||||
|
assert_eq!(got.lines().count(), 3);
|
||||||
|
assert!(
|
||||||
|
got.lines().nth(1).unwrap().contains(r"test\r\n"),
|
||||||
|
r"missing 'test\r\n' in '{}'",
|
||||||
|
got.lines().nth(1).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let matcher = RegexMatcherBuilder::new()
|
||||||
|
.crlf(true)
|
||||||
|
.build("test")
|
||||||
|
.unwrap();
|
||||||
|
let mut printer = JSONBuilder::new()
|
||||||
|
.build(vec![]);
|
||||||
|
SearcherBuilder::new()
|
||||||
|
.line_terminator(LineTerminator::crlf())
|
||||||
|
.build()
|
||||||
|
.search_reader(&matcher, haystack, printer.sink(&matcher))
|
||||||
|
.unwrap();
|
||||||
|
let got = printer_contents(&mut printer);
|
||||||
|
assert_eq!(got.lines().count(), 3);
|
||||||
|
assert!(
|
||||||
|
got.lines().nth(1).unwrap().contains(r"test\r\n"),
|
||||||
|
r"missing 'test\r\n' in '{}'",
|
||||||
|
got.lines().nth(1).unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -114,39 +114,6 @@ impl<'a> Data<'a> {
|
|||||||
// so we do the easy thing for now.
|
// so we do the easy thing for now.
|
||||||
Data::Text { text: path.to_string_lossy() }
|
Data::Text { text: path.to_string_lossy() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unused deserialization routines.
|
|
||||||
|
|
||||||
/*
|
|
||||||
fn into_bytes(self) -> Vec<u8> {
|
|
||||||
match self {
|
|
||||||
Data::Text { text } => text.into_bytes(),
|
|
||||||
Data::Bytes { bytes } => bytes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn into_path_buf(&self) -> PathBuf {
|
|
||||||
use std::os::unix::ffi::OsStrExt;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Data::Text { text } => PathBuf::from(text),
|
|
||||||
Data::Bytes { bytes } => {
|
|
||||||
PathBuf::from(OsStr::from_bytes(bytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
fn into_path_buf(&self) -> PathBuf {
|
|
||||||
match self {
|
|
||||||
Data::Text { text } => PathBuf::from(text),
|
|
||||||
Data::Bytes { bytes } => {
|
|
||||||
PathBuf::from(String::from_utf8_lossy(&bytes).into_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_base64<T, S>(
|
fn to_base64<T, S>(
|
||||||
@@ -178,36 +145,3 @@ where P: AsRef<Path>,
|
|||||||
{
|
{
|
||||||
path.as_ref().map(|p| Data::from_path(p.as_ref())).serialize(ser)
|
path.as_ref().map(|p| Data::from_path(p.as_ref())).serialize(ser)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following are some deserialization helpers, in case we decide to support
|
|
||||||
// deserialization of the above types.
|
|
||||||
|
|
||||||
/*
|
|
||||||
fn from_base64<'de, D>(
|
|
||||||
de: D,
|
|
||||||
) -> Result<Vec<u8>, D::Error>
|
|
||||||
where D: Deserializer<'de>
|
|
||||||
{
|
|
||||||
let encoded = String::deserialize(de)?;
|
|
||||||
let decoded = base64::decode(encoded.as_bytes())
|
|
||||||
.map_err(D::Error::custom)?;
|
|
||||||
Ok(decoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deser_bytes<'de, D>(
|
|
||||||
de: D,
|
|
||||||
) -> Result<Vec<u8>, D::Error>
|
|
||||||
where D: Deserializer<'de>
|
|
||||||
{
|
|
||||||
Data::deserialize(de).map(|datum| datum.into_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deser_path<'de, D>(
|
|
||||||
de: D,
|
|
||||||
) -> Result<Option<PathBuf>, D::Error>
|
|
||||||
where D: Deserializer<'de>
|
|
||||||
{
|
|
||||||
Option::<Data>::deserialize(de)
|
|
||||||
.map(|opt| opt.map(|datum| datum.into_path_buf()))
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
@@ -83,7 +83,7 @@ extern crate serde_derive;
|
|||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate termcolor;
|
extern crate termcolor;
|
||||||
|
|
||||||
pub use color::{ColorError, ColorSpecs, UserColorSpec};
|
pub use color::{ColorError, ColorSpecs, UserColorSpec, default_color_specs};
|
||||||
#[cfg(feature = "serde1")]
|
#[cfg(feature = "serde1")]
|
||||||
pub use json::{JSON, JSONBuilder, JSONSink};
|
pub use json::{JSON, JSONBuilder, JSONSink};
|
||||||
pub use standard::{Standard, StandardBuilder, StandardSink};
|
pub use standard::{Standard, StandardBuilder, StandardSink};
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/// Like assert_eq, but nicer output for long strings.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_eq_printed {
|
macro_rules! assert_eq_printed {
|
||||||
|
@@ -239,8 +239,9 @@ impl StandardBuilder {
|
|||||||
/// which may either be in index form (e.g., `$2`) or can reference named
|
/// 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`).
|
/// capturing groups if present in the original pattern (e.g., `$foo`).
|
||||||
///
|
///
|
||||||
/// For documentation on the full format, please see the `Matcher` trait's
|
/// For documentation on the full format, please see the `Capture` trait's
|
||||||
/// `interpolate` method.
|
/// `interpolate` method in the
|
||||||
|
/// [grep-printer](https://docs.rs/grep-printer) crate.
|
||||||
pub fn replacement(
|
pub fn replacement(
|
||||||
&mut self,
|
&mut self,
|
||||||
replacement: Option<Vec<u8>>,
|
replacement: Option<Vec<u8>>,
|
||||||
@@ -1201,6 +1202,9 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
if !self.wtr().borrow().supports_color() || spec.is_none() {
|
if !self.wtr().borrow().supports_color() || spec.is_none() {
|
||||||
return self.write_line(line);
|
return self.write_line(line);
|
||||||
}
|
}
|
||||||
|
if self.exceeds_max_columns(line) {
|
||||||
|
return self.write_exceeded_line();
|
||||||
|
}
|
||||||
|
|
||||||
let mut last_written =
|
let mut last_written =
|
||||||
if !self.config().trim_ascii {
|
if !self.config().trim_ascii {
|
||||||
@@ -1393,7 +1397,7 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn has_line_terminator(&self, buf: &[u8]) -> bool {
|
fn has_line_terminator(&self, buf: &[u8]) -> bool {
|
||||||
buf.last() == Some(&self.searcher.line_terminator().as_byte())
|
self.searcher.line_terminator().is_suffix(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_context(&self) -> bool {
|
fn is_context(&self) -> bool {
|
||||||
|
@@ -403,7 +403,7 @@ impl<W: WriteColor> Summary<W> {
|
|||||||
where M: Matcher,
|
where M: Matcher,
|
||||||
P: ?Sized + AsRef<Path>,
|
P: ?Sized + AsRef<Path>,
|
||||||
{
|
{
|
||||||
if !self.config.path {
|
if !self.config.path && !self.config.kind.requires_path() {
|
||||||
return self.sink(matcher);
|
return self.sink(matcher);
|
||||||
}
|
}
|
||||||
let stats =
|
let stats =
|
||||||
@@ -477,7 +477,10 @@ impl<'p, 's, M: Matcher, W: WriteColor> SummarySink<'p, 's, M, W> {
|
|||||||
/// This is unaffected by the result of searches before the previous
|
/// This is unaffected by the result of searches before the previous
|
||||||
/// search.
|
/// search.
|
||||||
pub fn has_match(&self) -> bool {
|
pub fn has_match(&self) -> bool {
|
||||||
self.match_count > 0
|
match self.summary.config.kind {
|
||||||
|
SummaryKind::PathWithoutMatch => self.match_count == 0,
|
||||||
|
_ => self.match_count > 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If binary data was found in the previous search, this returns the
|
/// If binary data was found in the previous search, this returns the
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-regex"
|
name = "grep-regex"
|
||||||
version = "0.1.0" #:version
|
version = "0.1.1" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
Use Rust's regex library with the 'grep' crate.
|
Use Rust's regex library with the 'grep' crate.
|
||||||
@@ -13,9 +13,9 @@ keywords = ["regex", "grep", "search", "pattern", "line"]
|
|||||||
license = "Unlicense/MIT"
|
license = "Unlicense/MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4"
|
log = "0.4.5"
|
||||||
grep-matcher = { version = "0.1.0", path = "../grep-matcher" }
|
grep-matcher = { version = "0.1.1", path = "../grep-matcher" }
|
||||||
regex = "1"
|
regex = "1.1"
|
||||||
regex-syntax = "0.6"
|
regex-syntax = "0.6.5"
|
||||||
thread_local = "0.3.6"
|
thread_local = "0.3.6"
|
||||||
utf8-ranges = "1"
|
utf8-ranges = "1.0.1"
|
||||||
|
@@ -160,6 +160,14 @@ impl ConfiguredHIR {
|
|||||||
non_matching_bytes(&self.expr)
|
non_matching_bytes(&self.expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if this regex needs to have its match offsets
|
||||||
|
/// tweaked because of CRLF support. Specifically, this occurs when the
|
||||||
|
/// CRLF hack is enabled and the regex is line anchored at the end. In
|
||||||
|
/// this case, matches that end with a `\r` have the `\r` stripped.
|
||||||
|
pub fn needs_crlf_stripped(&self) -> bool {
|
||||||
|
self.config.crlf && self.expr.is_line_anchored_end()
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds a regular expression from this HIR expression.
|
/// Builds a regular expression from this HIR expression.
|
||||||
pub fn regex(&self) -> Result<Regex, Error> {
|
pub fn regex(&self) -> Result<Regex, Error> {
|
||||||
self.pattern_to_regex(&self.expr.to_string())
|
self.pattern_to_regex(&self.expr.to_string())
|
||||||
|
@@ -1,5 +1,105 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use grep_matcher::{Match, Matcher, NoError};
|
||||||
|
use regex::bytes::Regex;
|
||||||
use regex_syntax::hir::{self, Hir, HirKind};
|
use regex_syntax::hir::{self, Hir, HirKind};
|
||||||
|
|
||||||
|
use config::ConfiguredHIR;
|
||||||
|
use error::Error;
|
||||||
|
use matcher::RegexCaptures;
|
||||||
|
|
||||||
|
/// A matcher for implementing "word match" semantics.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CRLFMatcher {
|
||||||
|
/// The regex.
|
||||||
|
regex: Regex,
|
||||||
|
/// A map from capture group name to capture group index.
|
||||||
|
names: HashMap<String, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CRLFMatcher {
|
||||||
|
/// Create a new matcher from the given pattern that strips `\r` from the
|
||||||
|
/// end of every match.
|
||||||
|
///
|
||||||
|
/// This panics if the given expression doesn't need its CRLF stripped.
|
||||||
|
pub fn new(expr: &ConfiguredHIR) -> Result<CRLFMatcher, Error> {
|
||||||
|
assert!(expr.needs_crlf_stripped());
|
||||||
|
|
||||||
|
let regex = expr.regex()?;
|
||||||
|
let mut names = HashMap::new();
|
||||||
|
for (i, optional_name) in regex.capture_names().enumerate() {
|
||||||
|
if let Some(name) = optional_name {
|
||||||
|
names.insert(name.to_string(), i.checked_sub(1).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(CRLFMatcher { regex, names })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Matcher for CRLFMatcher {
|
||||||
|
type Captures = RegexCaptures;
|
||||||
|
type Error = NoError;
|
||||||
|
|
||||||
|
fn find_at(
|
||||||
|
&self,
|
||||||
|
haystack: &[u8],
|
||||||
|
at: usize,
|
||||||
|
) -> Result<Option<Match>, NoError> {
|
||||||
|
let m = match self.regex.find_at(haystack, at) {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some(m) => Match::new(m.start(), m.end()),
|
||||||
|
};
|
||||||
|
Ok(Some(adjust_match(haystack, m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_captures(&self) -> Result<RegexCaptures, NoError> {
|
||||||
|
Ok(RegexCaptures::new(self.regex.capture_locations()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capture_count(&self) -> usize {
|
||||||
|
self.regex.captures_len().checked_sub(1).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capture_index(&self, name: &str) -> Option<usize> {
|
||||||
|
self.names.get(name).map(|i| *i)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn captures_at(
|
||||||
|
&self,
|
||||||
|
haystack: &[u8],
|
||||||
|
at: usize,
|
||||||
|
caps: &mut RegexCaptures,
|
||||||
|
) -> Result<bool, NoError> {
|
||||||
|
caps.strip_crlf(false);
|
||||||
|
let r = self.regex.captures_read_at(caps.locations(), haystack, at);
|
||||||
|
if !r.is_some() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the end of our match includes a `\r`, then strip it from all
|
||||||
|
// capture groups ending at the same location.
|
||||||
|
let end = caps.locations().get(0).unwrap().1;
|
||||||
|
if end > 0 && haystack.get(end - 1) == Some(&b'\r') {
|
||||||
|
caps.strip_crlf(true);
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We specifically do not implement other methods like find_iter or
|
||||||
|
// captures_iter. Namely, the iter methods are guaranteed to be correct
|
||||||
|
// by virtue of implementing find_at and captures_at above.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the given match ends with a `\r`, then return a new match that ends
|
||||||
|
/// immediately before the `\r`.
|
||||||
|
pub fn adjust_match(haystack: &[u8], m: Match) -> Match {
|
||||||
|
if m.end() > 0 && haystack.get(m.end() - 1) == Some(&b'\r') {
|
||||||
|
m.with_end(m.end() - 1)
|
||||||
|
} else {
|
||||||
|
m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Substitutes all occurrences of multi-line enabled `$` with `(?:\r?$)`.
|
/// Substitutes all occurrences of multi-line enabled `$` with `(?:\r?$)`.
|
||||||
///
|
///
|
||||||
/// This does not preserve the exact semantics of the given expression,
|
/// This does not preserve the exact semantics of the given expression,
|
||||||
|
@@ -166,10 +166,10 @@ fn union_required(expr: &Hir, lits: &mut Literals) {
|
|||||||
lits.cut();
|
lits.cut();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if lits2.contains_empty() {
|
if lits2.contains_empty() || !is_simple(&e) {
|
||||||
lits.cut();
|
lits.cut();
|
||||||
}
|
}
|
||||||
if !lits.cross_product(&lits2) {
|
if !lits.cross_product(&lits2) || !lits2.any_complete() {
|
||||||
// If this expression couldn't yield any literal that
|
// If this expression couldn't yield any literal that
|
||||||
// could be extended, then we need to quit. Since we're
|
// could be extended, then we need to quit. Since we're
|
||||||
// short-circuiting, we also need to freeze every member.
|
// short-circuiting, we also need to freeze every member.
|
||||||
@@ -250,6 +250,20 @@ fn alternate_literals<F: FnMut(&Hir, &mut Literals)>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_simple(expr: &Hir) -> bool {
|
||||||
|
match *expr.kind() {
|
||||||
|
HirKind::Empty
|
||||||
|
| HirKind::Literal(_)
|
||||||
|
| HirKind::Class(_)
|
||||||
|
| HirKind::Repetition(_)
|
||||||
|
| HirKind::Concat(_)
|
||||||
|
| HirKind::Alternation(_) => true,
|
||||||
|
HirKind::Anchor(_)
|
||||||
|
| HirKind::WordBoundary(_)
|
||||||
|
| HirKind::Group(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the number of characters in the given class.
|
/// Return the number of characters in the given class.
|
||||||
fn count_unicode_class(cls: &hir::ClassUnicode) -> u32 {
|
fn count_unicode_class(cls: &hir::ClassUnicode) -> u32 {
|
||||||
cls.iter().map(|r| 1 + (r.end() as u32 - r.start() as u32)).sum()
|
cls.iter().map(|r| 1 + (r.end() as u32 - r.start() as u32)).sum()
|
||||||
@@ -301,4 +315,12 @@ mod tests {
|
|||||||
// assert_eq!(one_regex(r"\w(foo|bar|baz)"), pat("foo|bar|baz"));
|
// assert_eq!(one_regex(r"\w(foo|bar|baz)"), pat("foo|bar|baz"));
|
||||||
// assert_eq!(one_regex(r"\w(foo|bar|baz)\w"), pat("foo|bar|baz"));
|
// assert_eq!(one_regex(r"\w(foo|bar|baz)\w"), pat("foo|bar|baz"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn regression_1064() {
|
||||||
|
// Regression from:
|
||||||
|
// https://github.com/BurntSushi/ripgrep/issues/1064
|
||||||
|
// assert_eq!(one_regex(r"a.*c"), pat("a"));
|
||||||
|
assert_eq!(one_regex(r"a(.*c)"), pat("a"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ use grep_matcher::{
|
|||||||
use regex::bytes::{CaptureLocations, Regex};
|
use regex::bytes::{CaptureLocations, Regex};
|
||||||
|
|
||||||
use config::{Config, ConfiguredHIR};
|
use config::{Config, ConfiguredHIR};
|
||||||
|
use crlf::CRLFMatcher;
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use word::WordMatcher;
|
use word::WordMatcher;
|
||||||
|
|
||||||
@@ -323,8 +324,15 @@ impl RegexMatcher {
|
|||||||
/// Create a new matcher from the given pattern using the default
|
/// Create a new matcher from the given pattern using the default
|
||||||
/// configuration, but matches lines terminated by `\n`.
|
/// configuration, but matches lines terminated by `\n`.
|
||||||
///
|
///
|
||||||
/// This returns an error if the given pattern contains a literal `\n`.
|
/// This is meant to be a convenience constructor for using a
|
||||||
/// Other uses of `\n` (such as in `\s`) are removed transparently.
|
/// `RegexMatcherBuilder` and setting its
|
||||||
|
/// [`line_terminator`](struct.RegexMatcherBuilder.html#method.line_terminator)
|
||||||
|
/// to `\n`. The purpose of using this constructor is to permit special
|
||||||
|
/// optimizations that help speed up line oriented search. These types of
|
||||||
|
/// optimizations are only appropriate when matches span no more than one
|
||||||
|
/// line. For this reason, this constructor will return an error if the
|
||||||
|
/// given pattern contains a literal `\n`. Other uses of `\n` (such as in
|
||||||
|
/// `\s`) are removed transparently.
|
||||||
pub fn new_line_matcher(pattern: &str) -> Result<RegexMatcher, Error> {
|
pub fn new_line_matcher(pattern: &str) -> Result<RegexMatcher, Error> {
|
||||||
RegexMatcherBuilder::new()
|
RegexMatcherBuilder::new()
|
||||||
.line_terminator(Some(b'\n'))
|
.line_terminator(Some(b'\n'))
|
||||||
@@ -337,6 +345,11 @@ impl RegexMatcher {
|
|||||||
enum RegexMatcherImpl {
|
enum RegexMatcherImpl {
|
||||||
/// The standard matcher used for all regular expressions.
|
/// The standard matcher used for all regular expressions.
|
||||||
Standard(StandardMatcher),
|
Standard(StandardMatcher),
|
||||||
|
/// A matcher that strips `\r` from the end of matches.
|
||||||
|
///
|
||||||
|
/// This is only used when the CRLF hack is enabled and the regex is line
|
||||||
|
/// anchored at the end.
|
||||||
|
CRLF(CRLFMatcher),
|
||||||
/// A matcher that only matches at word boundaries. This transforms the
|
/// A matcher that only matches at word boundaries. This transforms the
|
||||||
/// regex to `(^|\W)(...)($|\W)` instead of the more intuitive `\b(...)\b`.
|
/// regex to `(^|\W)(...)($|\W)` instead of the more intuitive `\b(...)\b`.
|
||||||
/// Because of this, the WordMatcher provides its own implementation of
|
/// Because of this, the WordMatcher provides its own implementation of
|
||||||
@@ -351,6 +364,8 @@ impl RegexMatcherImpl {
|
|||||||
fn new(expr: &ConfiguredHIR) -> Result<RegexMatcherImpl, Error> {
|
fn new(expr: &ConfiguredHIR) -> Result<RegexMatcherImpl, Error> {
|
||||||
if expr.config().word {
|
if expr.config().word {
|
||||||
Ok(RegexMatcherImpl::Word(WordMatcher::new(expr)?))
|
Ok(RegexMatcherImpl::Word(WordMatcher::new(expr)?))
|
||||||
|
} else if expr.needs_crlf_stripped() {
|
||||||
|
Ok(RegexMatcherImpl::CRLF(CRLFMatcher::new(expr)?))
|
||||||
} else {
|
} else {
|
||||||
Ok(RegexMatcherImpl::Standard(StandardMatcher::new(expr)?))
|
Ok(RegexMatcherImpl::Standard(StandardMatcher::new(expr)?))
|
||||||
}
|
}
|
||||||
@@ -372,6 +387,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.find_at(haystack, at),
|
Standard(ref m) => m.find_at(haystack, at),
|
||||||
|
CRLF(ref m) => m.find_at(haystack, at),
|
||||||
Word(ref m) => m.find_at(haystack, at),
|
Word(ref m) => m.find_at(haystack, at),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,6 +396,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.new_captures(),
|
Standard(ref m) => m.new_captures(),
|
||||||
|
CRLF(ref m) => m.new_captures(),
|
||||||
Word(ref m) => m.new_captures(),
|
Word(ref m) => m.new_captures(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,6 +405,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.capture_count(),
|
Standard(ref m) => m.capture_count(),
|
||||||
|
CRLF(ref m) => m.capture_count(),
|
||||||
Word(ref m) => m.capture_count(),
|
Word(ref m) => m.capture_count(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,6 +414,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.capture_index(name),
|
Standard(ref m) => m.capture_index(name),
|
||||||
|
CRLF(ref m) => m.capture_index(name),
|
||||||
Word(ref m) => m.capture_index(name),
|
Word(ref m) => m.capture_index(name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,6 +423,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.find(haystack),
|
Standard(ref m) => m.find(haystack),
|
||||||
|
CRLF(ref m) => m.find(haystack),
|
||||||
Word(ref m) => m.find(haystack),
|
Word(ref m) => m.find(haystack),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,6 +438,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.find_iter(haystack, matched),
|
Standard(ref m) => m.find_iter(haystack, matched),
|
||||||
|
CRLF(ref m) => m.find_iter(haystack, matched),
|
||||||
Word(ref m) => m.find_iter(haystack, matched),
|
Word(ref m) => m.find_iter(haystack, matched),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,6 +453,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.try_find_iter(haystack, matched),
|
Standard(ref m) => m.try_find_iter(haystack, matched),
|
||||||
|
CRLF(ref m) => m.try_find_iter(haystack, matched),
|
||||||
Word(ref m) => m.try_find_iter(haystack, matched),
|
Word(ref m) => m.try_find_iter(haystack, matched),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,6 +466,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.captures(haystack, caps),
|
Standard(ref m) => m.captures(haystack, caps),
|
||||||
|
CRLF(ref m) => m.captures(haystack, caps),
|
||||||
Word(ref m) => m.captures(haystack, caps),
|
Word(ref m) => m.captures(haystack, caps),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -459,6 +482,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.captures_iter(haystack, caps, matched),
|
Standard(ref m) => m.captures_iter(haystack, caps, matched),
|
||||||
|
CRLF(ref m) => m.captures_iter(haystack, caps, matched),
|
||||||
Word(ref m) => m.captures_iter(haystack, caps, matched),
|
Word(ref m) => m.captures_iter(haystack, caps, matched),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -474,6 +498,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.try_captures_iter(haystack, caps, matched),
|
Standard(ref m) => m.try_captures_iter(haystack, caps, matched),
|
||||||
|
CRLF(ref m) => m.try_captures_iter(haystack, caps, matched),
|
||||||
Word(ref m) => m.try_captures_iter(haystack, caps, matched),
|
Word(ref m) => m.try_captures_iter(haystack, caps, matched),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -487,6 +512,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.captures_at(haystack, at, caps),
|
Standard(ref m) => m.captures_at(haystack, at, caps),
|
||||||
|
CRLF(ref m) => m.captures_at(haystack, at, caps),
|
||||||
Word(ref m) => m.captures_at(haystack, at, caps),
|
Word(ref m) => m.captures_at(haystack, at, caps),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,6 +528,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.replace(haystack, dst, append),
|
Standard(ref m) => m.replace(haystack, dst, append),
|
||||||
|
CRLF(ref m) => m.replace(haystack, dst, append),
|
||||||
Word(ref m) => m.replace(haystack, dst, append),
|
Word(ref m) => m.replace(haystack, dst, append),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -520,6 +547,9 @@ impl Matcher for RegexMatcher {
|
|||||||
Standard(ref m) => {
|
Standard(ref m) => {
|
||||||
m.replace_with_captures(haystack, caps, dst, append)
|
m.replace_with_captures(haystack, caps, dst, append)
|
||||||
}
|
}
|
||||||
|
CRLF(ref m) => {
|
||||||
|
m.replace_with_captures(haystack, caps, dst, append)
|
||||||
|
}
|
||||||
Word(ref m) => {
|
Word(ref m) => {
|
||||||
m.replace_with_captures(haystack, caps, dst, append)
|
m.replace_with_captures(haystack, caps, dst, append)
|
||||||
}
|
}
|
||||||
@@ -530,6 +560,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.is_match(haystack),
|
Standard(ref m) => m.is_match(haystack),
|
||||||
|
CRLF(ref m) => m.is_match(haystack),
|
||||||
Word(ref m) => m.is_match(haystack),
|
Word(ref m) => m.is_match(haystack),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -542,6 +573,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.is_match_at(haystack, at),
|
Standard(ref m) => m.is_match_at(haystack, at),
|
||||||
|
CRLF(ref m) => m.is_match_at(haystack, at),
|
||||||
Word(ref m) => m.is_match_at(haystack, at),
|
Word(ref m) => m.is_match_at(haystack, at),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -553,6 +585,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.shortest_match(haystack),
|
Standard(ref m) => m.shortest_match(haystack),
|
||||||
|
CRLF(ref m) => m.shortest_match(haystack),
|
||||||
Word(ref m) => m.shortest_match(haystack),
|
Word(ref m) => m.shortest_match(haystack),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,6 +598,7 @@ impl Matcher for RegexMatcher {
|
|||||||
use self::RegexMatcherImpl::*;
|
use self::RegexMatcherImpl::*;
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
Standard(ref m) => m.shortest_match_at(haystack, at),
|
Standard(ref m) => m.shortest_match_at(haystack, at),
|
||||||
|
CRLF(ref m) => m.shortest_match_at(haystack, at),
|
||||||
Word(ref m) => m.shortest_match_at(haystack, at),
|
Word(ref m) => m.shortest_match_at(haystack, at),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -705,6 +739,9 @@ pub struct RegexCaptures {
|
|||||||
/// and the capturing groups must behave as if `(re)` is the `0`th capture
|
/// and the capturing groups must behave as if `(re)` is the `0`th capture
|
||||||
/// group.
|
/// group.
|
||||||
offset: usize,
|
offset: usize,
|
||||||
|
/// When enable, the end of a match has `\r` stripped from it, if one
|
||||||
|
/// exists.
|
||||||
|
strip_crlf: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Captures for RegexCaptures {
|
impl Captures for RegexCaptures {
|
||||||
@@ -713,8 +750,25 @@ impl Captures for RegexCaptures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, i: usize) -> Option<Match> {
|
fn get(&self, i: usize) -> Option<Match> {
|
||||||
let actual = i.checked_add(self.offset).unwrap();
|
if !self.strip_crlf {
|
||||||
self.locs.pos(actual).map(|(s, e)| Match::new(s, e))
|
let actual = i.checked_add(self.offset).unwrap();
|
||||||
|
return self.locs.pos(actual).map(|(s, e)| Match::new(s, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently don't support capture offsetting with CRLF stripping
|
||||||
|
assert_eq!(self.offset, 0);
|
||||||
|
let m = match self.locs.pos(i).map(|(s, e)| Match::new(s, e)) {
|
||||||
|
None => return None,
|
||||||
|
Some(m) => m,
|
||||||
|
};
|
||||||
|
// If the end position of this match corresponds to the end position
|
||||||
|
// of the overall match, then we apply our CRLF stripping. Otherwise,
|
||||||
|
// we cannot assume stripping is correct.
|
||||||
|
if i == 0 || m.end() == self.locs.pos(0).unwrap().1 {
|
||||||
|
Some(m.with_end(m.end() - 1))
|
||||||
|
} else {
|
||||||
|
Some(m)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -727,12 +781,16 @@ impl RegexCaptures {
|
|||||||
locs: CaptureLocations,
|
locs: CaptureLocations,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
) -> RegexCaptures {
|
) -> RegexCaptures {
|
||||||
RegexCaptures { locs, offset }
|
RegexCaptures { locs, offset, strip_crlf: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn locations(&mut self) -> &mut CaptureLocations {
|
pub(crate) fn locations(&mut self) -> &mut CaptureLocations {
|
||||||
&mut self.locs
|
&mut self.locs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn strip_crlf(&mut self, yes: bool) {
|
||||||
|
self.strip_crlf = yes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-searcher"
|
name = "grep-searcher"
|
||||||
version = "0.1.0" #:version
|
version = "0.1.2" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
Fast line oriented regex searching as a library.
|
Fast line oriented regex searching as a library.
|
||||||
@@ -13,23 +13,21 @@ keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
|||||||
license = "Unlicense/MIT"
|
license = "Unlicense/MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytecount = "0.3.1"
|
bytecount = "0.5"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8.14"
|
||||||
encoding_rs_io = "0.1.2"
|
encoding_rs_io = "0.1.4"
|
||||||
grep-matcher = { version = "0.1.0", path = "../grep-matcher" }
|
grep-matcher = { version = "0.1.1", path = "../grep-matcher" }
|
||||||
log = "0.4"
|
log = "0.4.5"
|
||||||
memchr = "2"
|
memchr = "2.1"
|
||||||
memmap = "0.6"
|
memmap = "0.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
grep-regex = { version = "0.1.0", path = "../grep-regex" }
|
grep-regex = { version = "0.1.1", path = "../grep-regex" }
|
||||||
regex = "1"
|
regex = "1.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
avx-accel = [
|
default = ["bytecount/runtime-dispatch-simd"]
|
||||||
"bytecount/avx-accel",
|
simd-accel = ["encoding_rs/simd-accel"]
|
||||||
]
|
|
||||||
simd-accel = [
|
# This feature is DEPRECATED. Runtime dispatch is used for SIMD now.
|
||||||
"bytecount/simd-accel",
|
avx-accel = []
|
||||||
"encoding_rs/simd-accel",
|
|
||||||
]
|
|
||||||
|
@@ -74,14 +74,11 @@ fn example() -> Result<(), Box<Error>> {
|
|||||||
let mut matches: Vec<(u64, String)> = vec![];
|
let mut matches: Vec<(u64, String)> = vec![];
|
||||||
Searcher::new().search_slice(&matcher, SHERLOCK, UTF8(|lnum, line| {
|
Searcher::new().search_slice(&matcher, SHERLOCK, UTF8(|lnum, line| {
|
||||||
// We are guaranteed to find a match, so the unwrap is OK.
|
// We are guaranteed to find a match, so the unwrap is OK.
|
||||||
eprintln!("LINE: {:?}", line);
|
|
||||||
let mymatch = matcher.find(line.as_bytes())?.unwrap();
|
let mymatch = matcher.find(line.as_bytes())?.unwrap();
|
||||||
matches.push((lnum, line[mymatch].to_string()));
|
matches.push((lnum, line[mymatch].to_string()));
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}))?;
|
}))?;
|
||||||
|
|
||||||
eprintln!("MATCHES: {:?}", matches);
|
|
||||||
|
|
||||||
assert_eq!(matches.len(), 2);
|
assert_eq!(matches.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
matches[0],
|
matches[0],
|
||||||
|
@@ -109,10 +109,17 @@ impl LineStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Count the number of occurrences of `line_term` in `bytes`.
|
/// Count the number of occurrences of `line_term` in `bytes`.
|
||||||
|
#[cfg(target_endian = "little")]
|
||||||
pub fn count(bytes: &[u8], line_term: u8) -> u64 {
|
pub fn count(bytes: &[u8], line_term: u8) -> u64 {
|
||||||
bytecount::count(bytes, line_term) as u64
|
bytecount::count(bytes, line_term) as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Count the number of occurrences of `line_term` in `bytes`.
|
||||||
|
#[cfg(target_endian = "big")]
|
||||||
|
pub fn count(bytes: &[u8], line_term: u8) -> u64 {
|
||||||
|
bytecount::naive_count(bytes, line_term) as u64
|
||||||
|
}
|
||||||
|
|
||||||
/// Given a line that possibly ends with a terminator, return that line without
|
/// Given a line that possibly ends with a terminator, return that line without
|
||||||
/// the terminator.
|
/// the terminator.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/// Like assert_eq, but nicer output for long strings.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_eq_printed {
|
macro_rules! assert_eq_printed {
|
||||||
|
@@ -424,16 +424,7 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
|||||||
}
|
}
|
||||||
self.count_lines(buf, range.start());
|
self.count_lines(buf, range.start());
|
||||||
let offset = self.absolute_byte_offset + range.start() as u64;
|
let offset = self.absolute_byte_offset + range.start() as u64;
|
||||||
let linebuf =
|
let linebuf = &buf[*range];
|
||||||
if self.config.line_term.is_crlf() {
|
|
||||||
// Normally, a line terminator is never part of a match, but
|
|
||||||
// if the line terminator is CRLF, then it's possible for `\r`
|
|
||||||
// to end up in the match, which we generally don't want. So
|
|
||||||
// we strip it here.
|
|
||||||
lines::without_terminator(&buf[*range], self.config.line_term)
|
|
||||||
} else {
|
|
||||||
&buf[*range]
|
|
||||||
};
|
|
||||||
let keepgoing = self.sink.matched(
|
let keepgoing = self.sink.matched(
|
||||||
&self.searcher,
|
&self.searcher,
|
||||||
&SinkMatch {
|
&SinkMatch {
|
||||||
|
@@ -307,6 +307,7 @@ impl SearcherBuilder {
|
|||||||
decode_builder
|
decode_builder
|
||||||
.encoding(self.config.encoding.as_ref().map(|e| e.0))
|
.encoding(self.config.encoding.as_ref().map(|e| e.0))
|
||||||
.utf8_passthru(true)
|
.utf8_passthru(true)
|
||||||
|
.strip_bom(true)
|
||||||
.bom_override(true);
|
.bom_override(true);
|
||||||
Searcher {
|
Searcher {
|
||||||
config: config,
|
config: config,
|
||||||
|
@@ -246,6 +246,53 @@ impl<'a, S: Sink> Sink for &'a mut S {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S: Sink + ?Sized> Sink for Box<S> {
|
||||||
|
type Error = S::Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn matched(
|
||||||
|
&mut self,
|
||||||
|
searcher: &Searcher,
|
||||||
|
mat: &SinkMatch,
|
||||||
|
) -> Result<bool, S::Error> {
|
||||||
|
(**self).matched(searcher, mat)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn context(
|
||||||
|
&mut self,
|
||||||
|
searcher: &Searcher,
|
||||||
|
context: &SinkContext,
|
||||||
|
) -> Result<bool, S::Error> {
|
||||||
|
(**self).context(searcher, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn context_break(
|
||||||
|
&mut self,
|
||||||
|
searcher: &Searcher,
|
||||||
|
) -> Result<bool, S::Error> {
|
||||||
|
(**self).context_break(searcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn begin(
|
||||||
|
&mut self,
|
||||||
|
searcher: &Searcher,
|
||||||
|
) -> Result<bool, S::Error> {
|
||||||
|
(**self).begin(searcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn finish(
|
||||||
|
&mut self,
|
||||||
|
searcher: &Searcher,
|
||||||
|
sink_finish: &SinkFinish,
|
||||||
|
) -> Result<(), S::Error> {
|
||||||
|
(**self).finish(searcher, sink_finish)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Summary data reported at the end of a search.
|
/// Summary data reported at the end of a search.
|
||||||
///
|
///
|
||||||
/// This reports data such as the total number of bytes searched and the
|
/// This reports data such as the total number of bytes searched and the
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep"
|
name = "grep"
|
||||||
version = "0.2.0" #:version
|
version = "0.2.3" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
Fast line oriented regex searching as a library.
|
Fast line oriented regex searching as a library.
|
||||||
@@ -13,18 +13,20 @@ keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
|||||||
license = "Unlicense/MIT"
|
license = "Unlicense/MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
grep-matcher = { version = "0.1.0", path = "../grep-matcher" }
|
grep-cli = { version = "0.1.1", path = "../grep-cli" }
|
||||||
grep-pcre2 = { version = "0.1.0", path = "../grep-pcre2", optional = true }
|
grep-matcher = { version = "0.1.1", path = "../grep-matcher" }
|
||||||
grep-printer = { version = "0.1.0", path = "../grep-printer" }
|
grep-pcre2 = { version = "0.1.2", path = "../grep-pcre2", optional = true }
|
||||||
grep-regex = { version = "0.1.0", path = "../grep-regex" }
|
grep-printer = { version = "0.1.1", path = "../grep-printer" }
|
||||||
grep-searcher = { version = "0.1.0", path = "../grep-searcher" }
|
grep-regex = { version = "0.1.1", path = "../grep-regex" }
|
||||||
|
grep-searcher = { version = "0.1.1", path = "../grep-searcher" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
atty = "0.2.11"
|
termcolor = "1.0.4"
|
||||||
termcolor = "1"
|
walkdir = "2.2.7"
|
||||||
walkdir = "2.2.0"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
avx-accel = ["grep-searcher/avx-accel"]
|
|
||||||
simd-accel = ["grep-searcher/simd-accel"]
|
simd-accel = ["grep-searcher/simd-accel"]
|
||||||
pcre2 = ["grep-pcre2"]
|
pcre2 = ["grep-pcre2"]
|
||||||
|
|
||||||
|
# This feature is DEPRECATED. Runtime dispatch is used for SIMD now.
|
||||||
|
avx-accel = []
|
||||||
|
@@ -1,29 +1,19 @@
|
|||||||
extern crate atty;
|
|
||||||
extern crate grep;
|
extern crate grep;
|
||||||
extern crate termcolor;
|
extern crate termcolor;
|
||||||
extern crate walkdir;
|
extern crate walkdir;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::error;
|
use std::error::Error;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::path::Path;
|
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::result;
|
|
||||||
|
|
||||||
|
use grep::cli;
|
||||||
use grep::printer::{ColorSpecs, StandardBuilder};
|
use grep::printer::{ColorSpecs, StandardBuilder};
|
||||||
use grep::regex::RegexMatcher;
|
use grep::regex::RegexMatcher;
|
||||||
use grep::searcher::{BinaryDetection, SearcherBuilder};
|
use grep::searcher::{BinaryDetection, SearcherBuilder};
|
||||||
use termcolor::{ColorChoice, StandardStream};
|
use termcolor::ColorChoice;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
macro_rules! fail {
|
|
||||||
($($tt:tt)*) => {
|
|
||||||
return Err(From::from(format!($($tt)*)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Box<error::Error>>;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if let Err(err) = try_main() {
|
if let Err(err) = try_main() {
|
||||||
eprintln!("{}", err);
|
eprintln!("{}", err);
|
||||||
@@ -31,45 +21,39 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_main() -> Result<()> {
|
fn try_main() -> Result<(), Box<Error>> {
|
||||||
let mut args: Vec<OsString> = env::args_os().collect();
|
let mut args: Vec<OsString> = env::args_os().collect();
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
fail!("Usage: simplegrep <pattern> [<path> ...]");
|
return Err("Usage: simplegrep <pattern> [<path> ...]".into());
|
||||||
}
|
}
|
||||||
if args.len() == 2 {
|
if args.len() == 2 {
|
||||||
args.push(OsString::from("./"));
|
args.push(OsString::from("./"));
|
||||||
}
|
}
|
||||||
let pattern = match args[1].clone().into_string() {
|
search(cli::pattern_from_os(&args[1])?, &args[2..])
|
||||||
Ok(pattern) => pattern,
|
|
||||||
Err(_) => {
|
|
||||||
fail!(
|
|
||||||
"pattern is not valid UTF-8: '{:?}'",
|
|
||||||
args[1].to_string_lossy()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
search(&pattern, &args[2..])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search(pattern: &str, paths: &[OsString]) -> Result<()> {
|
fn search(pattern: &str, paths: &[OsString]) -> Result<(), Box<Error>> {
|
||||||
let matcher = RegexMatcher::new_line_matcher(&pattern)?;
|
let matcher = RegexMatcher::new_line_matcher(&pattern)?;
|
||||||
let mut searcher = SearcherBuilder::new()
|
let mut searcher = SearcherBuilder::new()
|
||||||
.binary_detection(BinaryDetection::quit(b'\x00'))
|
.binary_detection(BinaryDetection::quit(b'\x00'))
|
||||||
|
.line_number(false)
|
||||||
.build();
|
.build();
|
||||||
let mut printer = StandardBuilder::new()
|
let mut printer = StandardBuilder::new()
|
||||||
.color_specs(colors())
|
.color_specs(ColorSpecs::default_with_color())
|
||||||
.build(StandardStream::stdout(color_choice()));
|
.build(cli::stdout(
|
||||||
|
if cli::is_tty_stdout() {
|
||||||
|
ColorChoice::Auto
|
||||||
|
} else {
|
||||||
|
ColorChoice::Never
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
for path in paths {
|
for path in paths {
|
||||||
for result in WalkDir::new(path) {
|
for result in WalkDir::new(path) {
|
||||||
let dent = match result {
|
let dent = match result {
|
||||||
Ok(dent) => dent,
|
Ok(dent) => dent,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!(
|
eprintln!("{}", err);
|
||||||
"{}: {}",
|
|
||||||
err.path().unwrap_or(Path::new("error")).display(),
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -88,20 +72,3 @@ fn search(pattern: &str, paths: &[OsString]) -> Result<()> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color_choice() -> ColorChoice {
|
|
||||||
if atty::is(atty::Stream::Stdout) {
|
|
||||||
ColorChoice::Auto
|
|
||||||
} else {
|
|
||||||
ColorChoice::Never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn colors() -> ColorSpecs {
|
|
||||||
ColorSpecs::new(&[
|
|
||||||
"path:fg:magenta".parse().unwrap(),
|
|
||||||
"line:fg:green".parse().unwrap(),
|
|
||||||
"match:fg:red".parse().unwrap(),
|
|
||||||
"match:style:bold".parse().unwrap(),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
@@ -14,6 +14,7 @@ A cookbook and a guide are planned.
|
|||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
pub extern crate grep_cli as cli;
|
||||||
pub extern crate grep_matcher as matcher;
|
pub extern crate grep_matcher as matcher;
|
||||||
#[cfg(feature = "pcre2")]
|
#[cfg(feature = "pcre2")]
|
||||||
pub extern crate grep_pcre2 as pcre2;
|
pub extern crate grep_pcre2 as pcre2;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ignore"
|
name = "ignore"
|
||||||
version = "0.4.3" #:version
|
version = "0.4.6" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
A fast library for efficiently matching ignore files such as `.gitignore`
|
A fast library for efficiently matching ignore files such as `.gitignore`
|
||||||
@@ -18,22 +18,21 @@ name = "ignore"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam = "0.3"
|
crossbeam-channel = "0.3.6"
|
||||||
globset = { version = "0.4.0", path = "../globset" }
|
globset = { version = "0.4.2", path = "../globset" }
|
||||||
lazy_static = "1"
|
lazy_static = "1.1"
|
||||||
log = "0.4"
|
log = "0.4.5"
|
||||||
memchr = "2"
|
memchr = "2.1"
|
||||||
regex = "1"
|
regex = "1.1"
|
||||||
same-file = "1"
|
same-file = "1.0.4"
|
||||||
thread_local = "0.3.2"
|
thread_local = "0.3.6"
|
||||||
walkdir = "2.2.0"
|
walkdir = "2.2.7"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.winapi]
|
[target.'cfg(windows)'.dependencies.winapi-util]
|
||||||
version = "0.3"
|
version = "0.1.2"
|
||||||
features = ["std", "winnt"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempdir = "0.3.5"
|
tempfile = "3.0.5"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
simd-accel = ["globset/simd-accel"]
|
simd-accel = ["globset/simd-accel"]
|
||||||
|
@@ -1,14 +1,12 @@
|
|||||||
extern crate crossbeam;
|
extern crate crossbeam_channel as channel;
|
||||||
extern crate ignore;
|
extern crate ignore;
|
||||||
extern crate walkdir;
|
extern crate walkdir;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use crossbeam::sync::MsQueue;
|
|
||||||
use ignore::WalkBuilder;
|
use ignore::WalkBuilder;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
@@ -16,7 +14,7 @@ fn main() {
|
|||||||
let mut path = env::args().nth(1).unwrap();
|
let mut path = env::args().nth(1).unwrap();
|
||||||
let mut parallel = false;
|
let mut parallel = false;
|
||||||
let mut simple = false;
|
let mut simple = false;
|
||||||
let queue: Arc<MsQueue<Option<DirEntry>>> = Arc::new(MsQueue::new());
|
let (tx, rx) = channel::bounded::<DirEntry>(100);
|
||||||
if path == "parallel" {
|
if path == "parallel" {
|
||||||
path = env::args().nth(2).unwrap();
|
path = env::args().nth(2).unwrap();
|
||||||
parallel = true;
|
parallel = true;
|
||||||
@@ -25,10 +23,9 @@ fn main() {
|
|||||||
simple = true;
|
simple = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let stdout_queue = queue.clone();
|
|
||||||
let stdout_thread = thread::spawn(move || {
|
let stdout_thread = thread::spawn(move || {
|
||||||
let mut stdout = io::BufWriter::new(io::stdout());
|
let mut stdout = io::BufWriter::new(io::stdout());
|
||||||
while let Some(dent) = stdout_queue.pop() {
|
for dent in rx {
|
||||||
write_path(&mut stdout, dent.path());
|
write_path(&mut stdout, dent.path());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -36,26 +33,26 @@ fn main() {
|
|||||||
if parallel {
|
if parallel {
|
||||||
let walker = WalkBuilder::new(path).threads(6).build_parallel();
|
let walker = WalkBuilder::new(path).threads(6).build_parallel();
|
||||||
walker.run(|| {
|
walker.run(|| {
|
||||||
let queue = queue.clone();
|
let tx = tx.clone();
|
||||||
Box::new(move |result| {
|
Box::new(move |result| {
|
||||||
use ignore::WalkState::*;
|
use ignore::WalkState::*;
|
||||||
|
|
||||||
queue.push(Some(DirEntry::Y(result.unwrap())));
|
tx.send(DirEntry::Y(result.unwrap())).unwrap();
|
||||||
Continue
|
Continue
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
} else if simple {
|
} else if simple {
|
||||||
let walker = WalkDir::new(path);
|
let walker = WalkDir::new(path);
|
||||||
for result in walker {
|
for result in walker {
|
||||||
queue.push(Some(DirEntry::X(result.unwrap())));
|
tx.send(DirEntry::X(result.unwrap())).unwrap();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let walker = WalkBuilder::new(path).build();
|
let walker = WalkBuilder::new(path).build();
|
||||||
for result in walker {
|
for result in walker {
|
||||||
queue.push(Some(DirEntry::Y(result.unwrap())));
|
tx.send(DirEntry::Y(result.unwrap())).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queue.push(None);
|
drop(tx);
|
||||||
stdout_thread.join().unwrap();
|
stdout_thread.join().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@ use gitignore::{self, Gitignore, GitignoreBuilder};
|
|||||||
use pathutil::{is_hidden, strip_prefix};
|
use pathutil::{is_hidden, strip_prefix};
|
||||||
use overrides::{self, Override};
|
use overrides::{self, Override};
|
||||||
use types::{self, Types};
|
use types::{self, Types};
|
||||||
|
use walk::DirEntry;
|
||||||
use {Error, Match, PartialErrorBuilder};
|
use {Error, Match, PartialErrorBuilder};
|
||||||
|
|
||||||
/// IgnoreMatch represents information about where a match came from when using
|
/// IgnoreMatch represents information about where a match came from when using
|
||||||
@@ -73,6 +74,8 @@ struct IgnoreOptions {
|
|||||||
git_ignore: bool,
|
git_ignore: bool,
|
||||||
/// Whether to read .git/info/exclude files.
|
/// Whether to read .git/info/exclude files.
|
||||||
git_exclude: bool,
|
git_exclude: bool,
|
||||||
|
/// Whether to ignore files case insensitively
|
||||||
|
ignore_case_insensitive: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ignore is a matcher useful for recursively walking one or more directories.
|
/// Ignore is a matcher useful for recursively walking one or more directories.
|
||||||
@@ -225,7 +228,11 @@ impl Ignore {
|
|||||||
Gitignore::empty()
|
Gitignore::empty()
|
||||||
} else {
|
} else {
|
||||||
let (m, err) =
|
let (m, err) =
|
||||||
create_gitignore(&dir, &self.0.custom_ignore_filenames);
|
create_gitignore(
|
||||||
|
&dir,
|
||||||
|
&self.0.custom_ignore_filenames,
|
||||||
|
self.0.opts.ignore_case_insensitive,
|
||||||
|
);
|
||||||
errs.maybe_push(err);
|
errs.maybe_push(err);
|
||||||
m
|
m
|
||||||
};
|
};
|
||||||
@@ -233,7 +240,12 @@ impl Ignore {
|
|||||||
if !self.0.opts.ignore {
|
if !self.0.opts.ignore {
|
||||||
Gitignore::empty()
|
Gitignore::empty()
|
||||||
} else {
|
} else {
|
||||||
let (m, err) = create_gitignore(&dir, &[".ignore"]);
|
let (m, err) =
|
||||||
|
create_gitignore(
|
||||||
|
&dir,
|
||||||
|
&[".ignore"],
|
||||||
|
self.0.opts.ignore_case_insensitive,
|
||||||
|
);
|
||||||
errs.maybe_push(err);
|
errs.maybe_push(err);
|
||||||
m
|
m
|
||||||
};
|
};
|
||||||
@@ -241,7 +253,12 @@ impl Ignore {
|
|||||||
if !self.0.opts.git_ignore {
|
if !self.0.opts.git_ignore {
|
||||||
Gitignore::empty()
|
Gitignore::empty()
|
||||||
} else {
|
} else {
|
||||||
let (m, err) = create_gitignore(&dir, &[".gitignore"]);
|
let (m, err) =
|
||||||
|
create_gitignore(
|
||||||
|
&dir,
|
||||||
|
&[".gitignore"],
|
||||||
|
self.0.opts.ignore_case_insensitive,
|
||||||
|
);
|
||||||
errs.maybe_push(err);
|
errs.maybe_push(err);
|
||||||
m
|
m
|
||||||
};
|
};
|
||||||
@@ -249,7 +266,12 @@ impl Ignore {
|
|||||||
if !self.0.opts.git_exclude {
|
if !self.0.opts.git_exclude {
|
||||||
Gitignore::empty()
|
Gitignore::empty()
|
||||||
} else {
|
} else {
|
||||||
let (m, err) = create_gitignore(&dir, &[".git/info/exclude"]);
|
let (m, err) =
|
||||||
|
create_gitignore(
|
||||||
|
&dir,
|
||||||
|
&[".git/info/exclude"],
|
||||||
|
self.0.opts.ignore_case_insensitive,
|
||||||
|
);
|
||||||
errs.maybe_push(err);
|
errs.maybe_push(err);
|
||||||
m
|
m
|
||||||
};
|
};
|
||||||
@@ -285,11 +307,23 @@ impl Ignore {
|
|||||||
|| has_explicit_ignores
|
|| has_explicit_ignores
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like `matched`, but works with a directory entry instead.
|
||||||
|
pub fn matched_dir_entry<'a>(
|
||||||
|
&'a self,
|
||||||
|
dent: &DirEntry,
|
||||||
|
) -> Match<IgnoreMatch<'a>> {
|
||||||
|
let m = self.matched(dent.path(), dent.is_dir());
|
||||||
|
if m.is_none() && self.0.opts.hidden && is_hidden(dent) {
|
||||||
|
return Match::Ignore(IgnoreMatch::hidden());
|
||||||
|
}
|
||||||
|
m
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a match indicating whether the given file path should be
|
/// Returns a match indicating whether the given file path should be
|
||||||
/// ignored or not.
|
/// ignored or not.
|
||||||
///
|
///
|
||||||
/// The match contains information about its origin.
|
/// The match contains information about its origin.
|
||||||
pub fn matched<'a, P: AsRef<Path>>(
|
fn matched<'a, P: AsRef<Path>>(
|
||||||
&'a self,
|
&'a self,
|
||||||
path: P,
|
path: P,
|
||||||
is_dir: bool,
|
is_dir: bool,
|
||||||
@@ -330,9 +364,6 @@ impl Ignore {
|
|||||||
whitelisted = mat;
|
whitelisted = mat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if whitelisted.is_none() && self.0.opts.hidden && is_hidden(path) {
|
|
||||||
return Match::Ignore(IgnoreMatch::hidden());
|
|
||||||
}
|
|
||||||
whitelisted
|
whitelisted
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,6 +514,7 @@ impl IgnoreBuilder {
|
|||||||
git_global: true,
|
git_global: true,
|
||||||
git_ignore: true,
|
git_ignore: true,
|
||||||
git_exclude: true,
|
git_exclude: true,
|
||||||
|
ignore_case_insensitive: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -496,7 +528,11 @@ impl IgnoreBuilder {
|
|||||||
if !self.opts.git_global {
|
if !self.opts.git_global {
|
||||||
Gitignore::empty()
|
Gitignore::empty()
|
||||||
} else {
|
} else {
|
||||||
let (gi, err) = Gitignore::global();
|
let mut builder = GitignoreBuilder::new("");
|
||||||
|
builder
|
||||||
|
.case_insensitive(self.opts.ignore_case_insensitive)
|
||||||
|
.unwrap();
|
||||||
|
let (gi, err) = builder.build_global();
|
||||||
if let Some(err) = err {
|
if let Some(err) = err {
|
||||||
debug!("{}", err);
|
debug!("{}", err);
|
||||||
}
|
}
|
||||||
@@ -627,6 +663,17 @@ impl IgnoreBuilder {
|
|||||||
self.opts.git_exclude = yes;
|
self.opts.git_exclude = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Process ignore files case insensitively
|
||||||
|
///
|
||||||
|
/// This is disabled by default.
|
||||||
|
pub fn ignore_case_insensitive(
|
||||||
|
&mut self,
|
||||||
|
yes: bool,
|
||||||
|
) -> &mut IgnoreBuilder {
|
||||||
|
self.opts.ignore_case_insensitive = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new gitignore matcher for the directory given.
|
/// Creates a new gitignore matcher for the directory given.
|
||||||
@@ -638,9 +685,11 @@ impl IgnoreBuilder {
|
|||||||
pub fn create_gitignore<T: AsRef<OsStr>>(
|
pub fn create_gitignore<T: AsRef<OsStr>>(
|
||||||
dir: &Path,
|
dir: &Path,
|
||||||
names: &[T],
|
names: &[T],
|
||||||
|
case_insensitive: bool,
|
||||||
) -> (Gitignore, Option<Error>) {
|
) -> (Gitignore, Option<Error>) {
|
||||||
let mut builder = GitignoreBuilder::new(dir);
|
let mut builder = GitignoreBuilder::new(dir);
|
||||||
let mut errs = PartialErrorBuilder::default();
|
let mut errs = PartialErrorBuilder::default();
|
||||||
|
builder.case_insensitive(case_insensitive).unwrap();
|
||||||
for name in names {
|
for name in names {
|
||||||
let gipath = dir.join(name.as_ref());
|
let gipath = dir.join(name.as_ref());
|
||||||
errs.maybe_push_ignore_io(builder.add(gipath));
|
errs.maybe_push_ignore_io(builder.add(gipath));
|
||||||
@@ -661,7 +710,7 @@ mod tests {
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use tempdir::TempDir;
|
use tempfile::{self, TempDir};
|
||||||
|
|
||||||
use dir::IgnoreBuilder;
|
use dir::IgnoreBuilder;
|
||||||
use gitignore::Gitignore;
|
use gitignore::Gitignore;
|
||||||
@@ -683,9 +732,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tmpdir(prefix: &str) -> TempDir {
|
||||||
|
tempfile::Builder::new().prefix(prefix).tempdir().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn explicit_ignore() {
|
fn explicit_ignore() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
wfile(td.path().join("not-an-ignore"), "foo\n!bar");
|
wfile(td.path().join("not-an-ignore"), "foo\n!bar");
|
||||||
|
|
||||||
let (gi, err) = Gitignore::new(td.path().join("not-an-ignore"));
|
let (gi, err) = Gitignore::new(td.path().join("not-an-ignore"));
|
||||||
@@ -700,7 +753,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn git_exclude() {
|
fn git_exclude() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
mkdirp(td.path().join(".git/info"));
|
mkdirp(td.path().join(".git/info"));
|
||||||
wfile(td.path().join(".git/info/exclude"), "foo\n!bar");
|
wfile(td.path().join(".git/info/exclude"), "foo\n!bar");
|
||||||
|
|
||||||
@@ -713,7 +766,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gitignore() {
|
fn gitignore() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
mkdirp(td.path().join(".git"));
|
mkdirp(td.path().join(".git"));
|
||||||
wfile(td.path().join(".gitignore"), "foo\n!bar");
|
wfile(td.path().join(".gitignore"), "foo\n!bar");
|
||||||
|
|
||||||
@@ -726,7 +779,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gitignore_no_git() {
|
fn gitignore_no_git() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
wfile(td.path().join(".gitignore"), "foo\n!bar");
|
wfile(td.path().join(".gitignore"), "foo\n!bar");
|
||||||
|
|
||||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||||
@@ -738,7 +791,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ignore() {
|
fn ignore() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
wfile(td.path().join(".ignore"), "foo\n!bar");
|
wfile(td.path().join(".ignore"), "foo\n!bar");
|
||||||
|
|
||||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||||
@@ -750,7 +803,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn custom_ignore() {
|
fn custom_ignore() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
let custom_ignore = ".customignore";
|
let custom_ignore = ".customignore";
|
||||||
wfile(td.path().join(custom_ignore), "foo\n!bar");
|
wfile(td.path().join(custom_ignore), "foo\n!bar");
|
||||||
|
|
||||||
@@ -766,7 +819,7 @@ mod tests {
|
|||||||
// Tests that a custom ignore file will override an .ignore.
|
// Tests that a custom ignore file will override an .ignore.
|
||||||
#[test]
|
#[test]
|
||||||
fn custom_ignore_over_ignore() {
|
fn custom_ignore_over_ignore() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
let custom_ignore = ".customignore";
|
let custom_ignore = ".customignore";
|
||||||
wfile(td.path().join(".ignore"), "foo");
|
wfile(td.path().join(".ignore"), "foo");
|
||||||
wfile(td.path().join(custom_ignore), "!foo");
|
wfile(td.path().join(custom_ignore), "!foo");
|
||||||
@@ -781,7 +834,7 @@ mod tests {
|
|||||||
// Tests that earlier custom ignore files have lower precedence than later.
|
// Tests that earlier custom ignore files have lower precedence than later.
|
||||||
#[test]
|
#[test]
|
||||||
fn custom_ignore_precedence() {
|
fn custom_ignore_precedence() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
let custom_ignore1 = ".customignore1";
|
let custom_ignore1 = ".customignore1";
|
||||||
let custom_ignore2 = ".customignore2";
|
let custom_ignore2 = ".customignore2";
|
||||||
wfile(td.path().join(custom_ignore1), "foo");
|
wfile(td.path().join(custom_ignore1), "foo");
|
||||||
@@ -798,7 +851,7 @@ mod tests {
|
|||||||
// Tests that an .ignore will override a .gitignore.
|
// Tests that an .ignore will override a .gitignore.
|
||||||
#[test]
|
#[test]
|
||||||
fn ignore_over_gitignore() {
|
fn ignore_over_gitignore() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
wfile(td.path().join(".gitignore"), "foo");
|
wfile(td.path().join(".gitignore"), "foo");
|
||||||
wfile(td.path().join(".ignore"), "!foo");
|
wfile(td.path().join(".ignore"), "!foo");
|
||||||
|
|
||||||
@@ -810,7 +863,7 @@ mod tests {
|
|||||||
// Tests that exclude has lower precedent than both .ignore and .gitignore.
|
// Tests that exclude has lower precedent than both .ignore and .gitignore.
|
||||||
#[test]
|
#[test]
|
||||||
fn exclude_lowest() {
|
fn exclude_lowest() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
wfile(td.path().join(".gitignore"), "!foo");
|
wfile(td.path().join(".gitignore"), "!foo");
|
||||||
wfile(td.path().join(".ignore"), "!bar");
|
wfile(td.path().join(".ignore"), "!bar");
|
||||||
mkdirp(td.path().join(".git/info"));
|
mkdirp(td.path().join(".git/info"));
|
||||||
@@ -825,8 +878,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn errored() {
|
fn errored() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
wfile(td.path().join(".gitignore"), "f**oo");
|
wfile(td.path().join(".gitignore"), "{foo");
|
||||||
|
|
||||||
let (_, err) = IgnoreBuilder::new().build().add_child(td.path());
|
let (_, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||||
assert!(err.is_some());
|
assert!(err.is_some());
|
||||||
@@ -834,9 +887,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn errored_both() {
|
fn errored_both() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
wfile(td.path().join(".gitignore"), "f**oo");
|
wfile(td.path().join(".gitignore"), "{foo");
|
||||||
wfile(td.path().join(".ignore"), "fo**o");
|
wfile(td.path().join(".ignore"), "{bar");
|
||||||
|
|
||||||
let (_, err) = IgnoreBuilder::new().build().add_child(td.path());
|
let (_, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||||
assert_eq!(2, partial(err.expect("an error")).len());
|
assert_eq!(2, partial(err.expect("an error")).len());
|
||||||
@@ -844,9 +897,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn errored_partial() {
|
fn errored_partial() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
mkdirp(td.path().join(".git"));
|
mkdirp(td.path().join(".git"));
|
||||||
wfile(td.path().join(".gitignore"), "f**oo\nbar");
|
wfile(td.path().join(".gitignore"), "{foo\nbar");
|
||||||
|
|
||||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||||
assert!(err.is_some());
|
assert!(err.is_some());
|
||||||
@@ -855,8 +908,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn errored_partial_and_ignore() {
|
fn errored_partial_and_ignore() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
wfile(td.path().join(".gitignore"), "f**oo\nbar");
|
wfile(td.path().join(".gitignore"), "{foo\nbar");
|
||||||
wfile(td.path().join(".ignore"), "!bar");
|
wfile(td.path().join(".ignore"), "!bar");
|
||||||
|
|
||||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||||
@@ -866,7 +919,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn not_present_empty() {
|
fn not_present_empty() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
|
|
||||||
let (_, err) = IgnoreBuilder::new().build().add_child(td.path());
|
let (_, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||||
assert!(err.is_none());
|
assert!(err.is_none());
|
||||||
@@ -876,7 +929,7 @@ mod tests {
|
|||||||
fn stops_at_git_dir() {
|
fn stops_at_git_dir() {
|
||||||
// This tests that .gitignore files beyond a .git barrier aren't
|
// This tests that .gitignore files beyond a .git barrier aren't
|
||||||
// matched, but .ignore files are.
|
// matched, but .ignore files are.
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
mkdirp(td.path().join(".git"));
|
mkdirp(td.path().join(".git"));
|
||||||
mkdirp(td.path().join("foo/.git"));
|
mkdirp(td.path().join("foo/.git"));
|
||||||
wfile(td.path().join(".gitignore"), "foo");
|
wfile(td.path().join(".gitignore"), "foo");
|
||||||
@@ -897,7 +950,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn absolute_parent() {
|
fn absolute_parent() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
mkdirp(td.path().join(".git"));
|
mkdirp(td.path().join(".git"));
|
||||||
mkdirp(td.path().join("foo"));
|
mkdirp(td.path().join("foo"));
|
||||||
wfile(td.path().join(".gitignore"), "bar");
|
wfile(td.path().join(".gitignore"), "bar");
|
||||||
@@ -920,7 +973,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn absolute_parent_anchored() {
|
fn absolute_parent_anchored() {
|
||||||
let td = TempDir::new("ignore-test-").unwrap();
|
let td = tmpdir("ignore-test-");
|
||||||
mkdirp(td.path().join(".git"));
|
mkdirp(td.path().join(".git"));
|
||||||
mkdirp(td.path().join("src/llvm"));
|
mkdirp(td.path().join("src/llvm"));
|
||||||
wfile(td.path().join(".gitignore"), "/llvm/\nfoo");
|
wfile(td.path().join(".gitignore"), "/llvm/\nfoo");
|
||||||
|
@@ -69,8 +69,7 @@ impl Glob {
|
|||||||
|
|
||||||
/// Returns true if and only if this glob has a `**/` prefix.
|
/// Returns true if and only if this glob has a `**/` prefix.
|
||||||
fn has_doublestar_prefix(&self) -> bool {
|
fn has_doublestar_prefix(&self) -> bool {
|
||||||
self.actual.starts_with("**/")
|
self.actual.starts_with("**/") || self.actual == "**"
|
||||||
|| (self.actual == "**" && self.is_only_dir)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,16 +126,7 @@ impl Gitignore {
|
|||||||
/// `$XDG_CONFIG_HOME/git/ignore` is read. If `$XDG_CONFIG_HOME` is not
|
/// `$XDG_CONFIG_HOME/git/ignore` is read. If `$XDG_CONFIG_HOME` is not
|
||||||
/// set or is empty, then `$HOME/.config/git/ignore` is used instead.
|
/// set or is empty, then `$HOME/.config/git/ignore` is used instead.
|
||||||
pub fn global() -> (Gitignore, Option<Error>) {
|
pub fn global() -> (Gitignore, Option<Error>) {
|
||||||
match gitconfig_excludes_path() {
|
GitignoreBuilder::new("").build_global()
|
||||||
None => (Gitignore::empty(), None),
|
|
||||||
Some(path) => {
|
|
||||||
if !path.is_file() {
|
|
||||||
(Gitignore::empty(), None)
|
|
||||||
} else {
|
|
||||||
Gitignore::new(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new empty gitignore matcher that never matches anything.
|
/// Creates a new empty gitignore matcher that never matches anything.
|
||||||
@@ -359,6 +349,36 @@ impl GitignoreBuilder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a global gitignore matcher using the configuration in this
|
||||||
|
/// builder.
|
||||||
|
///
|
||||||
|
/// This consumes ownership of the builder unlike `build` because it
|
||||||
|
/// must mutate the builder to add the global gitignore globs.
|
||||||
|
///
|
||||||
|
/// Note that this ignores the path given to this builder's constructor
|
||||||
|
/// and instead derives the path automatically from git's global
|
||||||
|
/// configuration.
|
||||||
|
pub fn build_global(mut self) -> (Gitignore, Option<Error>) {
|
||||||
|
match gitconfig_excludes_path() {
|
||||||
|
None => (Gitignore::empty(), None),
|
||||||
|
Some(path) => {
|
||||||
|
if !path.is_file() {
|
||||||
|
(Gitignore::empty(), None)
|
||||||
|
} else {
|
||||||
|
let mut errs = PartialErrorBuilder::default();
|
||||||
|
errs.maybe_push_ignore_io(self.add(path));
|
||||||
|
match self.build() {
|
||||||
|
Ok(gi) => (gi, errs.into_error_option()),
|
||||||
|
Err(err) => {
|
||||||
|
errs.push(err);
|
||||||
|
(Gitignore::empty(), errs.into_error_option())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add each glob from the file path given.
|
/// Add each glob from the file path given.
|
||||||
///
|
///
|
||||||
/// The file given should be formatted as a `gitignore` file.
|
/// The file given should be formatted as a `gitignore` file.
|
||||||
@@ -419,6 +439,8 @@ impl GitignoreBuilder {
|
|||||||
from: Option<PathBuf>,
|
from: Option<PathBuf>,
|
||||||
mut line: &str,
|
mut line: &str,
|
||||||
) -> Result<&mut GitignoreBuilder, Error> {
|
) -> Result<&mut GitignoreBuilder, Error> {
|
||||||
|
#![allow(deprecated)]
|
||||||
|
|
||||||
if line.starts_with("#") {
|
if line.starts_with("#") {
|
||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
@@ -435,7 +457,6 @@ impl GitignoreBuilder {
|
|||||||
is_whitelist: false,
|
is_whitelist: false,
|
||||||
is_only_dir: false,
|
is_only_dir: false,
|
||||||
};
|
};
|
||||||
let mut literal_separator = false;
|
|
||||||
let mut is_absolute = false;
|
let mut is_absolute = false;
|
||||||
if line.starts_with("\\!") || line.starts_with("\\#") {
|
if line.starts_with("\\!") || line.starts_with("\\#") {
|
||||||
line = &line[1..];
|
line = &line[1..];
|
||||||
@@ -450,7 +471,6 @@ impl GitignoreBuilder {
|
|||||||
// then the glob can only match the beginning of a path
|
// then the glob can only match the beginning of a path
|
||||||
// (relative to the location of gitignore). We achieve this by
|
// (relative to the location of gitignore). We achieve this by
|
||||||
// simply banning wildcards from matching /.
|
// simply banning wildcards from matching /.
|
||||||
literal_separator = true;
|
|
||||||
line = &line[1..];
|
line = &line[1..];
|
||||||
is_absolute = true;
|
is_absolute = true;
|
||||||
}
|
}
|
||||||
@@ -463,16 +483,11 @@ impl GitignoreBuilder {
|
|||||||
line = &line[..i];
|
line = &line[..i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If there is a literal slash, then we note that so that globbing
|
|
||||||
// doesn't let wildcards match slashes.
|
|
||||||
glob.actual = line.to_string();
|
glob.actual = line.to_string();
|
||||||
if is_absolute || line.chars().any(|c| c == '/') {
|
// If there is a literal slash, then this is a glob that must match the
|
||||||
literal_separator = true;
|
// entire path name. Otherwise, we should let it match anywhere, so use
|
||||||
}
|
// a **/ prefix.
|
||||||
// If there was a slash, then this is a glob that must match the entire
|
if !is_absolute && !line.chars().any(|c| c == '/') {
|
||||||
// path name. Otherwise, we should let it match anywhere, so use a **/
|
|
||||||
// prefix.
|
|
||||||
if !literal_separator {
|
|
||||||
// ... but only if we don't already have a **/ prefix.
|
// ... but only if we don't already have a **/ prefix.
|
||||||
if !glob.has_doublestar_prefix() {
|
if !glob.has_doublestar_prefix() {
|
||||||
glob.actual = format!("**/{}", glob.actual);
|
glob.actual = format!("**/{}", glob.actual);
|
||||||
@@ -486,7 +501,7 @@ impl GitignoreBuilder {
|
|||||||
}
|
}
|
||||||
let parsed =
|
let parsed =
|
||||||
GlobBuilder::new(&glob.actual)
|
GlobBuilder::new(&glob.actual)
|
||||||
.literal_separator(literal_separator)
|
.literal_separator(true)
|
||||||
.case_insensitive(self.case_insensitive)
|
.case_insensitive(self.case_insensitive)
|
||||||
.backslash_escape(true)
|
.backslash_escape(true)
|
||||||
.build()
|
.build()
|
||||||
@@ -503,12 +518,16 @@ impl GitignoreBuilder {
|
|||||||
|
|
||||||
/// Toggle whether the globs should be matched case insensitively or not.
|
/// Toggle whether the globs should be matched case insensitively or not.
|
||||||
///
|
///
|
||||||
/// When this option is changed, only globs added after the change will be affected.
|
/// When this option is changed, only globs added after the change will be
|
||||||
|
/// affected.
|
||||||
///
|
///
|
||||||
/// This is disabled by default.
|
/// This is disabled by default.
|
||||||
pub fn case_insensitive(
|
pub fn case_insensitive(
|
||||||
&mut self, yes: bool
|
&mut self,
|
||||||
|
yes: bool,
|
||||||
) -> Result<&mut GitignoreBuilder, Error> {
|
) -> Result<&mut GitignoreBuilder, Error> {
|
||||||
|
// TODO: This should not return a `Result`. Fix this in the next semver
|
||||||
|
// release.
|
||||||
self.case_insensitive = yes;
|
self.case_insensitive = yes;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
@@ -689,6 +708,9 @@ mod tests {
|
|||||||
ignored!(ig39, ROOT, "\\?", "?");
|
ignored!(ig39, ROOT, "\\?", "?");
|
||||||
ignored!(ig40, ROOT, "\\*", "*");
|
ignored!(ig40, ROOT, "\\*", "*");
|
||||||
ignored!(ig41, ROOT, "\\a", "a");
|
ignored!(ig41, ROOT, "\\a", "a");
|
||||||
|
ignored!(ig42, ROOT, "s*.rs", "sfoo.rs");
|
||||||
|
ignored!(ig43, ROOT, "**", "foo.rs");
|
||||||
|
ignored!(ig44, ROOT, "**/**/*", "a/foo.rs");
|
||||||
|
|
||||||
not_ignored!(ignot1, ROOT, "amonths", "months");
|
not_ignored!(ignot1, ROOT, "amonths", "months");
|
||||||
not_ignored!(ignot2, ROOT, "monthsa", "months");
|
not_ignored!(ignot2, ROOT, "monthsa", "months");
|
||||||
@@ -710,6 +732,7 @@ mod tests {
|
|||||||
not_ignored!(ignot16, ROOT, "*\n!**/", "foo", true);
|
not_ignored!(ignot16, ROOT, "*\n!**/", "foo", true);
|
||||||
not_ignored!(ignot17, ROOT, "src/*.rs", "src/grep/src/main.rs");
|
not_ignored!(ignot17, ROOT, "src/*.rs", "src/grep/src/main.rs");
|
||||||
not_ignored!(ignot18, ROOT, "path1/*", "path2/path1/foo");
|
not_ignored!(ignot18, ROOT, "path1/*", "path2/path1/foo");
|
||||||
|
not_ignored!(ignot19, ROOT, "s*.rs", "src/foo.rs");
|
||||||
|
|
||||||
fn bytes(s: &str) -> Vec<u8> {
|
fn bytes(s: &str) -> Vec<u8> {
|
||||||
s.to_string().into_bytes()
|
s.to_string().into_bytes()
|
||||||
|
@@ -46,7 +46,7 @@ See the documentation for `WalkBuilder` for many other options.
|
|||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
extern crate crossbeam;
|
extern crate crossbeam_channel as channel;
|
||||||
extern crate globset;
|
extern crate globset;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
@@ -56,11 +56,11 @@ extern crate memchr;
|
|||||||
extern crate regex;
|
extern crate regex;
|
||||||
extern crate same_file;
|
extern crate same_file;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate tempdir;
|
extern crate tempfile;
|
||||||
extern crate thread_local;
|
extern crate thread_local;
|
||||||
extern crate walkdir;
|
extern crate walkdir;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
extern crate winapi;
|
extern crate winapi_util;
|
||||||
|
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@@ -144,8 +144,11 @@ impl OverrideBuilder {
|
|||||||
///
|
///
|
||||||
/// This is disabled by default.
|
/// This is disabled by default.
|
||||||
pub fn case_insensitive(
|
pub fn case_insensitive(
|
||||||
&mut self, yes: bool
|
&mut self,
|
||||||
|
yes: bool,
|
||||||
) -> Result<&mut OverrideBuilder, Error> {
|
) -> Result<&mut OverrideBuilder, Error> {
|
||||||
|
// TODO: This should not return a `Result`. Fix this in the next semver
|
||||||
|
// release.
|
||||||
self.builder.case_insensitive(yes)?;
|
self.builder.case_insensitive(yes)?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
@@ -1,22 +1,56 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
/// Returns true if and only if this file path is considered to be hidden.
|
use walk::DirEntry;
|
||||||
|
|
||||||
|
/// Returns true if and only if this entry is considered to be hidden.
|
||||||
|
///
|
||||||
|
/// This only returns true if the base name of the path starts with a `.`.
|
||||||
|
///
|
||||||
|
/// On Unix, this implements a more optimized check.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn is_hidden<P: AsRef<Path>>(path: P) -> bool {
|
pub fn is_hidden(dent: &DirEntry) -> bool {
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
if let Some(name) = file_name(path.as_ref()) {
|
if let Some(name) = file_name(dent.path()) {
|
||||||
name.as_bytes().get(0) == Some(&b'.')
|
name.as_bytes().get(0) == Some(&b'.')
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this file path is considered to be hidden.
|
/// Returns true if and only if this entry is considered to be hidden.
|
||||||
#[cfg(not(unix))]
|
///
|
||||||
pub fn is_hidden<P: AsRef<Path>>(path: P) -> bool {
|
/// On Windows, this returns true if one of the following is true:
|
||||||
if let Some(name) = file_name(path.as_ref()) {
|
///
|
||||||
|
/// * The base name of the path starts with a `.`.
|
||||||
|
/// * The file attributes have the `HIDDEN` property set.
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn is_hidden(dent: &DirEntry) -> bool {
|
||||||
|
use std::os::windows::fs::MetadataExt;
|
||||||
|
use winapi_util::file;
|
||||||
|
|
||||||
|
// This looks like we're doing an extra stat call, but on Windows, the
|
||||||
|
// directory traverser reuses the metadata retrieved from each directory
|
||||||
|
// entry and stores it on the DirEntry itself. So this is "free."
|
||||||
|
if let Ok(md) = dent.metadata() {
|
||||||
|
if file::is_hidden(md.file_attributes() as u64) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(name) = file_name(dent.path()) {
|
||||||
|
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if this entry is considered to be hidden.
|
||||||
|
///
|
||||||
|
/// This only returns true if the base name of the path starts with a `.`.
|
||||||
|
#[cfg(not(any(unix, windows)))]
|
||||||
|
pub fn is_hidden(dent: &DirEntry) -> bool {
|
||||||
|
if let Some(name) = file_name(dent.path()) {
|
||||||
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@@ -103,11 +103,14 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("amake", &["*.mk", "*.bp"]),
|
("amake", &["*.mk", "*.bp"]),
|
||||||
("asciidoc", &["*.adoc", "*.asc", "*.asciidoc"]),
|
("asciidoc", &["*.adoc", "*.asc", "*.asciidoc"]),
|
||||||
("asm", &["*.asm", "*.s", "*.S"]),
|
("asm", &["*.asm", "*.s", "*.S"]),
|
||||||
|
("asp", &["*.aspx", "*.aspx.cs", "*.aspx.cs", "*.ascx", "*.ascx.cs", "*.ascx.vb"]),
|
||||||
("avro", &["*.avdl", "*.avpr", "*.avsc"]),
|
("avro", &["*.avdl", "*.avpr", "*.avsc"]),
|
||||||
("awk", &["*.awk"]),
|
("awk", &["*.awk"]),
|
||||||
("bazel", &["*.bzl", "WORKSPACE", "BUILD"]),
|
("bazel", &["*.bzl", "WORKSPACE", "BUILD", "BUILD.bazel"]),
|
||||||
("bitbake", &["*.bb", "*.bbappend", "*.bbclass", "*.conf", "*.inc"]),
|
("bitbake", &["*.bb", "*.bbappend", "*.bbclass", "*.conf", "*.inc"]),
|
||||||
("bzip2", &["*.bz2"]),
|
("brotli", &["*.br"]),
|
||||||
|
("buildstream", &["*.bst"]),
|
||||||
|
("bzip2", &["*.bz2", "*.tbz2"]),
|
||||||
("c", &["*.c", "*.h", "*.H", "*.cats"]),
|
("c", &["*.c", "*.h", "*.H", "*.cats"]),
|
||||||
("cabal", &["*.cabal"]),
|
("cabal", &["*.cabal"]),
|
||||||
("cbor", &["*.cbor"]),
|
("cbor", &["*.cbor"]),
|
||||||
@@ -127,7 +130,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("cshtml", &["*.cshtml"]),
|
("cshtml", &["*.cshtml"]),
|
||||||
("css", &["*.css", "*.scss"]),
|
("css", &["*.css", "*.scss"]),
|
||||||
("csv", &["*.csv"]),
|
("csv", &["*.csv"]),
|
||||||
("cython", &["*.pyx"]),
|
("cython", &["*.pyx", "*.pxi", "*.pxd"]),
|
||||||
("dart", &["*.dart"]),
|
("dart", &["*.dart"]),
|
||||||
("d", &["*.d"]),
|
("d", &["*.d"]),
|
||||||
("dhall", &["*.dhall"]),
|
("dhall", &["*.dhall"]),
|
||||||
@@ -145,7 +148,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("fsharp", &["*.fs", "*.fsx", "*.fsi"]),
|
("fsharp", &["*.fs", "*.fsx", "*.fsi"]),
|
||||||
("gn", &["*.gn", "*.gni"]),
|
("gn", &["*.gn", "*.gni"]),
|
||||||
("go", &["*.go"]),
|
("go", &["*.go"]),
|
||||||
("gzip", &["*.gz"]),
|
("gzip", &["*.gz", "*.tgz"]),
|
||||||
("groovy", &["*.groovy", "*.gradle"]),
|
("groovy", &["*.groovy", "*.gradle"]),
|
||||||
("h", &["*.h", "*.hpp"]),
|
("h", &["*.h", "*.hpp"]),
|
||||||
("hbs", &["*.hbs"]),
|
("hbs", &["*.hbs"]),
|
||||||
@@ -219,16 +222,19 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("objcpp", &["*.h", "*.mm"]),
|
("objcpp", &["*.h", "*.mm"]),
|
||||||
("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]),
|
("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]),
|
||||||
("org", &["*.org"]),
|
("org", &["*.org"]),
|
||||||
|
("pascal", &["*.pas", "*.dpr", "*.lpr", "*.pp", "*.inc"]),
|
||||||
("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm", "*.t"]),
|
("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm", "*.t"]),
|
||||||
("pdf", &["*.pdf"]),
|
("pdf", &["*.pdf"]),
|
||||||
("php", &["*.php", "*.php3", "*.php4", "*.php5", "*.phtml"]),
|
("php", &["*.php", "*.php3", "*.php4", "*.php5", "*.phtml"]),
|
||||||
("pod", &["*.pod"]),
|
("pod", &["*.pod"]),
|
||||||
|
("postscript", &[".eps", ".ps"]),
|
||||||
("protobuf", &["*.proto"]),
|
("protobuf", &["*.proto"]),
|
||||||
("ps", &["*.cdxml", "*.ps1", "*.ps1xml", "*.psd1", "*.psm1"]),
|
("ps", &["*.cdxml", "*.ps1", "*.ps1xml", "*.psd1", "*.psm1"]),
|
||||||
("puppet", &["*.erb", "*.pp", "*.rb"]),
|
("puppet", &["*.erb", "*.pp", "*.rb"]),
|
||||||
("purs", &["*.purs"]),
|
("purs", &["*.purs"]),
|
||||||
("py", &["*.py"]),
|
("py", &["*.py"]),
|
||||||
("qmake", &["*.pro", "*.pri", "*.prf"]),
|
("qmake", &["*.pro", "*.pri", "*.prf"]),
|
||||||
|
("qml", &["*.qml"]),
|
||||||
("readme", &["README*", "*README"]),
|
("readme", &["README*", "*README"]),
|
||||||
("r", &["*.R", "*.r", "*.Rmd", "*.Rnw"]),
|
("r", &["*.R", "*.r", "*.Rmd", "*.Rnw"]),
|
||||||
("rdoc", &["*.rdoc"]),
|
("rdoc", &["*.rdoc"]),
|
||||||
@@ -277,8 +283,9 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
]),
|
]),
|
||||||
("taskpaper", &["*.taskpaper"]),
|
("taskpaper", &["*.taskpaper"]),
|
||||||
("tcl", &["*.tcl"]),
|
("tcl", &["*.tcl"]),
|
||||||
("tex", &["*.tex", "*.ltx", "*.cls", "*.sty", "*.bib"]),
|
("tex", &["*.tex", "*.ltx", "*.cls", "*.sty", "*.bib", "*.dtx", "*.ins"]),
|
||||||
("textile", &["*.textile"]),
|
("textile", &["*.textile"]),
|
||||||
|
("thrift", &["*.thrift"]),
|
||||||
("tf", &["*.tf"]),
|
("tf", &["*.tf"]),
|
||||||
("ts", &["*.ts", "*.tsx"]),
|
("ts", &["*.ts", "*.tsx"]),
|
||||||
("txt", &["*.txt"]),
|
("txt", &["*.txt"]),
|
||||||
@@ -293,9 +300,10 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("wiki", &["*.mediawiki", "*.wiki"]),
|
("wiki", &["*.mediawiki", "*.wiki"]),
|
||||||
("webidl", &["*.idl", "*.webidl", "*.widl"]),
|
("webidl", &["*.idl", "*.webidl", "*.widl"]),
|
||||||
("xml", &["*.xml", "*.xml.dist"]),
|
("xml", &["*.xml", "*.xml.dist"]),
|
||||||
("xz", &["*.xz"]),
|
("xz", &["*.xz", "*.txz"]),
|
||||||
("yacc", &["*.y"]),
|
("yacc", &["*.y"]),
|
||||||
("yaml", &["*.yaml", "*.yml"]),
|
("yaml", &["*.yaml", "*.yml"]),
|
||||||
|
("zig", &["*.zig"]),
|
||||||
("zsh", &[
|
("zsh", &[
|
||||||
".zshenv", "zshenv",
|
".zshenv", "zshenv",
|
||||||
".zlogin", "zlogin",
|
".zlogin", "zlogin",
|
||||||
@@ -304,6 +312,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
".zshrc", "zshrc",
|
".zshrc", "zshrc",
|
||||||
"*.zsh",
|
"*.zsh",
|
||||||
]),
|
]),
|
||||||
|
("zstd", &["*.zst", "*.zstd"]),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Glob represents a single glob in a set of file type definitions.
|
/// Glob represents a single glob in a set of file type definitions.
|
||||||
@@ -342,6 +351,18 @@ impl<'a> Glob<'a> {
|
|||||||
fn unmatched() -> Glob<'a> {
|
fn unmatched() -> Glob<'a> {
|
||||||
Glob(GlobInner::UnmatchedIgnore)
|
Glob(GlobInner::UnmatchedIgnore)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the file type defintion that matched, if one exists. A file type
|
||||||
|
/// definition always exists when a specific definition matches a file
|
||||||
|
/// path.
|
||||||
|
pub fn file_type_def(&self) -> Option<&FileTypeDef> {
|
||||||
|
match self {
|
||||||
|
Glob(GlobInner::UnmatchedIgnore) => None,
|
||||||
|
Glob(GlobInner::Matched { def, .. }) => {
|
||||||
|
Some(def)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single file type definition.
|
/// A single file type definition.
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,14 @@
|
|||||||
class RipgrepBin < Formula
|
class RipgrepBin < Formula
|
||||||
version '0.9.0'
|
version '0.10.0'
|
||||||
desc "Recursively search directories for a regex pattern."
|
desc "Recursively search directories for a regex pattern."
|
||||||
homepage "https://github.com/BurntSushi/ripgrep"
|
homepage "https://github.com/BurntSushi/ripgrep"
|
||||||
|
|
||||||
if OS.mac?
|
if OS.mac?
|
||||||
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-apple-darwin.tar.gz"
|
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-apple-darwin.tar.gz"
|
||||||
sha256 "36003ea8b62ad6274dc14140039f448cdf5026827d53cf24dad2d84005557a8c"
|
sha256 "32754b4173ac87a7bfffd436d601a49362676eb1841ab33440f2f49c002c8967"
|
||||||
elsif OS.linux?
|
elsif OS.linux?
|
||||||
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-unknown-linux-musl.tar.gz"
|
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-unknown-linux-musl.tar.gz"
|
||||||
sha256 "2eb4443e58f95051ff76ea036ed1faf940d5a04af4e7ff5a7dbd74576b907e99"
|
sha256 "c76080aa807a339b44139885d77d15ad60ab8cdd2c2fdaf345d0985625bc0f97"
|
||||||
end
|
end
|
||||||
|
|
||||||
conflicts_with "ripgrep"
|
conflicts_with "ripgrep"
|
||||||
|
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
disable_all_formatting = true
|
33
scripts/copy-examples
Executable file
33
scripts/copy-examples
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
import argparse
|
||||||
|
import codecs
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
|
||||||
|
RE_EACH_CODE_BLOCK = re.compile(
|
||||||
|
r'(?s)(?:```|\{\{< high rust[^>]+>\}\})[^\n]*\n(.*?)(?:```|\{\{< /high >\}\})' # noqa
|
||||||
|
)
|
||||||
|
RE_MARKER = re.compile(r'^(?:# )?//([^/].*)$')
|
||||||
|
RE_STRIP_COMMENT = re.compile(r'^# ?')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
p = argparse.ArgumentParser()
|
||||||
|
p.add_argument('--rust-file', default='src/cookbook.rs')
|
||||||
|
p.add_argument('--example-dir', default='grep/examples')
|
||||||
|
args = p.parse_args()
|
||||||
|
|
||||||
|
with codecs.open(args.rust_file, encoding='utf-8') as f:
|
||||||
|
rustcode = f.read()
|
||||||
|
for m in RE_EACH_CODE_BLOCK.finditer(rustcode):
|
||||||
|
lines = m.group(1).splitlines()
|
||||||
|
marker, codelines = lines[0], lines[1:]
|
||||||
|
m = RE_MARKER.search(marker)
|
||||||
|
if m is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
code = '\n'.join(RE_STRIP_COMMENT.sub('', line) for line in codelines)
|
||||||
|
fpath = os.path.join(args.example_dir, m.group(1))
|
||||||
|
with codecs.open(fpath, mode='w+', encoding='utf-8') as f:
|
||||||
|
print(code, file=f)
|
489
src/app.rs
489
src/app.rs
@@ -9,16 +9,18 @@
|
|||||||
// is where we read clap's configuration from the end user's arguments and turn
|
// is where we read clap's configuration from the end user's arguments and turn
|
||||||
// it into a ripgrep-specific configuration type that is not coupled with clap.
|
// it into a ripgrep-specific configuration type that is not coupled with clap.
|
||||||
|
|
||||||
use clap::{self, App, AppSettings};
|
use clap::{self, App, AppSettings, crate_authors, crate_version};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
const ABOUT: &str = "
|
const ABOUT: &str = "
|
||||||
ripgrep (rg) recursively searches your current directory for a regex pattern.
|
ripgrep (rg) recursively searches your current directory for a regex pattern.
|
||||||
By default, ripgrep will respect your `.gitignore` and automatically skip
|
By default, ripgrep will respect your .gitignore and automatically skip hidden
|
||||||
hidden files/directories and binary files.
|
files/directories and binary files.
|
||||||
|
|
||||||
ripgrep's regex engine uses finite automata and guarantees linear time
|
ripgrep's default regex engine uses finite automata and guarantees linear
|
||||||
searching. Because of this, features like backreferences and arbitrary
|
time searching. Because of this, features like backreferences and arbitrary
|
||||||
lookaround are not supported.
|
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
|
ripgrep supports configuration files. Set RIPGREP_CONFIG_PATH to a
|
||||||
configuration file. The file can specify one shell argument per line. Lines
|
configuration file. The file can specify one shell argument per line. Lines
|
||||||
@@ -79,7 +81,7 @@ pub fn app() -> App<'static, 'static> {
|
|||||||
/// Return the "long" format of ripgrep's version string.
|
/// Return the "long" format of ripgrep's version string.
|
||||||
///
|
///
|
||||||
/// If a revision hash is given, then it is used. If one isn't given, then
|
/// If a revision hash is given, then it is used. If one isn't given, then
|
||||||
/// the RIPGREP_BUILD_GIT_HASH env var is inspect for it. If that isn't set,
|
/// the RIPGREP_BUILD_GIT_HASH env var is inspected for it. If that isn't set,
|
||||||
/// then a revision hash is not included in the version string returned.
|
/// then a revision hash is not included in the version string returned.
|
||||||
pub fn long_version(revision_hash: Option<&str>) -> String {
|
pub fn long_version(revision_hash: Option<&str>) -> String {
|
||||||
// Do we have a git hash?
|
// Do we have a git hash?
|
||||||
@@ -125,7 +127,7 @@ fn compile_cpu_features() -> Vec<&'static str> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the relevant CPU features enabled at runtime.
|
/// Returns the relevant CPU features enabled at runtime.
|
||||||
#[cfg(all(ripgrep_runtime_cpu, target_arch = "x86_64"))]
|
#[cfg(target_arch = "x86_64")]
|
||||||
fn runtime_cpu_features() -> Vec<&'static str> {
|
fn runtime_cpu_features() -> Vec<&'static str> {
|
||||||
// This is kind of a dirty violation of abstraction, since it assumes
|
// This is kind of a dirty violation of abstraction, since it assumes
|
||||||
// knowledge about what specific SIMD features are being used.
|
// knowledge about what specific SIMD features are being used.
|
||||||
@@ -145,7 +147,7 @@ fn runtime_cpu_features() -> Vec<&'static str> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the relevant CPU features enabled at runtime.
|
/// Returns the relevant CPU features enabled at runtime.
|
||||||
#[cfg(not(all(ripgrep_runtime_cpu, target_arch = "x86_64")))]
|
#[cfg(not(target_arch = "x86_64"))]
|
||||||
fn runtime_cpu_features() -> Vec<&'static str> {
|
fn runtime_cpu_features() -> Vec<&'static str> {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
@@ -536,9 +538,14 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
|||||||
// The positional arguments must be defined first and in order.
|
// The positional arguments must be defined first and in order.
|
||||||
arg_pattern(&mut args);
|
arg_pattern(&mut args);
|
||||||
arg_path(&mut args);
|
arg_path(&mut args);
|
||||||
// Flags can be defined in any order, but we do it alphabetically.
|
// Flags can be defined in any order, but we do it alphabetically. Note
|
||||||
|
// that each function may define multiple flags. For example,
|
||||||
|
// `flag_encoding` defines `--encoding` and `--no-encoding`. Most `--no`
|
||||||
|
// flags are hidden and merely mentioned in the docs of the corresponding
|
||||||
|
// "positive" flag.
|
||||||
flag_after_context(&mut args);
|
flag_after_context(&mut args);
|
||||||
flag_before_context(&mut args);
|
flag_before_context(&mut args);
|
||||||
|
flag_block_buffered(&mut args);
|
||||||
flag_byte_offset(&mut args);
|
flag_byte_offset(&mut args);
|
||||||
flag_case_sensitive(&mut args);
|
flag_case_sensitive(&mut args);
|
||||||
flag_color(&mut args);
|
flag_color(&mut args);
|
||||||
@@ -564,8 +571,10 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
|||||||
flag_iglob(&mut args);
|
flag_iglob(&mut args);
|
||||||
flag_ignore_case(&mut args);
|
flag_ignore_case(&mut args);
|
||||||
flag_ignore_file(&mut args);
|
flag_ignore_file(&mut args);
|
||||||
|
flag_ignore_file_case_insensitive(&mut args);
|
||||||
flag_invert_match(&mut args);
|
flag_invert_match(&mut args);
|
||||||
flag_json(&mut args);
|
flag_json(&mut args);
|
||||||
|
flag_line_buffered(&mut args);
|
||||||
flag_line_number(&mut args);
|
flag_line_number(&mut args);
|
||||||
flag_line_regexp(&mut args);
|
flag_line_regexp(&mut args);
|
||||||
flag_max_columns(&mut args);
|
flag_max_columns(&mut args);
|
||||||
@@ -577,19 +586,22 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
|||||||
flag_multiline_dotall(&mut args);
|
flag_multiline_dotall(&mut args);
|
||||||
flag_no_config(&mut args);
|
flag_no_config(&mut args);
|
||||||
flag_no_ignore(&mut args);
|
flag_no_ignore(&mut args);
|
||||||
|
flag_no_ignore_dot(&mut args);
|
||||||
flag_no_ignore_global(&mut args);
|
flag_no_ignore_global(&mut args);
|
||||||
flag_no_ignore_messages(&mut args);
|
flag_no_ignore_messages(&mut args);
|
||||||
flag_no_ignore_parent(&mut args);
|
flag_no_ignore_parent(&mut args);
|
||||||
flag_no_ignore_vcs(&mut args);
|
flag_no_ignore_vcs(&mut args);
|
||||||
flag_no_messages(&mut args);
|
flag_no_messages(&mut args);
|
||||||
|
flag_no_pcre2_unicode(&mut args);
|
||||||
flag_null(&mut args);
|
flag_null(&mut args);
|
||||||
flag_null_data(&mut args);
|
flag_null_data(&mut args);
|
||||||
|
flag_one_file_system(&mut args);
|
||||||
flag_only_matching(&mut args);
|
flag_only_matching(&mut args);
|
||||||
flag_path_separator(&mut args);
|
flag_path_separator(&mut args);
|
||||||
flag_passthru(&mut args);
|
flag_passthru(&mut args);
|
||||||
flag_pcre2(&mut args);
|
flag_pcre2(&mut args);
|
||||||
flag_pcre2_unicode(&mut args);
|
|
||||||
flag_pre(&mut args);
|
flag_pre(&mut args);
|
||||||
|
flag_pre_glob(&mut args);
|
||||||
flag_pretty(&mut args);
|
flag_pretty(&mut args);
|
||||||
flag_quiet(&mut args);
|
flag_quiet(&mut args);
|
||||||
flag_regex_size_limit(&mut args);
|
flag_regex_size_limit(&mut args);
|
||||||
@@ -598,6 +610,8 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
|||||||
flag_search_zip(&mut args);
|
flag_search_zip(&mut args);
|
||||||
flag_smart_case(&mut args);
|
flag_smart_case(&mut args);
|
||||||
flag_sort_files(&mut args);
|
flag_sort_files(&mut args);
|
||||||
|
flag_sort(&mut args);
|
||||||
|
flag_sortr(&mut args);
|
||||||
flag_stats(&mut args);
|
flag_stats(&mut args);
|
||||||
flag_text(&mut args);
|
flag_text(&mut args);
|
||||||
flag_threads(&mut args);
|
flag_threads(&mut args);
|
||||||
@@ -677,13 +691,49 @@ This overrides the --context flag.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flag_block_buffered(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str = "Force block buffering.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
When enabled, ripgrep will use block buffering. That is, whenever a matching
|
||||||
|
line is found, it will be written to an in-memory buffer and will not be
|
||||||
|
written to stdout until the buffer reaches a certain size. This is the default
|
||||||
|
when ripgrep's stdout is redirected to a pipeline or a file. When ripgrep's
|
||||||
|
stdout is connected to a terminal, line buffering will be used. Forcing block
|
||||||
|
buffering can be useful when dumping a large amount of contents to a terminal.
|
||||||
|
|
||||||
|
Forceful block buffering can be disabled with --no-block-buffered. Note that
|
||||||
|
using --no-block-buffered causes ripgrep to revert to its default behavior of
|
||||||
|
automatically detecting the buffering strategy. To force line buffering, use
|
||||||
|
the --line-buffered flag.
|
||||||
|
");
|
||||||
|
let arg = RGArg::switch("block-buffered")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.overrides("no-block-buffered")
|
||||||
|
.overrides("line-buffered")
|
||||||
|
.overrides("no-line-buffered");
|
||||||
|
args.push(arg);
|
||||||
|
|
||||||
|
let arg = RGArg::switch("no-block-buffered")
|
||||||
|
.hidden()
|
||||||
|
.overrides("block-buffered")
|
||||||
|
.overrides("line-buffered")
|
||||||
|
.overrides("no-line-buffered");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
fn flag_byte_offset(args: &mut Vec<RGArg>) {
|
fn flag_byte_offset(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str =
|
const SHORT: &str =
|
||||||
"Print the 0-based byte offset for each matching line.";
|
"Print the 0-based byte offset for each matching line.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
Print the 0-based byte offset within the input file
|
Print the 0-based byte offset within the input file before each line of output.
|
||||||
before each line of output. If -o (--only-matching) is
|
If -o (--only-matching) is specified, print the offset of the matching part
|
||||||
specified, print the offset of the matching part itself.
|
itself.
|
||||||
|
|
||||||
|
If ripgrep does transcoding, then the byte offset is in terms of the the result
|
||||||
|
of transcoding and not the original data. This applies similarly to another
|
||||||
|
transformation on the source, such as decompression or a --pre filter. Note
|
||||||
|
that when the PCRE2 regex engine is used, then UTF-8 transcoding is done by
|
||||||
|
default.
|
||||||
");
|
");
|
||||||
let arg = RGArg::switch("byte-offset").short("b")
|
let arg = RGArg::switch("byte-offset").short("b")
|
||||||
.help(SHORT).long_help(LONG);
|
.help(SHORT).long_help(LONG);
|
||||||
@@ -741,17 +791,17 @@ to one of eight choices: red, blue, green, cyan, magenta, yellow, white and
|
|||||||
black. Styles are limited to nobold, bold, nointense, intense, nounderline
|
black. Styles are limited to nobold, bold, nointense, intense, nounderline
|
||||||
or underline.
|
or underline.
|
||||||
|
|
||||||
The format of the flag is `{type}:{attribute}:{value}`. `{type}` should be
|
The format of the flag is '{type}:{attribute}:{value}'. '{type}' should be
|
||||||
one of path, line, column or match. `{attribute}` can be fg, bg or style.
|
one of path, line, column or match. '{attribute}' can be fg, bg or style.
|
||||||
`{value}` is either a color (for fg and bg) or a text style. A special format,
|
'{value}' is either a color (for fg and bg) or a text style. A special format,
|
||||||
`{type}:none`, will clear all color settings for `{type}`.
|
'{type}:none', will clear all color settings for '{type}'.
|
||||||
|
|
||||||
For example, the following command will change the match color to magenta and
|
For example, the following command will change the match color to magenta and
|
||||||
the background color for line numbers to yellow:
|
the background color for line numbers to yellow:
|
||||||
|
|
||||||
rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo.
|
rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo.
|
||||||
|
|
||||||
Extended colors can be used for `{value}` when the terminal supports ANSI color
|
Extended colors can be used for '{value}' when the terminal supports ANSI color
|
||||||
sequences. These are specified as either 'x' (256-color) or 'x,x,x' (24-bit
|
sequences. These are specified as either 'x' (256-color) or 'x,x,x' (24-bit
|
||||||
truecolor) where x is a number between 0 and 255 inclusive. x may be given as
|
truecolor) where x is a number between 0 and 255 inclusive. x may be given as
|
||||||
a normal decimal number or a hexadecimal number, which is prefixed by `0x`.
|
a normal decimal number or a hexadecimal number, which is prefixed by `0x`.
|
||||||
@@ -932,10 +982,15 @@ fn flag_encoding(args: &mut Vec<RGArg>) {
|
|||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
Specify the text encoding that ripgrep will use on all files searched. The
|
Specify the text encoding that ripgrep will use on all files searched. The
|
||||||
default value is 'auto', which will cause ripgrep to do a best effort automatic
|
default value is 'auto', which will cause ripgrep to do a best effort automatic
|
||||||
detection of encoding on a per-file basis. Other supported values can be found
|
detection of encoding on a per-file basis. Automatic detection in this case
|
||||||
in the list of labels here:
|
only applies to files that begin with a UTF-8 or UTF-16 byte-order mark (BOM).
|
||||||
|
No other automatic detection is performend.
|
||||||
|
|
||||||
|
Other supported values can be found in the list of labels here:
|
||||||
https://encoding.spec.whatwg.org/#concept-encoding-get
|
https://encoding.spec.whatwg.org/#concept-encoding-get
|
||||||
|
|
||||||
|
For more details on encoding and how ripgrep deals with it, see GUIDE.md.
|
||||||
|
|
||||||
This flag can be disabled with --no-encoding.
|
This flag can be disabled with --no-encoding.
|
||||||
");
|
");
|
||||||
let arg = RGArg::flag("encoding", "ENCODING").short("E")
|
let arg = RGArg::flag("encoding", "ENCODING").short("E")
|
||||||
@@ -969,7 +1024,7 @@ fn flag_files(args: &mut Vec<RGArg>) {
|
|||||||
const SHORT: &str = "Print each file that would be searched.";
|
const SHORT: &str = "Print each file that would be searched.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
Print each file that would be searched without actually performing the search.
|
Print each file that would be searched without actually performing the search.
|
||||||
This is useful to determine whether a particular file is being search or not.
|
This is useful to determine whether a particular file is being searched or not.
|
||||||
");
|
");
|
||||||
let arg = RGArg::switch("files")
|
let arg = RGArg::switch("files")
|
||||||
.help(SHORT).long_help(LONG)
|
.help(SHORT).long_help(LONG)
|
||||||
@@ -1161,6 +1216,26 @@ directly on the command line, then used -g instead.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flag_ignore_file_case_insensitive(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str = "Process ignore files case insensitively.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
Process ignore files (.gitignore, .ignore, etc.) case insensitively. Note that
|
||||||
|
this comes with a performance penalty and is most useful on case insensitive
|
||||||
|
file systems (such as Windows).
|
||||||
|
|
||||||
|
This flag can be disabled with the --no-ignore-file-case-insensitive flag.
|
||||||
|
");
|
||||||
|
let arg = RGArg::switch("ignore-file-case-insensitive")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.overrides("no-ignore-file-case-insensitive");
|
||||||
|
args.push(arg);
|
||||||
|
|
||||||
|
let arg = RGArg::switch("no-ignore-file-case-insensitive")
|
||||||
|
.hidden()
|
||||||
|
.overrides("ignore-file-case-insensitive");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
fn flag_invert_match(args: &mut Vec<RGArg>) {
|
fn flag_invert_match(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Invert matching.";
|
const SHORT: &str = "Invert matching.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
@@ -1231,6 +1306,37 @@ The JSON Lines format can be disabled with --no-json.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flag_line_buffered(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str = "Force line buffering.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
When enabled, ripgrep will use line buffering. That is, whenever a matching
|
||||||
|
line is found, it will be flushed to stdout immediately. This is the default
|
||||||
|
when ripgrep's stdout is connected to a terminal, but otherwise, ripgrep will
|
||||||
|
use block buffering, which is typically faster. This flag forces ripgrep to
|
||||||
|
use line buffering even if it would otherwise use block buffering. This is
|
||||||
|
typically useful in shell pipelines, e.g.,
|
||||||
|
'tail -f something.log | rg foo --line-buffered | rg bar'.
|
||||||
|
|
||||||
|
Forceful line buffering can be disabled with --no-line-buffered. Note that
|
||||||
|
using --no-line-buffered causes ripgrep to revert to its default behavior of
|
||||||
|
automatically detecting the buffering strategy. To force block buffering, use
|
||||||
|
the --block-buffered flag.
|
||||||
|
");
|
||||||
|
let arg = RGArg::switch("line-buffered")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.overrides("no-line-buffered")
|
||||||
|
.overrides("block-buffered")
|
||||||
|
.overrides("no-block-buffered");
|
||||||
|
args.push(arg);
|
||||||
|
|
||||||
|
let arg = RGArg::switch("no-line-buffered")
|
||||||
|
.hidden()
|
||||||
|
.overrides("line-buffered")
|
||||||
|
.overrides("block-buffered")
|
||||||
|
.overrides("no-block-buffered");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
fn flag_line_number(args: &mut Vec<RGArg>) {
|
fn flag_line_number(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Show line numbers.";
|
const SHORT: &str = "Show line numbers.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
@@ -1457,7 +1563,7 @@ fn flag_no_ignore(args: &mut Vec<RGArg>) {
|
|||||||
const SHORT: &str = "Don't respect ignore files.";
|
const SHORT: &str = "Don't respect ignore files.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
Don't respect ignore files (.gitignore, .ignore, etc.). This implies
|
Don't respect ignore files (.gitignore, .ignore, etc.). This implies
|
||||||
--no-ignore-parent and --no-ignore-vcs.
|
--no-ignore-parent, --no-ignore-dot and --no-ignore-vcs.
|
||||||
|
|
||||||
This flag can be disabled with the --ignore flag.
|
This flag can be disabled with the --ignore flag.
|
||||||
");
|
");
|
||||||
@@ -1472,6 +1578,24 @@ This flag can be disabled with the --ignore flag.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flag_no_ignore_dot(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str = "Don't respect .ignore files.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
Don't respect .ignore files.
|
||||||
|
|
||||||
|
This flag can be disabled with the --ignore-dot flag.
|
||||||
|
");
|
||||||
|
let arg = RGArg::switch("no-ignore-dot")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.overrides("ignore-dot");
|
||||||
|
args.push(arg);
|
||||||
|
|
||||||
|
let arg = RGArg::switch("ignore-dot")
|
||||||
|
.hidden()
|
||||||
|
.overrides("no-ignore-dot");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
fn flag_no_ignore_global(args: &mut Vec<RGArg>) {
|
fn flag_no_ignore_global(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Don't respect global ignore files.";
|
const SHORT: &str = "Don't respect global ignore files.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
@@ -1568,6 +1692,48 @@ This flag can be disabled with the --messages flag.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flag_no_pcre2_unicode(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str = "Disable Unicode mode for PCRE2 matching.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
When PCRE2 matching is enabled, this flag will disable Unicode mode, which is
|
||||||
|
otherwise enabled by default. If PCRE2 matching is not enabled, then this flag
|
||||||
|
has no effect.
|
||||||
|
|
||||||
|
When PCRE2's Unicode mode is enabled, several different types of patterns
|
||||||
|
become Unicode aware. This includes '\\b', '\\B', '\\w', '\\W', '\\d', '\\D',
|
||||||
|
'\\s' and '\\S'. Similarly, the '.' meta character will match any Unicode
|
||||||
|
codepoint instead of any byte. Caseless matching will also use Unicode simple
|
||||||
|
case folding instead of ASCII-only case insensitivity.
|
||||||
|
|
||||||
|
Unicode mode in PCRE2 represents a critical trade off in the user experience
|
||||||
|
of ripgrep. In particular, unlike the default regex engine, PCRE2 does not
|
||||||
|
support the ability to search possibly invalid UTF-8 with Unicode features
|
||||||
|
enabled. Instead, PCRE2 *requires* that everything it searches when Unicode
|
||||||
|
mode is enabled is valid UTF-8. (Or valid UTF-16/UTF-32, but for the purposes
|
||||||
|
of ripgrep, we only discuss UTF-8.) This means that if you have PCRE2's Unicode
|
||||||
|
mode enabled and you attempt to search invalid UTF-8, then the search for that
|
||||||
|
file will halt and print an error. For this reason, when PCRE2's Unicode mode
|
||||||
|
is enabled, ripgrep will automatically \"fix\" invalid UTF-8 sequences by
|
||||||
|
replacing them with the Unicode replacement codepoint.
|
||||||
|
|
||||||
|
If you would rather see the encoding errors surfaced by PCRE2 when Unicode mode
|
||||||
|
is enabled, then pass the --no-encoding flag to disable all transcoding.
|
||||||
|
|
||||||
|
Related flags: --pcre2
|
||||||
|
|
||||||
|
This flag can be disabled with --pcre2-unicode.
|
||||||
|
");
|
||||||
|
let arg = RGArg::switch("no-pcre2-unicode")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.overrides("pcre2-unicode");
|
||||||
|
args.push(arg);
|
||||||
|
|
||||||
|
let arg = RGArg::switch("pcre2-unicode")
|
||||||
|
.hidden()
|
||||||
|
.overrides("no-pcre2-unicode");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
fn flag_null(args: &mut Vec<RGArg>) {
|
fn flag_null(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Print a NUL byte after file paths.";
|
const SHORT: &str = "Print a NUL byte after file paths.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
@@ -1604,6 +1770,33 @@ Using this flag implies -a/--text.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flag_one_file_system(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str =
|
||||||
|
"Do not descend into directories on other file systems.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
When enabled, ripgrep will not cross file system boundaries relative to where
|
||||||
|
the search started from.
|
||||||
|
|
||||||
|
Note that this applies to each path argument given to ripgrep. For example, in
|
||||||
|
the command 'rg --one-file-system /foo/bar /quux/baz', ripgrep will search both
|
||||||
|
'/foo/bar' and '/quux/baz' even if they are on different file systems, but will
|
||||||
|
not cross a file system boundary when traversing each path's directory tree.
|
||||||
|
|
||||||
|
This is similar to find's '-xdev' or '-mount' flag.
|
||||||
|
|
||||||
|
This flag can be disabled with --no-one-file-system.
|
||||||
|
");
|
||||||
|
let arg = RGArg::switch("one-file-system")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.overrides("no-one-file-system");
|
||||||
|
args.push(arg);
|
||||||
|
|
||||||
|
let arg = RGArg::switch("no-one-file-system")
|
||||||
|
.hidden()
|
||||||
|
.overrides("one-file-system");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
fn flag_only_matching(args: &mut Vec<RGArg>) {
|
fn flag_only_matching(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Print only matches parts of a line.";
|
const SHORT: &str = "Print only matches parts of a line.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
@@ -1658,6 +1851,8 @@ Note that PCRE2 is an optional ripgrep feature. If PCRE2 wasn't included in
|
|||||||
your build of ripgrep, then using this flag will result in ripgrep printing
|
your build of ripgrep, then using this flag will result in ripgrep printing
|
||||||
an error message and exiting.
|
an error message and exiting.
|
||||||
|
|
||||||
|
Related flags: --no-pcre2-unicode
|
||||||
|
|
||||||
This flag can be disabled with --no-pcre2.
|
This flag can be disabled with --no-pcre2.
|
||||||
");
|
");
|
||||||
let arg = RGArg::switch("pcre2").short("P")
|
let arg = RGArg::switch("pcre2").short("P")
|
||||||
@@ -1671,43 +1866,94 @@ This flag can be disabled with --no-pcre2.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flag_pcre2_unicode(args: &mut Vec<RGArg>) {
|
fn flag_pre(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Enable Unicode mode for PCRE2 matching.";
|
const SHORT: &str = "search outputs of COMMAND FILE for each FILE";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
When PCRE2 matching is enabled, this flag will enable Unicode mode. If PCRE2
|
For each input FILE, search the standard output of COMMAND FILE rather than the
|
||||||
matching is not enabled, then this flag has no effect.
|
contents of FILE. This option expects the COMMAND program to either be an
|
||||||
|
absolute path or to be available in your PATH. Either an empty string COMMAND
|
||||||
|
or the `--no-pre` flag will disable this behavior.
|
||||||
|
|
||||||
This flag is enabled by default when PCRE2 matching is enabled.
|
WARNING: When this flag is set, ripgrep will unconditionally spawn a
|
||||||
|
process for every file that is searched. Therefore, this can incur an
|
||||||
|
unnecessarily large performance penalty if you don't otherwise need the
|
||||||
|
flexibility offered by this flag.
|
||||||
|
|
||||||
When PCRE2's Unicode mode is enabled several different types of patterns become
|
A preprocessor is not run when ripgrep is searching stdin.
|
||||||
Unicode aware. This includes '\\b', '\\B', '\\w', '\\W', '\\d', '\\D', '\\s'
|
|
||||||
and '\\S'. Similarly, the '.' meta character will match any Unicode codepoint
|
|
||||||
instead of any byte. Caseless matching will also use Unicode simple case
|
|
||||||
folding instead of ASCII-only case insensitivity.
|
|
||||||
|
|
||||||
Unicode mode in PCRE2 represents a critical trade off in the user experience
|
When searching over sets of files that may require one of several decoders
|
||||||
of ripgrep. In particular, unlike the default regex engine, PCRE2 does not
|
as preprocessors, COMMAND should be a wrapper program or script which first
|
||||||
support the ability to search possibly invalid UTF-8 with Unicode features
|
classifies FILE based on magic numbers/content or based on the FILE name and
|
||||||
enabled. Instead, PCRE2 *requires* that everything it searches when Unicode
|
then dispatches to an appropriate preprocessor. Each COMMAND also has its
|
||||||
mode is enabled is valid UTF-8. (Or valid UTF-16/UTF-32, but for the purposes
|
standard input connected to FILE for convenience.
|
||||||
of ripgrep, we only discuss UTF-8.) This means that if you have PCRE2's Unicode
|
|
||||||
mode enabled and you attempt to search invalid UTF-8, then the search for that
|
|
||||||
file will halt and print an error. For this reason, when PCRE2's Unicode mode
|
|
||||||
is enabled, ripgrep will automatically \"fix\" invalid UTF-8 sequences by
|
|
||||||
replacing them with the Unicode replacement codepoint.
|
|
||||||
|
|
||||||
If you would rather see the encoding errors surfaced by PCRE2 when Unicode mode
|
For example, a shell script for COMMAND might look like:
|
||||||
is enabled, then pass the --no-encoding flag to disable all transcoding.
|
|
||||||
|
|
||||||
This flag can be disabled with --no-pcre2-unicode.
|
case \"$1\" in
|
||||||
|
*.pdf)
|
||||||
|
exec pdftotext \"$1\" -
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
case $(file \"$1\") in
|
||||||
|
*Zstandard*)
|
||||||
|
exec pzstd -cdq
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exec cat
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
The above script uses `pdftotext` to convert a PDF file to plain text. For
|
||||||
|
all other files, the script uses the `file` utility to sniff the type of the
|
||||||
|
file based on its contents. If it is a compressed file in the Zstandard format,
|
||||||
|
then `pzstd` is used to decompress the contents to stdout.
|
||||||
|
|
||||||
|
This overrides the -z/--search-zip flag.
|
||||||
");
|
");
|
||||||
let arg = RGArg::switch("pcre2-unicode")
|
let arg = RGArg::flag("pre", "COMMAND")
|
||||||
.help(SHORT).long_help(LONG);
|
.help(SHORT).long_help(LONG)
|
||||||
|
.overrides("no-pre")
|
||||||
|
.overrides("search-zip");
|
||||||
args.push(arg);
|
args.push(arg);
|
||||||
|
|
||||||
let arg = RGArg::switch("no-pcre2-unicode")
|
let arg = RGArg::switch("no-pre")
|
||||||
.hidden()
|
.hidden()
|
||||||
.overrides("pcre2-unicode");
|
.overrides("pre");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flag_pre_glob(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str =
|
||||||
|
"Include or exclude files from a preprocessing command.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
This flag works in conjunction with the --pre flag. Namely, when one or more
|
||||||
|
--pre-glob flags are given, then only files that match the given set of globs
|
||||||
|
will be handed to the command specified by the --pre flag. Any non-matching
|
||||||
|
files will be searched without using the preprocessor command.
|
||||||
|
|
||||||
|
This flag is useful when searching many files with the --pre flag. Namely,
|
||||||
|
it permits the ability to avoid process overhead for files that don't need
|
||||||
|
preprocessing. For example, given the following shell script, 'pre-pdftotext':
|
||||||
|
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
pdftotext \"$1\" -
|
||||||
|
|
||||||
|
then it is possible to use '--pre pre-pdftotext --pre-glob \'*.pdf\'' to make
|
||||||
|
it so ripgrep only executes the 'pre-pdftotext' command on files with a '.pdf'
|
||||||
|
extension.
|
||||||
|
|
||||||
|
Multiple --pre-glob flags may be used. Globbing rules match .gitignore globs.
|
||||||
|
Precede a glob with a ! to exclude it.
|
||||||
|
|
||||||
|
This flag has no effect if the --pre flag is not used.
|
||||||
|
");
|
||||||
|
let arg = RGArg::flag("pre-glob", "GLOB")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.multiple()
|
||||||
|
.allow_leading_hyphen();
|
||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1798,9 +2044,9 @@ This flag can be used with the -o/--only-matching flag.
|
|||||||
fn flag_search_zip(args: &mut Vec<RGArg>) {
|
fn flag_search_zip(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Search in compressed files.";
|
const SHORT: &str = "Search in compressed files.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
Search in compressed files. Currently gz, bz2, xz, lzma and lz4 files are
|
Search in compressed files. Currently gzip, bzip2, xz, LZ4, LZMA, Brotli and
|
||||||
supported. This option expects the decompression binaries to be available in
|
Zstd files are supported. This option expects the decompression binaries to be
|
||||||
your PATH.
|
available in your PATH.
|
||||||
|
|
||||||
This flag can be disabled with --no-search-zip.
|
This flag can be disabled with --no-search-zip.
|
||||||
");
|
");
|
||||||
@@ -1816,64 +2062,6 @@ This flag can be disabled with --no-search-zip.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flag_pre(args: &mut Vec<RGArg>) {
|
|
||||||
const SHORT: &str = "search outputs of COMMAND FILE for each FILE";
|
|
||||||
const LONG: &str = long!("\
|
|
||||||
For each input FILE, search the standard output of COMMAND FILE rather than the
|
|
||||||
contents of FILE. This option expects the COMMAND program to either be an
|
|
||||||
absolute path or to be available in your PATH. Either an empty string COMMAND
|
|
||||||
or the `--no-pre` flag will disable this behavior.
|
|
||||||
|
|
||||||
WARNING: When this flag is set, ripgrep will unconditionally spawn a
|
|
||||||
process for every file that is searched. Therefore, this can incur an
|
|
||||||
unnecessarily large performance penalty if you don't otherwise need the
|
|
||||||
flexibility offered by this flag.
|
|
||||||
|
|
||||||
A preprocessor is not run when ripgrep is searching stdin.
|
|
||||||
|
|
||||||
When searching over sets of files that may require one of several decoders
|
|
||||||
as preprocessors, COMMAND should be a wrapper program or script which first
|
|
||||||
classifies FILE based on magic numbers/content or based on the FILE name and
|
|
||||||
then dispatches to an appropriate preprocessor. Each COMMAND also has its
|
|
||||||
standard input connected to FILE for convenience.
|
|
||||||
|
|
||||||
For example, a shell script for COMMAND might look like:
|
|
||||||
|
|
||||||
case \"$1\" in
|
|
||||||
*.pdf)
|
|
||||||
exec pdftotext \"$1\" -
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
case $(file \"$1\") in
|
|
||||||
*Zstandard*)
|
|
||||||
exec pzstd -cdq
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
exec cat
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
The above script uses `pdftotext` to convert a PDF file to plain text. For
|
|
||||||
all other files, the script uses the `file` utility to sniff the type of the
|
|
||||||
file based on its contents. If it is a compressed file in the Zstandard format,
|
|
||||||
then `pzstd` is used to decompress the contents to stdout.
|
|
||||||
|
|
||||||
This overrides the -z/--search-zip flag.
|
|
||||||
");
|
|
||||||
let arg = RGArg::flag("pre", "COMMAND")
|
|
||||||
.help(SHORT).long_help(LONG)
|
|
||||||
.overrides("no-pre")
|
|
||||||
.overrides("search-zip");
|
|
||||||
args.push(arg);
|
|
||||||
|
|
||||||
let arg = RGArg::switch("no-pre")
|
|
||||||
.hidden()
|
|
||||||
.overrides("pre");
|
|
||||||
args.push(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flag_smart_case(args: &mut Vec<RGArg>) {
|
fn flag_smart_case(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Smart case search.";
|
const SHORT: &str = "Smart case search.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
@@ -1890,8 +2078,10 @@ This overrides the -s/--case-sensitive and -i/--ignore-case flags.
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn flag_sort_files(args: &mut Vec<RGArg>) {
|
fn flag_sort_files(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Sort results by file path. Implies --threads=1.";
|
const SHORT: &str = "DEPRECATED";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
|
DEPRECATED: Use --sort or --sortr instead.
|
||||||
|
|
||||||
Sort results by file path. Note that this currently disables all parallelism
|
Sort results by file path. Note that this currently disables all parallelism
|
||||||
and runs search in a single thread.
|
and runs search in a single thread.
|
||||||
|
|
||||||
@@ -1899,12 +2089,83 @@ This flag can be disabled with --no-sort-files.
|
|||||||
");
|
");
|
||||||
let arg = RGArg::switch("sort-files")
|
let arg = RGArg::switch("sort-files")
|
||||||
.help(SHORT).long_help(LONG)
|
.help(SHORT).long_help(LONG)
|
||||||
.overrides("no-sort-files");
|
.hidden()
|
||||||
|
.overrides("no-sort-files")
|
||||||
|
.overrides("sort")
|
||||||
|
.overrides("sortr");
|
||||||
args.push(arg);
|
args.push(arg);
|
||||||
|
|
||||||
let arg = RGArg::switch("no-sort-files")
|
let arg = RGArg::switch("no-sort-files")
|
||||||
.hidden()
|
.hidden()
|
||||||
.overrides("sort-files");
|
.overrides("sort-files")
|
||||||
|
.overrides("sort")
|
||||||
|
.overrides("sortr");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flag_sort(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str =
|
||||||
|
"Sort results in ascending order. Implies --threads=1.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
This flag enables sorting of results in ascending order. The possible values
|
||||||
|
for this flag are:
|
||||||
|
|
||||||
|
path Sort by file path.
|
||||||
|
modified Sort by the last modified time on a file.
|
||||||
|
accessed Sort by the last accessed time on a file.
|
||||||
|
created Sort by the creation time on a file.
|
||||||
|
none Do not sort results.
|
||||||
|
|
||||||
|
If the sorting criteria isn't available on your system (for example, creation
|
||||||
|
time is not available on ext4 file systems), then ripgrep will attempt to
|
||||||
|
detect this and print an error without searching any results. Otherwise, the
|
||||||
|
sort order is unspecified.
|
||||||
|
|
||||||
|
To sort results in reverse or descending order, use the --sortr flag. Also,
|
||||||
|
this flag overrides --sortr.
|
||||||
|
|
||||||
|
Note that sorting results currently always forces ripgrep to abandon
|
||||||
|
parallelism and run in a single thread.
|
||||||
|
");
|
||||||
|
let arg = RGArg::flag("sort", "SORTBY")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.possible_values(&["path", "modified", "accessed", "created", "none"])
|
||||||
|
.overrides("sortr")
|
||||||
|
.overrides("sort-files")
|
||||||
|
.overrides("no-sort-files");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flag_sortr(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str =
|
||||||
|
"Sort results in descending order. Implies --threads=1.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
This flag enables sorting of results in descending order. The possible values
|
||||||
|
for this flag are:
|
||||||
|
|
||||||
|
path Sort by file path.
|
||||||
|
modified Sort by the last modified time on a file.
|
||||||
|
accessed Sort by the last accessed time on a file.
|
||||||
|
created Sort by the creation time on a file.
|
||||||
|
none Do not sort results.
|
||||||
|
|
||||||
|
If the sorting criteria isn't available on your system (for example, creation
|
||||||
|
time is not available on ext4 file systems), then ripgrep will attempt to
|
||||||
|
detect this and print an error without searching any results. Otherwise, the
|
||||||
|
sort order is unspecified.
|
||||||
|
|
||||||
|
To sort results in ascending order, use the --sort flag. Also, this flag
|
||||||
|
overrides --sort.
|
||||||
|
|
||||||
|
Note that sorting results currently always forces ripgrep to abandon
|
||||||
|
parallelism and run in a single thread.
|
||||||
|
");
|
||||||
|
let arg = RGArg::flag("sortr", "SORTBY")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.possible_values(&["path", "modified", "accessed", "created", "none"])
|
||||||
|
.overrides("sort")
|
||||||
|
.overrides("sort-files")
|
||||||
|
.overrides("no-sort-files");
|
||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
488
src/args.rs
488
src/args.rs
@@ -1,13 +1,15 @@
|
|||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fs::File;
|
use std::fs;
|
||||||
use std::io::{self, BufRead};
|
use std::io::{self, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use atty;
|
|
||||||
use clap;
|
use clap;
|
||||||
|
use grep::cli;
|
||||||
use grep::matcher::LineTerminator;
|
use grep::matcher::LineTerminator;
|
||||||
#[cfg(feature = "pcre2")]
|
#[cfg(feature = "pcre2")]
|
||||||
use grep::pcre2::{
|
use grep::pcre2::{
|
||||||
@@ -19,6 +21,7 @@ use grep::printer::{
|
|||||||
JSON, JSONBuilder,
|
JSON, JSONBuilder,
|
||||||
Standard, StandardBuilder,
|
Standard, StandardBuilder,
|
||||||
Summary, SummaryBuilder, SummaryKind,
|
Summary, SummaryBuilder, SummaryKind,
|
||||||
|
default_color_specs,
|
||||||
};
|
};
|
||||||
use grep::regex::{
|
use grep::regex::{
|
||||||
RegexMatcher as RustRegexMatcher,
|
RegexMatcher as RustRegexMatcher,
|
||||||
@@ -32,22 +35,22 @@ use ignore::types::{FileTypeDef, Types, TypesBuilder};
|
|||||||
use ignore::{Walk, WalkBuilder, WalkParallel};
|
use ignore::{Walk, WalkBuilder, WalkParallel};
|
||||||
use log;
|
use log;
|
||||||
use num_cpus;
|
use num_cpus;
|
||||||
use path_printer::{PathPrinter, PathPrinterBuilder};
|
use regex;
|
||||||
use regex::{self, Regex};
|
|
||||||
use same_file::Handle;
|
|
||||||
use termcolor::{
|
use termcolor::{
|
||||||
WriteColor,
|
WriteColor,
|
||||||
BufferedStandardStream, BufferWriter, ColorChoice, StandardStream,
|
BufferWriter, ColorChoice,
|
||||||
};
|
};
|
||||||
|
|
||||||
use app;
|
use crate::app;
|
||||||
use config;
|
use crate::config;
|
||||||
use logger::Logger;
|
use crate::logger::Logger;
|
||||||
use messages::{set_messages, set_ignore_messages};
|
use crate::messages::{set_messages, set_ignore_messages};
|
||||||
use search::{PatternMatcher, Printer, SearchWorker, SearchWorkerBuilder};
|
use crate::path_printer::{PathPrinter, PathPrinterBuilder};
|
||||||
use subject::SubjectBuilder;
|
use crate::search::{
|
||||||
use unescape::{escape, unescape};
|
PatternMatcher, Printer, SearchWorker, SearchWorkerBuilder,
|
||||||
use Result;
|
};
|
||||||
|
use crate::subject::SubjectBuilder;
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
/// The command that ripgrep should execute based on the command line
|
/// The command that ripgrep should execute based on the command line
|
||||||
/// configuration.
|
/// configuration.
|
||||||
@@ -128,7 +131,7 @@ impl Args {
|
|||||||
// trying to parse config files. If a config file exists and has
|
// trying to parse config files. If a config file exists and has
|
||||||
// arguments, then we re-parse argv, otherwise we just use the matches
|
// arguments, then we re-parse argv, otherwise we just use the matches
|
||||||
// we have here.
|
// we have here.
|
||||||
let early_matches = ArgMatches::new(app::app().get_matches());
|
let early_matches = ArgMatches::new(clap_matches(env::args_os())?);
|
||||||
set_messages(!early_matches.is_present("no-messages"));
|
set_messages(!early_matches.is_present("no-messages"));
|
||||||
set_ignore_messages(!early_matches.is_present("no-ignore-messages"));
|
set_ignore_messages(!early_matches.is_present("no-ignore-messages"));
|
||||||
|
|
||||||
@@ -143,7 +146,7 @@ impl Args {
|
|||||||
log::set_max_level(log::LevelFilter::Warn);
|
log::set_max_level(log::LevelFilter::Warn);
|
||||||
}
|
}
|
||||||
|
|
||||||
let matches = early_matches.reconfigure();
|
let matches = early_matches.reconfigure()?;
|
||||||
// The logging level may have changed if we brought in additional
|
// The logging level may have changed if we brought in additional
|
||||||
// arguments from a configuration file, so recheck it and set the log
|
// arguments from a configuration file, so recheck it and set the log
|
||||||
// level as appropriate.
|
// level as appropriate.
|
||||||
@@ -265,6 +268,11 @@ impl Args {
|
|||||||
Ok(builder.build(wtr))
|
Ok(builder.build(wtr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if ripgrep should be "quiet."
|
||||||
|
pub fn quiet(&self) -> bool {
|
||||||
|
self.matches().is_present("quiet")
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if and only if the search should quit after finding the
|
/// Returns true if and only if the search should quit after finding the
|
||||||
/// first match.
|
/// first match.
|
||||||
pub fn quit_after_match(&self) -> Result<bool> {
|
pub fn quit_after_match(&self) -> Result<bool> {
|
||||||
@@ -285,6 +293,7 @@ impl Args {
|
|||||||
builder
|
builder
|
||||||
.json_stats(self.matches().is_present("json"))
|
.json_stats(self.matches().is_present("json"))
|
||||||
.preprocessor(self.matches().preprocessor())
|
.preprocessor(self.matches().preprocessor())
|
||||||
|
.preprocessor_globs(self.matches().preprocessor_globs()?)
|
||||||
.search_zip(self.matches().is_present("search-zip"));
|
.search_zip(self.matches().is_present("search-zip"));
|
||||||
Ok(builder.build(matcher, searcher, printer))
|
Ok(builder.build(matcher, searcher, printer))
|
||||||
}
|
}
|
||||||
@@ -307,20 +316,20 @@ impl Args {
|
|||||||
/// file or a stream such as stdin.
|
/// file or a stream such as stdin.
|
||||||
pub fn subject_builder(&self) -> SubjectBuilder {
|
pub fn subject_builder(&self) -> SubjectBuilder {
|
||||||
let mut builder = SubjectBuilder::new();
|
let mut builder = SubjectBuilder::new();
|
||||||
builder
|
builder.strip_dot_prefix(self.using_default_path());
|
||||||
.strip_dot_prefix(self.using_default_path())
|
|
||||||
.skip(self.matches().stdout_handle());
|
|
||||||
builder
|
builder
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute the given function with a writer to stdout that enables color
|
/// Execute the given function with a writer to stdout that enables color
|
||||||
/// support based on the command line configuration.
|
/// support based on the command line configuration.
|
||||||
pub fn stdout(&self) -> Box<WriteColor + Send> {
|
pub fn stdout(&self) -> cli::StandardStream {
|
||||||
let color_choice = self.matches().color_choice();
|
let color = self.matches().color_choice();
|
||||||
if atty::is(atty::Stream::Stdout) {
|
if self.matches().is_present("line-buffered") {
|
||||||
Box::new(StandardStream::stdout(color_choice))
|
cli::stdout_buffered_line(color)
|
||||||
|
} else if self.matches().is_present("block-buffered") {
|
||||||
|
cli::stdout_buffered_block(color)
|
||||||
} else {
|
} else {
|
||||||
Box::new(BufferedStandardStream::stdout(color_choice))
|
cli::stdout(color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,6 +369,120 @@ enum OutputKind {
|
|||||||
JSON,
|
JSON,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The sort criteria, if present.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
struct SortBy {
|
||||||
|
/// Whether to reverse the sort criteria (i.e., descending order).
|
||||||
|
reverse: bool,
|
||||||
|
/// The actual sorting criteria.
|
||||||
|
kind: SortByKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum SortByKind {
|
||||||
|
/// No sorting at all.
|
||||||
|
None,
|
||||||
|
/// Sort by path.
|
||||||
|
Path,
|
||||||
|
/// Sort by last modified time.
|
||||||
|
LastModified,
|
||||||
|
/// Sort by last accessed time.
|
||||||
|
LastAccessed,
|
||||||
|
/// Sort by creation time.
|
||||||
|
Created,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SortBy {
|
||||||
|
fn asc(kind: SortByKind) -> SortBy {
|
||||||
|
SortBy { reverse: false, kind: kind }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desc(kind: SortByKind) -> SortBy {
|
||||||
|
SortBy { reverse: true, kind: kind }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn none() -> SortBy {
|
||||||
|
SortBy::asc(SortByKind::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to check that the sorting criteria selected is actually supported.
|
||||||
|
/// If it isn't, then an error is returned.
|
||||||
|
fn check(&self) -> Result<()> {
|
||||||
|
match self.kind {
|
||||||
|
SortByKind::None | SortByKind::Path => {}
|
||||||
|
SortByKind::LastModified => {
|
||||||
|
env::current_exe()?.metadata()?.modified()?;
|
||||||
|
}
|
||||||
|
SortByKind::LastAccessed => {
|
||||||
|
env::current_exe()?.metadata()?.accessed()?;
|
||||||
|
}
|
||||||
|
SortByKind::Created => {
|
||||||
|
env::current_exe()?.metadata()?.created()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_walk_builder(self, builder: &mut WalkBuilder) {
|
||||||
|
// This isn't entirely optimal. In particular, we will wind up issuing
|
||||||
|
// a stat for many files redundantly. Aside from having potentially
|
||||||
|
// inconsistent results with respect to sorting, this is also slow.
|
||||||
|
// We could fix this here at the expense of memory by caching stat
|
||||||
|
// calls. A better fix would be to find a way to push this down into
|
||||||
|
// directory traversal itself, but that's a somewhat nasty change.
|
||||||
|
match self.kind {
|
||||||
|
SortByKind::None => {}
|
||||||
|
SortByKind::Path => {
|
||||||
|
if self.reverse {
|
||||||
|
builder.sort_by_file_name(|a, b| a.cmp(b).reverse());
|
||||||
|
} else {
|
||||||
|
builder.sort_by_file_name(|a, b| a.cmp(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SortByKind::LastModified => {
|
||||||
|
builder.sort_by_file_path(move |a, b| {
|
||||||
|
sort_by_metadata_time(
|
||||||
|
a, b,
|
||||||
|
self.reverse,
|
||||||
|
|md| md.modified(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
SortByKind::LastAccessed => {
|
||||||
|
builder.sort_by_file_path(move |a, b| {
|
||||||
|
sort_by_metadata_time(
|
||||||
|
a, b,
|
||||||
|
self.reverse,
|
||||||
|
|md| md.accessed(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
SortByKind::Created => {
|
||||||
|
builder.sort_by_file_path(move |a, b| {
|
||||||
|
sort_by_metadata_time(
|
||||||
|
a, b,
|
||||||
|
self.reverse,
|
||||||
|
|md| md.created(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SortByKind {
|
||||||
|
fn new(kind: &str) -> SortByKind {
|
||||||
|
match kind {
|
||||||
|
"none" => SortByKind::None,
|
||||||
|
"path" => SortByKind::Path,
|
||||||
|
"modified" => SortByKind::LastModified,
|
||||||
|
"accessed" => SortByKind::LastAccessed,
|
||||||
|
"created" => SortByKind::Created,
|
||||||
|
_ => SortByKind::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ArgMatches {
|
impl ArgMatches {
|
||||||
/// Create an ArgMatches from clap's parse result.
|
/// Create an ArgMatches from clap's parse result.
|
||||||
fn new(clap_matches: clap::ArgMatches<'static>) -> ArgMatches {
|
fn new(clap_matches: clap::ArgMatches<'static>) -> ArgMatches {
|
||||||
@@ -373,25 +496,27 @@ impl ArgMatches {
|
|||||||
///
|
///
|
||||||
/// If there are no additional arguments from the environment (e.g., a
|
/// If there are no additional arguments from the environment (e.g., a
|
||||||
/// config file), then the given matches are returned as is.
|
/// config file), then the given matches are returned as is.
|
||||||
fn reconfigure(self) -> ArgMatches {
|
fn reconfigure(self) -> Result<ArgMatches> {
|
||||||
// If the end user says no config, then respect it.
|
// If the end user says no config, then respect it.
|
||||||
if self.is_present("no-config") {
|
if self.is_present("no-config") {
|
||||||
debug!("not reading config files because --no-config is present");
|
log::debug!(
|
||||||
return self;
|
"not reading config files because --no-config is present"
|
||||||
|
);
|
||||||
|
return Ok(self);
|
||||||
}
|
}
|
||||||
// If the user wants ripgrep to use a config file, then parse args
|
// If the user wants ripgrep to use a config file, then parse args
|
||||||
// from that first.
|
// from that first.
|
||||||
let mut args = config::args();
|
let mut args = config::args();
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
return self;
|
return Ok(self);
|
||||||
}
|
}
|
||||||
let mut cliargs = env::args_os();
|
let mut cliargs = env::args_os();
|
||||||
if let Some(bin) = cliargs.next() {
|
if let Some(bin) = cliargs.next() {
|
||||||
args.insert(0, bin);
|
args.insert(0, bin);
|
||||||
}
|
}
|
||||||
args.extend(cliargs);
|
args.extend(cliargs);
|
||||||
debug!("final argv: {:?}", args);
|
log::debug!("final argv: {:?}", args);
|
||||||
ArgMatches::new(app::app().get_matches_from(args))
|
Ok(ArgMatches(clap_matches(args)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the result of parsing CLI arguments into ripgrep's higher level
|
/// Convert the result of parsing CLI arguments into ripgrep's higher level
|
||||||
@@ -500,7 +625,10 @@ impl ArgMatches {
|
|||||||
if let Some(limit) = self.dfa_size_limit()? {
|
if let Some(limit) = self.dfa_size_limit()? {
|
||||||
builder.dfa_size_limit(limit);
|
builder.dfa_size_limit(limit);
|
||||||
}
|
}
|
||||||
Ok(builder.build(&patterns.join("|"))?)
|
match builder.build(&patterns.join("|")) {
|
||||||
|
Ok(m) => Ok(m),
|
||||||
|
Err(err) => Err(From::from(suggest_multiline(err.to_string()))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a matcher using PCRE2.
|
/// Build a matcher using PCRE2.
|
||||||
@@ -515,17 +643,17 @@ impl ArgMatches {
|
|||||||
.caseless(self.case_insensitive())
|
.caseless(self.case_insensitive())
|
||||||
.multi_line(true)
|
.multi_line(true)
|
||||||
.word(self.is_present("word-regexp"));
|
.word(self.is_present("word-regexp"));
|
||||||
// For whatever reason, the JIT craps out during compilation with a
|
// For whatever reason, the JIT craps out during regex compilation with
|
||||||
// "no more memory" error on 32 bit systems. So don't use it there.
|
// a "no more memory" error on 32 bit systems. So don't use it there.
|
||||||
if !cfg!(target_pointer_width = "32") {
|
if !cfg!(target_pointer_width = "32") {
|
||||||
builder.jit(true);
|
builder.jit_if_available(true);
|
||||||
}
|
}
|
||||||
if self.pcre2_unicode() {
|
if self.pcre2_unicode() {
|
||||||
builder.utf(true).ucp(true);
|
builder.utf(true).ucp(true);
|
||||||
if self.encoding()?.is_some() {
|
if self.encoding()?.is_some() {
|
||||||
// SAFETY: If an encoding was specified, then we're guaranteed
|
// SAFETY: If an encoding was specified, then we're guaranteed
|
||||||
// to get valid UTF-8, so we can disable PCRE2's UTF checking.
|
// to get valid UTF-8, so we can disable PCRE2's UTF checking.
|
||||||
// (Feeding invalid UTF-8 to PCRE2 is UB.)
|
// (Feeding invalid UTF-8 to PCRE2 is undefined behavior.)
|
||||||
unsafe {
|
unsafe {
|
||||||
builder.disable_utf_check();
|
builder.disable_utf_check();
|
||||||
}
|
}
|
||||||
@@ -663,23 +791,23 @@ impl ArgMatches {
|
|||||||
.follow_links(self.is_present("follow"))
|
.follow_links(self.is_present("follow"))
|
||||||
.max_filesize(self.max_file_size()?)
|
.max_filesize(self.max_file_size()?)
|
||||||
.threads(self.threads()?)
|
.threads(self.threads()?)
|
||||||
|
.same_file_system(self.is_present("one-file-system"))
|
||||||
|
.skip_stdout(!self.is_present("files"))
|
||||||
.overrides(self.overrides()?)
|
.overrides(self.overrides()?)
|
||||||
.types(self.types()?)
|
.types(self.types()?)
|
||||||
.hidden(!self.hidden())
|
.hidden(!self.hidden())
|
||||||
.parents(!self.no_ignore_parent())
|
.parents(!self.no_ignore_parent())
|
||||||
.ignore(!self.no_ignore())
|
.ignore(!self.no_ignore_dot())
|
||||||
.git_global(
|
.git_global(!self.no_ignore_vcs() && !self.no_ignore_global())
|
||||||
!self.no_ignore()
|
.git_ignore(!self.no_ignore_vcs())
|
||||||
&& !self.no_ignore_vcs()
|
.git_exclude(!self.no_ignore_vcs())
|
||||||
&& !self.no_ignore_global())
|
.ignore_case_insensitive(self.ignore_file_case_insensitive());
|
||||||
.git_ignore(!self.no_ignore() && !self.no_ignore_vcs())
|
|
||||||
.git_exclude(!self.no_ignore() && !self.no_ignore_vcs());
|
|
||||||
if !self.no_ignore() {
|
if !self.no_ignore() {
|
||||||
builder.add_custom_ignore_filename(".rgignore");
|
builder.add_custom_ignore_filename(".rgignore");
|
||||||
}
|
}
|
||||||
if self.is_present("sort-files") {
|
let sortby = self.sort_by()?;
|
||||||
builder.sort_by_file_name(|a, b| a.cmp(b));
|
sortby.check()?;
|
||||||
}
|
sortby.configure_walk_builder(&mut builder);
|
||||||
Ok(builder)
|
Ok(builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -738,7 +866,7 @@ impl ArgMatches {
|
|||||||
} else if preference == "ansi" {
|
} else if preference == "ansi" {
|
||||||
ColorChoice::AlwaysAnsi
|
ColorChoice::AlwaysAnsi
|
||||||
} else if preference == "auto" {
|
} else if preference == "auto" {
|
||||||
if atty::is(atty::Stream::Stdout) || self.is_present("pretty") {
|
if cli::is_tty_stdout() || self.is_present("pretty") {
|
||||||
ColorChoice::Auto
|
ColorChoice::Auto
|
||||||
} else {
|
} else {
|
||||||
ColorChoice::Never
|
ColorChoice::Never
|
||||||
@@ -754,15 +882,7 @@ impl ArgMatches {
|
|||||||
/// is returned.
|
/// is returned.
|
||||||
fn color_specs(&self) -> Result<ColorSpecs> {
|
fn color_specs(&self) -> Result<ColorSpecs> {
|
||||||
// Start with a default set of color specs.
|
// Start with a default set of color specs.
|
||||||
let mut specs = vec![
|
let mut specs = default_color_specs();
|
||||||
#[cfg(unix)]
|
|
||||||
"path:fg:magenta".parse().unwrap(),
|
|
||||||
#[cfg(windows)]
|
|
||||||
"path:fg:cyan".parse().unwrap(),
|
|
||||||
"line:fg:green".parse().unwrap(),
|
|
||||||
"match:fg:red".parse().unwrap(),
|
|
||||||
"match:style:bold".parse().unwrap(),
|
|
||||||
];
|
|
||||||
for spec_str in self.values_of_lossy_vec("colors") {
|
for spec_str in self.values_of_lossy_vec("colors") {
|
||||||
specs.push(spec_str.parse()?);
|
specs.push(spec_str.parse()?);
|
||||||
}
|
}
|
||||||
@@ -798,9 +918,9 @@ impl ArgMatches {
|
|||||||
///
|
///
|
||||||
/// If one was not provided, the default `--` is returned.
|
/// If one was not provided, the default `--` is returned.
|
||||||
fn context_separator(&self) -> Vec<u8> {
|
fn context_separator(&self) -> Vec<u8> {
|
||||||
match self.value_of_lossy("context-separator") {
|
match self.value_of_os("context-separator") {
|
||||||
None => b"--".to_vec(),
|
None => b"--".to_vec(),
|
||||||
Some(sep) => unescape(&sep),
|
Some(sep) => cli::unescape_os(&sep),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,7 +995,7 @@ impl ArgMatches {
|
|||||||
if self.is_present("no-heading") || self.is_present("vimgrep") {
|
if self.is_present("no-heading") || self.is_present("vimgrep") {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
atty::is(atty::Stream::Stdout)
|
cli::is_tty_stdout()
|
||||||
|| self.is_present("heading")
|
|| self.is_present("heading")
|
||||||
|| self.is_present("pretty")
|
|| self.is_present("pretty")
|
||||||
}
|
}
|
||||||
@@ -887,6 +1007,11 @@ impl ArgMatches {
|
|||||||
self.is_present("hidden") || self.unrestricted_count() >= 2
|
self.is_present("hidden") || self.unrestricted_count() >= 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if ignore files should be processed case insensitively.
|
||||||
|
fn ignore_file_case_insensitive(&self) -> bool {
|
||||||
|
self.is_present("ignore-file-case-insensitive")
|
||||||
|
}
|
||||||
|
|
||||||
/// Return all of the ignore file paths given on the command line.
|
/// Return all of the ignore file paths given on the command line.
|
||||||
fn ignore_paths(&self) -> Vec<PathBuf> {
|
fn ignore_paths(&self) -> Vec<PathBuf> {
|
||||||
let paths = match self.values_of_os("ignore-file") {
|
let paths = match self.values_of_os("ignore-file") {
|
||||||
@@ -927,7 +1052,7 @@ impl ArgMatches {
|
|||||||
// generally want to show line numbers by default when printing to a
|
// generally want to show line numbers by default when printing to a
|
||||||
// tty for human consumption, except for one interesting case: when
|
// tty for human consumption, except for one interesting case: when
|
||||||
// we're only searching stdin. This makes pipelines work as expected.
|
// we're only searching stdin. This makes pipelines work as expected.
|
||||||
(atty::is(atty::Stream::Stdout) && !self.is_only_stdin(paths))
|
(cli::is_tty_stdout() && !self.is_only_stdin(paths))
|
||||||
|| self.is_present("line-number")
|
|| self.is_present("line-number")
|
||||||
|| self.is_present("column")
|
|| self.is_present("column")
|
||||||
|| self.is_present("pretty")
|
|| self.is_present("pretty")
|
||||||
@@ -981,6 +1106,11 @@ impl ArgMatches {
|
|||||||
self.is_present("no-ignore") || self.unrestricted_count() >= 1
|
self.is_present("no-ignore") || self.unrestricted_count() >= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if .ignore files should be ignored.
|
||||||
|
fn no_ignore_dot(&self) -> bool {
|
||||||
|
self.is_present("no-ignore-dot") || self.no_ignore()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if global ignore files should be ignored.
|
/// Returns true if global ignore files should be ignored.
|
||||||
fn no_ignore_global(&self) -> bool {
|
fn no_ignore_global(&self) -> bool {
|
||||||
self.is_present("no-ignore-global") || self.no_ignore()
|
self.is_present("no-ignore-global") || self.no_ignore()
|
||||||
@@ -1027,7 +1157,7 @@ impl ArgMatches {
|
|||||||
builder.add(&glob)?;
|
builder.add(&glob)?;
|
||||||
}
|
}
|
||||||
// This only enables case insensitivity for subsequent globs.
|
// This only enables case insensitivity for subsequent globs.
|
||||||
builder.case_insensitive(true)?;
|
builder.case_insensitive(true).unwrap();
|
||||||
for glob in self.values_of_lossy_vec("iglob") {
|
for glob in self.values_of_lossy_vec("iglob") {
|
||||||
builder.add(&glob)?;
|
builder.add(&glob)?;
|
||||||
}
|
}
|
||||||
@@ -1062,8 +1192,7 @@ impl ArgMatches {
|
|||||||
let file_is_stdin = self.values_of_os("file")
|
let file_is_stdin = self.values_of_os("file")
|
||||||
.map_or(false, |mut files| files.any(|f| f == "-"));
|
.map_or(false, |mut files| files.any(|f| f == "-"));
|
||||||
let search_cwd =
|
let search_cwd =
|
||||||
atty::is(atty::Stream::Stdin)
|
!cli::is_readable_stdin()
|
||||||
|| !stdin_is_readable()
|
|
||||||
|| (self.is_present("file") && file_is_stdin)
|
|| (self.is_present("file") && file_is_stdin)
|
||||||
|| self.is_present("files")
|
|| self.is_present("files")
|
||||||
|| self.is_present("type-list");
|
|| self.is_present("type-list");
|
||||||
@@ -1079,9 +1208,9 @@ impl ArgMatches {
|
|||||||
/// If the provided path separator is more than a single byte, then an
|
/// If the provided path separator is more than a single byte, then an
|
||||||
/// error is returned.
|
/// error is returned.
|
||||||
fn path_separator(&self) -> Result<Option<u8>> {
|
fn path_separator(&self) -> Result<Option<u8>> {
|
||||||
let sep = match self.value_of_lossy("path-separator") {
|
let sep = match self.value_of_os("path-separator") {
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
Some(sep) => unescape(&sep),
|
Some(sep) => cli::unescape_os(&sep),
|
||||||
};
|
};
|
||||||
if sep.is_empty() {
|
if sep.is_empty() {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -1092,7 +1221,7 @@ impl ArgMatches {
|
|||||||
In some shells on Windows '/' is automatically \
|
In some shells on Windows '/' is automatically \
|
||||||
expanded. Use '//' instead.",
|
expanded. Use '//' instead.",
|
||||||
sep.len(),
|
sep.len(),
|
||||||
escape(&sep),
|
cli::escape(&sep),
|
||||||
)))
|
)))
|
||||||
} else {
|
} else {
|
||||||
Ok(Some(sep[0]))
|
Ok(Some(sep[0]))
|
||||||
@@ -1139,18 +1268,18 @@ impl ArgMatches {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(files) = self.values_of_os("file") {
|
if let Some(paths) = self.values_of_os("file") {
|
||||||
for file in files {
|
for path in paths {
|
||||||
if file == "-" {
|
if path == "-" {
|
||||||
let stdin = io::stdin();
|
pats.extend(cli::patterns_from_stdin()?
|
||||||
for line in stdin.lock().lines() {
|
.into_iter()
|
||||||
pats.push(self.pattern_from_str(&line?));
|
.map(|p| self.pattern_from_string(p))
|
||||||
}
|
);
|
||||||
} else {
|
} else {
|
||||||
let f = File::open(file)?;
|
pats.extend(cli::patterns_from_path(path)?
|
||||||
for line in io::BufReader::new(f).lines() {
|
.into_iter()
|
||||||
pats.push(self.pattern_from_str(&line?));
|
.map(|p| self.pattern_from_string(p))
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1172,20 +1301,24 @@ impl ArgMatches {
|
|||||||
///
|
///
|
||||||
/// If the pattern is not valid UTF-8, then an error is returned.
|
/// If the pattern is not valid UTF-8, then an error is returned.
|
||||||
fn pattern_from_os_str(&self, pat: &OsStr) -> Result<String> {
|
fn pattern_from_os_str(&self, pat: &OsStr) -> Result<String> {
|
||||||
let s = pattern_to_str(pat)?;
|
let s = cli::pattern_from_os(pat)?;
|
||||||
Ok(self.pattern_from_str(s))
|
Ok(self.pattern_from_str(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a &str pattern to a String pattern. The pattern is escaped
|
/// Converts a &str pattern to a String pattern. The pattern is escaped
|
||||||
/// if -F/--fixed-strings is set.
|
/// if -F/--fixed-strings is set.
|
||||||
fn pattern_from_str(&self, pat: &str) -> String {
|
fn pattern_from_str(&self, pat: &str) -> String {
|
||||||
let litpat = self.pattern_literal(pat.to_string());
|
self.pattern_from_string(pat.to_string())
|
||||||
let s = self.pattern_line(litpat);
|
}
|
||||||
|
|
||||||
if s.is_empty() {
|
/// Applies additional processing on the given pattern if necessary
|
||||||
|
/// (such as escaping meta characters or turning it into a line regex).
|
||||||
|
fn pattern_from_string(&self, pat: String) -> String {
|
||||||
|
let pat = self.pattern_line(self.pattern_literal(pat));
|
||||||
|
if pat.is_empty() {
|
||||||
self.pattern_empty()
|
self.pattern_empty()
|
||||||
} else {
|
} else {
|
||||||
s
|
pat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1222,6 +1355,17 @@ impl ArgMatches {
|
|||||||
Some(Path::new(path).to_path_buf())
|
Some(Path::new(path).to_path_buf())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds the set of globs for filtering files to apply to the --pre
|
||||||
|
/// flag. If no --pre-globs are available, then this always returns an
|
||||||
|
/// empty set of globs.
|
||||||
|
fn preprocessor_globs(&self) -> Result<Override> {
|
||||||
|
let mut builder = OverrideBuilder::new(env::current_dir()?);
|
||||||
|
for glob in self.values_of_lossy_vec("pre-glob") {
|
||||||
|
builder.add(&glob)?;
|
||||||
|
}
|
||||||
|
Ok(builder.build()?)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse the regex-size-limit argument option into a byte count.
|
/// Parse the regex-size-limit argument option into a byte count.
|
||||||
fn regex_size_limit(&self) -> Result<Option<usize>> {
|
fn regex_size_limit(&self) -> Result<Option<usize>> {
|
||||||
let r = self.parse_human_readable_size("regex-size-limit")?;
|
let r = self.parse_human_readable_size("regex-size-limit")?;
|
||||||
@@ -1233,6 +1377,22 @@ impl ArgMatches {
|
|||||||
self.value_of_lossy("replace").map(|s| s.into_bytes())
|
self.value_of_lossy("replace").map(|s| s.into_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the sorting criteria based on command line parameters.
|
||||||
|
fn sort_by(&self) -> Result<SortBy> {
|
||||||
|
// For backcompat, continue supporting deprecated --sort-files flag.
|
||||||
|
if self.is_present("sort-files") {
|
||||||
|
return Ok(SortBy::asc(SortByKind::Path));
|
||||||
|
}
|
||||||
|
let sortby = match self.value_of_lossy("sort") {
|
||||||
|
None => match self.value_of_lossy("sortr") {
|
||||||
|
None => return Ok(SortBy::none()),
|
||||||
|
Some(choice) => SortBy::desc(SortByKind::new(&choice)),
|
||||||
|
}
|
||||||
|
Some(choice) => SortBy::asc(SortByKind::new(&choice)),
|
||||||
|
};
|
||||||
|
Ok(sortby)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if and only if aggregate statistics for a search should
|
/// Returns true if and only if aggregate statistics for a search should
|
||||||
/// be tracked.
|
/// be tracked.
|
||||||
///
|
///
|
||||||
@@ -1243,28 +1403,6 @@ impl ArgMatches {
|
|||||||
self.output_kind() == OutputKind::JSON || self.is_present("stats")
|
self.output_kind() == OutputKind::JSON || self.is_present("stats")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a handle to stdout for filtering search.
|
|
||||||
///
|
|
||||||
/// A handle is returned if and only if ripgrep's stdout is being
|
|
||||||
/// redirected to a file. The handle returned corresponds to that file.
|
|
||||||
///
|
|
||||||
/// This can be used to ensure that we do not attempt to search a file
|
|
||||||
/// that ripgrep is writing to.
|
|
||||||
fn stdout_handle(&self) -> Option<Handle> {
|
|
||||||
let h = match Handle::stdout() {
|
|
||||||
Err(_) => return None,
|
|
||||||
Ok(h) => h,
|
|
||||||
};
|
|
||||||
let md = match h.as_file().metadata() {
|
|
||||||
Err(_) => return None,
|
|
||||||
Ok(md) => md,
|
|
||||||
};
|
|
||||||
if !md.is_file() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// When the output format is `Summary`, this returns the type of summary
|
/// When the output format is `Summary`, this returns the type of summary
|
||||||
/// output to show.
|
/// output to show.
|
||||||
///
|
///
|
||||||
@@ -1288,7 +1426,7 @@ impl ArgMatches {
|
|||||||
|
|
||||||
/// Return the number of threads that should be used for parallelism.
|
/// Return the number of threads that should be used for parallelism.
|
||||||
fn threads(&self) -> Result<usize> {
|
fn threads(&self) -> Result<usize> {
|
||||||
if self.is_present("sort-files") {
|
if self.sort_by()?.kind != SortByKind::None {
|
||||||
return Ok(1);
|
return Ok(1);
|
||||||
}
|
}
|
||||||
let threads = self.usize_of("threads")?.unwrap_or(0);
|
let threads = self.usize_of("threads")?.unwrap_or(0);
|
||||||
@@ -1386,40 +1524,11 @@ impl ArgMatches {
|
|||||||
&self,
|
&self,
|
||||||
arg_name: &str,
|
arg_name: &str,
|
||||||
) -> Result<Option<u64>> {
|
) -> Result<Option<u64>> {
|
||||||
lazy_static! {
|
let size = match self.value_of_lossy(arg_name) {
|
||||||
static ref RE: Regex = Regex::new(r"^([0-9]+)([KMG])?$").unwrap();
|
None => return Ok(None),
|
||||||
}
|
Some(size) => size,
|
||||||
|
|
||||||
let arg_value = match self.value_of_lossy(arg_name) {
|
|
||||||
Some(x) => x,
|
|
||||||
None => return Ok(None)
|
|
||||||
};
|
};
|
||||||
let caps = RE
|
Ok(Some(cli::parse_human_readable_size(&size)?))
|
||||||
.captures(&arg_value)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
format!("invalid format for {}", arg_name)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let value = caps[1].parse::<u64>()?;
|
|
||||||
let suffix = caps.get(2).map(|x| x.as_str());
|
|
||||||
|
|
||||||
let v_10 = value.checked_mul(1024);
|
|
||||||
let v_20 = v_10.and_then(|x| x.checked_mul(1024));
|
|
||||||
let v_30 = v_20.and_then(|x| x.checked_mul(1024));
|
|
||||||
let try_suffix = |x: Option<u64>| {
|
|
||||||
if x.is_some() {
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
Err(From::from(format!("number too large for {}", arg_name)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match suffix {
|
|
||||||
None => Ok(Some(value)),
|
|
||||||
Some("K") => try_suffix(v_10),
|
|
||||||
Some("M") => try_suffix(v_20),
|
|
||||||
Some("G") => try_suffix(v_30),
|
|
||||||
_ => Err(From::from(format!("invalid suffix for {}", arg_name)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1453,21 +1562,6 @@ impl ArgMatches {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert an OsStr to a Unicode string.
|
|
||||||
///
|
|
||||||
/// Patterns _must_ be valid UTF-8, so if the given OsStr isn't valid UTF-8,
|
|
||||||
/// this returns an error.
|
|
||||||
fn pattern_to_str(s: &OsStr) -> Result<&str> {
|
|
||||||
s.to_str().ok_or_else(|| {
|
|
||||||
From::from(format!(
|
|
||||||
"Argument '{}' is not valid UTF-8. \
|
|
||||||
Use hex escape sequences to match arbitrary \
|
|
||||||
bytes in a pattern (e.g., \\xFF).",
|
|
||||||
s.to_string_lossy()
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inspect an error resulting from building a Rust regex matcher, and if it's
|
/// Inspect an error resulting from building a Rust regex matcher, and if it's
|
||||||
/// believed to correspond to a syntax error that PCRE2 could handle, then
|
/// believed to correspond to a syntax error that PCRE2 could handle, then
|
||||||
/// add a message to suggest the use of -P/--pcre2.
|
/// add a message to suggest the use of -P/--pcre2.
|
||||||
@@ -1483,6 +1577,17 @@ and look-around.", msg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn suggest_multiline(msg: String) -> String {
|
||||||
|
if msg.contains("the literal") && msg.contains("not allowed") {
|
||||||
|
format!("{}
|
||||||
|
|
||||||
|
Consider enabling multiline mode with the --multiline flag (or -U for short).
|
||||||
|
When multiline mode is enabled, new line characters can be matched.", msg)
|
||||||
|
} else {
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert the result of parsing a human readable file size to a `usize`,
|
/// Convert the result of parsing a human readable file size to a `usize`,
|
||||||
/// failing if the type does not fit.
|
/// failing if the type does not fit.
|
||||||
fn u64_to_usize(
|
fn u64_to_usize(
|
||||||
@@ -1502,32 +1607,59 @@ fn u64_to_usize(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if stdin is deemed searchable.
|
/// Builds a comparator for sorting two files according to a system time
|
||||||
#[cfg(unix)]
|
/// extracted from the file's metadata.
|
||||||
fn stdin_is_readable() -> bool {
|
///
|
||||||
use std::os::unix::fs::FileTypeExt;
|
/// If there was a problem extracting the metadata or if the time is not
|
||||||
|
/// available, then both entries compare equal.
|
||||||
let ft = match Handle::stdin().and_then(|h| h.as_file().metadata()) {
|
fn sort_by_metadata_time<G>(
|
||||||
Err(_) => return false,
|
p1: &Path,
|
||||||
Ok(md) => md.file_type(),
|
p2: &Path,
|
||||||
|
reverse: bool,
|
||||||
|
get_time: G,
|
||||||
|
) -> cmp::Ordering
|
||||||
|
where G: Fn(&fs::Metadata) -> io::Result<SystemTime>
|
||||||
|
{
|
||||||
|
let t1 = match p1.metadata().and_then(|md| get_time(&md)) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(_) => return cmp::Ordering::Equal,
|
||||||
};
|
};
|
||||||
ft.is_file() || ft.is_fifo()
|
let t2 = match p2.metadata().and_then(|md| get_time(&md)) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(_) => return cmp::Ordering::Equal,
|
||||||
|
};
|
||||||
|
if reverse {
|
||||||
|
t1.cmp(&t2).reverse()
|
||||||
|
} else {
|
||||||
|
t1.cmp(&t2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if stdin is deemed searchable.
|
/// Returns a clap matches object if the given arguments parse successfully.
|
||||||
#[cfg(windows)]
|
///
|
||||||
fn stdin_is_readable() -> bool {
|
/// Otherwise, if an error occurred, then it is returned unless the error
|
||||||
use std::os::windows::io::AsRawHandle;
|
/// corresponds to a `--help` or `--version` request. In which case, the
|
||||||
use winapi::um::fileapi::GetFileType;
|
/// corresponding output is printed and the current process is exited
|
||||||
use winapi::um::winbase::{FILE_TYPE_DISK, FILE_TYPE_PIPE};
|
/// successfully.
|
||||||
|
fn clap_matches<I, T>(
|
||||||
let handle = match Handle::stdin() {
|
args: I,
|
||||||
Err(_) => return false,
|
) -> Result<clap::ArgMatches<'static>>
|
||||||
Ok(handle) => handle,
|
where I: IntoIterator<Item=T>,
|
||||||
|
T: Into<OsString> + Clone
|
||||||
|
{
|
||||||
|
let err = match app::app().get_matches_from_safe(args) {
|
||||||
|
Ok(matches) => return Ok(matches),
|
||||||
|
Err(err) => err,
|
||||||
};
|
};
|
||||||
let raw_handle = handle.as_raw_handle();
|
if err.use_stderr() {
|
||||||
// SAFETY: As far as I can tell, it's not possible to use GetFileType in
|
return Err(err.into());
|
||||||
// a way that violates safety. We give it a handle and we get an integer.
|
}
|
||||||
let ft = unsafe { GetFileType(raw_handle) };
|
// Explicitly ignore any error returned by writeln!. The most likely error
|
||||||
ft == FILE_TYPE_DISK || ft == FILE_TYPE_PIPE
|
// at this point is a broken pipe error, in which case, we want to ignore
|
||||||
|
// it and exit quietly.
|
||||||
|
//
|
||||||
|
// (This is the point of this helper function. clap's functionality for
|
||||||
|
// doing this will panic on a broken pipe error.)
|
||||||
|
let _ = writeln!(io::stdout(), "{}", err);
|
||||||
|
process::exit(0);
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,9 @@ use std::io::{self, BufRead};
|
|||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use Result;
|
use log;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
/// Return a sequence of arguments derived from ripgrep rc configuration files.
|
/// Return a sequence of arguments derived from ripgrep rc configuration files.
|
||||||
pub fn args() -> Vec<OsString> {
|
pub fn args() -> Vec<OsString> {
|
||||||
@@ -34,7 +36,7 @@ pub fn args() -> Vec<OsString> {
|
|||||||
message!("{}:{}", config_path.display(), err);
|
message!("{}:{}", config_path.display(), err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug!(
|
log::debug!(
|
||||||
"{}: arguments loaded from config file: {:?}",
|
"{}: arguments loaded from config file: {:?}",
|
||||||
config_path.display(),
|
config_path.display(),
|
||||||
args
|
args
|
||||||
|
@@ -1,190 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::fmt;
|
|
||||||
use std::io::{self, Read};
|
|
||||||
use std::path::Path;
|
|
||||||
use std::process::{self, Stdio};
|
|
||||||
|
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
|
||||||
|
|
||||||
/// A decompression command, contains the command to be spawned as well as any
|
|
||||||
/// necessary CLI args.
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
struct DecompressionCommand {
|
|
||||||
cmd: &'static str,
|
|
||||||
args: &'static [&'static str],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DecompressionCommand {
|
|
||||||
/// Create a new decompress command
|
|
||||||
fn new(
|
|
||||||
cmd: &'static str,
|
|
||||||
args: &'static [&'static str],
|
|
||||||
) -> DecompressionCommand {
|
|
||||||
DecompressionCommand {
|
|
||||||
cmd, args
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for DecompressionCommand {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{} {}", self.cmd, self.args.join(" "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref DECOMPRESSION_COMMANDS: HashMap<
|
|
||||||
&'static str,
|
|
||||||
DecompressionCommand,
|
|
||||||
> = {
|
|
||||||
let mut m = HashMap::new();
|
|
||||||
|
|
||||||
const ARGS: &[&str] = &["-d", "-c"];
|
|
||||||
m.insert("gz", DecompressionCommand::new("gzip", ARGS));
|
|
||||||
m.insert("bz2", DecompressionCommand::new("bzip2", ARGS));
|
|
||||||
m.insert("xz", DecompressionCommand::new("xz", ARGS));
|
|
||||||
m.insert("lz4", DecompressionCommand::new("lz4", ARGS));
|
|
||||||
|
|
||||||
const LZMA_ARGS: &[&str] = &["--format=lzma", "-d", "-c"];
|
|
||||||
m.insert("lzma", DecompressionCommand::new("xz", LZMA_ARGS));
|
|
||||||
|
|
||||||
m
|
|
||||||
};
|
|
||||||
static ref SUPPORTED_COMPRESSION_FORMATS: GlobSet = {
|
|
||||||
let mut builder = GlobSetBuilder::new();
|
|
||||||
builder.add(Glob::new("*.gz").unwrap());
|
|
||||||
builder.add(Glob::new("*.bz2").unwrap());
|
|
||||||
builder.add(Glob::new("*.xz").unwrap());
|
|
||||||
builder.add(Glob::new("*.lz4").unwrap());
|
|
||||||
builder.add(Glob::new("*.lzma").unwrap());
|
|
||||||
builder.build().unwrap()
|
|
||||||
};
|
|
||||||
static ref TAR_ARCHIVE_FORMATS: GlobSet = {
|
|
||||||
let mut builder = GlobSetBuilder::new();
|
|
||||||
builder.add(Glob::new("*.tar.gz").unwrap());
|
|
||||||
builder.add(Glob::new("*.tar.xz").unwrap());
|
|
||||||
builder.add(Glob::new("*.tar.bz2").unwrap());
|
|
||||||
builder.add(Glob::new("*.tar.lz4").unwrap());
|
|
||||||
builder.add(Glob::new("*.tgz").unwrap());
|
|
||||||
builder.add(Glob::new("*.txz").unwrap());
|
|
||||||
builder.add(Glob::new("*.tbz2").unwrap());
|
|
||||||
builder.build().unwrap()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// DecompressionReader provides an `io::Read` implementation for a limited
|
|
||||||
/// set of compression formats.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DecompressionReader {
|
|
||||||
cmd: DecompressionCommand,
|
|
||||||
child: process::Child,
|
|
||||||
done: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DecompressionReader {
|
|
||||||
/// Returns a handle to the stdout of the spawned decompression process for
|
|
||||||
/// `path`, which can be directly searched in the worker. When the returned
|
|
||||||
/// value is exhausted, the underlying process is reaped. If the underlying
|
|
||||||
/// process fails, then its stderr is read and converted into a normal
|
|
||||||
/// io::Error.
|
|
||||||
///
|
|
||||||
/// If there is any error in spawning the decompression command, then
|
|
||||||
/// return `None`, after outputting any necessary debug or error messages.
|
|
||||||
pub fn from_path(path: &Path) -> Option<DecompressionReader> {
|
|
||||||
let extension = match path.extension().and_then(OsStr::to_str) {
|
|
||||||
Some(extension) => extension,
|
|
||||||
None => {
|
|
||||||
debug!(
|
|
||||||
"{}: failed to get compresson extension", path.display());
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let decompression_cmd = match DECOMPRESSION_COMMANDS.get(extension) {
|
|
||||||
Some(cmd) => cmd,
|
|
||||||
None => {
|
|
||||||
debug!(
|
|
||||||
"{}: failed to get decompression command", path.display());
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let cmd = process::Command::new(decompression_cmd.cmd)
|
|
||||||
.args(decompression_cmd.args)
|
|
||||||
.arg(path)
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.stderr(Stdio::piped())
|
|
||||||
.spawn();
|
|
||||||
let child = match cmd {
|
|
||||||
Ok(process) => process,
|
|
||||||
Err(_) => {
|
|
||||||
debug!(
|
|
||||||
"{}: decompression command '{}' not found",
|
|
||||||
path.display(), decompression_cmd.cmd);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Some(DecompressionReader::new(*decompression_cmd, child))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(
|
|
||||||
cmd: DecompressionCommand,
|
|
||||||
child: process::Child,
|
|
||||||
) -> DecompressionReader {
|
|
||||||
DecompressionReader {
|
|
||||||
cmd: cmd,
|
|
||||||
child: child,
|
|
||||||
done: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_error(&mut self) -> io::Result<io::Error> {
|
|
||||||
let mut errbytes = vec![];
|
|
||||||
self.child.stderr.as_mut().unwrap().read_to_end(&mut errbytes)?;
|
|
||||||
let errstr = String::from_utf8_lossy(&errbytes);
|
|
||||||
let errstr = errstr.trim();
|
|
||||||
|
|
||||||
Ok(if errstr.is_empty() {
|
|
||||||
let msg = format!("decompression command failed: '{}'", self.cmd);
|
|
||||||
io::Error::new(io::ErrorKind::Other, msg)
|
|
||||||
} else {
|
|
||||||
let msg = format!(
|
|
||||||
"decompression command '{}' failed: {}", self.cmd, errstr);
|
|
||||||
io::Error::new(io::ErrorKind::Other, msg)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Read for DecompressionReader {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
if self.done {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
let nread = self.child.stdout.as_mut().unwrap().read(buf)?;
|
|
||||||
if nread == 0 {
|
|
||||||
self.done = true;
|
|
||||||
// Reap the child now that we're done reading.
|
|
||||||
// If the command failed, report stderr as an error.
|
|
||||||
if !self.child.wait()?.success() {
|
|
||||||
return Err(self.read_error()?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(nread)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the given path contains a supported compression format or
|
|
||||||
/// is a TAR archive.
|
|
||||||
pub fn is_compressed(path: &Path) -> bool {
|
|
||||||
is_supported_compression_format(path) || is_tar_archive(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the given path matches any one of the supported compression
|
|
||||||
/// formats
|
|
||||||
fn is_supported_compression_format(path: &Path) -> bool {
|
|
||||||
SUPPORTED_COMPRESSION_FORMATS.is_match(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the given path matches any of the known TAR file formats.
|
|
||||||
fn is_tar_archive(path: &Path) -> bool {
|
|
||||||
TAR_ARCHIVE_FORMATS.is_match(path)
|
|
||||||
}
|
|
74
src/main.rs
74
src/main.rs
@@ -1,23 +1,4 @@
|
|||||||
extern crate atty;
|
use std::io::{self, Write};
|
||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
extern crate globset;
|
|
||||||
extern crate grep;
|
|
||||||
extern crate ignore;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
extern crate num_cpus;
|
|
||||||
extern crate regex;
|
|
||||||
extern crate same_file;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_json;
|
|
||||||
extern crate termcolor;
|
|
||||||
#[cfg(windows)]
|
|
||||||
extern crate winapi;
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
@@ -33,44 +14,45 @@ mod messages;
|
|||||||
mod app;
|
mod app;
|
||||||
mod args;
|
mod args;
|
||||||
mod config;
|
mod config;
|
||||||
mod decompressor;
|
|
||||||
mod preprocessor;
|
|
||||||
mod logger;
|
mod logger;
|
||||||
mod path_printer;
|
mod path_printer;
|
||||||
mod search;
|
mod search;
|
||||||
mod subject;
|
mod subject;
|
||||||
mod unescape;
|
|
||||||
|
|
||||||
type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>;
|
type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
match Args::parse().and_then(try_main) {
|
if let Err(err) = Args::parse().and_then(try_main) {
|
||||||
Ok(true) => process::exit(0),
|
eprintln!("{}", err);
|
||||||
Ok(false) => process::exit(1),
|
process::exit(2);
|
||||||
Err(err) => {
|
|
||||||
eprintln!("{}", err);
|
|
||||||
process::exit(2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_main(args: Args) -> Result<bool> {
|
fn try_main(args: Args) -> Result<()> {
|
||||||
use args::Command::*;
|
use args::Command::*;
|
||||||
|
|
||||||
match args.command()? {
|
let matched =
|
||||||
Search => search(args),
|
match args.command()? {
|
||||||
SearchParallel => search_parallel(args),
|
Search => search(&args),
|
||||||
SearchNever => Ok(false),
|
SearchParallel => search_parallel(&args),
|
||||||
Files => files(args),
|
SearchNever => Ok(false),
|
||||||
FilesParallel => files_parallel(args),
|
Files => files(&args),
|
||||||
Types => types(args),
|
FilesParallel => files_parallel(&args),
|
||||||
|
Types => types(&args),
|
||||||
|
}?;
|
||||||
|
if matched && (args.quiet() || !messages::errored()) {
|
||||||
|
process::exit(0)
|
||||||
|
} else if messages::errored() {
|
||||||
|
process::exit(2)
|
||||||
|
} else {
|
||||||
|
process::exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The top-level entry point for single-threaded search. This recursively
|
/// The top-level entry point for single-threaded search. This recursively
|
||||||
/// steps through the file list (current directory by default) and searches
|
/// steps through the file list (current directory by default) and searches
|
||||||
/// each file sequentially.
|
/// each file sequentially.
|
||||||
fn search(args: Args) -> Result<bool> {
|
fn search(args: &Args) -> Result<bool> {
|
||||||
let started_at = Instant::now();
|
let started_at = Instant::now();
|
||||||
let quit_after_match = args.quit_after_match()?;
|
let quit_after_match = args.quit_after_match()?;
|
||||||
let subject_builder = args.subject_builder();
|
let subject_builder = args.subject_builder();
|
||||||
@@ -90,7 +72,7 @@ fn search(args: Args) -> Result<bool> {
|
|||||||
if err.kind() == io::ErrorKind::BrokenPipe {
|
if err.kind() == io::ErrorKind::BrokenPipe {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
message!("{}: {}", subject.path().display(), err);
|
err_message!("{}: {}", subject.path().display(), err);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -113,7 +95,7 @@ fn search(args: Args) -> Result<bool> {
|
|||||||
/// The top-level entry point for multi-threaded search. The parallelism is
|
/// The top-level entry point for multi-threaded search. The parallelism is
|
||||||
/// itself achieved by the recursive directory traversal. All we need to do is
|
/// itself achieved by the recursive directory traversal. All we need to do is
|
||||||
/// feed it a worker for performing a search on each file.
|
/// feed it a worker for performing a search on each file.
|
||||||
fn search_parallel(args: Args) -> Result<bool> {
|
fn search_parallel(args: &Args) -> Result<bool> {
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::atomic::Ordering::SeqCst;
|
use std::sync::atomic::Ordering::SeqCst;
|
||||||
|
|
||||||
@@ -149,7 +131,7 @@ fn search_parallel(args: Args) -> Result<bool> {
|
|||||||
let search_result = match searcher.search(&subject) {
|
let search_result = match searcher.search(&subject) {
|
||||||
Ok(search_result) => search_result,
|
Ok(search_result) => search_result,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
message!("{}: {}", subject.path().display(), err);
|
err_message!("{}: {}", subject.path().display(), err);
|
||||||
return WalkState::Continue;
|
return WalkState::Continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -166,7 +148,7 @@ fn search_parallel(args: Args) -> Result<bool> {
|
|||||||
return WalkState::Quit;
|
return WalkState::Quit;
|
||||||
}
|
}
|
||||||
// Otherwise, we continue on our merry way.
|
// Otherwise, we continue on our merry way.
|
||||||
message!("{}: {}", subject.path().display(), err);
|
err_message!("{}: {}", subject.path().display(), err);
|
||||||
}
|
}
|
||||||
if matched.load(SeqCst) && quit_after_match {
|
if matched.load(SeqCst) && quit_after_match {
|
||||||
WalkState::Quit
|
WalkState::Quit
|
||||||
@@ -191,7 +173,7 @@ fn search_parallel(args: Args) -> Result<bool> {
|
|||||||
/// The top-level entry point for listing files without searching them. This
|
/// The top-level entry point for listing files without searching them. This
|
||||||
/// recursively steps through the file list (current directory by default) and
|
/// recursively steps through the file list (current directory by default) and
|
||||||
/// prints each path sequentially using a single thread.
|
/// prints each path sequentially using a single thread.
|
||||||
fn files(args: Args) -> Result<bool> {
|
fn files(args: &Args) -> Result<bool> {
|
||||||
let quit_after_match = args.quit_after_match()?;
|
let quit_after_match = args.quit_after_match()?;
|
||||||
let subject_builder = args.subject_builder();
|
let subject_builder = args.subject_builder();
|
||||||
let mut matched = false;
|
let mut matched = false;
|
||||||
@@ -221,7 +203,7 @@ fn files(args: Args) -> Result<bool> {
|
|||||||
/// The top-level entry point for listing files without searching them. This
|
/// The top-level entry point for listing files without searching them. This
|
||||||
/// recursively steps through the file list (current directory by default) and
|
/// recursively steps through the file list (current directory by default) and
|
||||||
/// prints each path sequentially using multiple threads.
|
/// prints each path sequentially using multiple threads.
|
||||||
fn files_parallel(args: Args) -> Result<bool> {
|
fn files_parallel(args: &Args) -> Result<bool> {
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::atomic::Ordering::SeqCst;
|
use std::sync::atomic::Ordering::SeqCst;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
@@ -273,7 +255,7 @@ fn files_parallel(args: Args) -> Result<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The top-level entry point for --type-list.
|
/// The top-level entry point for --type-list.
|
||||||
fn types(args: Args) -> Result<bool> {
|
fn types(args: &Args) -> Result<bool> {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let mut stdout = args.stdout();
|
let mut stdout = args.stdout();
|
||||||
for def in args.type_defs()? {
|
for def in args.type_defs()? {
|
||||||
|
@@ -1,21 +1,35 @@
|
|||||||
use std::sync::atomic::{ATOMIC_BOOL_INIT, AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
static MESSAGES: AtomicBool = ATOMIC_BOOL_INIT;
|
static MESSAGES: AtomicBool = AtomicBool::new(false);
|
||||||
static IGNORE_MESSAGES: AtomicBool = ATOMIC_BOOL_INIT;
|
static IGNORE_MESSAGES: AtomicBool = AtomicBool::new(false);
|
||||||
|
static ERRORED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
/// Emit a non-fatal error message, unless messages were disabled.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! message {
|
macro_rules! message {
|
||||||
($($tt:tt)*) => {
|
($($tt:tt)*) => {
|
||||||
if ::messages::messages() {
|
if crate::messages::messages() {
|
||||||
eprintln!($($tt)*);
|
eprintln!($($tt)*);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like message, but sets ripgrep's "errored" flag, which controls the exit
|
||||||
|
/// status.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! err_message {
|
||||||
|
($($tt:tt)*) => {
|
||||||
|
crate::messages::set_errored();
|
||||||
|
message!($($tt)*);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emit a non-fatal ignore-related error message (like a parse error), unless
|
||||||
|
/// ignore-messages were disabled.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! ignore_message {
|
macro_rules! ignore_message {
|
||||||
($($tt:tt)*) => {
|
($($tt:tt)*) => {
|
||||||
if ::messages::messages() && ::messages::ignore_messages() {
|
if crate::messages::messages() && crate::messages::ignore_messages() {
|
||||||
eprintln!($($tt)*);
|
eprintln!($($tt)*);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,3 +62,13 @@ pub fn ignore_messages() -> bool {
|
|||||||
pub fn set_ignore_messages(yes: bool) {
|
pub fn set_ignore_messages(yes: bool) {
|
||||||
IGNORE_MESSAGES.store(yes, Ordering::SeqCst)
|
IGNORE_MESSAGES.store(yes, Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if ripgrep came across a non-fatal error.
|
||||||
|
pub fn errored() -> bool {
|
||||||
|
ERRORED.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicate that ripgrep has come across a non-fatal error.
|
||||||
|
pub fn set_errored() {
|
||||||
|
ERRORED.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
@@ -1,93 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Read};
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::process::{self, Stdio};
|
|
||||||
|
|
||||||
/// PreprocessorReader provides an `io::Read` impl to read kids output.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PreprocessorReader {
|
|
||||||
cmd: PathBuf,
|
|
||||||
path: PathBuf,
|
|
||||||
child: process::Child,
|
|
||||||
done: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PreprocessorReader {
|
|
||||||
/// Returns a handle to the stdout of the spawned preprocessor process for
|
|
||||||
/// `path`, which can be directly searched in the worker. When the returned
|
|
||||||
/// value is exhausted, the underlying process is reaped. If the underlying
|
|
||||||
/// process fails, then its stderr is read and converted into a normal
|
|
||||||
/// io::Error.
|
|
||||||
///
|
|
||||||
/// If there is any error in spawning the preprocessor command, then
|
|
||||||
/// return the corresponding error.
|
|
||||||
pub fn from_cmd_path(
|
|
||||||
cmd: PathBuf,
|
|
||||||
path: &Path,
|
|
||||||
) -> io::Result<PreprocessorReader> {
|
|
||||||
let child = process::Command::new(&cmd)
|
|
||||||
.arg(path)
|
|
||||||
.stdin(Stdio::from(File::open(path)?))
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.stderr(Stdio::piped())
|
|
||||||
.spawn()
|
|
||||||
.map_err(|err| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
format!(
|
|
||||||
"error running preprocessor command '{}': {}",
|
|
||||||
cmd.display(),
|
|
||||||
err,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Ok(PreprocessorReader {
|
|
||||||
cmd: cmd,
|
|
||||||
path: path.to_path_buf(),
|
|
||||||
child: child,
|
|
||||||
done: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_error(&mut self) -> io::Result<io::Error> {
|
|
||||||
let mut errbytes = vec![];
|
|
||||||
self.child.stderr.as_mut().unwrap().read_to_end(&mut errbytes)?;
|
|
||||||
let errstr = String::from_utf8_lossy(&errbytes);
|
|
||||||
let errstr = errstr.trim();
|
|
||||||
|
|
||||||
Ok(if errstr.is_empty() {
|
|
||||||
let msg = format!(
|
|
||||||
"preprocessor command failed: '{} {}'",
|
|
||||||
self.cmd.display(),
|
|
||||||
self.path.display(),
|
|
||||||
);
|
|
||||||
io::Error::new(io::ErrorKind::Other, msg)
|
|
||||||
} else {
|
|
||||||
let msg = format!(
|
|
||||||
"preprocessor command failed: '{} {}': {}",
|
|
||||||
self.cmd.display(),
|
|
||||||
self.path.display(),
|
|
||||||
errstr,
|
|
||||||
);
|
|
||||||
io::Error::new(io::ErrorKind::Other, msg)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Read for PreprocessorReader {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
if self.done {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
let nread = self.child.stdout.as_mut().unwrap().read(buf)?;
|
|
||||||
if nread == 0 {
|
|
||||||
self.done = true;
|
|
||||||
// Reap the child now that we're done reading.
|
|
||||||
// If the command failed, report stderr as an error.
|
|
||||||
if !self.child.wait()?.success() {
|
|
||||||
return Err(self.read_error()?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(nread)
|
|
||||||
}
|
|
||||||
}
|
|
109
src/search.rs
109
src/search.rs
@@ -1,19 +1,22 @@
|
|||||||
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use grep::cli;
|
||||||
use grep::matcher::Matcher;
|
use grep::matcher::Matcher;
|
||||||
#[cfg(feature = "pcre2")]
|
#[cfg(feature = "pcre2")]
|
||||||
use grep::pcre2::{RegexMatcher as PCRE2RegexMatcher};
|
use grep::pcre2::{RegexMatcher as PCRE2RegexMatcher};
|
||||||
use grep::printer::{JSON, Standard, Summary, Stats};
|
use grep::printer::{JSON, Standard, Summary, Stats};
|
||||||
use grep::regex::{RegexMatcher as RustRegexMatcher};
|
use grep::regex::{RegexMatcher as RustRegexMatcher};
|
||||||
use grep::searcher::Searcher;
|
use grep::searcher::Searcher;
|
||||||
|
use ignore::overrides::Override;
|
||||||
use serde_json as json;
|
use serde_json as json;
|
||||||
|
use serde_json::json;
|
||||||
use termcolor::WriteColor;
|
use termcolor::WriteColor;
|
||||||
|
|
||||||
use decompressor::{DecompressionReader, is_compressed};
|
use crate::subject::Subject;
|
||||||
use preprocessor::PreprocessorReader;
|
|
||||||
use subject::Subject;
|
|
||||||
|
|
||||||
/// The configuration for the search worker. Among a few other things, the
|
/// The configuration for the search worker. Among a few other things, the
|
||||||
/// configuration primarily controls the way we show search results to users
|
/// configuration primarily controls the way we show search results to users
|
||||||
@@ -22,6 +25,7 @@ use subject::Subject;
|
|||||||
struct Config {
|
struct Config {
|
||||||
json_stats: bool,
|
json_stats: bool,
|
||||||
preprocessor: Option<PathBuf>,
|
preprocessor: Option<PathBuf>,
|
||||||
|
preprocessor_globs: Override,
|
||||||
search_zip: bool,
|
search_zip: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +34,7 @@ impl Default for Config {
|
|||||||
Config {
|
Config {
|
||||||
json_stats: false,
|
json_stats: false,
|
||||||
preprocessor: None,
|
preprocessor: None,
|
||||||
|
preprocessor_globs: Override::empty(),
|
||||||
search_zip: false,
|
search_zip: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,6 +44,8 @@ impl Default for Config {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SearchWorkerBuilder {
|
pub struct SearchWorkerBuilder {
|
||||||
config: Config,
|
config: Config,
|
||||||
|
command_builder: cli::CommandReaderBuilder,
|
||||||
|
decomp_builder: cli::DecompressionReaderBuilder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SearchWorkerBuilder {
|
impl Default for SearchWorkerBuilder {
|
||||||
@@ -50,7 +57,17 @@ impl Default for SearchWorkerBuilder {
|
|||||||
impl SearchWorkerBuilder {
|
impl SearchWorkerBuilder {
|
||||||
/// Create a new builder for configuring and constructing a search worker.
|
/// Create a new builder for configuring and constructing a search worker.
|
||||||
pub fn new() -> SearchWorkerBuilder {
|
pub fn new() -> SearchWorkerBuilder {
|
||||||
SearchWorkerBuilder { config: Config::default() }
|
let mut cmd_builder = cli::CommandReaderBuilder::new();
|
||||||
|
cmd_builder.async_stderr(true);
|
||||||
|
|
||||||
|
let mut decomp_builder = cli::DecompressionReaderBuilder::new();
|
||||||
|
decomp_builder.async_stderr(true);
|
||||||
|
|
||||||
|
SearchWorkerBuilder {
|
||||||
|
config: Config::default(),
|
||||||
|
command_builder: cmd_builder,
|
||||||
|
decomp_builder: decomp_builder,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new search worker using the given searcher, matcher and
|
/// Create a new search worker using the given searcher, matcher and
|
||||||
@@ -62,7 +79,12 @@ impl SearchWorkerBuilder {
|
|||||||
printer: Printer<W>,
|
printer: Printer<W>,
|
||||||
) -> SearchWorker<W> {
|
) -> SearchWorker<W> {
|
||||||
let config = self.config.clone();
|
let config = self.config.clone();
|
||||||
SearchWorker { config, matcher, searcher, printer }
|
let command_builder = self.command_builder.clone();
|
||||||
|
let decomp_builder = self.decomp_builder.clone();
|
||||||
|
SearchWorker {
|
||||||
|
config, command_builder, decomp_builder,
|
||||||
|
matcher, searcher, printer,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Forcefully use JSON to emit statistics, even if the underlying printer
|
/// Forcefully use JSON to emit statistics, even if the underlying printer
|
||||||
@@ -90,6 +112,17 @@ impl SearchWorkerBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the globs for determining which files should be run through the
|
||||||
|
/// preprocessor. By default, with no globs and a preprocessor specified,
|
||||||
|
/// every file is run through the preprocessor.
|
||||||
|
pub fn preprocessor_globs(
|
||||||
|
&mut self,
|
||||||
|
globs: Override,
|
||||||
|
) -> &mut SearchWorkerBuilder {
|
||||||
|
self.config.preprocessor_globs = globs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Enable the decompression and searching of common compressed files.
|
/// Enable the decompression and searching of common compressed files.
|
||||||
///
|
///
|
||||||
/// When enabled, if a particular file path is recognized as a compressed
|
/// When enabled, if a particular file path is recognized as a compressed
|
||||||
@@ -237,6 +270,8 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SearchWorker<W> {
|
pub struct SearchWorker<W> {
|
||||||
config: Config,
|
config: Config,
|
||||||
|
command_builder: cli::CommandReaderBuilder,
|
||||||
|
decomp_builder: cli::DecompressionReaderBuilder,
|
||||||
matcher: PatternMatcher,
|
matcher: PatternMatcher,
|
||||||
searcher: Searcher,
|
searcher: Searcher,
|
||||||
printer: Printer<W>,
|
printer: Printer<W>,
|
||||||
@@ -278,20 +313,66 @@ impl<W: WriteColor> SearchWorker<W> {
|
|||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
// A `return` here appeases the borrow checker. NLL will fix this.
|
// A `return` here appeases the borrow checker. NLL will fix this.
|
||||||
return self.search_reader(path, stdin.lock());
|
return self.search_reader(path, stdin.lock());
|
||||||
} else if self.config.preprocessor.is_some() {
|
} else if self.should_preprocess(path) {
|
||||||
let cmd = self.config.preprocessor.clone().unwrap();
|
self.search_preprocessor(path)
|
||||||
let rdr = PreprocessorReader::from_cmd_path(cmd, path)?;
|
} else if self.should_decompress(path) {
|
||||||
self.search_reader(path, rdr)
|
self.search_decompress(path)
|
||||||
} else if self.config.search_zip && is_compressed(path) {
|
|
||||||
match DecompressionReader::from_path(path) {
|
|
||||||
None => Ok(SearchResult::default()),
|
|
||||||
Some(rdr) => self.search_reader(path, rdr),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.search_path(path)
|
self.search_path(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if the given file path should be
|
||||||
|
/// decompressed before searching.
|
||||||
|
fn should_decompress(&self, path: &Path) -> bool {
|
||||||
|
if !self.config.search_zip {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.decomp_builder.get_matcher().has_command(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if the given file path should be run through
|
||||||
|
/// the preprocessor.
|
||||||
|
fn should_preprocess(&self, path: &Path) -> bool {
|
||||||
|
if !self.config.preprocessor.is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if self.config.preprocessor_globs.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
!self.config.preprocessor_globs.matched(path, false).is_ignore()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search the given file path by first asking the preprocessor for the
|
||||||
|
/// data to search instead of opening the path directly.
|
||||||
|
fn search_preprocessor(
|
||||||
|
&mut self,
|
||||||
|
path: &Path,
|
||||||
|
) -> io::Result<SearchResult> {
|
||||||
|
let bin = self.config.preprocessor.clone().unwrap();
|
||||||
|
let mut cmd = Command::new(&bin);
|
||||||
|
cmd.arg(path).stdin(Stdio::from(File::open(path)?));
|
||||||
|
|
||||||
|
let rdr = self.command_builder.build(&mut cmd)?;
|
||||||
|
self.search_reader(path, rdr).map_err(|err| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!("preprocessor command failed: '{:?}': {}", cmd, err),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to decompress the data at the given file path and search the
|
||||||
|
/// result. If the given file path isn't recognized as a compressed file,
|
||||||
|
/// then search it without doing any decompression.
|
||||||
|
fn search_decompress(
|
||||||
|
&mut self,
|
||||||
|
path: &Path,
|
||||||
|
) -> io::Result<SearchResult> {
|
||||||
|
let rdr = self.decomp_builder.build(path)?;
|
||||||
|
self.search_reader(path, rdr)
|
||||||
|
}
|
||||||
|
|
||||||
/// Search the contents of the given file path.
|
/// Search the contents of the given file path.
|
||||||
fn search_path(&mut self, path: &Path) -> io::Result<SearchResult> {
|
fn search_path(&mut self, path: &Path) -> io::Result<SearchResult> {
|
||||||
use self::PatternMatcher::*;
|
use self::PatternMatcher::*;
|
||||||
|
111
src/subject.rs
111
src/subject.rs
@@ -1,26 +1,18 @@
|
|||||||
use std::io;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use ignore::{self, DirEntry};
|
use ignore::{self, DirEntry};
|
||||||
use same_file::Handle;
|
use log;
|
||||||
|
|
||||||
/// A configuration for describing how subjects should be built.
|
/// A configuration for describing how subjects should be built.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Config {
|
struct Config {
|
||||||
skip: Option<Arc<Handle>>,
|
|
||||||
strip_dot_prefix: bool,
|
strip_dot_prefix: bool,
|
||||||
separator: Option<u8>,
|
|
||||||
terminator: Option<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Config {
|
fn default() -> Config {
|
||||||
Config {
|
Config {
|
||||||
skip: None,
|
|
||||||
strip_dot_prefix: false,
|
strip_dot_prefix: false,
|
||||||
separator: None,
|
|
||||||
terminator: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +41,7 @@ impl SubjectBuilder {
|
|||||||
match result {
|
match result {
|
||||||
Ok(dent) => self.build(dent),
|
Ok(dent) => self.build(dent),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
message!("{}", err);
|
err_message!("{}", err);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,26 +63,6 @@ impl SubjectBuilder {
|
|||||||
if subj.dent.is_stdin() {
|
if subj.dent.is_stdin() {
|
||||||
return Some(subj);
|
return Some(subj);
|
||||||
}
|
}
|
||||||
// If we're supposed to skip a particular file, then skip it.
|
|
||||||
if let Some(ref handle) = self.config.skip {
|
|
||||||
match subj.equals(handle) {
|
|
||||||
Ok(false) => {} // fallthrough
|
|
||||||
Ok(true) => {
|
|
||||||
debug!(
|
|
||||||
"ignoring {}: (probably same file as stdout)",
|
|
||||||
subj.dent.path().display()
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
debug!(
|
|
||||||
"ignoring {}: got error: {}",
|
|
||||||
subj.dent.path().display(), err
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If this subject has a depth of 0, then it was provided explicitly
|
// If this subject has a depth of 0, then it was provided explicitly
|
||||||
// by an end user (or via a shell glob). In this case, we always want
|
// by an end user (or via a shell glob). In this case, we always want
|
||||||
// to search it if it even smells like a file (e.g., a symlink).
|
// to search it if it even smells like a file (e.g., a symlink).
|
||||||
@@ -108,7 +80,7 @@ impl SubjectBuilder {
|
|||||||
// directory. Otherwise, emitting messages for directories is just
|
// directory. Otherwise, emitting messages for directories is just
|
||||||
// noisy.
|
// noisy.
|
||||||
if !subj.is_dir() {
|
if !subj.is_dir() {
|
||||||
debug!(
|
log::debug!(
|
||||||
"ignoring {}: failed to pass subject filter: \
|
"ignoring {}: failed to pass subject filter: \
|
||||||
file type: {:?}, metadata: {:?}",
|
file type: {:?}, metadata: {:?}",
|
||||||
subj.dent.path().display(),
|
subj.dent.path().display(),
|
||||||
@@ -119,22 +91,6 @@ impl SubjectBuilder {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When provided, subjects that represent the same file as the handle
|
|
||||||
/// given will be skipped.
|
|
||||||
///
|
|
||||||
/// Typically, it is useful to pass a handle referring to stdout, such
|
|
||||||
/// that the file being written to isn't searched, which can lead to
|
|
||||||
/// an unbounded feedback mechanism.
|
|
||||||
///
|
|
||||||
/// Only one handle to skip can be provided.
|
|
||||||
pub fn skip(
|
|
||||||
&mut self,
|
|
||||||
handle: Option<Handle>,
|
|
||||||
) -> &mut SubjectBuilder {
|
|
||||||
self.config.skip = handle.map(Arc::new);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// When enabled, if the subject's file path starts with `./` then it is
|
/// When enabled, if the subject's file path starts with `./` then it is
|
||||||
/// stripped.
|
/// stripped.
|
||||||
///
|
///
|
||||||
@@ -171,60 +127,23 @@ impl Subject {
|
|||||||
self.dent.is_stdin()
|
self.dent.is_stdin()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this subject points to a directory.
|
/// Returns true if and only if this subject points to a directory after
|
||||||
///
|
/// following symbolic links.
|
||||||
/// This works around a bug in Rust's standard library:
|
|
||||||
/// https://github.com/rust-lang/rust/issues/46484
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn is_dir(&self) -> bool {
|
fn is_dir(&self) -> bool {
|
||||||
use std::os::windows::fs::MetadataExt;
|
let ft = match self.dent.file_type() {
|
||||||
use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY;
|
None => return false,
|
||||||
|
Some(ft) => ft,
|
||||||
self.dent.metadata().map(|md| {
|
};
|
||||||
md.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0
|
if ft.is_dir() {
|
||||||
}).unwrap_or(false)
|
return true;
|
||||||
}
|
}
|
||||||
|
// If this is a symlink, then we want to follow it to determine
|
||||||
/// Returns true if and only if this subject points to a directory.
|
// whether it's a directory or not.
|
||||||
#[cfg(not(windows))]
|
self.dent.path_is_symlink() && self.dent.path().is_dir()
|
||||||
fn is_dir(&self) -> bool {
|
|
||||||
self.dent.file_type().map_or(false, |ft| ft.is_dir())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this subject points to a file.
|
/// Returns true if and only if this subject points to a file.
|
||||||
///
|
|
||||||
/// This works around a bug in Rust's standard library:
|
|
||||||
/// https://github.com/rust-lang/rust/issues/46484
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn is_file(&self) -> bool {
|
|
||||||
!self.is_dir()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if and only if this subject points to a file.
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
fn is_file(&self) -> bool {
|
fn is_file(&self) -> bool {
|
||||||
self.dent.file_type().map_or(false, |ft| ft.is_file())
|
self.dent.file_type().map_or(false, |ft| ft.is_file())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this subject is believed to be equivalent
|
|
||||||
/// to the given handle. If there was a problem querying this subject for
|
|
||||||
/// information to determine equality, then that error is returned.
|
|
||||||
fn equals(&self, handle: &Handle) -> io::Result<bool> {
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn never_equal(dent: &DirEntry, handle: &Handle) -> bool {
|
|
||||||
dent.ino() != Some(handle.ino())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
fn never_equal(_: &DirEntry, _: &Handle) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we know for sure that these two things aren't equal, then avoid
|
|
||||||
// the costly extra stat call to determine equality.
|
|
||||||
if self.dent.is_stdin() || never_equal(&self.dent, handle) {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
Handle::from_path(self.path()).map(|h| &h == handle)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
137
src/unescape.rs
137
src/unescape.rs
@@ -1,137 +0,0 @@
|
|||||||
/// A single state in the state machine used by `unescape`.
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
||||||
enum State {
|
|
||||||
/// The state after seeing a `\`.
|
|
||||||
Escape,
|
|
||||||
/// The state after seeing a `\x`.
|
|
||||||
HexFirst,
|
|
||||||
/// The state after seeing a `\x[0-9A-Fa-f]`.
|
|
||||||
HexSecond(char),
|
|
||||||
/// Default state.
|
|
||||||
Literal,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Escapes an arbitrary byte slice such that it can be presented as a human
|
|
||||||
/// readable string.
|
|
||||||
pub fn escape(bytes: &[u8]) -> String {
|
|
||||||
use std::ascii::escape_default;
|
|
||||||
|
|
||||||
let escaped = bytes.iter().flat_map(|&b| escape_default(b)).collect();
|
|
||||||
String::from_utf8(escaped).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unescapes a string given on the command line. It supports a limited set of
|
|
||||||
/// escape sequences:
|
|
||||||
///
|
|
||||||
/// * `\t`, `\r` and `\n` are mapped to their corresponding ASCII bytes.
|
|
||||||
/// * `\xZZ` hexadecimal escapes are mapped to their byte.
|
|
||||||
pub fn unescape(s: &str) -> Vec<u8> {
|
|
||||||
use self::State::*;
|
|
||||||
|
|
||||||
let mut bytes = vec![];
|
|
||||||
let mut state = Literal;
|
|
||||||
for c in s.chars() {
|
|
||||||
match state {
|
|
||||||
Escape => {
|
|
||||||
match c {
|
|
||||||
'n' => { bytes.push(b'\n'); state = Literal; }
|
|
||||||
'r' => { bytes.push(b'\r'); state = Literal; }
|
|
||||||
't' => { bytes.push(b'\t'); state = Literal; }
|
|
||||||
'x' => { state = HexFirst; }
|
|
||||||
c => {
|
|
||||||
bytes.extend(format!(r"\{}", c).into_bytes());
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HexFirst => {
|
|
||||||
match c {
|
|
||||||
'0'...'9' | 'A'...'F' | 'a'...'f' => {
|
|
||||||
state = HexSecond(c);
|
|
||||||
}
|
|
||||||
c => {
|
|
||||||
bytes.extend(format!(r"\x{}", c).into_bytes());
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HexSecond(first) => {
|
|
||||||
match c {
|
|
||||||
'0'...'9' | 'A'...'F' | 'a'...'f' => {
|
|
||||||
let ordinal = format!("{}{}", first, c);
|
|
||||||
let byte = u8::from_str_radix(&ordinal, 16).unwrap();
|
|
||||||
bytes.push(byte);
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
c => {
|
|
||||||
let original = format!(r"\x{}{}", first, c);
|
|
||||||
bytes.extend(original.into_bytes());
|
|
||||||
state = Literal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Literal => {
|
|
||||||
match c {
|
|
||||||
'\\' => { state = Escape; }
|
|
||||||
c => { bytes.extend(c.to_string().as_bytes()); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match state {
|
|
||||||
Escape => bytes.push(b'\\'),
|
|
||||||
HexFirst => bytes.extend(b"\\x"),
|
|
||||||
HexSecond(c) => bytes.extend(format!("\\x{}", c).into_bytes()),
|
|
||||||
Literal => {}
|
|
||||||
}
|
|
||||||
bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::unescape;
|
|
||||||
|
|
||||||
fn b(bytes: &'static [u8]) -> Vec<u8> {
|
|
||||||
bytes.to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unescape_nul() {
|
|
||||||
assert_eq!(b(b"\x00"), unescape(r"\x00"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unescape_nl() {
|
|
||||||
assert_eq!(b(b"\n"), unescape(r"\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unescape_tab() {
|
|
||||||
assert_eq!(b(b"\t"), unescape(r"\t"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unescape_carriage() {
|
|
||||||
assert_eq!(b(b"\r"), unescape(r"\r"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unescape_nothing_simple() {
|
|
||||||
assert_eq!(b(b"\\a"), unescape(r"\a"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unescape_nothing_hex0() {
|
|
||||||
assert_eq!(b(b"\\x"), unescape(r"\x"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unescape_nothing_hex1() {
|
|
||||||
assert_eq!(b(b"\\xz"), unescape(r"\xz"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unescape_nothing_hex2() {
|
|
||||||
assert_eq!(b(b"\\xzz"), unescape(r"\xzz"));
|
|
||||||
}
|
|
||||||
}
|
|
2
tests/data/sherlock.br
Normal file
2
tests/data/sherlock.br
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
n<01><><EFBFBD>-_<>.<2E> <0C><><11><>cM<63><4D><EFBFBD><EFBFBD>Y<EFBFBD>4<08><><EFBFBD><EFBFBD><EFBFBD>Ya<0B>-L<>O(<28>8<EFBFBD>sn^Gwш!,<2C>
|
||||||
|
KD<EFBFBD><EFBFBD>/7<><37>th<74><1A><0C><10>]j<02><><EFBFBD><EFBFBD><EFBFBD>E_;d<1E>rF<72>Qs<51>/:DIVB}<7D>T7<54><37>ѵ<04><16>H<EFBFBD>2<EFBFBD><32><EFBFBD>)<29><>M[u<><75><EFBFBD>i<EFBFBD><69><EFBFBD><0F>50ڮ<30>Y6<><36><EFBFBD><EFBFBD><17><><EFBFBD><EFBFBD><07>%<25>ר_<D7A8><5F>U by<62>4<EFBFBD><34>Ϡ<EFBFBD>!&<26>g<><15>#<23>
|
BIN
tests/data/sherlock.zst
Normal file
BIN
tests/data/sherlock.zst
Normal file
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
use hay::{SHERLOCK, SHERLOCK_CRLF};
|
use crate::hay::{SHERLOCK, SHERLOCK_CRLF};
|
||||||
use util::{Dir, TestCommand, sort_lines};
|
use crate::util::{Dir, TestCommand, sort_lines};
|
||||||
|
|
||||||
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||||
rgtest!(f1_sjis, |dir: Dir, mut cmd: TestCommand| {
|
rgtest!(f1_sjis, |dir: Dir, mut cmd: TestCommand| {
|
||||||
@@ -629,3 +629,19 @@ rgtest!(f993_null_data, |dir: Dir, mut cmd: TestCommand| {
|
|||||||
let expected = "foo\x00bar\x00baz\x00";
|
let expected = "foo\x00bar\x00baz\x00";
|
||||||
eqnice!(expected, cmd.stdout());
|
eqnice!(expected, cmd.stdout());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1138
|
||||||
|
rgtest!(f1138_no_ignore_dot, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "foo");
|
||||||
|
dir.create(".ignore", "bar");
|
||||||
|
dir.create(".fzf-ignore", "quux");
|
||||||
|
dir.create("foo", "");
|
||||||
|
dir.create("bar", "");
|
||||||
|
dir.create("quux", "");
|
||||||
|
|
||||||
|
cmd.arg("--sort").arg("path").arg("--files");
|
||||||
|
eqnice!("quux\n", cmd.stdout());
|
||||||
|
eqnice!("bar\nquux\n", cmd.arg("--no-ignore-dot").stdout());
|
||||||
|
eqnice!("bar\n", cmd.arg("--ignore-file").arg(".fzf-ignore").stdout());
|
||||||
|
});
|
||||||
|
111
tests/json.rs
111
tests/json.rs
@@ -1,9 +1,10 @@
|
|||||||
use std::time;
|
use std::time;
|
||||||
|
|
||||||
|
use serde_derive::Deserialize;
|
||||||
use serde_json as json;
|
use serde_json as json;
|
||||||
|
|
||||||
use hay::{SHERLOCK, SHERLOCK_CRLF};
|
use crate::hay::{SHERLOCK, SHERLOCK_CRLF};
|
||||||
use util::{Dir, TestCommand};
|
use crate::util::{Dir, TestCommand};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(tag = "type", content = "data")]
|
#[serde(tag = "type", content = "data")]
|
||||||
@@ -152,7 +153,10 @@ rgtest!(basic, |dir: Dir, mut cmd: TestCommand| {
|
|||||||
msgs[1].unwrap_context(),
|
msgs[1].unwrap_context(),
|
||||||
Context {
|
Context {
|
||||||
path: Some(Data::text("sherlock")),
|
path: Some(Data::text("sherlock")),
|
||||||
lines: Data::text("Holmeses, success in the province of detective work must always\n"),
|
lines: Data::text(
|
||||||
|
"Holmeses, success in the province of \
|
||||||
|
detective work must always\n",
|
||||||
|
),
|
||||||
line_number: Some(2),
|
line_number: Some(2),
|
||||||
absolute_offset: 65,
|
absolute_offset: 65,
|
||||||
submatches: vec![],
|
submatches: vec![],
|
||||||
@@ -162,7 +166,10 @@ rgtest!(basic, |dir: Dir, mut cmd: TestCommand| {
|
|||||||
msgs[2].unwrap_match(),
|
msgs[2].unwrap_match(),
|
||||||
Match {
|
Match {
|
||||||
path: Some(Data::text("sherlock")),
|
path: Some(Data::text("sherlock")),
|
||||||
lines: Data::text("be, to a very large extent, the result of luck. Sherlock Holmes\n"),
|
lines: Data::text(
|
||||||
|
"be, to a very large extent, the result of luck. \
|
||||||
|
Sherlock Holmes\n",
|
||||||
|
),
|
||||||
line_number: Some(3),
|
line_number: Some(3),
|
||||||
absolute_offset: 129,
|
absolute_offset: 129,
|
||||||
submatches: vec![
|
submatches: vec![
|
||||||
@@ -211,7 +218,9 @@ rgtest!(notutf8, |dir: Dir, mut cmd: TestCommand| {
|
|||||||
let contents = &b"quux\xFFbaz"[..];
|
let contents = &b"quux\xFFbaz"[..];
|
||||||
|
|
||||||
// APFS does not support creating files with invalid UTF-8 bytes, so just
|
// APFS does not support creating files with invalid UTF-8 bytes, so just
|
||||||
// skip the test if we can't create our file.
|
// skip the test if we can't create our file. Presumably we don't need this
|
||||||
|
// check if we're already skipping it on macOS, but maybe other file
|
||||||
|
// systems won't like this test either?
|
||||||
if !dir.try_create_bytes(OsStr::from_bytes(name), contents).is_ok() {
|
if !dir.try_create_bytes(OsStr::from_bytes(name), contents).is_ok() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -241,6 +250,49 @@ rgtest!(notutf8, |dir: Dir, mut cmd: TestCommand| {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
rgtest!(notutf8_file, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
|
// This test does not work with PCRE2 because PCRE2 does not support the
|
||||||
|
// `u` flag.
|
||||||
|
if dir.is_pcre2() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = "foo";
|
||||||
|
let contents = &b"quux\xFFbaz"[..];
|
||||||
|
|
||||||
|
// APFS does not support creating files with invalid UTF-8 bytes, so just
|
||||||
|
// skip the test if we can't create our file.
|
||||||
|
if !dir.try_create_bytes(OsStr::new(name), contents).is_ok() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cmd.arg("--json").arg(r"(?-u)\xFF");
|
||||||
|
|
||||||
|
let msgs = json_decode(&cmd.stdout());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
msgs[0].unwrap_begin(),
|
||||||
|
Begin { path: Some(Data::text("foo")) }
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
msgs[1].unwrap_match(),
|
||||||
|
Match {
|
||||||
|
path: Some(Data::text("foo")),
|
||||||
|
lines: Data::bytes("cXV1eP9iYXo="),
|
||||||
|
line_number: Some(1),
|
||||||
|
absolute_offset: 0,
|
||||||
|
submatches: vec![
|
||||||
|
SubMatch {
|
||||||
|
m: Data::bytes("/w=="),
|
||||||
|
start: 4,
|
||||||
|
end: 5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// See: https://github.com/BurntSushi/ripgrep/issues/416
|
// See: https://github.com/BurntSushi/ripgrep/issues/416
|
||||||
//
|
//
|
||||||
// This test in particular checks that our match does _not_ include the `\r`
|
// This test in particular checks that our match does _not_ include the `\r`
|
||||||
@@ -261,3 +313,52 @@ rgtest!(crlf, |dir: Dir, mut cmd: TestCommand| {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1095
|
||||||
|
//
|
||||||
|
// This test checks that we don't drop the \r\n in a matching line when --crlf
|
||||||
|
// mode is enabled.
|
||||||
|
rgtest!(r1095_missing_crlf, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "test\r\n");
|
||||||
|
|
||||||
|
// Check without --crlf flag.
|
||||||
|
let msgs = json_decode(&cmd.arg("--json").arg("test").stdout());
|
||||||
|
assert_eq!(msgs.len(), 4);
|
||||||
|
assert_eq!(msgs[1].unwrap_match().lines, Data::text("test\r\n"));
|
||||||
|
|
||||||
|
// Now check with --crlf flag.
|
||||||
|
let msgs = json_decode(&cmd.arg("--crlf").stdout());
|
||||||
|
assert_eq!(msgs.len(), 4);
|
||||||
|
assert_eq!(msgs[1].unwrap_match().lines, Data::text("test\r\n"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1095
|
||||||
|
//
|
||||||
|
// This test checks that we don't return empty submatches when matching a `\n`
|
||||||
|
// in CRLF mode.
|
||||||
|
rgtest!(r1095_crlf_empty_match, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "test\r\n\n");
|
||||||
|
|
||||||
|
// Check without --crlf flag.
|
||||||
|
let msgs = json_decode(&cmd.arg("-U").arg("--json").arg("\n").stdout());
|
||||||
|
assert_eq!(msgs.len(), 5);
|
||||||
|
|
||||||
|
let m = msgs[1].unwrap_match();
|
||||||
|
assert_eq!(m.lines, Data::text("test\r\n"));
|
||||||
|
assert_eq!(m.submatches[0].m, Data::text("\n"));
|
||||||
|
|
||||||
|
let m = msgs[2].unwrap_match();
|
||||||
|
assert_eq!(m.lines, Data::text("\n"));
|
||||||
|
assert_eq!(m.submatches[0].m, Data::text("\n"));
|
||||||
|
|
||||||
|
// Now check with --crlf flag.
|
||||||
|
let msgs = json_decode(&cmd.arg("--crlf").stdout());
|
||||||
|
|
||||||
|
let m = msgs[1].unwrap_match();
|
||||||
|
assert_eq!(m.lines, Data::text("test\r\n"));
|
||||||
|
assert_eq!(m.submatches[0].m, Data::text("\n"));
|
||||||
|
|
||||||
|
let m = msgs[2].unwrap_match();
|
||||||
|
assert_eq!(m.lines, Data::text("\n"));
|
||||||
|
assert_eq!(m.submatches[0].m, Data::text("\n"));
|
||||||
|
});
|
||||||
|
@@ -3,11 +3,11 @@ macro_rules! rgtest {
|
|||||||
($name:ident, $fun:expr) => {
|
($name:ident, $fun:expr) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let (dir, cmd) = ::util::setup(stringify!($name));
|
let (dir, cmd) = crate::util::setup(stringify!($name));
|
||||||
$fun(dir, cmd);
|
$fun(dir, cmd);
|
||||||
|
|
||||||
if cfg!(feature = "pcre2") {
|
if cfg!(feature = "pcre2") {
|
||||||
let (dir, cmd) = ::util::setup_pcre2(stringify!($name));
|
let (dir, cmd) = crate::util::setup_pcre2(stringify!($name));
|
||||||
$fun(dir, cmd);
|
$fun(dir, cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
use hay::SHERLOCK;
|
use crate::hay::SHERLOCK;
|
||||||
use util::{Dir, TestCommand, cmd_exists, sort_lines};
|
use crate::util::{Dir, TestCommand, cmd_exists, sort_lines};
|
||||||
|
|
||||||
// This file contains "miscellaneous" tests that were either written before
|
// This file contains "miscellaneous" tests that were either written before
|
||||||
// features were tracked more explicitly, or were simply written without
|
// features were tracked more explicitly, or were simply written without
|
||||||
@@ -816,6 +816,24 @@ be, to a very large extent, the result of luck. Sherlock Holmes
|
|||||||
eqnice!(expected, cmd.stdout());
|
eqnice!(expected, cmd.stdout());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
rgtest!(preprocessing_glob, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
if !cmd_exists("xzcat") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create_bytes("sherlock.xz", include_bytes!("./data/sherlock.xz"));
|
||||||
|
cmd.args(&["--pre", "xzcat", "--pre-glob", "*.xz", "Sherlock"]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
sherlock.xz:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
sherlock.xz:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
";
|
||||||
|
eqnice!(sort_lines(expected), sort_lines(&cmd.stdout()));
|
||||||
|
});
|
||||||
|
|
||||||
rgtest!(compressed_gzip, |dir: Dir, mut cmd: TestCommand| {
|
rgtest!(compressed_gzip, |dir: Dir, mut cmd: TestCommand| {
|
||||||
if !cmd_exists("gzip") {
|
if !cmd_exists("gzip") {
|
||||||
return;
|
return;
|
||||||
@@ -891,6 +909,36 @@ be, to a very large extent, the result of luck. Sherlock Holmes
|
|||||||
eqnice!(expected, cmd.stdout());
|
eqnice!(expected, cmd.stdout());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
rgtest!(compressed_brotli, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
if !cmd_exists("brotli") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.create_bytes("sherlock.br", include_bytes!("./data/sherlock.br"));
|
||||||
|
cmd.arg("-z").arg("Sherlock").arg("sherlock.br");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(compressed_zstd, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
if !cmd_exists("zstd") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.create_bytes("sherlock.zst", include_bytes!("./data/sherlock.zst"));
|
||||||
|
cmd.arg("-z").arg("Sherlock").arg("sherlock.zst");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
rgtest!(compressed_failing_gzip, |dir: Dir, mut cmd: TestCommand| {
|
rgtest!(compressed_failing_gzip, |dir: Dir, mut cmd: TestCommand| {
|
||||||
if !cmd_exists("gzip") {
|
if !cmd_exists("gzip") {
|
||||||
return;
|
return;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
use hay::SHERLOCK;
|
use crate::hay::SHERLOCK;
|
||||||
use util::{Dir, TestCommand};
|
use crate::util::{Dir, TestCommand};
|
||||||
|
|
||||||
// This tests that multiline matches that span multiple lines, but where
|
// This tests that multiline matches that span multiple lines, but where
|
||||||
// multiple matches may begin and end on the same line work correctly.
|
// multiple matches may begin and end on the same line work correctly.
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
use hay::SHERLOCK;
|
use crate::hay::SHERLOCK;
|
||||||
use util::{Dir, TestCommand, sort_lines};
|
use crate::util::{Dir, TestCommand, sort_lines};
|
||||||
|
|
||||||
// See: https://github.com/BurntSushi/ripgrep/issues/16
|
// See: https://github.com/BurntSushi/ripgrep/issues/16
|
||||||
rgtest!(r16, |dir: Dir, mut cmd: TestCommand| {
|
rgtest!(r16, |dir: Dir, mut cmd: TestCommand| {
|
||||||
@@ -562,3 +562,135 @@ rgtest!(r900, |dir: Dir, mut cmd: TestCommand| {
|
|||||||
|
|
||||||
cmd.arg("-fpat").arg("sherlock").assert_err();
|
cmd.arg("-fpat").arg("sherlock").assert_err();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1064
|
||||||
|
rgtest!(r1064, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("input", "abc");
|
||||||
|
eqnice!("input:abc\n", cmd.arg("a(.*c)").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1174
|
||||||
|
rgtest!(r1098, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "a**b");
|
||||||
|
dir.create("afoob", "test");
|
||||||
|
cmd.arg("test").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1130
|
||||||
|
rgtest!(r1130, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "test");
|
||||||
|
eqnice!(
|
||||||
|
"foo\n",
|
||||||
|
cmd.arg("--files-with-matches").arg("test").arg("foo").stdout()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
eqnice!(
|
||||||
|
"foo\n",
|
||||||
|
cmd.arg("--files-without-match").arg("nada").arg("foo").stdout()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1159
|
||||||
|
rgtest!(r1159_invalid_flag, |_: Dir, mut cmd: TestCommand| {
|
||||||
|
cmd.arg("--wat").assert_exit_code(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1159
|
||||||
|
rgtest!(r1159_exit_status, |dir: Dir, _: TestCommand| {
|
||||||
|
dir.create("foo", "test");
|
||||||
|
|
||||||
|
// search with a match gets 0 exit status.
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.arg("test").assert_exit_code(0);
|
||||||
|
|
||||||
|
// search with --quiet and a match gets 0 exit status.
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.arg("-q").arg("test").assert_exit_code(0);
|
||||||
|
|
||||||
|
// search with a match and an error gets 2 exit status.
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.arg("test").arg("no-file").assert_exit_code(2);
|
||||||
|
|
||||||
|
// search with a match in --quiet mode and an error gets 0 exit status.
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.arg("-q").arg("test").arg("foo").arg("no-file").assert_exit_code(0);
|
||||||
|
|
||||||
|
// search with no match gets 1 exit status.
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.arg("nada").assert_exit_code(1);
|
||||||
|
|
||||||
|
// search with --quiet and no match gets 1 exit status.
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.arg("-q").arg("nada").assert_exit_code(1);
|
||||||
|
|
||||||
|
// search with no match and an error gets 2 exit status.
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.arg("nada").arg("no-file").assert_exit_code(2);
|
||||||
|
|
||||||
|
// search with no match in --quiet mode and an error gets 2 exit status.
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.arg("-q").arg("nada").arg("foo").arg("no-file").assert_exit_code(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1163
|
||||||
|
rgtest!(r1163, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("bom.txt", "\u{FEFF}test123\ntest123");
|
||||||
|
eqnice!(
|
||||||
|
"bom.txt:test123\nbom.txt:test123\n",
|
||||||
|
cmd.arg("^test123").stdout()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1164
|
||||||
|
rgtest!(r1164, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "myfile");
|
||||||
|
dir.create("MYFILE", "test");
|
||||||
|
|
||||||
|
cmd.arg("--ignore-file-case-insensitive").arg("test").assert_err();
|
||||||
|
eqnice!(
|
||||||
|
"MYFILE:test\n",
|
||||||
|
cmd.arg("--no-ignore-file-case-insensitive").stdout()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1173
|
||||||
|
rgtest!(r1173, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "**");
|
||||||
|
dir.create("foo", "test");
|
||||||
|
cmd.arg("test").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1174
|
||||||
|
rgtest!(r1174, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "**/**/*");
|
||||||
|
dir.create_dir("a");
|
||||||
|
dir.create("a/foo", "test");
|
||||||
|
cmd.arg("test").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1176
|
||||||
|
rgtest!(r1176_literal_file, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("patterns", "foo(bar\n");
|
||||||
|
dir.create("test", "foo(bar");
|
||||||
|
|
||||||
|
eqnice!(
|
||||||
|
"foo(bar\n",
|
||||||
|
cmd.arg("-F").arg("-f").arg("patterns").arg("test").stdout()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1176
|
||||||
|
rgtest!(r1176_line_regex, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("patterns", "foo\n");
|
||||||
|
dir.create("test", "foobar\nfoo\nbarfoo\n");
|
||||||
|
|
||||||
|
eqnice!(
|
||||||
|
"foo\n",
|
||||||
|
cmd.arg("-x").arg("-f").arg("patterns").arg("test").stdout()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@@ -1,8 +1,3 @@
|
|||||||
extern crate serde;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate serde_json;
|
|
||||||
|
|
||||||
// Macros useful for testing.
|
// Macros useful for testing.
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
@@ -103,6 +103,7 @@ impl Dir {
|
|||||||
|
|
||||||
/// Try to create a new file with the given name and contents in this
|
/// Try to create a new file with the given name and contents in this
|
||||||
/// directory.
|
/// directory.
|
||||||
|
#[allow(dead_code)] // unused on Windows
|
||||||
pub fn try_create<P: AsRef<Path>>(
|
pub fn try_create<P: AsRef<Path>>(
|
||||||
&self,
|
&self,
|
||||||
name: P,
|
name: P,
|
||||||
@@ -222,6 +223,7 @@ impl Dir {
|
|||||||
/// Creates a file symlink to the src with the given target name
|
/// Creates a file symlink to the src with the given target name
|
||||||
/// in this directory.
|
/// in this directory.
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
#[allow(dead_code)] // unused on Windows
|
||||||
pub fn link_file<S: AsRef<Path>, T: AsRef<Path>>(
|
pub fn link_file<S: AsRef<Path>, T: AsRef<Path>>(
|
||||||
&self,
|
&self,
|
||||||
src: S,
|
src: S,
|
||||||
|
Reference in New Issue
Block a user