mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-08-16 12:43:49 -07:00
Compare commits
19 Commits
ag/libripg
...
grep-match
Author | SHA1 | Date | |
---|---|---|---|
|
7eaaa04c69 | ||
|
87a627631c | ||
|
9df60e164e | ||
|
afa06c518a | ||
|
e46aeb34f8 | ||
|
d8f187e990 | ||
|
7d93d2ab05 | ||
|
9ca2d68e94 | ||
|
60b0e3ff80 | ||
|
3a1c081c13 | ||
|
d5c0b03030 | ||
|
eb184d7711 | ||
|
bb110c1ebe | ||
|
d9ca529356 | ||
|
0958837ee1 | ||
|
94be3bd4bb | ||
|
deb1de6e1e | ||
|
6afdf15d85 | ||
|
6cda7b24e9 |
@@ -17,6 +17,8 @@ addons:
|
|||||||
# Needed for testing decompression search.
|
# Needed for testing decompression search.
|
||||||
- xz-utils
|
- xz-utils
|
||||||
- liblz4-tool
|
- liblz4-tool
|
||||||
|
# For building MUSL static builds on Linux.
|
||||||
|
- musl-tools
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
@@ -99,7 +101,6 @@ branches:
|
|||||||
only:
|
only:
|
||||||
# Pushes and PR to the master branch
|
# Pushes and PR to the master branch
|
||||||
- master
|
- master
|
||||||
- ag/libripgrep
|
|
||||||
# Ruby regex to match tags. Required, or travis won't trigger deploys when
|
# Ruby regex to match tags. Required, or travis won't trigger deploys when
|
||||||
# a new tag is pushed.
|
# a new tag is pushed.
|
||||||
- /^\d+\.\d+\.\d+.*$/
|
- /^\d+\.\d+\.\d+.*$/
|
||||||
|
65
CHANGELOG.md
65
CHANGELOG.md
@@ -1,3 +1,68 @@
|
|||||||
|
0.10.0 (TBD)
|
||||||
|
============
|
||||||
|
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
|
||||||
|
libripgrep. The entirety of ripgrep's core search and printing code has been
|
||||||
|
rewritten and generalized so that anyone can make use of it.
|
||||||
|
|
||||||
|
Major new features include PCRE2 support, multi-line search and a JSON output
|
||||||
|
format.
|
||||||
|
|
||||||
|
**BREAKING CHANGES**:
|
||||||
|
|
||||||
|
* The match semantics of `-w/--word-regexp` have changed slightly. They used
|
||||||
|
to be `\b(?:<your pattern>)\b`, but now it's
|
||||||
|
`(?:^|\W)(?:<your pattern>)(?:$|\W)`.
|
||||||
|
See [#389](https://github.com/BurntSushi/ripgrep/issues/389) for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
Feature enhancements:
|
||||||
|
|
||||||
|
* [FEATURE #162](https://github.com/BurntSushi/ripgrep/issues/162):
|
||||||
|
libripgrep is now a thing, composed of the following crates:
|
||||||
|
`grep`, `grep-matcher`, `grep-pcre2`, `grep-printer`, `grep-regex` and
|
||||||
|
`grep-searcher`.
|
||||||
|
* [FEATURE #176](https://github.com/BurntSushi/ripgrep/issues/176):
|
||||||
|
Add `-U/--multiline` flag that permits matching over multiple lines.
|
||||||
|
* [FEATURE #188](https://github.com/BurntSushi/ripgrep/issues/188):
|
||||||
|
Add `-P/--pcre2` flag that gives support for look-around and backreferences.
|
||||||
|
* [FEATURE #244](https://github.com/BurntSushi/ripgrep/issues/244):
|
||||||
|
Add `--json` flag that prints results in a JSON Lines format.
|
||||||
|
* [FEATURE #416](https://github.com/BurntSushi/ripgrep/issues/416):
|
||||||
|
Add `--crlf` flag to permit `$` to work with carriage returns on Windows.
|
||||||
|
* [FEATURE #917](https://github.com/BurntSushi/ripgrep/issues/917):
|
||||||
|
The `--trim` flag strips prefix whitespace from all lines printed.
|
||||||
|
* [FEATURE #993](https://github.com/BurntSushi/ripgrep/issues/993):
|
||||||
|
Add `--null-data` flag, which makes ripgrep use NUL as a line terminator.
|
||||||
|
* [FEATURE #997](https://github.com/BurntSushi/ripgrep/issues/997):
|
||||||
|
The `--passthru` flag now works with the `--replace` flag.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* [BUG #2](https://github.com/BurntSushi/ripgrep/issues/2):
|
||||||
|
Searching with non-zero context can now use memory maps if appropriate.
|
||||||
|
* [BUG #200](https://github.com/BurntSushi/ripgrep/issues/200):
|
||||||
|
ripgrep will now stop correctly when its output pipe is closed.
|
||||||
|
* [BUG #389](https://github.com/BurntSushi/ripgrep/issues/389):
|
||||||
|
The `-w/--word-regexp` flag now works more intuitively.
|
||||||
|
* [BUG #643](https://github.com/BurntSushi/ripgrep/issues/643):
|
||||||
|
Detection of readable stdin has improved on Windows.
|
||||||
|
* [BUG #441](https://github.com/BurntSushi/ripgrep/issues/441),
|
||||||
|
[BUG #690](https://github.com/BurntSushi/ripgrep/issues/690),
|
||||||
|
[BUG #980](https://github.com/BurntSushi/ripgrep/issues/980):
|
||||||
|
Matching empty lines now works correctly in several corner cases.
|
||||||
|
* [BUG #764](https://github.com/BurntSushi/ripgrep/issues/764):
|
||||||
|
Color escape sequences now coalesce, which reduces output size.
|
||||||
|
* [BUG #922](https://github.com/BurntSushi/ripgrep/issues/922):
|
||||||
|
ripgrep is now more robust with respect to memory maps failing.
|
||||||
|
* [BUG #937](https://github.com/BurntSushi/ripgrep/issues/937):
|
||||||
|
Color escape sequences are no longer emitted for empty matches.
|
||||||
|
* [BUG #940](https://github.com/BurntSushi/ripgrep/issues/940):
|
||||||
|
Context from the `--passthru` flag should not impact process exit status.
|
||||||
|
* [BUG #1013](https://github.com/BurntSushi/ripgrep/issues/1013):
|
||||||
|
Add compile time and runtime CPU features to `--version` output.
|
||||||
|
|
||||||
|
|
||||||
0.9.0 (2018-08-03)
|
0.9.0 (2018-08-03)
|
||||||
==================
|
==================
|
||||||
This is a new minor version release of ripgrep that contains some minor new
|
This is a new minor version release of ripgrep that contains some minor new
|
||||||
|
248
Cargo.lock
generated
248
Cargo.lock
generated
@@ -19,7 +19,7 @@ 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.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.43 (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.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
@@ -29,7 +29,7 @@ name = "base64"
|
|||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
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.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -48,12 +48,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.2.3"
|
version = "1.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.18"
|
||||||
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.4"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -74,26 +79,21 @@ name = "crossbeam"
|
|||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dtoa"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.4"
|
version = "0.8.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.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs_io"
|
name = "encoding_rs_io"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
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.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"encoding_rs 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -127,7 +127,7 @@ dependencies = [
|
|||||||
"aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"aho-corasick 0.6.6 (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.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
@@ -136,57 +136,68 @@ dependencies = [
|
|||||||
name = "grep"
|
name = "grep"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grep-matcher 0.0.1",
|
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grep-printer 0.0.1",
|
"grep-matcher 0.1.0",
|
||||||
"grep-regex 0.0.1",
|
"grep-pcre2 0.1.0",
|
||||||
"grep-searcher 0.0.1",
|
"grep-printer 0.1.0",
|
||||||
|
"grep-regex 0.1.0",
|
||||||
|
"grep-searcher 0.1.0",
|
||||||
|
"termcolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-matcher"
|
name = "grep-matcher"
|
||||||
version = "0.0.1"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grep-pcre2"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"grep-matcher 0.1.0",
|
||||||
|
"pcre2 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-printer"
|
name = "grep-printer"
|
||||||
version = "0.0.1"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grep-matcher 0.0.1",
|
"grep-matcher 0.1.0",
|
||||||
"grep-regex 0.0.1",
|
"grep-regex 0.1.0",
|
||||||
"grep-searcher 0.0.1",
|
"grep-searcher 0.1.0",
|
||||||
"log 0.4.3 (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.70 (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.70 (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.24 (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.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-regex"
|
name = "grep-regex"
|
||||||
version = "0.0.1"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grep-matcher 0.0.1",
|
"grep-matcher 0.1.0",
|
||||||
"log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.0.2 (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.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"thread_local 0.3.5 (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.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep-searcher"
|
name = "grep-searcher"
|
||||||
version = "0.0.1"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytecount 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bytecount 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"encoding_rs 0.8.4 (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_io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"encoding_rs_io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grep-matcher 0.0.1",
|
"grep-matcher 0.1.0",
|
||||||
"grep-regex 0.0.1",
|
"grep-regex 0.1.0",
|
||||||
"log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -198,14 +209,14 @@ version = "0.4.3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"globset 0.4.1",
|
"globset 0.4.1",
|
||||||
"lazy_static 1.0.2 (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.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.0.2 (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.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"thread_local 0.3.5 (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.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"walkdir 2.2.0 (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.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -216,20 +227,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
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 = [
|
||||||
|
"version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.42"
|
version = "0.2.43"
|
||||||
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.3"
|
version = "0.4.4"
|
||||||
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.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -237,7 +251,7 @@ name = "memchr"
|
|||||||
version = "2.0.1"
|
version = "2.0.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.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -245,7 +259,7 @@ name = "memmap"
|
|||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
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.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.43 (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.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -254,12 +268,39 @@ name = "num_cpus"
|
|||||||
version = "1.8.0"
|
version = "1.8.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.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pcre2"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.43 (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)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pcre2-sys"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "0.4.9"
|
version = "0.4.13"
|
||||||
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)",
|
||||||
@@ -267,19 +308,19 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "0.6.3"
|
version = "0.6.6"
|
||||||
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.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"proc-macro2 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
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"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.43 (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.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -304,7 +345,7 @@ dependencies = [
|
|||||||
"aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"aho-corasick 0.6.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.0.1 (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.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"thread_local 0.3.5 (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.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -333,16 +374,23 @@ dependencies = [
|
|||||||
"globset 0.4.1",
|
"globset 0.4.1",
|
||||||
"grep 0.2.0",
|
"grep 0.2.0",
|
||||||
"ignore 0.4.3",
|
"ignore 0.4.3",
|
||||||
"lazy_static 1.0.2 (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.3 (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.8.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.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.0.2 (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.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_json 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_derive 1.0.71 (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)",
|
"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)",
|
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "safemem"
|
name = "safemem"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -358,27 +406,27 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.70"
|
version = "1.0.71"
|
||||||
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.70"
|
version = "1.0.71"
|
||||||
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.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"proc-macro2 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"quote 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"syn 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"syn 0.14.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.24"
|
version = "1.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ryu 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -393,11 +441,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "0.14.4"
|
version = "0.14.8"
|
||||||
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.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"proc-macro2 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"quote 0.6.6 (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)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -406,7 +454,7 @@ name = "tempdir"
|
|||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
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.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.4.3 (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)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -423,7 +471,7 @@ 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.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.43 (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.40 (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)",
|
||||||
]
|
]
|
||||||
@@ -438,11 +486,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
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 = [
|
||||||
"lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -460,27 +507,19 @@ name = "unicode-xid"
|
|||||||
version = "0.1.0"
|
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]]
|
|
||||||
name = "unreachable"
|
|
||||||
version = "1.0.0"
|
|
||||||
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]]
|
[[package]]
|
||||||
name = "utf8-ranges"
|
name = "utf8-ranges"
|
||||||
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"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "void"
|
name = "version_check"
|
||||||
version = "1.0.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"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.1.4"
|
version = "2.2.0"
|
||||||
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.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -521,52 +560,55 @@ dependencies = [
|
|||||||
"checksum base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "85415d2594767338a74a30c1d370b2f3262ec1b4ed2d7bba5b3faf4de40467d9"
|
"checksum base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "85415d2594767338a74a30c1d370b2f3262ec1b4ed2d7bba5b3faf4de40467d9"
|
||||||
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
|
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
|
||||||
"checksum bytecount 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f861d9ce359f56dbcb6e0c2a1cb84e52ad732cadb57b806adeb3c7668caccbd8"
|
"checksum bytecount 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f861d9ce359f56dbcb6e0c2a1cb84e52ad732cadb57b806adeb3c7668caccbd8"
|
||||||
"checksum byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9"
|
"checksum byteorder 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8389c509ec62b9fe8eca58c502a0acaf017737355615243496cde4994f8fa4f9"
|
||||||
"checksum cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efe5c877e17a9c717a0bf3613b2709f723202c4e4675cc8f12926ded29bcb17e"
|
"checksum cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "2119ea4867bd2b8ed3aecab467709720b2d55b1bcfe09f772fd68066eaf15275"
|
||||||
|
"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3"
|
||||||
"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 crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19"
|
||||||
"checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd"
|
"checksum encoding_rs 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2a91912d6f37c6a8fef8a2316a862542d036f13c923ad518b5aca7bcaac7544c"
|
||||||
"checksum encoding_rs 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88a1b66a0d28af4b03a8c8278c6dcb90e6e600d89c14500a9e7a02e64b9ee3ac"
|
"checksum encoding_rs_io 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f222ff554d6e172f3569a2d7d0fd8061d54215984ef67b24ce031c1fcbf2c9b3"
|
||||||
"checksum encoding_rs_io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad0ffe753ba194ef1bc070e8d61edaadb1536c05e364fc9178ca6cbde10922c4"
|
|
||||||
"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-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||||
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
"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.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5adb58558dcd1d786b5f0bd15f3226ee23486e24b7b58304b60f64dc68e62606"
|
||||||
"checksum lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fb497c35d362b6a331cfd94956a07fc2c78a4604cdbee844a81170386b996dd3"
|
"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7"
|
||||||
"checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1"
|
"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d"
|
||||||
"checksum log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "61bd98ae7f7b754bc53dca7d44b604f733c6bba044ea6f41bc8d89272d8161d2"
|
"checksum log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cba860f648db8e6f269df990180c2217f333472b4a6e901e97446858487971e2"
|
||||||
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
||||||
"checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff"
|
"checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff"
|
||||||
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
|
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
|
||||||
"checksum proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "cccdc7557a98fe98453030f077df7f3a042052fae465bb61d2c2c41435cfd9b6"
|
"checksum pcre2 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c16ec0e30c17f938a2da8ff970ad9a4100166d0538898dcc035b55c393cab54"
|
||||||
"checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035"
|
"checksum pcre2-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a9027f9474e4e13d3b965538aafcaebe48c803488ad76b3c97ef061a8324695f"
|
||||||
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
|
"checksum pkg-config 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "104630aa1c83213cbc76db0703630fcb0421dac3585063be4ce9a8a2feeaa745"
|
||||||
|
"checksum proc-macro2 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "ee5697238f0d893c7f0ecc59c0999f18d2af85e424de441178bcacc9f9e6cf67"
|
||||||
|
"checksum quote 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ed7d650913520df631972f21e104a4fa2f9c82a14afc65d17b388a2e29731e7c"
|
||||||
|
"checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd"
|
||||||
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
|
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
|
||||||
"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.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5bbbea44c5490a1e84357ff28b7d518b4619a159fed5d25f6c1de2d19cc42814"
|
||||||
"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d"
|
"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d"
|
||||||
"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 safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
|
"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
|
||||||
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
|
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
|
||||||
"checksum serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)" = "0c3adf19c07af6d186d91dae8927b83b0553d07ca56cbf7f2f32560455c91920"
|
"checksum serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)" = "6dfad05c8854584e5f72fb859385ecdfa03af69c3fd0572f0da2d4c95f060bdb"
|
||||||
"checksum serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)" = "3525a779832b08693031b8ecfb0de81cd71cfd3812088fafe9a7496789572124"
|
"checksum serde_derive 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)" = "b719c6d5e9f73fbc37892246d5852333f040caa617b8873c6aced84bcb28e7bb"
|
||||||
"checksum serde_json 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c6908c7b925cd6c590358a4034de93dbddb20c45e1d021931459fd419bf0e2"
|
"checksum serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "44dd2cfde475037451fa99b7e5df77aa3cfd1536575fa8e7a538ab36dcde49ae"
|
||||||
"checksum simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3686dd9418ebcc3a26a0c0ae56deab0681e53fe899af91f5bbcee667ebffb1"
|
"checksum simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3686dd9418ebcc3a26a0c0ae56deab0681e53fe899af91f5bbcee667ebffb1"
|
||||||
"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.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2beff8ebc3658f07512a413866875adddd20f4fd47b2a4e6c9da65cd281baaea"
|
"checksum syn 0.14.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b7bfcbb0c068d0f642a0ffbd5c604965a360a61f99e8add013cef23a838614f3"
|
||||||
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||||
"checksum termcolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "722426c4a0539da2c4ffd9b419d90ad540b4cff4a053be9069c908d4d07e2836"
|
"checksum termcolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "722426c4a0539da2c4ffd9b419d90ad540b4cff4a053be9069c908d4d07e2836"
|
||||||
"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.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
|
"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.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
|
||||||
"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 unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
|
||||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
||||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
"checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051"
|
||||||
"checksum walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63636bd0eb3d00ccb8b9036381b526efac53caf112b7783b730ab3f8e44da369"
|
"checksum walkdir 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1b768ba943161a9226ccd59b26bcd901e5d60e6061f4fcad3034784e0c7372b"
|
||||||
"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
|
"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
|
||||||
"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-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"
|
||||||
|
15
Cargo.toml
15
Cargo.toml
@@ -33,8 +33,14 @@ path = "tests/tests.rs"
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"grep", "globset", "ignore",
|
"globset",
|
||||||
"grep-matcher", "grep-printer", "grep-regex", "grep-searcher",
|
"grep",
|
||||||
|
"grep-matcher",
|
||||||
|
"grep-pcre2",
|
||||||
|
"grep-printer",
|
||||||
|
"grep-regex",
|
||||||
|
"grep-searcher",
|
||||||
|
"ignore",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -67,9 +73,14 @@ version = "2.29.4"
|
|||||||
default-features = false
|
default-features = false
|
||||||
features = ["suggestions", "color"]
|
features = ["suggestions", "color"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde = "1"
|
||||||
|
serde_derive = "1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
avx-accel = ["grep/avx-accel"]
|
avx-accel = ["grep/avx-accel"]
|
||||||
simd-accel = ["grep/simd-accel"]
|
simd-accel = ["grep/simd-accel"]
|
||||||
|
pcre2 = ["grep/pcre2"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 1
|
debug = 1
|
||||||
|
328
FAQ.md
328
FAQ.md
@@ -16,6 +16,7 @@
|
|||||||
* [How do I get around the regex size limit?](#size-limit)
|
* [How do I get around the regex size limit?](#size-limit)
|
||||||
* [How do I make the `-f/--file` flag faster?](#dfa-size)
|
* [How do I make the `-f/--file` flag faster?](#dfa-size)
|
||||||
* [How do I make the output look like The Silver Searcher's output?](#silver-searcher-output)
|
* [How do I make the output look like The Silver Searcher's output?](#silver-searcher-output)
|
||||||
|
* [Why does ripgrep get slower when I enabled PCRE2 regexes?](#pcre2-slow)
|
||||||
* [When I run `rg`, why does it execute some other command?](#rg-other-cmd)
|
* [When I run `rg`, why does it execute some other command?](#rg-other-cmd)
|
||||||
* [How do I create an alias for ripgrep on Windows?](#rg-alias-windows)
|
* [How do I create an alias for ripgrep on Windows?](#rg-alias-windows)
|
||||||
* [How do I create a PowerShell profile?](#powershell-profile)
|
* [How do I create a PowerShell profile?](#powershell-profile)
|
||||||
@@ -157,13 +158,37 @@ tool. With that said,
|
|||||||
How do I use lookaround and/or backreferences?
|
How do I use lookaround and/or backreferences?
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
This isn't currently possible. ripgrep uses finite automata to implement
|
ripgrep's default regex engine does not support lookaround or backreferences.
|
||||||
regular expression search, and in turn, guarantees linear time searching on all
|
This is primarily because the default regex engine is implemented using finite
|
||||||
inputs. It is difficult to efficiently support lookaround and backreferences in
|
state machines in order to guarantee a linear worst case time complexity on all
|
||||||
finite automata engines, so ripgrep does not provide these features.
|
inputs. Backreferences are not possible to implement in this paradigm, and
|
||||||
|
lookaround appears difficult to do efficiently.
|
||||||
|
|
||||||
If a production quality regular expression engine with these features is ever
|
However, ripgrep optionally supports using PCRE2 as the regex engine instead of
|
||||||
written in Rust, then it is possible ripgrep will provide it as an opt-in
|
the default one based on finite state machines. You can enable PCRE2 with the
|
||||||
|
`-P/--pcre2` flag. For example, in the root of the ripgrep repo, you can easily
|
||||||
|
find all palindromes:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rg -P '(\w{10})\1'
|
||||||
|
tests/misc.rs
|
||||||
|
483: cmd.arg("--max-filesize").arg("44444444444444444444");
|
||||||
|
globset/src/glob.rs
|
||||||
|
1206: matches!(match7, "a*a*a*a*a*a*a*a*a", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||||
|
```
|
||||||
|
|
||||||
|
If your version of ripgrep doesn't support PCRE2, then you'll get an error
|
||||||
|
message when you try to use the `-P/--pcre2` flag:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rg -P '(\w{10})\1'
|
||||||
|
PCRE2 is not available in this build of ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
|
Most of the releases distributed by the ripgrep project here on GitHub will
|
||||||
|
come bundled with PCRE2 enabled. If you installed ripgrep through a different
|
||||||
|
means (like your system's package manager), then please reach out to the
|
||||||
|
maintainer of that package to see whether it's possible to enable the PCRE2
|
||||||
feature.
|
feature.
|
||||||
|
|
||||||
|
|
||||||
@@ -368,6 +393,297 @@ $ RIPGREP_CONFIG_PATH=$HOME/.config/ripgrep/rc rg foo
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
<h3 name="pcre2-slow">
|
||||||
|
Why does ripgrep get slower when I enable PCRE2 regexes?
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
When you use the `--pcre2` (`-P` for short) flag, ripgrep will use the PCRE2
|
||||||
|
regex engine instead of the default. Both regex engines are quite fast,
|
||||||
|
but PCRE2 provides a number of additional features such as look-around and
|
||||||
|
backreferences that many enjoy using. This is largely because PCRE2 uses
|
||||||
|
a backtracking implementation where as the default regex engine uses a finite
|
||||||
|
automaton based implementation. The former provides the ability to add lots of
|
||||||
|
bells and whistles over the latter, but the latter executes with worst case
|
||||||
|
linear time complexity.
|
||||||
|
|
||||||
|
With that out of the way, if you've used `-P` with ripgrep, you may have
|
||||||
|
noticed that it can be slower. The reasons for why this is are quite complex,
|
||||||
|
and they are complex because the optimizations that ripgrep uses to implement
|
||||||
|
fast search are complex.
|
||||||
|
|
||||||
|
The task ripgrep has before it is somewhat simple; all it needs to do is search
|
||||||
|
a file for occurrences of some pattern and then print the lines containing
|
||||||
|
those occurrences. The problem lies in what is considered a valid match and how
|
||||||
|
exactly we read the bytes from a file.
|
||||||
|
|
||||||
|
In terms of what is considered a valid match, remember that ripgrep will only
|
||||||
|
report matches spanning a single line by default. The problem here is that
|
||||||
|
some patterns can match across multiple lines, and ripgrep needs to prevent
|
||||||
|
that from happening. For example, `foo\sbar` will match `foo\nbar`. The most
|
||||||
|
obvious way to achieve this is to read the data from a file, and then apply
|
||||||
|
the pattern search to that data for each line. The problem with this approach
|
||||||
|
is that it can be quite slow; it would be much faster to let the pattern
|
||||||
|
search across as much data as possible. It's faster because it gets rid of the
|
||||||
|
overhead of finding the boundaries of every line, and also because it gets rid
|
||||||
|
of the overhead of starting and stopping the pattern search for every single
|
||||||
|
line. (This is operating under the general assumption that matching lines are
|
||||||
|
much rarer than non-matching lines.)
|
||||||
|
|
||||||
|
It turns out that we can use the faster approach by applying a very simple
|
||||||
|
restriction to the pattern: *statically prevent* the pattern from matching
|
||||||
|
through a `\n` character. Namely, when given a pattern like `foo\sbar`,
|
||||||
|
ripgrep will remove `\n` from the `\s` character class automatically. In some
|
||||||
|
cases, a simple removal is not so easy. For example, ripgrep will return an
|
||||||
|
error when your pattern includes a `\n` literal:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rg '\n'
|
||||||
|
the literal '"\n"' is not allowed in a regex
|
||||||
|
```
|
||||||
|
|
||||||
|
So what does this have to do with PCRE2? Well, ripgrep's default regex engine
|
||||||
|
exposes APIs for doing syntactic analysis on the pattern in a way that makes
|
||||||
|
it quite easy to strip `\n` from the pattern (or otherwise detect it and report
|
||||||
|
an error if stripping isn't possible). PCRE2 seemingly does not provide a
|
||||||
|
similar API, so ripgrep does not do any stripping when PCRE2 is enabled. This
|
||||||
|
forces ripgrep to use the "slow" search strategy of searching each line
|
||||||
|
individually.
|
||||||
|
|
||||||
|
OK, so if enabling PCRE2 slows down the default method of searching because it
|
||||||
|
forces matches to be limited to a single line, then why is PCRE2 also sometimes
|
||||||
|
slower when performing multiline searches? Well, that's because there are
|
||||||
|
*multiple* reasons why using PCRE2 in ripgrep can be slower than the default
|
||||||
|
regex engine. This time, blame PCRE2's Unicode support, which ripgrep enables
|
||||||
|
by default. In particular, PCRE2 cannot simultaneously enable Unicode support
|
||||||
|
and search arbitrary data. That is, when PCRE2's Unicode support is enabled,
|
||||||
|
the data **must** be valid UTF-8 (to do otherwise is to invoke undefined
|
||||||
|
behavior). This is in contrast to ripgrep's default regex engine, which can
|
||||||
|
enable Unicode support and still search arbitrary data. ripgrep's default
|
||||||
|
regex engine simply won't match invalid UTF-8 for a pattern that can otherwise
|
||||||
|
only match valid UTF-8. Why doesn't PCRE2 do the same? This author isn't
|
||||||
|
familiar with its internals, so we can't comment on it here.
|
||||||
|
|
||||||
|
The bottom line here is that we can't enable PCRE2's Unicode support without
|
||||||
|
simultaneously incurring a performance penalty for ensuring that we are
|
||||||
|
searching valid UTF-8. In particular, ripgrep will transcode the contents
|
||||||
|
of each file to UTF-8 while replacing invalid UTF-8 data with the Unicode
|
||||||
|
replacement codepoint. ripgrep then disables PCRE2's own internal UTF-8
|
||||||
|
checking, since we've guaranteed the data we hand it will be valid UTF-8. The
|
||||||
|
reason why ripgrep takes this approach is because if we do hand PCRE2 invalid
|
||||||
|
UTF-8, then it will report a match error if it comes across an invalid UTF-8
|
||||||
|
sequence. This is not good news for ripgrep, since it will stop it from
|
||||||
|
searching the rest of the file, and will also print potentially undesirable
|
||||||
|
error messages to users.
|
||||||
|
|
||||||
|
All right, the above is a lot of information to swallow if you aren't already
|
||||||
|
familiar with ripgrep internals. Let's make this concrete with some examples.
|
||||||
|
First, let's get some data big enough to magnify the performance differences:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -O 'https://burntsushi.net/stuff/subtitles2016-sample.gz'
|
||||||
|
$ gzip -d subtitles2016-sample
|
||||||
|
$ md5sum subtitles2016-sample
|
||||||
|
e3cb796a20bbc602fbfd6bb43bda45f5 subtitles2016-sample
|
||||||
|
```
|
||||||
|
|
||||||
|
To search this data, we will use the pattern `^\w{42}$`, which contains exactly
|
||||||
|
one hit in the file and has no literals. Having no literals is important,
|
||||||
|
because it ensures that the regex engine won't use literal optimizations to
|
||||||
|
speed up the search. In other words, it lets us reason coherently about the
|
||||||
|
actual task that the regex engine is performing.
|
||||||
|
|
||||||
|
Let's now walk through a few examples in light of the information above. First,
|
||||||
|
let's consider the default search using ripgrep's default regex engine and
|
||||||
|
then the same search with PCRE2:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ time rg '^\w{42}$' subtitles2016-sample
|
||||||
|
21225780:EverymajordevelopmentinthehistoryofAmerica
|
||||||
|
|
||||||
|
real 0m1.783s
|
||||||
|
user 0m1.731s
|
||||||
|
sys 0m0.051s
|
||||||
|
|
||||||
|
$ time rg -P '^\w{42}$' subtitles2016-sample
|
||||||
|
21225780:EverymajordevelopmentinthehistoryofAmerica
|
||||||
|
|
||||||
|
real 0m2.458s
|
||||||
|
user 0m2.419s
|
||||||
|
sys 0m0.038s
|
||||||
|
```
|
||||||
|
|
||||||
|
In this particular example, both pattern searches are using a Unicode aware
|
||||||
|
`\w` character class and both are counting lines in order to report line
|
||||||
|
numbers. The key difference here is that the first search will not search
|
||||||
|
line by line, but the second one will. We can observe which strategy ripgrep
|
||||||
|
uses by passing the `--trace` flag:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rg '^\w{42}$' subtitles2016-sample --trace
|
||||||
|
[... snip ...]
|
||||||
|
TRACE|grep_searcher::searcher|grep-searcher/src/searcher/mod.rs:622: Some("subtitles2016-sample"): searching via memory map
|
||||||
|
TRACE|grep_searcher::searcher|grep-searcher/src/searcher/mod.rs:712: slice reader: searching via slice-by-line strategy
|
||||||
|
TRACE|grep_searcher::searcher::core|grep-searcher/src/searcher/core.rs:61: searcher core: will use fast line searcher
|
||||||
|
[... snip ...]
|
||||||
|
|
||||||
|
$ rg -P '^\w{42}$' subtitles2016-sample --trace
|
||||||
|
[... snip ...]
|
||||||
|
TRACE|grep_searcher::searcher|grep-searcher/src/searcher/mod.rs:622: Some("subtitles2016-sample"): searching via memory map
|
||||||
|
TRACE|grep_searcher::searcher|grep-searcher/src/searcher/mod.rs:705: slice reader: needs transcoding, using generic reader
|
||||||
|
TRACE|grep_searcher::searcher|grep-searcher/src/searcher/mod.rs:685: generic reader: searching via roll buffer strategy
|
||||||
|
TRACE|grep_searcher::searcher::core|grep-searcher/src/searcher/core.rs:63: searcher core: will use slow line searcher
|
||||||
|
[... snip ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
The first says it is using the "fast line searcher" where as the latter says
|
||||||
|
it is using the "slow line searcher." The latter also shows that we are
|
||||||
|
decoding the contents of the file, which also impacts performance.
|
||||||
|
|
||||||
|
Interestingly, in this case, the pattern does not match a `\n` and the file
|
||||||
|
we're searching is valid UTF-8, so neither the slow line-by-line search
|
||||||
|
strategy nor the decoding are necessary. We could fix the former issue with
|
||||||
|
better PCRE2 introspection APIs. We can actually fix the latter issue with
|
||||||
|
ripgrep's `--no-encoding` flag, which prevents the automatic UTF-8 decoding,
|
||||||
|
but will enable PCRE2's own UTF-8 validity checking. Unfortunately, it's slower
|
||||||
|
in my build of ripgrep:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ time rg -P '^\w{42}$' subtitles2016-sample --no-encoding
|
||||||
|
21225780:EverymajordevelopmentinthehistoryofAmerica
|
||||||
|
|
||||||
|
real 0m3.074s
|
||||||
|
user 0m3.021s
|
||||||
|
sys 0m0.051s
|
||||||
|
```
|
||||||
|
|
||||||
|
(Tip: use the `--trace` flag to verify that no decoding in ripgrep is
|
||||||
|
happening.)
|
||||||
|
|
||||||
|
A possible reason why PCRE2's UTF-8 checking is slower is because it might
|
||||||
|
not be better than the highly optimized UTF-8 checking routines found in the
|
||||||
|
[`encoding_rs`](https://github.com/hsivonen/encoding_rs) library, which is what
|
||||||
|
ripgrep uses for UTF-8 decoding. Moreover, my build of ripgrep enables
|
||||||
|
`encoding_rs`'s SIMD optimizations, which may be in play here.
|
||||||
|
|
||||||
|
Also, note that using the `--no-encoding` flag can cause PCRE2 to report
|
||||||
|
invalid UTF-8 errors, which causes ripgrep to stop searching the file:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cat invalid-utf8
|
||||||
|
foobar
|
||||||
|
|
||||||
|
$ xxd invalid-utf8
|
||||||
|
00000000: 666f 6fff 6261 720a foo.bar.
|
||||||
|
|
||||||
|
$ rg foo invalid-utf8
|
||||||
|
1:foobar
|
||||||
|
|
||||||
|
$ rg -P foo invalid-utf8
|
||||||
|
1:foo<6F>bar
|
||||||
|
|
||||||
|
$ rg -P foo invalid-utf8 --no-encoding
|
||||||
|
invalid-utf8: PCRE2: error matching: UTF-8 error: illegal byte (0xfe or 0xff)
|
||||||
|
```
|
||||||
|
|
||||||
|
All right, so at this point, you might think that we could remove the penalty
|
||||||
|
for line-by-line searching by enabling multiline search. After all, our
|
||||||
|
particular pattern can't match across multiple lines anyway, so we'll still get
|
||||||
|
the results we want. Let's try it:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ time rg -U '^\w{42}$' subtitles2016-sample
|
||||||
|
21225780:EverymajordevelopmentinthehistoryofAmerica
|
||||||
|
|
||||||
|
real 0m1.803s
|
||||||
|
user 0m1.748s
|
||||||
|
sys 0m0.054s
|
||||||
|
|
||||||
|
$ time rg -P -U '^\w{42}$' subtitles2016-sample
|
||||||
|
21225780:EverymajordevelopmentinthehistoryofAmerica
|
||||||
|
|
||||||
|
real 0m2.962s
|
||||||
|
user 0m2.246s
|
||||||
|
sys 0m0.713s
|
||||||
|
```
|
||||||
|
|
||||||
|
Search times remain the same with the default regex engine, but the PCRE2
|
||||||
|
search gets _slower_. What happened? The secrets can be revealed with the
|
||||||
|
`--trace` flag once again. In the former case, ripgrep actually detects that
|
||||||
|
the pattern can't match across multiple lines, and so will fall back to the
|
||||||
|
"fast line search" strategy as with our search without `-U`.
|
||||||
|
|
||||||
|
However, for PCRE2, things are much worse. Namely, since Unicode mode is still
|
||||||
|
enabled, ripgrep is still going to decode UTF-8 to ensure that it hands only
|
||||||
|
valid UTF-8 to PCRE2. Unfortunately, one key downside of multiline search is
|
||||||
|
that ripgrep cannot do it incrementally. Since matches can be arbitrarily long,
|
||||||
|
ripgrep actually needs the entire file in memory at once. Normally, we can use
|
||||||
|
a memory map for this, but because we need to UTF-8 decode the file before
|
||||||
|
searching it, ripgrep winds up reading the entire contents of the file on to
|
||||||
|
the heap before executing a search. Owch.
|
||||||
|
|
||||||
|
OK, so Unicode is killing us here. The file we're searching is _mostly_ ASCII,
|
||||||
|
so maybe we're OK with missing some data. (Try `rg '[\w--\p{ascii}]'` to see
|
||||||
|
non-ASCII word characters that an ASCII-only `\w` character class would miss.)
|
||||||
|
We can disable Unicode in both searches, but this is done differently depending
|
||||||
|
on the regex engine we use:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ time rg '(?-u)^\w{42}$' subtitles2016-sample
|
||||||
|
21225780:EverymajordevelopmentinthehistoryofAmerica
|
||||||
|
|
||||||
|
real 0m1.714s
|
||||||
|
user 0m1.669s
|
||||||
|
sys 0m0.044s
|
||||||
|
|
||||||
|
[andrew@Cheetah 2016] time rg -P '^\w{42}$' subtitles2016-sample --no-pcre2-unicode
|
||||||
|
21225780:EverymajordevelopmentinthehistoryofAmerica
|
||||||
|
|
||||||
|
real 0m1.997s
|
||||||
|
user 0m1.958s
|
||||||
|
sys 0m0.037s
|
||||||
|
```
|
||||||
|
|
||||||
|
For the most part, ripgrep's default regex engine performs about the same.
|
||||||
|
PCRE2 does improve a little bit, and is now almost as fast as the default
|
||||||
|
regex engine. If you look at the output of `--trace`, you'll see that ripgrep
|
||||||
|
will no longer perform UTF-8 decoding, but it does still use the slow
|
||||||
|
line-by-line searcher.
|
||||||
|
|
||||||
|
At this point, we can combine all of our insights above: let's try to get off
|
||||||
|
of the slow line-by-line searcher by enabling multiline mode, and let's stop
|
||||||
|
UTF-8 decoding by disabling Unicode support:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ time rg -U '(?-u)^\w{42}$' subtitles2016-sample
|
||||||
|
21225780:EverymajordevelopmentinthehistoryofAmerica
|
||||||
|
|
||||||
|
real 0m1.714s
|
||||||
|
user 0m1.655s
|
||||||
|
sys 0m0.058s
|
||||||
|
|
||||||
|
$ time rg -P -U '^\w{42}$' subtitles2016-sample --no-pcre2-unicode
|
||||||
|
21225780:EverymajordevelopmentinthehistoryofAmerica
|
||||||
|
|
||||||
|
real 0m1.121s
|
||||||
|
user 0m1.071s
|
||||||
|
sys 0m0.048s
|
||||||
|
```
|
||||||
|
|
||||||
|
Ah, there's PCRE2's JIT shining! ripgrep's default regex engine once again
|
||||||
|
remains about the same, but PCRE2 no longer needs to search line-by-line and it
|
||||||
|
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
|
||||||
|
a brief and interesting historical note, the configuration of "memory map +
|
||||||
|
multiline + no-Unicode" is exactly the configuration used by The Silver
|
||||||
|
Searcher. This analysis perhaps sheds some reasoning as to why it converged on
|
||||||
|
that specific setting!)
|
||||||
|
|
||||||
|
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
|
||||||
|
multiple lines, then enable multiline mode with `-U` and disable PCRE2's
|
||||||
|
Unicode support with the `--no-pcre2-unicode` flag.
|
||||||
|
|
||||||
|
|
||||||
<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?
|
||||||
</h3>
|
</h3>
|
||||||
|
128
README.md
128
README.md
@@ -7,7 +7,7 @@ available for [every release](https://github.com/BurntSushi/ripgrep/releases).
|
|||||||
ripgrep is similar to other popular search tools like The Silver Searcher,
|
ripgrep is similar to other popular search tools like The Silver Searcher,
|
||||||
ack and grep.
|
ack and grep.
|
||||||
|
|
||||||
[](https://travis-ci.org/BurntSushi/ripgrep)
|
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||||
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||||
[](https://crates.io/crates/ripgrep)
|
[](https://crates.io/crates/ripgrep)
|
||||||
|
|
||||||
@@ -85,14 +85,16 @@ increases the times to `2.640s` for ripgrep and `10.277s` for GNU grep.
|
|||||||
|
|
||||||
### Why should I use ripgrep?
|
### Why should I use ripgrep?
|
||||||
|
|
||||||
* It can replace many use cases served by both The Silver Searcher and GNU grep
|
* It can replace many use cases served by other search tools
|
||||||
because it is generally faster than both. (See [the FAQ](FAQ.md#posix4ever)
|
because it contains most of their features and is generally faster. (See
|
||||||
for more details on whether ripgrep can truly replace grep.)
|
[the FAQ](FAQ.md#posix4ever) for more details on whether ripgrep can truly
|
||||||
* Like The Silver Searcher, ripgrep defaults to recursive directory search
|
replace grep.)
|
||||||
and won't search files ignored by your `.gitignore` files. It also ignores
|
* Like other tools specialized to code search, ripgrep defaults to recursive
|
||||||
hidden and binary files by default. ripgrep also implements full support
|
directory search and won't search files ignored by your `.gitignore` files.
|
||||||
for `.gitignore`, whereas there are many bugs related to that functionality
|
It also ignores hidden and binary files by default. ripgrep also implements
|
||||||
in The Silver Searcher.
|
full support for `.gitignore`, whereas there are many bugs related to that
|
||||||
|
functionality in other code search tools claiming to provide the same
|
||||||
|
functionality.
|
||||||
* ripgrep can search specific types of files. For example, `rg -tpy foo`
|
* ripgrep can search specific types of files. For example, `rg -tpy foo`
|
||||||
limits your search to Python files and `rg -Tjs foo` excludes Javascript
|
limits your search to Python files and `rg -Tjs foo` excludes Javascript
|
||||||
files from your search. ripgrep can be taught about new file types with
|
files from your search. ripgrep can be taught about new file types with
|
||||||
@@ -117,22 +119,24 @@ bugs, and Unicode support.
|
|||||||
|
|
||||||
### Why shouldn't I use ripgrep?
|
### Why shouldn't I use ripgrep?
|
||||||
|
|
||||||
I'd like to try to convince you why you *shouldn't* use ripgrep. This should
|
Despite initially not wanting to add every feature under the sun to ripgrep,
|
||||||
give you a glimpse at some important downsides or missing features of
|
over time, ripgrep has grown support for most features found in other file
|
||||||
ripgrep.
|
searching tools. This includes searching for results spanning across multiple
|
||||||
|
lines, and opt-in support for PCRE2, which provides look-around and
|
||||||
|
backreference support.
|
||||||
|
|
||||||
* ripgrep uses a regex engine based on finite automata, so if you want fancy
|
At this point, the primary reasons not to use ripgrep probably consist of one
|
||||||
regex features such as backreferences or lookaround, ripgrep won't provide
|
or more of the following:
|
||||||
them to you. ripgrep does support lots of things though, including, but not
|
|
||||||
limited to: lazy quantification (e.g., `a+?`), repetitions (e.g., `a{2,5}`),
|
|
||||||
begin/end assertions (e.g., `^\w+$`), word boundaries (e.g., `\bfoo\b`), and
|
|
||||||
support for Unicode categories (e.g., `\p{Sc}` to match currency symbols or
|
|
||||||
`\p{Lu}` to match any uppercase letter). (Fancier regexes will never be
|
|
||||||
supported.)
|
|
||||||
* ripgrep doesn't have multiline search. (Will happen as an opt-in feature.)
|
|
||||||
|
|
||||||
In other words, if you like fancy regexes or multiline search, then ripgrep
|
* You need a portable and ubiquitous tool. While ripgrep works on Windows,
|
||||||
may not quite meet your needs (yet).
|
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.
|
||||||
|
* There still exists some other minor feature (or bug) found in another tool
|
||||||
|
that isn't in ripgrep.
|
||||||
|
* There is a performance edge case where ripgrep doesn't do well where another
|
||||||
|
tool does do well. (Please file a bug report!)
|
||||||
|
* ripgrep isn't possible to install on your machine or isn't available for your
|
||||||
|
platform. (Please file a bug report!)
|
||||||
|
|
||||||
|
|
||||||
### Is it really faster than everything else?
|
### Is it really faster than everything else?
|
||||||
@@ -145,7 +149,8 @@ Summarizing, ripgrep is fast because:
|
|||||||
* It is built on top of
|
* It is built on top of
|
||||||
[Rust's regex engine](https://github.com/rust-lang-nursery/regex).
|
[Rust's regex engine](https://github.com/rust-lang-nursery/regex).
|
||||||
Rust's regex engine uses finite automata, SIMD and aggressive literal
|
Rust's regex engine uses finite automata, SIMD and aggressive literal
|
||||||
optimizations to make searching very fast.
|
optimizations to make searching very fast. (PCRE2 support can be opted into
|
||||||
|
with the `-P/--pcre2` flag.)
|
||||||
* Rust's regex library maintains performance with full Unicode support by
|
* Rust's regex library maintains performance with full Unicode support by
|
||||||
building UTF-8 decoding directly into its deterministic finite automaton
|
building UTF-8 decoding directly into its deterministic finite automaton
|
||||||
engine.
|
engine.
|
||||||
@@ -168,6 +173,11 @@ Andy Lester, author of [ack](https://beyondgrep.com/), has published an
|
|||||||
excellent table comparing the features of ack, ag, git-grep, GNU grep and
|
excellent table comparing the features of ack, ag, git-grep, GNU grep and
|
||||||
ripgrep: https://beyondgrep.com/feature-comparison/
|
ripgrep: https://beyondgrep.com/feature-comparison/
|
||||||
|
|
||||||
|
Note that ripgrep has grown a few significant new features recently that
|
||||||
|
are not yet present in Andy's table. This includes, but is not limited to,
|
||||||
|
configuration files, passthru, support for searching compressed files,
|
||||||
|
multiline search and opt-in fancy regex support via PCRE2.
|
||||||
|
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
@@ -207,13 +217,15 @@ If you're a **MacPorts** user, then you can install ripgrep from the
|
|||||||
$ sudo port install ripgrep
|
$ sudo port install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Windows Chocolatey** user, then you can install ripgrep from the [official repo](https://chocolatey.org/packages/ripgrep):
|
If you're a **Windows Chocolatey** user, then you can install ripgrep from the
|
||||||
|
[official repo](https://chocolatey.org/packages/ripgrep):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ choco install ripgrep
|
$ choco install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Windows Scoop** user, then you can install ripgrep from the [official bucket](https://github.com/lukesampson/scoop/blob/master/bucket/ripgrep.json):
|
If you're a **Windows Scoop** user, then you can install ripgrep from the
|
||||||
|
[official bucket](https://github.com/lukesampson/scoop/blob/master/bucket/ripgrep.json):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ scoop install ripgrep
|
$ scoop install ripgrep
|
||||||
@@ -225,32 +237,37 @@ If you're an **Arch Linux** user, then you can install ripgrep from the official
|
|||||||
$ pacman -S ripgrep
|
$ pacman -S ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Gentoo** user, you can install ripgrep from the [official repo](https://packages.gentoo.org/packages/sys-apps/ripgrep):
|
If you're a **Gentoo** user, you can install ripgrep from the
|
||||||
|
[official repo](https://packages.gentoo.org/packages/sys-apps/ripgrep):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ emerge sys-apps/ripgrep
|
$ emerge sys-apps/ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Fedora 27+** user, you can install ripgrep from official repositories.
|
If you're a **Fedora 27+** user, you can install ripgrep from official
|
||||||
|
repositories.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sudo dnf install ripgrep
|
$ sudo dnf install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Fedora 24+** user, you can install ripgrep from [copr](https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/):
|
If you're a **Fedora 24+** user, you can install ripgrep from
|
||||||
|
[copr](https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sudo dnf copr enable carlwgeorge/ripgrep
|
$ sudo dnf copr enable carlwgeorge/ripgrep
|
||||||
$ sudo dnf install ripgrep
|
$ sudo dnf install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're an **openSUSE Tumbleweed** user, you can install ripgrep from the [official repo](http://software.opensuse.org/package/ripgrep):
|
If you're an **openSUSE Tumbleweed** user, you can install ripgrep from the
|
||||||
|
[official repo](http://software.opensuse.org/package/ripgrep):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sudo zypper install ripgrep
|
$ sudo zypper install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **RHEL/CentOS 7** user, you can install ripgrep from [copr](https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/):
|
If you're a **RHEL/CentOS 7** user, you can install ripgrep from
|
||||||
|
[copr](https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sudo yum-config-manager --add-repo=https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/repo/epel-7/carlwgeorge-ripgrep-epel-7.repo
|
$ sudo yum-config-manager --add-repo=https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/repo/epel-7/carlwgeorge-ripgrep-epel-7.repo
|
||||||
@@ -271,8 +288,14 @@ then ripgrep can be installed using a binary `.deb` file provided in each
|
|||||||
ripgrep is not in the official Debian or Ubuntu repositories.
|
ripgrep is not in the official Debian or Ubuntu repositories.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ curl -LO https://github.com/BurntSushi/ripgrep/releases/download/0.8.1/ripgrep_0.8.1_amd64.deb
|
$ curl -LO https://github.com/BurntSushi/ripgrep/releases/download/0.9.0/ripgrep_0.9.0_amd64.deb
|
||||||
$ sudo dpkg -i ripgrep_0.8.1_amd64.deb
|
$ sudo dpkg -i ripgrep_0.9.0_amd64.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
If you run Debian Buster (currently Debian testing) or Debian sid, ripgrep is
|
||||||
|
[officially maintained by Debian](https://tracker.debian.org/pkg/rust-ripgrep).
|
||||||
|
```
|
||||||
|
$ 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
|
||||||
@@ -280,25 +303,29 @@ 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
|
||||||
longer a recommended installation option.)
|
longer a recommended installation option.)
|
||||||
|
|
||||||
If you're a **FreeBSD** user, then you can install ripgrep from the [official ports](https://www.freshports.org/textproc/ripgrep/):
|
If you're a **FreeBSD** user, then you can install ripgrep from the
|
||||||
|
[official ports](https://www.freshports.org/textproc/ripgrep/):
|
||||||
|
|
||||||
```
|
```
|
||||||
# pkg install ripgrep
|
# pkg install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're an **OpenBSD** user, then you can install ripgrep from the [official ports](http://openports.se/textproc/ripgrep):
|
If you're an **OpenBSD** user, then you can install ripgrep from the
|
||||||
|
[official ports](http://openports.se/textproc/ripgrep):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ doas pkg_add ripgrep
|
$ doas pkg_add ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **NetBSD** user, then you can install ripgrep from [pkgsrc](http://pkgsrc.se/textproc/ripgrep):
|
If you're a **NetBSD** user, then you can install ripgrep from
|
||||||
|
[pkgsrc](http://pkgsrc.se/textproc/ripgrep):
|
||||||
|
|
||||||
```
|
```
|
||||||
# pkgin install ripgrep
|
# pkgin install ripgrep
|
||||||
```
|
```
|
||||||
|
|
||||||
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.23.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
|
||||||
@@ -347,6 +374,35 @@ are not necessary to get SIMD optimizations for search; those are enabled
|
|||||||
automatically. Hopefully, some day, the `simd-accel` and `avx-accel` features
|
automatically. Hopefully, some day, the `simd-accel` and `avx-accel` features
|
||||||
will similarly become unnecessary.
|
will similarly become unnecessary.
|
||||||
|
|
||||||
|
Finally, optional PCRE2 support can be built with ripgrep by enabling the
|
||||||
|
`pcre2` feature:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo build --release --features 'pcre2'
|
||||||
|
```
|
||||||
|
|
||||||
|
(Tip: use `--features 'pcre2 simd-accel avx-accel'` to also include compile
|
||||||
|
time SIMD optimizations.)
|
||||||
|
|
||||||
|
Enabling the PCRE2 feature will attempt to automatically find and link with
|
||||||
|
your system's PCRE2 library via `pkg-config`. If one doesn't exist, then
|
||||||
|
ripgrep will build PCRE2 from source using your system's C compiler and then
|
||||||
|
statically link it into the final executable. Static linking can be forced even
|
||||||
|
when there is an available PCRE2 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
|
||||||
|
library on your system (consult your friendly neighborhood package manager).
|
||||||
|
Then you just need to add MUSL support to your Rust toolchain and rebuild
|
||||||
|
ripgrep, which yields a fully static executable:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rustup target add x86_64-unknown-linux-musl
|
||||||
|
$ cargo build --release --target x86_64-unknown-linux-musl
|
||||||
|
```
|
||||||
|
|
||||||
|
Applying the `--features` flag from above works as expected.
|
||||||
|
|
||||||
|
|
||||||
### Running tests
|
### Running tests
|
||||||
|
|
||||||
|
26
appveyor.yml
26
appveyor.yml
@@ -1,8 +1,6 @@
|
|||||||
# Inspired from https://github.com/habitat-sh/habitat/blob/master/appveyor.yml
|
|
||||||
cache:
|
cache:
|
||||||
- c:\cargo\registry
|
- c:\cargo\registry
|
||||||
- c:\cargo\git
|
- c:\cargo\git
|
||||||
- c:\projects\ripgrep\target
|
|
||||||
|
|
||||||
init:
|
init:
|
||||||
- mkdir c:\cargo
|
- mkdir c:\cargo
|
||||||
@@ -19,14 +17,20 @@ environment:
|
|||||||
PROJECT_NAME: ripgrep
|
PROJECT_NAME: ripgrep
|
||||||
RUST_BACKTRACE: full
|
RUST_BACKTRACE: full
|
||||||
matrix:
|
matrix:
|
||||||
- TARGET: i686-pc-windows-gnu
|
|
||||||
CHANNEL: stable
|
|
||||||
- TARGET: i686-pc-windows-msvc
|
|
||||||
CHANNEL: stable
|
|
||||||
- TARGET: x86_64-pc-windows-gnu
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
CHANNEL: stable
|
CHANNEL: stable
|
||||||
|
BITS: 64
|
||||||
|
MSYS2: 1
|
||||||
- TARGET: x86_64-pc-windows-msvc
|
- TARGET: x86_64-pc-windows-msvc
|
||||||
CHANNEL: stable
|
CHANNEL: stable
|
||||||
|
BITS: 64
|
||||||
|
- TARGET: i686-pc-windows-gnu
|
||||||
|
CHANNEL: stable
|
||||||
|
BITS: 32
|
||||||
|
MSYS2: 1
|
||||||
|
- TARGET: i686-pc-windows-msvc
|
||||||
|
CHANNEL: stable
|
||||||
|
BITS: 32
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
@@ -35,8 +39,9 @@ matrix:
|
|||||||
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
||||||
install:
|
install:
|
||||||
- curl -sSf -o rustup-init.exe https://win.rustup.rs/
|
- curl -sSf -o rustup-init.exe https://win.rustup.rs/
|
||||||
- rustup-init.exe -y --default-host %TARGET% --no-modify-path
|
- rustup-init.exe -y --default-host %TARGET%
|
||||||
- if defined MSYS2_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS2_BITS%\bin
|
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||||
|
- if defined MSYS2 set PATH=C:\msys64\mingw%BITS%\bin;%PATH%
|
||||||
- rustc -V
|
- rustc -V
|
||||||
- cargo -V
|
- cargo -V
|
||||||
|
|
||||||
@@ -46,11 +51,11 @@ build: false
|
|||||||
# Equivalent to Travis' `script` phase
|
# Equivalent to Travis' `script` phase
|
||||||
# TODO modify this phase as you see fit
|
# TODO modify this phase as you see fit
|
||||||
test_script:
|
test_script:
|
||||||
- cargo test --verbose --all
|
- cargo test --verbose --all --features pcre2
|
||||||
|
|
||||||
before_deploy:
|
before_deploy:
|
||||||
# Generate artifacts for release
|
# Generate artifacts for release
|
||||||
- cargo build --release
|
- cargo build --release --features pcre2
|
||||||
- mkdir staging
|
- mkdir staging
|
||||||
- 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
|
||||||
@@ -78,7 +83,6 @@ branches:
|
|||||||
only:
|
only:
|
||||||
- /\d+\.\d+\.\d+/
|
- /\d+\.\d+\.\d+/
|
||||||
- master
|
- master
|
||||||
- ag/libripgrep
|
|
||||||
# - appveyor
|
# - appveyor
|
||||||
# - /\d+\.\d+\.\d+/
|
# - /\d+\.\d+\.\d+/
|
||||||
# except:
|
# except:
|
||||||
|
77
build.rs
77
build.rs
@@ -4,6 +4,7 @@ extern crate clap;
|
|||||||
extern crate lazy_static;
|
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;
|
||||||
@@ -18,6 +19,22 @@ 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") {
|
||||||
@@ -182,3 +199,63 @@ 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -8,7 +8,11 @@ set -ex
|
|||||||
|
|
||||||
# Generate artifacts for release
|
# Generate artifacts for release
|
||||||
mk_artifacts() {
|
mk_artifacts() {
|
||||||
|
if is_arm; then
|
||||||
cargo build --target "$TARGET" --release
|
cargo build --target "$TARGET" --release
|
||||||
|
else
|
||||||
|
cargo build --target "$TARGET" --release --features 'pcre2'
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
mk_tarball() {
|
mk_tarball() {
|
||||||
|
@@ -8,7 +8,11 @@ set -ex
|
|||||||
|
|
||||||
main() {
|
main() {
|
||||||
# Test a normal debug build.
|
# Test a normal debug build.
|
||||||
cargo build --target "$TARGET" --verbose --all
|
if is_arm; then
|
||||||
|
cargo build --target "$TARGET" --verbose
|
||||||
|
else
|
||||||
|
cargo build --target "$TARGET" --verbose --all --features 'pcre2'
|
||||||
|
fi
|
||||||
|
|
||||||
# Show the output of the most recent build.rs stderr.
|
# Show the output of the most recent build.rs stderr.
|
||||||
set +x
|
set +x
|
||||||
@@ -40,7 +44,7 @@ main() {
|
|||||||
"$(dirname "${0}")/test_complete.sh"
|
"$(dirname "${0}")/test_complete.sh"
|
||||||
|
|
||||||
# Run tests for ripgrep and all sub-crates.
|
# Run tests for ripgrep and all sub-crates.
|
||||||
cargo test --target "$TARGET" --verbose --all
|
cargo test --target "$TARGET" --verbose --all --features 'pcre2'
|
||||||
}
|
}
|
||||||
|
|
||||||
main
|
main
|
||||||
|
@@ -39,12 +39,14 @@ main() {
|
|||||||
print -rl - 'Comparing options:' "-$rg" "+$_rg"
|
print -rl - 'Comparing options:' "-$rg" "+$_rg"
|
||||||
|
|
||||||
# 'Parse' options out of the `--help` output. To prevent false positives we
|
# 'Parse' options out of the `--help` output. To prevent false positives we
|
||||||
# only look at lines where the first non-white-space character is `-`
|
# only look at lines where the first non-white-space character is `-`, or
|
||||||
|
# where a long option starting with certain letters (see `_rg`) is found.
|
||||||
|
# Occasionally we may have to handle some manually, however
|
||||||
help_args=( ${(f)"$(
|
help_args=( ${(f)"$(
|
||||||
$rg --help |
|
$rg --help |
|
||||||
$rg -- '^\s*-' |
|
$rg -i -- '^\s+--?[a-z0-9]|--[imnp]' |
|
||||||
$rg -io -- '[\t ,](-[a-z0-9]|--[a-z0-9-]+)\b' |
|
$rg -ior '$1' -- $'[\t /\"\'`.,](-[a-z0-9]|--[a-z0-9-]+)\\b' |
|
||||||
tr -d '\t ,' |
|
$rg -v -- --print0 | # False positives
|
||||||
sort -u
|
sort -u
|
||||||
)"} )
|
)"} )
|
||||||
|
|
||||||
@@ -58,8 +60,6 @@ main() {
|
|||||||
comp_args=( ${comp_args%%-[:[]*} ) # Strip everything after -optname-
|
comp_args=( ${comp_args%%-[:[]*} ) # Strip everything after -optname-
|
||||||
comp_args=( ${comp_args%%[:+=[]*} ) # Strip everything after other optspecs
|
comp_args=( ${comp_args%%[:+=[]*} ) # Strip everything after other optspecs
|
||||||
comp_args=( ${comp_args##[^-]*} ) # Remove non-options
|
comp_args=( ${comp_args##[^-]*} ) # Remove non-options
|
||||||
|
|
||||||
# This probably isn't necessary, but we should ensure the same order
|
|
||||||
comp_args=( ${(f)"$( print -rl - $comp_args | sort -u )"} )
|
comp_args=( ${(f)"$( print -rl - $comp_args | sort -u )"} )
|
||||||
|
|
||||||
(( $#help_args )) || {
|
(( $#help_args )) || {
|
||||||
|
@@ -55,13 +55,6 @@ gcc_prefix() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
is_ssse3_target() {
|
|
||||||
case "$(architecture)" in
|
|
||||||
amd64) return 0 ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
is_x86() {
|
is_x86() {
|
||||||
case "$(architecture)" in
|
case "$(architecture)" in
|
||||||
amd64|i386) return 0 ;;
|
amd64|i386) return 0 ;;
|
||||||
|
216
complete/_rg
216
complete/_rg
@@ -6,8 +6,8 @@
|
|||||||
# Run ci/test_complete.sh after building to ensure that the options supported by
|
# Run ci/test_complete.sh after building to ensure that the options supported by
|
||||||
# this function stay in synch with the `rg` binary.
|
# this function stay in synch with the `rg` binary.
|
||||||
#
|
#
|
||||||
# @see http://zsh.sourceforge.net/Doc/Release/Completion-System.html
|
# For convenience, a completion reference guide is included at the bottom of
|
||||||
# @see https://github.com/zsh-users/zsh/blob/master/Etc/completion-style-guide
|
# this file.
|
||||||
#
|
#
|
||||||
# Originally based on code from the zsh-users project — see copyright notice
|
# Originally based on code from the zsh-users project — see copyright notice
|
||||||
# below.
|
# below.
|
||||||
@@ -26,8 +26,10 @@ _rg() {
|
|||||||
# style set. Note that this prefix check has to be updated manually to account
|
# style set. Note that this prefix check has to be updated manually to account
|
||||||
# for all of the potential negation options listed below!
|
# for all of the potential negation options listed below!
|
||||||
if
|
if
|
||||||
# (--[imn]* => --ignore*, --messages, --no-*)
|
# We also want to list all of these options during testing
|
||||||
[[ $PREFIX$SUFFIX == --[imn]* ]] ||
|
[[ $_RG_COMPLETE_LIST_ARGS == (1|t*|y*) ]] ||
|
||||||
|
# (--[imnp]* => --ignore*, --messages, --no-*, --pcre2-unicode)
|
||||||
|
[[ $PREFIX$SUFFIX == --[imnp]* ]] ||
|
||||||
zstyle -t ":complete:$curcontext:*" complete-all
|
zstyle -t ":complete:$curcontext:*" complete-all
|
||||||
then
|
then
|
||||||
no=
|
no=
|
||||||
@@ -61,8 +63,12 @@ _rg() {
|
|||||||
$no"--no-column[don't show column numbers for matches]"
|
$no"--no-column[don't show column numbers for matches]"
|
||||||
|
|
||||||
+ '(count)' # Counting options
|
+ '(count)' # Counting options
|
||||||
'(passthru)'{-c,--count}'[only show count of matching lines for each file]'
|
{-c,--count}'[only show count of matching lines for each file]'
|
||||||
'(passthru)--count-matches[only show count of individual matches for each file]'
|
'--count-matches[only show count of individual matches for each file]'
|
||||||
|
|
||||||
|
+ '(encoding)' # Encoding options
|
||||||
|
{-E+,--encoding=}'[specify text encoding of files to search]: :_rg_encodings'
|
||||||
|
$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'
|
'*'{-f+,--file=}'[specify file containing patterns to search for]: :_files'
|
||||||
@@ -111,10 +117,19 @@ _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]'
|
||||||
|
|
||||||
+ '(line)' # Line-number options
|
+ '(json)' # JSON options
|
||||||
|
'--json[output results in JSON Lines format]'
|
||||||
|
$no"--no-json[don't output results in JSON Lines format]"
|
||||||
|
|
||||||
|
+ '(line-number)' # Line-number options
|
||||||
{-n,--line-number}'[show line numbers for matches]'
|
{-n,--line-number}'[show line numbers for matches]'
|
||||||
{-N,--no-line-number}"[don't show line numbers for matches]"
|
{-N,--no-line-number}"[don't show line numbers for matches]"
|
||||||
|
|
||||||
|
+ '(line-terminator)' # Line-terminator options
|
||||||
|
'--crlf[use CRLF as line terminator]'
|
||||||
|
$no"--no-crlf[don't use CRLF as line terminator]"
|
||||||
|
'(text)--null-data[use NUL as line terminator]'
|
||||||
|
|
||||||
+ '(max-depth)' # Directory-depth options
|
+ '(max-depth)' # Directory-depth options
|
||||||
'--max-depth=[specify max number of directories to descend]:number of directories'
|
'--max-depth=[specify max number of directories to descend]:number of directories'
|
||||||
'!--maxdepth=:number of directories'
|
'!--maxdepth=:number of directories'
|
||||||
@@ -131,16 +146,28 @@ _rg() {
|
|||||||
'--mmap[search using memory maps when possible]'
|
'--mmap[search using memory maps when possible]'
|
||||||
"--no-mmap[don't search using memory maps]"
|
"--no-mmap[don't search using memory maps]"
|
||||||
|
|
||||||
+ '(multiline)' # multiline options
|
+ '(multiline)' # Multiline options
|
||||||
'--multiline[permit matching across multiple lines]'
|
{-U,--multiline}'[permit matching across multiple lines]'
|
||||||
$no"--no-multiline[restrict matches to at most one line each]"
|
$no'(multiline-dotall)--no-multiline[restrict matches to at most one line each]'
|
||||||
|
|
||||||
|
+ '(multiline-dotall)' # Multiline DOTALL options
|
||||||
|
'(--no-multiline)--multiline-dotall[allow "." to match newline (with -U)]'
|
||||||
|
$no"(--no-multiline)--no-multiline-dotall[don't allow \".\" to match newline (with -U)]"
|
||||||
|
|
||||||
+ '(only)' # Only-match options
|
+ '(only)' # Only-match options
|
||||||
'(passthru replace)'{-o,--only-matching}'[show only matching part of each line]'
|
{-o,--only-matching}'[show only matching part of each line]'
|
||||||
|
|
||||||
+ '(passthru)' # Pass-through options
|
+ '(passthru)' # Pass-through options
|
||||||
'(--vimgrep count only replace)--passthru[show both matching and non-matching lines]'
|
'(--vimgrep)--passthru[show both matching and non-matching lines]'
|
||||||
'!(--vimgrep count only replace)--passthrough'
|
'!(--vimgrep)--passthrough'
|
||||||
|
|
||||||
|
+ '(pcre2)' # PCRE2 options
|
||||||
|
{-P,--pcre2}'[enable matching with PCRE2]'
|
||||||
|
$no'(pcre2-unicode)--no-pcre2[disable matching with PCRE2]'
|
||||||
|
|
||||||
|
+ '(pcre2-unicode)' # PCRE2 Unicode options
|
||||||
|
$no'(--no-pcre2-unicode)--pcre2-unicode[enable PCRE2 Unicode mode (with -P)]'
|
||||||
|
'(--no-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'
|
||||||
@@ -154,22 +181,27 @@ _rg() {
|
|||||||
'(1 file)*'{-e+,--regexp=}'[specify pattern]:pattern'
|
'(1 file)*'{-e+,--regexp=}'[specify pattern]:pattern'
|
||||||
|
|
||||||
+ '(replace)' # Replacement options
|
+ '(replace)' # Replacement options
|
||||||
'(count only passthru)'{-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-files[sort results by file path (disables parallelism)]'
|
||||||
$no"--no-sort-files[don't sort results by file path]"
|
$no"--no-sort-files[don't sort results by file path]"
|
||||||
|
|
||||||
+ stats # Statistics options
|
+ '(stats)' # Statistics options
|
||||||
'(--files file-match)--stats[show search statistics]'
|
'(--files file-match)--stats[show search statistics]'
|
||||||
|
$no"--no-stats[don't show search statistics]"
|
||||||
|
|
||||||
+ '(text)' # Binary-search options
|
+ '(text)' # Binary-search options
|
||||||
{-a,--text}'[search binary files as if they were text]'
|
{-a,--text}'[search binary files as if they were text]'
|
||||||
$no"--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-files)'{-j+,--threads=}'[specify approximate number of threads to use]:number of threads'
|
||||||
|
|
||||||
|
+ '(trim)' # Trim options
|
||||||
|
'--trim[trim any ASCII whitespace prefix from each line]'
|
||||||
|
$no"--no-trim[don't trim ASCII whitespace prefix from each line]"
|
||||||
|
|
||||||
+ type # Type options
|
+ type # Type options
|
||||||
'*'{-t+,--type=}'[only search files matching specified type]: :_rg_types'
|
'*'{-t+,--type=}'[only search files matching specified type]: :_rg_types'
|
||||||
'*--type-add=[add new glob for specified file type]: :->typespec'
|
'*--type-add=[add new glob for specified file type]: :->typespec'
|
||||||
@@ -198,7 +230,6 @@ _rg() {
|
|||||||
'--context-separator=[specify string used to separate non-continuous context lines in output]:separator'
|
'--context-separator=[specify string used to separate non-continuous context lines in output]:separator'
|
||||||
'--debug[show debug messages]'
|
'--debug[show debug messages]'
|
||||||
'--dfa-size-limit=[specify upper size limit of generated DFA]:DFA size (bytes)'
|
'--dfa-size-limit=[specify upper size limit of generated DFA]:DFA size (bytes)'
|
||||||
'(-E --encoding)'{-E+,--encoding=}'[specify text encoding of files to search]: :_rg_encodings'
|
|
||||||
"(1 stats)--files[show each file that would be searched (but don't search)]"
|
"(1 stats)--files[show each file that would be searched (but don't search)]"
|
||||||
'*--ignore-file=[specify additional ignore file]:ignore file:_files'
|
'*--ignore-file=[specify additional ignore file]:ignore file:_files'
|
||||||
'(-v --invert-match)'{-v,--invert-match}'[invert matching]'
|
'(-v --invert-match)'{-v,--invert-match}'[invert matching]'
|
||||||
@@ -331,6 +362,157 @@ _rg_types() {
|
|||||||
|
|
||||||
_rg "$@"
|
_rg "$@"
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# ZSH COMPLETION REFERENCE
|
||||||
|
#
|
||||||
|
# For the convenience of developers who aren't especially familiar with zsh
|
||||||
|
# completion functions, a brief reference guide follows. This is in no way
|
||||||
|
# comprehensive; it covers just enough of the basic structure, syntax, and
|
||||||
|
# conventions to help someone make simple changes like adding new options. For
|
||||||
|
# more complete documentation regarding zsh completion functions, please see the
|
||||||
|
# following:
|
||||||
|
#
|
||||||
|
# * http://zsh.sourceforge.net/Doc/Release/Completion-System.html
|
||||||
|
# * https://github.com/zsh-users/zsh/blob/master/Etc/completion-style-guide
|
||||||
|
#
|
||||||
|
# OVERVIEW
|
||||||
|
#
|
||||||
|
# Most zsh completion functions are defined in terms of `_arguments`, which is a
|
||||||
|
# shell function that takes a series of argument specifications. The specs for
|
||||||
|
# `rg` are stored in an array, which is common for more complex functions; the
|
||||||
|
# elements of the array are passed to `_arguments` on invocation.
|
||||||
|
#
|
||||||
|
# ARGUMENT-SPECIFICATION SYNTAX
|
||||||
|
#
|
||||||
|
# The following is a contrived example of the argument specs for a simple tool:
|
||||||
|
#
|
||||||
|
# '(: * -)'{-h,--help}'[display help information]'
|
||||||
|
# '(-q -v --quiet --verbose)'{-q,--quiet}'[decrease output verbosity]'
|
||||||
|
# '!(-q -v --quiet --verbose)--silent'
|
||||||
|
# '(-q -v --quiet --verbose)'{-v,--verbose}'[increase output verbosity]'
|
||||||
|
# '--color=[specify when to use colors]:when:(always never auto)'
|
||||||
|
# '*:example file:_files'
|
||||||
|
#
|
||||||
|
# Although there may appear to be six specs here, there are actually nine; we
|
||||||
|
# use brace expansion to combine specs for options that go by multiple names,
|
||||||
|
# like `-q` and `--quiet`. This is customary, and ties in with the fact that zsh
|
||||||
|
# merges completion possibilities together when they have the same description.
|
||||||
|
#
|
||||||
|
# The first line defines the option `-h`/`--help`. With most tools, it isn't
|
||||||
|
# useful to complete anything after `--help` because it effectively overrides
|
||||||
|
# all others; the `(: * -)` at the beginning of the spec tells zsh not to
|
||||||
|
# complete any other operands (`:` and `*`) or options (`-`) after this one has
|
||||||
|
# been used. The `[...]` at the end associates a description with `-h`/`--help`;
|
||||||
|
# as mentioned, zsh will see the identical descriptions and merge these options
|
||||||
|
# together when offering completion possibilities.
|
||||||
|
#
|
||||||
|
# The next line defines `-q`/`--quiet`. Here we don't want to suppress further
|
||||||
|
# completions entirely, but we don't want to offer `-q` if `--quiet` has been
|
||||||
|
# given (since they do the same thing), nor do we want to offer `-v` (since it
|
||||||
|
# doesn't make sense to be quiet and verbose at the same time). We don't need to
|
||||||
|
# tell zsh not to offer `--quiet` a second time, since that's the default
|
||||||
|
# behaviour, but since this line expands to two specs describing `-q` *and*
|
||||||
|
# `--quiet` we do need to explicitly list all of them here.
|
||||||
|
#
|
||||||
|
# The next line defines a hidden option `--silent` — maybe it's a deprecated
|
||||||
|
# synonym for `--quiet`. The leading `!` indicates that zsh shouldn't offer this
|
||||||
|
# option during completion. The benefit of providing a spec for an option that
|
||||||
|
# shouldn't be completed is that, if someone *does* use it, we can correctly
|
||||||
|
# suppress completion of other options afterwards.
|
||||||
|
#
|
||||||
|
# The next line defines `-v`/`--verbose`; this works just like `-q`/`--quiet`.
|
||||||
|
#
|
||||||
|
# The next line defines `--color`. In this example, `--color` doesn't have a
|
||||||
|
# corresponding short option, so we don't need to use brace expansion. Further,
|
||||||
|
# there are no other options it's exclusive with (just itself), so we don't need
|
||||||
|
# to define those at the beginning. However, it does take a mandatory argument.
|
||||||
|
# The `=` at the end of `--color=` indicates that the argument may appear either
|
||||||
|
# like `--color always` or like `--color=always`; this is how most GNU-style
|
||||||
|
# command-line tools work. The corresponding short option would normally use `+`
|
||||||
|
# — for example, `-c+` would allow either `-c always` or `-calways`. For this
|
||||||
|
# option, the arguments are known ahead of time, so we can simply list them in
|
||||||
|
# parentheses at the end (`when` is used as the description for the argument).
|
||||||
|
#
|
||||||
|
# The last line defines an operand (a non-option argument). In this example, the
|
||||||
|
# operand can be used any number of times (the leading `*`), and it should be a
|
||||||
|
# file path, so we tell zsh to call the `_files` function to complete it. The
|
||||||
|
# `example file` in the middle is the description to use for this operand; we
|
||||||
|
# could use a space instead to accept the default provided by `_files`.
|
||||||
|
#
|
||||||
|
# GROUPING ARGUMENT SPECIFICATIONS
|
||||||
|
#
|
||||||
|
# Newer versions of zsh support grouping argument specs together. All specs
|
||||||
|
# following a `+` and then a group name are considered to be members of the
|
||||||
|
# named group. Grouping is useful mostly for organisational purposes; it makes
|
||||||
|
# the relationship between different options more obvious, and makes it easier
|
||||||
|
# to specify exclusions.
|
||||||
|
#
|
||||||
|
# We could rewrite our example above using grouping as follows:
|
||||||
|
#
|
||||||
|
# '(: * -)'{-h,--help}'[display help information]'
|
||||||
|
# '--color=[specify when to use colors]:when:(always never auto)'
|
||||||
|
# '*:example file:_files'
|
||||||
|
# + '(verbosity)'
|
||||||
|
# {-q,--quiet}'[decrease output verbosity]'
|
||||||
|
# '!--silent'
|
||||||
|
# {-v,--verbose}'[increase output verbosity]'
|
||||||
|
#
|
||||||
|
# Here we take advantage of a useful feature of spec grouping — when the group
|
||||||
|
# name is surrounded by parentheses, as in `(verbosity)`, it tells zsh that all
|
||||||
|
# of the options in that group are exclusive with each other. As a result, we
|
||||||
|
# don't need to manually list out the exclusions at the beginning of each
|
||||||
|
# option.
|
||||||
|
#
|
||||||
|
# Groups can also be referred to by name in other argument specs; for example:
|
||||||
|
#
|
||||||
|
# '(xyz)--aaa' '*: :_files'
|
||||||
|
# + xyz --xxx --yyy --zzz
|
||||||
|
#
|
||||||
|
# Here we use the group name `xyz` to tell zsh that `--xxx`, `--yyy`, and
|
||||||
|
# `--zzz` are not to be completed after `--aaa`. This makes the exclusion list
|
||||||
|
# much more compact and reusable.
|
||||||
|
#
|
||||||
|
# CONVENTIONS
|
||||||
|
#
|
||||||
|
# zsh completion functions generally adhere to the following conventions:
|
||||||
|
#
|
||||||
|
# * Use two spaces for indentation
|
||||||
|
# * Combine specs for options with different names using brace expansion
|
||||||
|
# * In combined specs, list the short option first (as in `{-a,--text}`)
|
||||||
|
# * Use `+` or `=` as described above for options that take arguments
|
||||||
|
# * Provide a description for all options, option-arguments, and operands
|
||||||
|
# * Capitalise/punctuate argument descriptions as phrases, not complete
|
||||||
|
# sentences — 'display help information', never 'Display help information.'
|
||||||
|
# (but still capitalise acronyms and proper names)
|
||||||
|
# * Write argument descriptions as verb phrases — 'display x', 'enable y',
|
||||||
|
# 'use z'
|
||||||
|
# * Word descriptions to make it clear when an option expects an argument;
|
||||||
|
# usually this is done with the word 'specify', as in 'specify x' or
|
||||||
|
# 'use specified x')
|
||||||
|
# * Write argument descriptions as tersely as possible — for example, articles
|
||||||
|
# like 'a' and 'the' should be omitted unless it would be confusing
|
||||||
|
#
|
||||||
|
# Other conventions currently used by this function:
|
||||||
|
#
|
||||||
|
# * Order argument specs alphabetically by group name, then option name
|
||||||
|
# * Group options that are directly related, mutually exclusive, or frequently
|
||||||
|
# referenced by other argument specs
|
||||||
|
# * Use only characters in the set [a-z0-9_-] in group names
|
||||||
|
# * Order exclusion lists as follows: short options, long options, groups
|
||||||
|
# * Use American English in descriptions
|
||||||
|
# * Use 'don't' in descriptions instead of 'do not'
|
||||||
|
# * Word descriptions for related options as similarly as possible. For example,
|
||||||
|
# `--foo[enable foo]` and `--no-foo[disable foo]`, or `--foo[use foo]` and
|
||||||
|
# `--no-foo[don't use foo]`
|
||||||
|
# * Word descriptions to make it clear when an option only makes sense with
|
||||||
|
# another option, usually by adding '(with -x)' to the end
|
||||||
|
# * Don't quote strings or variables unnecessarily. When quotes are required,
|
||||||
|
# prefer single-quotes to double-quotes
|
||||||
|
# * Prefix option specs with `$no` when the option serves only to negate the
|
||||||
|
# behaviour of another option that must be provided explicitly by the user.
|
||||||
|
# This prevents rarely used options from cluttering up the completion menu
|
||||||
|
################################################################################
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Copyright (c) 2011 Github zsh-users - http://github.com/zsh-users
|
# Copyright (c) 2011 Github zsh-users - http://github.com/zsh-users
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
|
@@ -4,7 +4,7 @@ Cross platform single glob and glob set matching. Glob set matching is the
|
|||||||
process of matching one or more glob patterns against a single candidate path
|
process of matching one or more glob patterns against a single candidate path
|
||||||
simultaneously, and returning all of the globs that matched.
|
simultaneously, and returning all of the globs that matched.
|
||||||
|
|
||||||
[](https://travis-ci.org/BurntSushi/ripgrep)
|
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||||
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||||
[](https://crates.io/crates/globset)
|
[](https://crates.io/crates/globset)
|
||||||
|
|
||||||
|
@@ -470,7 +470,6 @@ impl GlobSetBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new pattern to this set.
|
/// Add a new pattern to this set.
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn add(&mut self, pat: Glob) -> &mut GlobSetBuilder {
|
pub fn add(&mut self, pat: Glob) -> &mut GlobSetBuilder {
|
||||||
self.pats.push(pat);
|
self.pats.push(pat);
|
||||||
self
|
self
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-matcher"
|
name = "grep-matcher"
|
||||||
version = "0.0.1" #:version
|
version = "0.1.0" #: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.
|
||||||
|
@@ -1,4 +1,36 @@
|
|||||||
grep
|
grep-matcher
|
||||||
----
|
------------
|
||||||
This is a *library* that provides grep-style line-by-line regex searching (with
|
This crate provides a low level interface for describing regular expression
|
||||||
comparable performance to `grep` itself).
|
matchers. The `grep` crate uses this interface in order to make the regex
|
||||||
|
engine it uses pluggable.
|
||||||
|
|
||||||
|
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||||
|
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||||
|
[](https://crates.io/crates/grep-matcher)
|
||||||
|
|
||||||
|
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
[https://docs.rs/grep-matcher](https://docs.rs/grep-matcher)
|
||||||
|
|
||||||
|
**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-matcher = "0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
and this to your crate root:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate grep_matcher;
|
||||||
|
```
|
||||||
|
@@ -1,5 +1,39 @@
|
|||||||
/*!
|
/*!
|
||||||
An interface for regular expressions, with a focus on line oriented search.
|
This crate provides an interface for regular expressions, with a focus on line
|
||||||
|
oriented search. The purpose of this crate is to provide a low level matching
|
||||||
|
interface that permits any kind of substring or regex implementation to power
|
||||||
|
the search routines provided by the
|
||||||
|
[`grep-searcher`](https://docs.rs/grep-searcher)
|
||||||
|
crate.
|
||||||
|
|
||||||
|
The primary thing provided by this crate is the
|
||||||
|
[`Matcher`](trait.Matcher.html)
|
||||||
|
trait. The trait defines an abstract interface for text search. It is robust
|
||||||
|
enough to support everything from basic substring search all the way to
|
||||||
|
arbitrarily complex regular expression implementations without sacrificing
|
||||||
|
performance.
|
||||||
|
|
||||||
|
A key design decision made in this crate is the use of *internal iteration*,
|
||||||
|
or otherwise known as the "push" model of searching. In this paradigm,
|
||||||
|
implementations of the `Matcher` trait will drive search and execute callbacks
|
||||||
|
provided by the caller when a match is found. This is in contrast to the
|
||||||
|
usual style of *external iteration* (the "pull" model) found throughout the
|
||||||
|
Rust ecosystem. There are two primary reasons why internal iteration was
|
||||||
|
chosen:
|
||||||
|
|
||||||
|
* Some search implementations may themselves require internal iteration.
|
||||||
|
Converting an internal iterator to an external iterator can be non-trivial
|
||||||
|
and sometimes even practically impossible.
|
||||||
|
* Rust's type system isn't quite expressive enough to write a generic interface
|
||||||
|
using external iteration without giving something else up (namely, ease of
|
||||||
|
use and/or performance).
|
||||||
|
|
||||||
|
In other words, internal iteration was chosen because it is the lowest common
|
||||||
|
denominator and because it is probably the least bad way of expressing the
|
||||||
|
interface in today's Rust. As a result, this trait isn't specifically intended
|
||||||
|
for everyday use, although, you might find it to be a happy price to pay if you
|
||||||
|
want to write code that is generic over multiple different regex
|
||||||
|
implementations.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
@@ -186,6 +220,7 @@ enum LineTerminatorImp {
|
|||||||
|
|
||||||
impl LineTerminator {
|
impl LineTerminator {
|
||||||
/// Return a new single-byte line terminator. Any byte is valid.
|
/// Return a new single-byte line terminator. Any byte is valid.
|
||||||
|
#[inline]
|
||||||
pub fn byte(byte: u8) -> LineTerminator {
|
pub fn byte(byte: u8) -> LineTerminator {
|
||||||
LineTerminator(LineTerminatorImp::Byte([byte]))
|
LineTerminator(LineTerminatorImp::Byte([byte]))
|
||||||
}
|
}
|
||||||
@@ -194,11 +229,13 @@ impl LineTerminator {
|
|||||||
///
|
///
|
||||||
/// When this option is used, consumers may generally treat a lone `\n` as
|
/// When this option is used, consumers may generally treat a lone `\n` as
|
||||||
/// a line terminator in addition to `\r\n`.
|
/// a line terminator in addition to `\r\n`.
|
||||||
|
#[inline]
|
||||||
pub fn crlf() -> LineTerminator {
|
pub fn crlf() -> LineTerminator {
|
||||||
LineTerminator(LineTerminatorImp::CRLF)
|
LineTerminator(LineTerminatorImp::CRLF)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this line terminator is CRLF.
|
/// Returns true if and only if this line terminator is CRLF.
|
||||||
|
#[inline]
|
||||||
pub fn is_crlf(&self) -> bool {
|
pub fn is_crlf(&self) -> bool {
|
||||||
self.0 == LineTerminatorImp::CRLF
|
self.0 == LineTerminatorImp::CRLF
|
||||||
}
|
}
|
||||||
@@ -208,6 +245,7 @@ impl LineTerminator {
|
|||||||
/// If the line terminator is CRLF, then this returns `\n`. This is
|
/// If the line terminator is CRLF, then this returns `\n`. This is
|
||||||
/// useful for routines that, for example, find line boundaries by treating
|
/// useful for routines that, for example, find line boundaries by treating
|
||||||
/// `\n` as a line terminator even when it isn't preceded by `\r`.
|
/// `\n` as a line terminator even when it isn't preceded by `\r`.
|
||||||
|
#[inline]
|
||||||
pub fn as_byte(&self) -> u8 {
|
pub fn as_byte(&self) -> u8 {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
LineTerminatorImp::Byte(array) => array[0],
|
LineTerminatorImp::Byte(array) => array[0],
|
||||||
@@ -221,6 +259,7 @@ impl LineTerminator {
|
|||||||
/// `CRLF`, in which case, it returns `\r\n`.
|
/// `CRLF`, in which case, it returns `\r\n`.
|
||||||
///
|
///
|
||||||
/// The slice returned is guaranteed to have length at least `1`.
|
/// The slice returned is guaranteed to have length at least `1`.
|
||||||
|
#[inline]
|
||||||
pub fn as_bytes(&self) -> &[u8] {
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
LineTerminatorImp::Byte(ref array) => array,
|
LineTerminatorImp::Byte(ref array) => array,
|
||||||
@@ -230,6 +269,7 @@ impl LineTerminator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LineTerminator {
|
impl Default for LineTerminator {
|
||||||
|
#[inline]
|
||||||
fn default() -> LineTerminator {
|
fn default() -> LineTerminator {
|
||||||
LineTerminator::byte(b'\n')
|
LineTerminator::byte(b'\n')
|
||||||
}
|
}
|
||||||
@@ -324,12 +364,12 @@ impl ByteSet {
|
|||||||
///
|
///
|
||||||
/// Principally, this trait provides a way to access capturing groups
|
/// Principally, this trait provides a way to access capturing groups
|
||||||
/// in a uniform way that does not require any specific representation.
|
/// in a uniform way that does not require any specific representation.
|
||||||
/// Namely, differ matcher implementations may require different in-memory
|
/// Namely, different matcher implementations may require different in-memory
|
||||||
/// representations of capturing groups. This trait permits matchers to
|
/// representations of capturing groups. This trait permits matchers to
|
||||||
/// maintain their specific in-memory representation.
|
/// maintain their specific in-memory representation.
|
||||||
///
|
///
|
||||||
/// Note that this trait explicitly does not provide a way to construct a new
|
/// Note that this trait explicitly does not provide a way to construct a new
|
||||||
/// captures value. Instead, it is the responsibility of a `Matcher` to build
|
/// capture value. Instead, it is the responsibility of a `Matcher` to build
|
||||||
/// one, which might require knowledge of the matcher's internal implementation
|
/// one, which might require knowledge of the matcher's internal implementation
|
||||||
/// details.
|
/// details.
|
||||||
pub trait Captures {
|
pub trait Captures {
|
||||||
@@ -426,7 +466,7 @@ impl Captures for NoCaptures {
|
|||||||
/// This error type implements the `std::error::Error` and `fmt::Display`
|
/// This error type implements the `std::error::Error` and `fmt::Display`
|
||||||
/// traits for use in matcher implementations that can never produce errors.
|
/// traits for use in matcher implementations that can never produce errors.
|
||||||
///
|
///
|
||||||
/// The `fmt::Display` impl for this type panics.
|
/// The `fmt::Debug` and `fmt::Display` impls for this type panics.
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub struct NoError(());
|
pub struct NoError(());
|
||||||
|
|
||||||
@@ -463,6 +503,20 @@ pub enum LineMatchKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A matcher defines an interface for regular expression implementations.
|
/// A matcher defines an interface for regular expression implementations.
|
||||||
|
///
|
||||||
|
/// While this trait is large, there are only two required methods that
|
||||||
|
/// implementors must provide: `find_at` and `new_captures`. If captures
|
||||||
|
/// aren't supported by your implementation, then `new_captures` can be
|
||||||
|
/// implemented with
|
||||||
|
/// [`NoCaptures`](struct.NoCaptures.html). If your implementation does support
|
||||||
|
/// capture groups, then you should also implement the other capture related
|
||||||
|
/// methods, as dictated by the documentation. Crucially, this includes
|
||||||
|
/// `captures_at`.
|
||||||
|
///
|
||||||
|
/// The rest of the methods on this trait provide default implementations on
|
||||||
|
/// top of `find_at` and `new_captures`. It is not uncommon for implementations
|
||||||
|
/// to be able to provide faster variants of some methods; in those cases,
|
||||||
|
/// simply override the default implementation.
|
||||||
pub trait Matcher {
|
pub trait Matcher {
|
||||||
/// The concrete type of capturing groups used for this matcher.
|
/// The concrete type of capturing groups used for this matcher.
|
||||||
///
|
///
|
||||||
|
17
grep-pcre2/Cargo.toml
Normal file
17
grep-pcre2/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "grep-pcre2"
|
||||||
|
version = "0.1.0" #:version
|
||||||
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
|
description = """
|
||||||
|
Use PCRE2 with the 'grep' crate.
|
||||||
|
"""
|
||||||
|
documentation = "https://docs.rs/grep-pcre2"
|
||||||
|
homepage = "https://github.com/BurntSushi/ripgrep"
|
||||||
|
repository = "https://github.com/BurntSushi/ripgrep"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["regex", "grep", "pcre", "backreference", "look"]
|
||||||
|
license = "Unlicense/MIT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
grep-matcher = { version = "0.1.0", path = "../grep-matcher" }
|
||||||
|
pcre2 = "0.1"
|
21
grep-pcre2/LICENSE-MIT
Normal file
21
grep-pcre2/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.
|
39
grep-pcre2/README.md
Normal file
39
grep-pcre2/README.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
grep-pcre2
|
||||||
|
----------
|
||||||
|
The `grep-pcre2` crate provides an implementation of the `Matcher` trait from
|
||||||
|
the `grep-matcher` crate. This implementation permits PCRE2 to be used in the
|
||||||
|
`grep` crate for fast line oriented searching.
|
||||||
|
|
||||||
|
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||||
|
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||||
|
[](https://crates.io/crates/grep-pcre2)
|
||||||
|
|
||||||
|
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
[https://docs.rs/grep-pcre2](https://docs.rs/grep-pcre2)
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
If you're looking to just use PCRE2 from Rust, then you probably want the
|
||||||
|
[`pcre2`](https://docs.rs/pcre2)
|
||||||
|
crate, which provide high level safe bindings to PCRE2.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Add this to your `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
grep-pcre2 = "0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
and this to your crate root:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate grep_pcre2;
|
||||||
|
```
|
24
grep-pcre2/UNLICENSE
Normal file
24
grep-pcre2/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/>
|
59
grep-pcre2/src/error.rs
Normal file
59
grep-pcre2/src/error.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// An error that can occur in this crate.
|
||||||
|
///
|
||||||
|
/// Generally, this error corresponds to problems building a regular
|
||||||
|
/// expression, whether it's in parsing, compilation or a problem with
|
||||||
|
/// guaranteeing a configured optimization.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Error {
|
||||||
|
kind: ErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub(crate) fn regex<E: error::Error>(err: E) -> Error {
|
||||||
|
Error { kind: ErrorKind::Regex(err.to_string()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the kind of this error.
|
||||||
|
pub fn kind(&self) -> &ErrorKind {
|
||||||
|
&self.kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of an error that can occur.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
/// An error that occurred as a result of parsing a regular expression.
|
||||||
|
/// This can be a syntax error or an error that results from attempting to
|
||||||
|
/// compile a regular expression that is too big.
|
||||||
|
///
|
||||||
|
/// The string here is the underlying error converted to a string.
|
||||||
|
Regex(String),
|
||||||
|
/// Hints that destructuring should not be exhaustive.
|
||||||
|
///
|
||||||
|
/// This enum may grow additional variants, so this makes sure clients
|
||||||
|
/// don't count on exhaustive matching. (Otherwise, adding a new variant
|
||||||
|
/// could break existing code.)
|
||||||
|
#[doc(hidden)]
|
||||||
|
__Nonexhaustive,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match self.kind {
|
||||||
|
ErrorKind::Regex(_) => "regex error",
|
||||||
|
ErrorKind::__Nonexhaustive => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self.kind {
|
||||||
|
ErrorKind::Regex(ref s) => write!(f, "{}", s),
|
||||||
|
ErrorKind::__Nonexhaustive => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
grep-pcre2/src/lib.rs
Normal file
15
grep-pcre2/src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/*!
|
||||||
|
An implementation of `grep-matcher`'s `Matcher` trait for
|
||||||
|
[PCRE2](https://www.pcre.org/).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
extern crate grep_matcher;
|
||||||
|
extern crate pcre2;
|
||||||
|
|
||||||
|
pub use error::{Error, ErrorKind};
|
||||||
|
pub use matcher::{RegexCaptures, RegexMatcher, RegexMatcherBuilder};
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
mod matcher;
|
425
grep-pcre2/src/matcher.rs
Normal file
425
grep-pcre2/src/matcher.rs
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use grep_matcher::{Captures, Match, Matcher};
|
||||||
|
use pcre2::bytes::{CaptureLocations, Regex, RegexBuilder};
|
||||||
|
|
||||||
|
use error::Error;
|
||||||
|
|
||||||
|
/// A builder for configuring the compilation of a PCRE2 regex.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RegexMatcherBuilder {
|
||||||
|
builder: RegexBuilder,
|
||||||
|
case_smart: bool,
|
||||||
|
word: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegexMatcherBuilder {
|
||||||
|
/// Create a new matcher builder with a default configuration.
|
||||||
|
pub fn new() -> RegexMatcherBuilder {
|
||||||
|
RegexMatcherBuilder {
|
||||||
|
builder: RegexBuilder::new(),
|
||||||
|
case_smart: false,
|
||||||
|
word: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile the given pattern into a PCRE matcher using the current
|
||||||
|
/// configuration.
|
||||||
|
///
|
||||||
|
/// If there was a problem compiling the pattern, then an error is
|
||||||
|
/// returned.
|
||||||
|
pub fn build(&self, pattern: &str) -> Result<RegexMatcher, Error> {
|
||||||
|
let mut builder = self.builder.clone();
|
||||||
|
if self.case_smart && !has_uppercase_literal(pattern) {
|
||||||
|
builder.caseless(true);
|
||||||
|
}
|
||||||
|
let res =
|
||||||
|
if self.word {
|
||||||
|
let pattern = format!(r"(?<!\w)(?:{})(?!\w)", pattern);
|
||||||
|
builder.build(&pattern)
|
||||||
|
} else {
|
||||||
|
builder.build(pattern)
|
||||||
|
};
|
||||||
|
res.map_err(Error::regex).map(|regex| {
|
||||||
|
let mut names = HashMap::new();
|
||||||
|
for (i, name) in regex.capture_names().iter().enumerate() {
|
||||||
|
if let Some(ref name) = *name {
|
||||||
|
names.insert(name.to_string(), i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RegexMatcher { regex, names }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables case insensitive matching.
|
||||||
|
///
|
||||||
|
/// If the `utf` option is also set, then Unicode case folding is used
|
||||||
|
/// to determine case insensitivity. When the `utf` option is not set,
|
||||||
|
/// then only standard ASCII case insensitivity is considered.
|
||||||
|
///
|
||||||
|
/// This option corresponds to the `i` flag.
|
||||||
|
pub fn caseless(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
|
self.builder.caseless(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether to enable "smart case" or not.
|
||||||
|
///
|
||||||
|
/// When smart case is enabled, the builder will automatically enable
|
||||||
|
/// case insensitive matching based on how the pattern is written. Namely,
|
||||||
|
/// case insensitive mode is enabled when both of the following things
|
||||||
|
/// are believed to be true:
|
||||||
|
///
|
||||||
|
/// 1. The pattern contains at least one literal character. For example,
|
||||||
|
/// `a\w` contains a literal (`a`) but `\w` does not.
|
||||||
|
/// 2. Of the literals in the pattern, none of them are considered to be
|
||||||
|
/// uppercase according to Unicode. For example, `foo\pL` has no
|
||||||
|
/// uppercase literals but `Foo\pL` does.
|
||||||
|
///
|
||||||
|
/// Note that the implementation of this is not perfect. Namely, `\p{Ll}`
|
||||||
|
/// will prevent case insensitive matching even though it is part of a meta
|
||||||
|
/// sequence. This bug will probably never be fixed.
|
||||||
|
pub fn case_smart(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
|
self.case_smart = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables "dot all" matching.
|
||||||
|
///
|
||||||
|
/// When enabled, the `.` metacharacter in the pattern matches any
|
||||||
|
/// character, include `\n`. When disabled (the default), `.` will match
|
||||||
|
/// any character except for `\n`.
|
||||||
|
///
|
||||||
|
/// This option corresponds to the `s` flag.
|
||||||
|
pub fn dotall(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
|
self.builder.dotall(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable "extended" mode in the pattern, where whitespace is ignored.
|
||||||
|
///
|
||||||
|
/// This option corresponds to the `x` flag.
|
||||||
|
pub fn extended(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
|
self.builder.extended(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable multiline matching mode.
|
||||||
|
///
|
||||||
|
/// When enabled, the `^` and `$` anchors will match both at the beginning
|
||||||
|
/// and end of a subject string, in addition to matching at the start of
|
||||||
|
/// a line and the end of a line. When disabled, the `^` and `$` anchors
|
||||||
|
/// will only match at the beginning and end of a subject string.
|
||||||
|
///
|
||||||
|
/// This option corresponds to the `m` flag.
|
||||||
|
pub fn multi_line(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
|
self.builder.multi_line(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable matching of CRLF as a line terminator.
|
||||||
|
///
|
||||||
|
/// When enabled, anchors such as `^` and `$` will match any of the
|
||||||
|
/// following as a line terminator: `\r`, `\n` or `\r\n`.
|
||||||
|
///
|
||||||
|
/// This is disabled by default, in which case, only `\n` is recognized as
|
||||||
|
/// a line terminator.
|
||||||
|
pub fn crlf(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
|
self.builder.crlf(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Require that all matches occur on word boundaries.
|
||||||
|
///
|
||||||
|
/// Enabling this option is subtly different than putting `\b` assertions
|
||||||
|
/// on both sides of your pattern. In particular, a `\b` assertion requires
|
||||||
|
/// that one side of it match a word character while the other match a
|
||||||
|
/// non-word character. This option, in contrast, merely requires that
|
||||||
|
/// one side match a non-word character.
|
||||||
|
///
|
||||||
|
/// For example, `\b-2\b` will not match `foo -2 bar` since `-` is not a
|
||||||
|
/// word character. However, `-2` with this `word` option enabled will
|
||||||
|
/// match the `-2` in `foo -2 bar`.
|
||||||
|
pub fn word(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
|
self.word = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable Unicode matching mode.
|
||||||
|
///
|
||||||
|
/// When enabled, the following patterns become Unicode aware: `\b`, `\B`,
|
||||||
|
/// `\d`, `\D`, `\s`, `\S`, `\w`, `\W`.
|
||||||
|
///
|
||||||
|
/// When set, this implies UTF matching mode. It is not possible to enable
|
||||||
|
/// Unicode matching mode without enabling UTF matching mode.
|
||||||
|
///
|
||||||
|
/// This is disabled by default.
|
||||||
|
pub fn ucp(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
|
self.builder.ucp(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable UTF matching mode.
|
||||||
|
///
|
||||||
|
/// When enabled, characters are treated as sequences of code units that
|
||||||
|
/// make up a single codepoint instead of as single bytes. For example,
|
||||||
|
/// this will cause `.` to match any single UTF-8 encoded codepoint, where
|
||||||
|
/// as when this is disabled, `.` will any single byte (except for `\n` in
|
||||||
|
/// both cases, unless "dot all" mode is enabled).
|
||||||
|
///
|
||||||
|
/// Note that when UTF matching mode is enabled, every search performed
|
||||||
|
/// will do a UTF-8 validation check, which can impact performance. The
|
||||||
|
/// UTF-8 check can be disabled via the `disable_utf_check` option, but it
|
||||||
|
/// is undefined behavior to enable UTF matching mode and search invalid
|
||||||
|
/// UTF-8.
|
||||||
|
///
|
||||||
|
/// This is disabled by default.
|
||||||
|
pub fn utf(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
|
self.builder.utf(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When UTF matching mode is enabled, this will disable the UTF checking
|
||||||
|
/// that PCRE2 will normally perform automatically. If UTF matching mode
|
||||||
|
/// is not enabled, then this has no effect.
|
||||||
|
///
|
||||||
|
/// UTF checking is enabled by default when UTF matching mode is enabled.
|
||||||
|
/// If UTF matching mode is enabled and UTF checking is enabled, then PCRE2
|
||||||
|
/// will return an error if you attempt to search a subject string that is
|
||||||
|
/// not valid UTF-8.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// It is undefined behavior to disable the UTF check in UTF matching mode
|
||||||
|
/// and search a subject string that is not valid UTF-8. When the UTF check
|
||||||
|
/// is disabled, callers must guarantee that the subject string is valid
|
||||||
|
/// UTF-8.
|
||||||
|
pub unsafe fn disable_utf_check(&mut self) -> &mut RegexMatcherBuilder {
|
||||||
|
self.builder.disable_utf_check();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable PCRE2's JIT.
|
||||||
|
///
|
||||||
|
/// This generally speeds up matching quite a bit. The downside is that it
|
||||||
|
/// can increase the time it takes to compile a pattern.
|
||||||
|
///
|
||||||
|
/// This is disabled by default.
|
||||||
|
pub fn jit(&mut self, yes: bool) -> &mut RegexMatcherBuilder {
|
||||||
|
self.builder.jit(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An implementation of the `Matcher` trait using PCRE2.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RegexMatcher {
|
||||||
|
regex: Regex,
|
||||||
|
names: HashMap<String, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegexMatcher {
|
||||||
|
/// Create a new matcher from the given pattern using the default
|
||||||
|
/// configuration.
|
||||||
|
pub fn new(pattern: &str) -> Result<RegexMatcher, Error> {
|
||||||
|
RegexMatcherBuilder::new().build(pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Matcher for RegexMatcher {
|
||||||
|
type Captures = RegexCaptures;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn find_at(
|
||||||
|
&self,
|
||||||
|
haystack: &[u8],
|
||||||
|
at: usize,
|
||||||
|
) -> Result<Option<Match>, Error> {
|
||||||
|
Ok(self.regex
|
||||||
|
.find_at(haystack, at)
|
||||||
|
.map_err(Error::regex)?
|
||||||
|
.map(|m| Match::new(m.start(), m.end())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_captures(&self) -> Result<RegexCaptures, Error> {
|
||||||
|
Ok(RegexCaptures::new(self.regex.capture_locations()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capture_count(&self) -> usize {
|
||||||
|
self.regex.captures_len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capture_index(&self, name: &str) -> Option<usize> {
|
||||||
|
self.names.get(name).map(|i| *i)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_find_iter<F, E>(
|
||||||
|
&self,
|
||||||
|
haystack: &[u8],
|
||||||
|
mut matched: F,
|
||||||
|
) -> Result<Result<(), E>, Error>
|
||||||
|
where F: FnMut(Match) -> Result<bool, E>
|
||||||
|
{
|
||||||
|
for result in self.regex.find_iter(haystack) {
|
||||||
|
let m = result.map_err(Error::regex)?;
|
||||||
|
match matched(Match::new(m.start(), m.end())) {
|
||||||
|
Ok(true) => continue,
|
||||||
|
Ok(false) => return Ok(Ok(())),
|
||||||
|
Err(err) => return Ok(Err(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn captures_at(
|
||||||
|
&self,
|
||||||
|
haystack: &[u8],
|
||||||
|
at: usize,
|
||||||
|
caps: &mut RegexCaptures,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
|
Ok(self.regex
|
||||||
|
.captures_read_at(&mut caps.locs, haystack, at)
|
||||||
|
.map_err(Error::regex)?
|
||||||
|
.is_some())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the match offsets of each capturing group in a match.
|
||||||
|
///
|
||||||
|
/// The first, or `0`th capture group, always corresponds to the entire match
|
||||||
|
/// and is guaranteed to be present when a match occurs. The next capture
|
||||||
|
/// group, at index `1`, corresponds to the first capturing group in the regex,
|
||||||
|
/// ordered by the position at which the left opening parenthesis occurs.
|
||||||
|
///
|
||||||
|
/// Note that not all capturing groups are guaranteed to be present in a match.
|
||||||
|
/// For example, in the regex, `(?P<foo>\w)|(?P<bar>\W)`, only one of `foo`
|
||||||
|
/// or `bar` will ever be set in any given match.
|
||||||
|
///
|
||||||
|
/// In order to access a capture group by name, you'll need to first find the
|
||||||
|
/// index of the group using the corresponding matcher's `capture_index`
|
||||||
|
/// method, and then use that index with `RegexCaptures::get`.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RegexCaptures {
|
||||||
|
/// Where the locations are stored.
|
||||||
|
locs: CaptureLocations,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Captures for RegexCaptures {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.locs.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, i: usize) -> Option<Match> {
|
||||||
|
self.locs.get(i).map(|(s, e)| Match::new(s, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegexCaptures {
|
||||||
|
pub(crate) fn new(locs: CaptureLocations) -> RegexCaptures {
|
||||||
|
RegexCaptures { locs }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine whether the pattern contains an uppercase character which should
|
||||||
|
/// negate the effect of the smart-case option.
|
||||||
|
///
|
||||||
|
/// Ideally we would be able to check the AST in order to correctly handle
|
||||||
|
/// things like '\p{Ll}' and '\p{Lu}' (which should be treated as explicitly
|
||||||
|
/// cased), but PCRE doesn't expose enough details for that kind of analysis.
|
||||||
|
/// For now, our 'good enough' solution is to simply perform a semi-naïve
|
||||||
|
/// scan of the input pattern and ignore all characters following a '\'. The
|
||||||
|
/// This at least lets us support the most common cases, like 'foo\w' and
|
||||||
|
/// 'foo\S', in an intuitive manner.
|
||||||
|
fn has_uppercase_literal(pattern: &str) -> bool {
|
||||||
|
let mut chars = pattern.chars();
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
if c == '\\' {
|
||||||
|
chars.next();
|
||||||
|
} else if c.is_uppercase() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use grep_matcher::{LineMatchKind, Matcher};
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Test that enabling word matches does the right thing and demonstrate
|
||||||
|
// the difference between it and surrounding the regex in `\b`.
|
||||||
|
#[test]
|
||||||
|
fn word() {
|
||||||
|
let matcher = RegexMatcherBuilder::new()
|
||||||
|
.word(true)
|
||||||
|
.build(r"-2")
|
||||||
|
.unwrap();
|
||||||
|
assert!(matcher.is_match(b"abc -2 foo").unwrap());
|
||||||
|
|
||||||
|
let matcher = RegexMatcherBuilder::new()
|
||||||
|
.word(false)
|
||||||
|
.build(r"\b-2\b")
|
||||||
|
.unwrap();
|
||||||
|
assert!(!matcher.is_match(b"abc -2 foo").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that enabling CRLF permits `$` to match at the end of a line.
|
||||||
|
#[test]
|
||||||
|
fn line_terminator_crlf() {
|
||||||
|
// Test normal use of `$` with a `\n` line terminator.
|
||||||
|
let matcher = RegexMatcherBuilder::new()
|
||||||
|
.multi_line(true)
|
||||||
|
.build(r"abc$")
|
||||||
|
.unwrap();
|
||||||
|
assert!(matcher.is_match(b"abc\n").unwrap());
|
||||||
|
|
||||||
|
// Test that `$` doesn't match at `\r\n` boundary normally.
|
||||||
|
let matcher = RegexMatcherBuilder::new()
|
||||||
|
.multi_line(true)
|
||||||
|
.build(r"abc$")
|
||||||
|
.unwrap();
|
||||||
|
assert!(!matcher.is_match(b"abc\r\n").unwrap());
|
||||||
|
|
||||||
|
// Now check the CRLF handling.
|
||||||
|
let matcher = RegexMatcherBuilder::new()
|
||||||
|
.multi_line(true)
|
||||||
|
.crlf(true)
|
||||||
|
.build(r"abc$")
|
||||||
|
.unwrap();
|
||||||
|
assert!(matcher.is_match(b"abc\r\n").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that smart case works.
|
||||||
|
#[test]
|
||||||
|
fn case_smart() {
|
||||||
|
let matcher = RegexMatcherBuilder::new()
|
||||||
|
.case_smart(true)
|
||||||
|
.build(r"abc")
|
||||||
|
.unwrap();
|
||||||
|
assert!(matcher.is_match(b"ABC").unwrap());
|
||||||
|
|
||||||
|
let matcher = RegexMatcherBuilder::new()
|
||||||
|
.case_smart(true)
|
||||||
|
.build(r"aBc")
|
||||||
|
.unwrap();
|
||||||
|
assert!(!matcher.is_match(b"ABC").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that finding candidate lines works as expected.
|
||||||
|
#[test]
|
||||||
|
fn candidate_lines() {
|
||||||
|
fn is_confirmed(m: LineMatchKind) -> bool {
|
||||||
|
match m {
|
||||||
|
LineMatchKind::Confirmed(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let matcher = RegexMatcherBuilder::new()
|
||||||
|
.build(r"\wfoo\s")
|
||||||
|
.unwrap();
|
||||||
|
let m = matcher.find_candidate_line(b"afoo ").unwrap().unwrap();
|
||||||
|
assert!(is_confirmed(m));
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-printer"
|
name = "grep-printer"
|
||||||
version = "0.0.1" #:version
|
version = "0.1.0" #: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
|
||||||
@@ -19,13 +19,12 @@ serde1 = ["base64", "serde", "serde_derive", "serde_json"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = { version = "0.9", optional = true }
|
base64 = { version = "0.9", optional = true }
|
||||||
grep-matcher = { version = "0.0.1", path = "../grep-matcher" }
|
grep-matcher = { version = "0.1.0", path = "../grep-matcher" }
|
||||||
grep-searcher = { version = "0.0.1", path = "../grep-searcher" }
|
grep-searcher = { version = "0.1.0", path = "../grep-searcher" }
|
||||||
log = "0.4"
|
|
||||||
termcolor = "1"
|
termcolor = "1"
|
||||||
serde = { version = "1", optional = true }
|
serde = { version = "1", optional = true }
|
||||||
serde_derive = { version = "1", optional = true }
|
serde_derive = { version = "1", optional = true }
|
||||||
serde_json = { version = "1", optional = true }
|
serde_json = { version = "1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
grep-regex = { version = "0.0.1", path = "../grep-regex" }
|
grep-regex = { version = "0.1.0", path = "../grep-regex" }
|
||||||
|
@@ -1,4 +1,35 @@
|
|||||||
grep
|
grep-printer
|
||||||
----
|
------------
|
||||||
This is a *library* that provides grep-style line-by-line regex searching (with
|
Print results from line oriented searching in a human readable, aggregate or
|
||||||
comparable performance to `grep` itself).
|
JSON Lines format.
|
||||||
|
|
||||||
|
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||||
|
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||||
|
[](https://crates.io/crates/grep-printer)
|
||||||
|
|
||||||
|
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
[https://docs.rs/grep-printer](https://docs.rs/grep-printer)
|
||||||
|
|
||||||
|
**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-printer = "0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
and this to your crate root:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate grep_printer;
|
||||||
|
```
|
||||||
|
@@ -91,7 +91,7 @@ impl JSONBuilder {
|
|||||||
/// When enabled, the `begin` and `end` messages are always emitted, even
|
/// When enabled, the `begin` and `end` messages are always emitted, even
|
||||||
/// when no match is found.
|
/// when no match is found.
|
||||||
///
|
///
|
||||||
/// When disabled, the `begin` and `end` messages are only shown is there
|
/// When disabled, the `begin` and `end` messages are only shown if there
|
||||||
/// is at least one `match` or `context` message.
|
/// is at least one `match` or `context` message.
|
||||||
///
|
///
|
||||||
/// This is disabled by default.
|
/// This is disabled by default.
|
||||||
@@ -108,7 +108,7 @@ impl JSONBuilder {
|
|||||||
///
|
///
|
||||||
/// # Format
|
/// # Format
|
||||||
///
|
///
|
||||||
/// This section describe the JSON format used by this printer.
|
/// This section describes the JSON format used by this printer.
|
||||||
///
|
///
|
||||||
/// To skip the rigamarole, take a look at the
|
/// To skip the rigamarole, take a look at the
|
||||||
/// [example](#example)
|
/// [example](#example)
|
||||||
@@ -619,6 +619,13 @@ impl<'p, 's, M: Matcher, W: io::Write> JSONSink<'p, 's, M, W> {
|
|||||||
matches.push(m);
|
matches.push(m);
|
||||||
true
|
true
|
||||||
}).map_err(io::Error::error_message)?;
|
}).map_err(io::Error::error_message)?;
|
||||||
|
// Don't report empty matches appearing at the end of the bytes.
|
||||||
|
if !matches.is_empty()
|
||||||
|
&& matches.last().unwrap().is_empty()
|
||||||
|
&& matches.last().unwrap().start() >= bytes.len()
|
||||||
|
{
|
||||||
|
matches.pop().unwrap();
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
/*!
|
/*!
|
||||||
This crate provides a featureful and fast printer for showing search results
|
This crate provides featureful and fast printers that interoperate with the
|
||||||
in a human readable way, and another printer for showing results in a machine
|
[`grep-searcher`](https://docs.rs/grep-searcher)
|
||||||
readable way.
|
crate.
|
||||||
|
|
||||||
# Brief overview
|
# Brief overview
|
||||||
|
|
||||||
@@ -74,8 +74,6 @@ extern crate grep_matcher;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate grep_regex;
|
extern crate grep_regex;
|
||||||
extern crate grep_searcher;
|
extern crate grep_searcher;
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
#[cfg(feature = "serde1")]
|
#[cfg(feature = "serde1")]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[cfg(feature = "serde1")]
|
#[cfg(feature = "serde1")]
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::{Cell, RefCell};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -139,6 +139,8 @@ impl StandardBuilder {
|
|||||||
/// A [`UserColorSpec`](struct.UserColorSpec.html) can be constructed from
|
/// A [`UserColorSpec`](struct.UserColorSpec.html) can be constructed from
|
||||||
/// a string in accordance with the color specification format. See the
|
/// a string in accordance with the color specification format. See the
|
||||||
/// `UserColorSpec` type documentation for more details on the format.
|
/// `UserColorSpec` type documentation for more details on the format.
|
||||||
|
/// A [`ColorSpecs`](struct.ColorSpecs.html) can then be generated from
|
||||||
|
/// zero or more `UserColorSpec`s.
|
||||||
///
|
///
|
||||||
/// Regardless of the color specifications provided here, whether color
|
/// Regardless of the color specifications provided here, whether color
|
||||||
/// is actually used or not is determined by the implementation of
|
/// is actually used or not is determined by the implementation of
|
||||||
@@ -475,6 +477,7 @@ impl<W: WriteColor> Standard<W> {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
let needs_match_granularity = self.needs_match_granularity();
|
||||||
StandardSink {
|
StandardSink {
|
||||||
matcher: matcher,
|
matcher: matcher,
|
||||||
standard: self,
|
standard: self,
|
||||||
@@ -485,6 +488,7 @@ impl<W: WriteColor> Standard<W> {
|
|||||||
after_context_remaining: 0,
|
after_context_remaining: 0,
|
||||||
binary_byte_offset: None,
|
binary_byte_offset: None,
|
||||||
stats: stats,
|
stats: stats,
|
||||||
|
needs_match_granularity: needs_match_granularity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,6 +515,7 @@ impl<W: WriteColor> Standard<W> {
|
|||||||
};
|
};
|
||||||
let ppath = PrinterPath::with_separator(
|
let ppath = PrinterPath::with_separator(
|
||||||
path.as_ref(), self.config.separator_path);
|
path.as_ref(), self.config.separator_path);
|
||||||
|
let needs_match_granularity = self.needs_match_granularity();
|
||||||
StandardSink {
|
StandardSink {
|
||||||
matcher: matcher,
|
matcher: matcher,
|
||||||
standard: self,
|
standard: self,
|
||||||
@@ -521,8 +526,32 @@ impl<W: WriteColor> Standard<W> {
|
|||||||
after_context_remaining: 0,
|
after_context_remaining: 0,
|
||||||
binary_byte_offset: None,
|
binary_byte_offset: None,
|
||||||
stats: stats,
|
stats: stats,
|
||||||
|
needs_match_granularity: needs_match_granularity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if the configuration of the printer requires
|
||||||
|
/// us to find each individual match in the lines reported by the searcher.
|
||||||
|
///
|
||||||
|
/// We care about this distinction because finding each individual match
|
||||||
|
/// costs more, so we only do it when we need to.
|
||||||
|
fn needs_match_granularity(&self) -> bool {
|
||||||
|
let supports_color = self.wtr.borrow().supports_color();
|
||||||
|
let match_colored = !self.config.colors.matched().is_none();
|
||||||
|
|
||||||
|
// Coloring requires identifying each individual match.
|
||||||
|
(supports_color && match_colored)
|
||||||
|
// The column feature requires finding the position of the first match.
|
||||||
|
|| self.config.column
|
||||||
|
// Requires finding each match for performing replacement.
|
||||||
|
|| self.config.replacement.is_some()
|
||||||
|
// Emitting a line for each match requires finding each match.
|
||||||
|
|| self.config.per_match
|
||||||
|
// Emitting only the match requires finding each match.
|
||||||
|
|| self.config.only_matching
|
||||||
|
// Computing certain statistics requires finding each match.
|
||||||
|
|| self.config.stats
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W> Standard<W> {
|
impl<W> Standard<W> {
|
||||||
@@ -581,6 +610,7 @@ pub struct StandardSink<'p, 's, M: Matcher, W: 's> {
|
|||||||
after_context_remaining: u64,
|
after_context_remaining: u64,
|
||||||
binary_byte_offset: Option<u64>,
|
binary_byte_offset: Option<u64>,
|
||||||
stats: Option<Stats>,
|
stats: Option<Stats>,
|
||||||
|
needs_match_granularity: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'p, 's, M: Matcher, W: WriteColor> StandardSink<'p, 's, M, W> {
|
impl<'p, 's, M: Matcher, W: WriteColor> StandardSink<'p, 's, M, W> {
|
||||||
@@ -632,7 +662,7 @@ impl<'p, 's, M: Matcher, W: WriteColor> StandardSink<'p, 's, M, W> {
|
|||||||
/// locations if the current configuration demands match granularity.
|
/// locations if the current configuration demands match granularity.
|
||||||
fn record_matches(&mut self, bytes: &[u8]) -> io::Result<()> {
|
fn record_matches(&mut self, bytes: &[u8]) -> io::Result<()> {
|
||||||
self.standard.matches.clear();
|
self.standard.matches.clear();
|
||||||
if !self.needs_match_granularity() {
|
if !self.needs_match_granularity {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// If printing requires knowing the location of each individual match,
|
// If printing requires knowing the location of each individual match,
|
||||||
@@ -647,6 +677,13 @@ impl<'p, 's, M: Matcher, W: WriteColor> StandardSink<'p, 's, M, W> {
|
|||||||
matches.push(m);
|
matches.push(m);
|
||||||
true
|
true
|
||||||
}).map_err(io::Error::error_message)?;
|
}).map_err(io::Error::error_message)?;
|
||||||
|
// Don't report empty matches appearing at the end of the bytes.
|
||||||
|
if !matches.is_empty()
|
||||||
|
&& matches.last().unwrap().is_empty()
|
||||||
|
&& matches.last().unwrap().start() >= bytes.len()
|
||||||
|
{
|
||||||
|
matches.pop().unwrap();
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,29 +707,6 @@ impl<'p, 's, M: Matcher, W: WriteColor> StandardSink<'p, 's, M, W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if the configuration of the printer requires
|
|
||||||
/// us to find each individual match in the lines reported by the searcher.
|
|
||||||
///
|
|
||||||
/// We care about this distinction because finding each individual match
|
|
||||||
/// costs more, so we only do it when we need to.
|
|
||||||
fn needs_match_granularity(&self) -> bool {
|
|
||||||
let supports_color = self.standard.wtr.borrow().supports_color();
|
|
||||||
let match_colored = !self.standard.config.colors.matched().is_none();
|
|
||||||
|
|
||||||
// Coloring requires identifying each individual match.
|
|
||||||
(supports_color && match_colored)
|
|
||||||
// The column feature requires finding the position of the first match.
|
|
||||||
|| self.standard.config.column
|
|
||||||
// Requires finding each match for performing replacement.
|
|
||||||
|| self.standard.config.replacement.is_some()
|
|
||||||
// Emitting a line for each match requires finding each match.
|
|
||||||
|| self.standard.config.per_match
|
|
||||||
// Emitting only the match requires finding each match.
|
|
||||||
|| self.standard.config.only_matching
|
|
||||||
// Computing certain statistics requires finding each match.
|
|
||||||
|| self.standard.config.stats
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if this printer should quit.
|
/// Returns true if this printer should quit.
|
||||||
///
|
///
|
||||||
/// This implements the logic for handling quitting after seeing a certain
|
/// This implements the logic for handling quitting after seeing a certain
|
||||||
@@ -805,6 +819,8 @@ struct StandardImpl<'a, M: 'a + Matcher, W: 'a> {
|
|||||||
searcher: &'a Searcher,
|
searcher: &'a Searcher,
|
||||||
sink: &'a StandardSink<'a, 'a, M, W>,
|
sink: &'a StandardSink<'a, 'a, M, W>,
|
||||||
sunk: Sunk<'a>,
|
sunk: Sunk<'a>,
|
||||||
|
/// Set to true if and only if we are writing a match with color.
|
||||||
|
in_color_match: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
||||||
@@ -817,6 +833,7 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
searcher: searcher,
|
searcher: searcher,
|
||||||
sink: sink,
|
sink: sink,
|
||||||
sunk: Sunk::empty(),
|
sunk: Sunk::empty(),
|
||||||
|
in_color_match: Cell::new(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,38 +877,14 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
self.write_search_prelude()?;
|
self.write_search_prelude()?;
|
||||||
if self.sunk.matches().is_empty() {
|
if self.sunk.matches().is_empty() {
|
||||||
if self.multi_line() && !self.is_context() {
|
if self.multi_line() && !self.is_context() {
|
||||||
trace!(
|
|
||||||
"{:?}:{:?}:{}: sinking via sink_fast_multi_line",
|
|
||||||
self.sink.path,
|
|
||||||
self.sunk.line_number(),
|
|
||||||
self.sunk.absolute_byte_offset()
|
|
||||||
);
|
|
||||||
self.sink_fast_multi_line()
|
self.sink_fast_multi_line()
|
||||||
} else {
|
} else {
|
||||||
trace!(
|
|
||||||
"{:?}:{:?}:{}: sinking via sink_fast",
|
|
||||||
self.sink.path,
|
|
||||||
self.sunk.line_number(),
|
|
||||||
self.sunk.absolute_byte_offset()
|
|
||||||
);
|
|
||||||
self.sink_fast()
|
self.sink_fast()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if self.multi_line() && !self.is_context() {
|
if self.multi_line() && !self.is_context() {
|
||||||
trace!(
|
|
||||||
"{:?}:{:?}:{}: sinking via sink_slow_multi_line",
|
|
||||||
self.sink.path,
|
|
||||||
self.sunk.line_number(),
|
|
||||||
self.sunk.absolute_byte_offset()
|
|
||||||
);
|
|
||||||
self.sink_slow_multi_line()
|
self.sink_slow_multi_line()
|
||||||
} else {
|
} else {
|
||||||
trace!(
|
|
||||||
"{:?}:{:?}:{}: sinking via sink_slow",
|
|
||||||
self.sink.path,
|
|
||||||
self.sunk.line_number(),
|
|
||||||
self.sunk.absolute_byte_offset()
|
|
||||||
);
|
|
||||||
self.sink_slow()
|
self.sink_slow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -992,7 +985,6 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let line_term = self.searcher.line_terminator().as_byte();
|
let line_term = self.searcher.line_terminator().as_byte();
|
||||||
let spec = self.config().colors.matched();
|
|
||||||
let bytes = self.sunk.bytes();
|
let bytes = self.sunk.bytes();
|
||||||
let matches = self.sunk.matches();
|
let matches = self.sunk.matches();
|
||||||
let mut midx = 0;
|
let mut midx = 0;
|
||||||
@@ -1023,6 +1015,7 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
midx += 1;
|
midx += 1;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
self.end_color_match()?;
|
||||||
self.write(&bytes[line])?;
|
self.write(&bytes[line])?;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1031,14 +1024,17 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
|
|
||||||
if line.start() < m.start() {
|
if line.start() < m.start() {
|
||||||
let upto = cmp::min(line.end(), m.start());
|
let upto = cmp::min(line.end(), m.start());
|
||||||
|
self.end_color_match()?;
|
||||||
self.write(&bytes[line.with_end(upto)])?;
|
self.write(&bytes[line.with_end(upto)])?;
|
||||||
line = line.with_start(upto);
|
line = line.with_start(upto);
|
||||||
} else {
|
} else {
|
||||||
let upto = cmp::min(line.end(), m.end());
|
let upto = cmp::min(line.end(), m.end());
|
||||||
self.write_spec(spec, &bytes[line.with_end(upto)])?;
|
self.start_color_match()?;
|
||||||
|
self.write(&bytes[line.with_end(upto)])?;
|
||||||
line = line.with_start(upto);
|
line = line.with_start(upto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.end_color_match()?;
|
||||||
self.write_line_term()?;
|
self.write_line_term()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1153,6 +1149,7 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
/// Write the beginning part of a matching line. This (may) include things
|
/// Write the beginning part of a matching line. This (may) include things
|
||||||
/// like the file path, line number among others, depending on the
|
/// like the file path, line number among others, depending on the
|
||||||
/// configuration and the parameters given.
|
/// configuration and the parameters given.
|
||||||
|
#[inline(always)]
|
||||||
fn write_prelude(
|
fn write_prelude(
|
||||||
&self,
|
&self,
|
||||||
absolute_byte_offset: u64,
|
absolute_byte_offset: u64,
|
||||||
@@ -1178,6 +1175,7 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn write_line(
|
fn write_line(
|
||||||
&self,
|
&self,
|
||||||
line: &[u8],
|
line: &[u8],
|
||||||
@@ -1214,21 +1212,21 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
).start()
|
).start()
|
||||||
};
|
};
|
||||||
for mut m in matches.iter().map(|&m| m) {
|
for mut m in matches.iter().map(|&m| m) {
|
||||||
if last_written <= m.start() {
|
if last_written < m.start() {
|
||||||
|
self.end_color_match()?;
|
||||||
self.write(&line[last_written..m.start()])?;
|
self.write(&line[last_written..m.start()])?;
|
||||||
} else if last_written < m.end() {
|
} else if last_written < m.end() {
|
||||||
m = m.with_start(last_written);
|
m = m.with_start(last_written);
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if !m.is_empty() {
|
||||||
|
self.start_color_match()?;
|
||||||
|
self.write(&line[m])?;
|
||||||
|
}
|
||||||
last_written = m.end();
|
last_written = m.end();
|
||||||
// This conditional checks if the match is both empty *and*
|
|
||||||
// past the end of the line. In this case, we never want to
|
|
||||||
// emit an additional color escape.
|
|
||||||
if m.start() != m.end() || m.end() != line.len() {
|
|
||||||
self.write_spec(spec, &line[m])?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
self.end_color_match()?;
|
||||||
self.write(&line[last_written..])?;
|
self.write(&line[last_written..])?;
|
||||||
if !self.has_line_terminator(line) {
|
if !self.has_line_terminator(line) {
|
||||||
self.write_line_term()?;
|
self.write_line_term()?;
|
||||||
@@ -1365,6 +1363,24 @@ impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_color_match(&self) -> io::Result<()> {
|
||||||
|
if self.in_color_match.get() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.wtr().borrow_mut().set_color(self.config().colors.matched())?;
|
||||||
|
self.in_color_match.set(true);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_color_match(&self) -> io::Result<()> {
|
||||||
|
if !self.in_color_match.get() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.wtr().borrow_mut().reset()?;
|
||||||
|
self.in_color_match.set(false);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn write_trim(&self, buf: &[u8]) -> io::Result<()> {
|
fn write_trim(&self, buf: &[u8]) -> io::Result<()> {
|
||||||
if !self.config().trim_ascii {
|
if !self.config().trim_ascii {
|
||||||
return self.write(buf);
|
return self.write(buf);
|
||||||
|
@@ -190,6 +190,8 @@ impl SummaryBuilder {
|
|||||||
/// A [`UserColorSpec`](struct.UserColorSpec.html) can be constructed from
|
/// A [`UserColorSpec`](struct.UserColorSpec.html) can be constructed from
|
||||||
/// a string in accordance with the color specification format. See the
|
/// a string in accordance with the color specification format. See the
|
||||||
/// `UserColorSpec` type documentation for more details on the format.
|
/// `UserColorSpec` type documentation for more details on the format.
|
||||||
|
/// A [`ColorSpecs`](struct.ColorSpecs.html) can then be generated from
|
||||||
|
/// zero or more `UserColorSpec`s.
|
||||||
///
|
///
|
||||||
/// Regardless of the color specifications provided here, whether color
|
/// Regardless of the color specifications provided here, whether color
|
||||||
/// is actually used or not is determined by the implementation of
|
/// is actually used or not is determined by the implementation of
|
||||||
|
@@ -157,6 +157,7 @@ pub struct Sunk<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Sunk<'a> {
|
impl<'a> Sunk<'a> {
|
||||||
|
#[inline]
|
||||||
pub fn empty() -> Sunk<'static> {
|
pub fn empty() -> Sunk<'static> {
|
||||||
Sunk {
|
Sunk {
|
||||||
bytes: &[],
|
bytes: &[],
|
||||||
@@ -168,6 +169,7 @@ impl<'a> Sunk<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn from_sink_match(
|
pub fn from_sink_match(
|
||||||
sunk: &'a SinkMatch<'a>,
|
sunk: &'a SinkMatch<'a>,
|
||||||
original_matches: &'a [Match],
|
original_matches: &'a [Match],
|
||||||
@@ -186,6 +188,7 @@ impl<'a> Sunk<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn from_sink_context(
|
pub fn from_sink_context(
|
||||||
sunk: &'a SinkContext<'a>,
|
sunk: &'a SinkContext<'a>,
|
||||||
original_matches: &'a [Match],
|
original_matches: &'a [Match],
|
||||||
@@ -204,30 +207,37 @@ impl<'a> Sunk<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn context_kind(&self) -> Option<&'a SinkContextKind> {
|
pub fn context_kind(&self) -> Option<&'a SinkContextKind> {
|
||||||
self.context_kind
|
self.context_kind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn bytes(&self) -> &'a [u8] {
|
pub fn bytes(&self) -> &'a [u8] {
|
||||||
self.bytes
|
self.bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn matches(&self) -> &'a [Match] {
|
pub fn matches(&self) -> &'a [Match] {
|
||||||
self.matches
|
self.matches
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn original_matches(&self) -> &'a [Match] {
|
pub fn original_matches(&self) -> &'a [Match] {
|
||||||
self.original_matches
|
self.original_matches
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn lines(&self, line_term: u8) -> LineIter<'a> {
|
pub fn lines(&self, line_term: u8) -> LineIter<'a> {
|
||||||
LineIter::new(line_term, self.bytes())
|
LineIter::new(line_term, self.bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn absolute_byte_offset(&self) -> u64 {
|
pub fn absolute_byte_offset(&self) -> u64 {
|
||||||
self.absolute_byte_offset
|
self.absolute_byte_offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn line_number(&self) -> Option<u64> {
|
pub fn line_number(&self) -> Option<u64> {
|
||||||
self.line_number
|
self.line_number
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-regex"
|
name = "grep-regex"
|
||||||
version = "0.0.1" #:version
|
version = "0.1.0" #: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.
|
||||||
@@ -14,8 +14,8 @@ license = "Unlicense/MIT"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
grep-matcher = { version = "0.0.1", path = "../grep-matcher" }
|
grep-matcher = { version = "0.1.0", path = "../grep-matcher" }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
regex-syntax = "0.6"
|
regex-syntax = "0.6"
|
||||||
thread_local = "0.3.5"
|
thread_local = "0.3.6"
|
||||||
utf8-ranges = "1"
|
utf8-ranges = "1"
|
||||||
|
@@ -1,4 +1,35 @@
|
|||||||
grep
|
grep-regex
|
||||||
----
|
----------
|
||||||
This is a *library* that provides grep-style line-by-line regex searching (with
|
The `grep-regex` crate provides an implementation of the `Matcher` trait from
|
||||||
comparable performance to `grep` itself).
|
the `grep-matcher` crate. This implementation permits Rust's regex engine to
|
||||||
|
be used in the `grep` crate for fast line oriented searching.
|
||||||
|
|
||||||
|
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||||
|
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||||
|
[](https://crates.io/crates/grep-regex)
|
||||||
|
|
||||||
|
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
[https://docs.rs/grep-regex](https://docs.rs/grep-regex)
|
||||||
|
|
||||||
|
**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-regex = "0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
and this to your crate root:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate grep_regex;
|
||||||
|
```
|
||||||
|
@@ -201,21 +201,6 @@ impl ConfiguredHIR {
|
|||||||
}
|
}
|
||||||
match LiteralSets::new(&self.expr).one_regex() {
|
match LiteralSets::new(&self.expr).one_regex() {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
/*
|
|
||||||
if !self.config.crlf {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
// If we're trying to support CRLF, then our "fast" line
|
|
||||||
// oriented regex needs `$` to be able to match at a `\r\n`
|
|
||||||
// boundary. The regex engine doesn't support this, so we
|
|
||||||
// "fake" it by replacing `$` with `(?:\r?$)`. Since the
|
|
||||||
// fast line regex is only used to detect lines, this never
|
|
||||||
// infects match offsets. Namely, the regex generated via
|
|
||||||
// `self.expr` is matched against lines with line terminators
|
|
||||||
// stripped.
|
|
||||||
let pattern = crlfify(self.expr.clone()).to_string();
|
|
||||||
self.pattern_to_regex(&pattern).map(Some)
|
|
||||||
*/
|
|
||||||
Some(pattern) => self.pattern_to_regex(&pattern).map(Some),
|
Some(pattern) => self.pattern_to_regex(&pattern).map(Some),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -263,7 +263,7 @@ impl RegexMatcherBuilder {
|
|||||||
/// be slightly different than what one would expect given the pattern.
|
/// be slightly different than what one would expect given the pattern.
|
||||||
/// This is the trade off made: in many cases, `$` will "just work" in the
|
/// This is the trade off made: in many cases, `$` will "just work" in the
|
||||||
/// presence of `\r\n` line terminators, but matches may require some
|
/// presence of `\r\n` line terminators, but matches may require some
|
||||||
/// trimming to faithfully represent the indended match.
|
/// trimming to faithfully represent the intended match.
|
||||||
///
|
///
|
||||||
/// Note that if you do not wish to set the line terminator but would still
|
/// Note that if you do not wish to set the line terminator but would still
|
||||||
/// like `$` to match `\r\n` line terminators, then it is valid to call
|
/// like `$` to match `\r\n` line terminators, then it is valid to call
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep-searcher"
|
name = "grep-searcher"
|
||||||
version = "0.0.1" #:version
|
version = "0.1.0" #: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.
|
||||||
@@ -15,14 +15,14 @@ license = "Unlicense/MIT"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bytecount = "0.3.1"
|
bytecount = "0.3.1"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
encoding_rs_io = "0.1"
|
encoding_rs_io = "0.1.2"
|
||||||
grep-matcher = { version = "0.0.1", path = "../grep-matcher" }
|
grep-matcher = { version = "0.1.0", path = "../grep-matcher" }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
memchr = "2"
|
memchr = "2"
|
||||||
memmap = "0.6"
|
memmap = "0.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
grep-regex = { version = "0.0.1", path = "../grep-regex" }
|
grep-regex = { version = "0.1.0", path = "../grep-regex" }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@@ -1,4 +1,37 @@
|
|||||||
grep
|
grep-searcher
|
||||||
----
|
-------------
|
||||||
This is a *library* that provides grep-style line-by-line regex searching (with
|
A high level library for executing fast line oriented searches. This handles
|
||||||
comparable performance to `grep` itself).
|
things like reporting contextual lines, counting lines, inverting a search,
|
||||||
|
detecting binary data, automatic UTF-16 transcoding and deciding whether or not
|
||||||
|
to use memory maps.
|
||||||
|
|
||||||
|
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||||
|
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||||
|
[](https://crates.io/crates/grep-searcher)
|
||||||
|
|
||||||
|
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
[https://docs.rs/grep-searcher](https://docs.rs/grep-searcher)
|
||||||
|
|
||||||
|
**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-searcher = "0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
and this to your crate root:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate grep_searcher;
|
||||||
|
```
|
||||||
|
@@ -294,8 +294,8 @@ pub struct LineBuffer {
|
|||||||
/// has been exhausted.
|
/// has been exhausted.
|
||||||
last_lineterm: usize,
|
last_lineterm: usize,
|
||||||
/// The end position of the buffer. This is always greater than or equal to
|
/// The end position of the buffer. This is always greater than or equal to
|
||||||
/// lastnl. The bytes between lastnl and end, if any, always correspond to
|
/// last_lineterm. The bytes between last_lineterm and end, if any, always
|
||||||
/// a partial line.
|
/// correspond to a partial line.
|
||||||
end: usize,
|
end: usize,
|
||||||
/// The absolute byte offset corresponding to `pos`. This is most typically
|
/// The absolute byte offset corresponding to `pos`. This is most typically
|
||||||
/// not a valid index into addressable memory, but rather, an offset that
|
/// not a valid index into addressable memory, but rather, an offset that
|
||||||
@@ -475,8 +475,26 @@ impl LineBuffer {
|
|||||||
// in bounds, which they should always be, and we enforce with
|
// in bounds, which they should always be, and we enforce with
|
||||||
// an assert above.
|
// an assert above.
|
||||||
//
|
//
|
||||||
// TODO: It seems like it should be possible to do this in safe
|
// It seems like it should be possible to do this in safe code that
|
||||||
// code that results in the same codegen.
|
// results in the same codegen. I tried the obvious:
|
||||||
|
//
|
||||||
|
// for (src, dst) in (self.pos..self.end).zip(0..) {
|
||||||
|
// self.buf[dst] = self.buf[src];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// But the above does not work, and in fact compiles down to a slow
|
||||||
|
// byte-by-byte loop. I tried a few other minor variations, but
|
||||||
|
// alas, better minds might prevail.
|
||||||
|
//
|
||||||
|
// Overall, this doesn't save us *too* much. It mostly matters when
|
||||||
|
// the number of bytes we're copying is large, which can happen
|
||||||
|
// if the searcher is asked to produce a lot of context. We could
|
||||||
|
// decide this isn't worth it, but it does make an appreciable
|
||||||
|
// impact at or around the context=30 range on my machine.
|
||||||
|
//
|
||||||
|
// We could also use a temporary buffer that compiles down to two
|
||||||
|
// memcpys and is faster than the byte-at-a-time loop, but it
|
||||||
|
// complicates our options for limiting memory allocation a bit.
|
||||||
ptr::copy(
|
ptr::copy(
|
||||||
self.buf[self.pos..].as_ptr(),
|
self.buf[self.pos..].as_ptr(),
|
||||||
self.buf.as_mut_ptr(),
|
self.buf.as_mut_ptr(),
|
||||||
@@ -485,7 +503,7 @@ impl LineBuffer {
|
|||||||
}
|
}
|
||||||
self.pos = 0;
|
self.pos = 0;
|
||||||
self.last_lineterm = roll_len;
|
self.last_lineterm = roll_len;
|
||||||
self.end = self.last_lineterm;
|
self.end = roll_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensures that the internal buffer has a non-zero amount of free space
|
/// Ensures that the internal buffer has a non-zero amount of free space
|
||||||
|
@@ -72,7 +72,18 @@ impl LineStep {
|
|||||||
///
|
///
|
||||||
/// The range returned includes the line terminator. Ranges are always
|
/// The range returned includes the line terminator. Ranges are always
|
||||||
/// non-empty.
|
/// non-empty.
|
||||||
pub fn next(&mut self, mut bytes: &[u8]) -> Option<(usize, usize)> {
|
pub fn next(&mut self, bytes: &[u8]) -> Option<(usize, usize)> {
|
||||||
|
self.next_impl(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like next, but returns a `Match` instead of a tuple.
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn next_match(&mut self, bytes: &[u8]) -> Option<Match> {
|
||||||
|
self.next_impl(bytes).map(|(s, e)| Match::new(s, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn next_impl(&mut self, mut bytes: &[u8]) -> Option<(usize, usize)> {
|
||||||
bytes = &bytes[..self.end];
|
bytes = &bytes[..self.end];
|
||||||
match memchr(self.line_term, &bytes[self.pos..]) {
|
match memchr(self.line_term, &bytes[self.pos..]) {
|
||||||
None => {
|
None => {
|
||||||
@@ -95,11 +106,6 @@ impl LineStep {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like next, but returns a `Match` instead of a tuple.
|
|
||||||
pub(crate) fn next_match(&mut self, bytes: &[u8]) -> Option<Match> {
|
|
||||||
self.next(bytes).map(|(s, e)| Match::new(s, e))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Count the number of occurrences of `line_term` in `bytes`.
|
/// Count the number of occurrences of `line_term` in `bytes`.
|
||||||
@@ -109,9 +115,11 @@ pub fn count(bytes: &[u8], line_term: u8) -> 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)]
|
||||||
pub fn without_terminator(bytes: &[u8], line_term: LineTerminator) -> &[u8] {
|
pub fn without_terminator(bytes: &[u8], line_term: LineTerminator) -> &[u8] {
|
||||||
let line_term = line_term.as_bytes();
|
let line_term = line_term.as_bytes();
|
||||||
if bytes.get(bytes.len().saturating_sub(line_term.len())..) == Some(line_term) {
|
let start = bytes.len().saturating_sub(line_term.len());
|
||||||
|
if bytes.get(start..) == Some(line_term) {
|
||||||
return &bytes[..bytes.len() - line_term.len()];
|
return &bytes[..bytes.len() - line_term.len()];
|
||||||
}
|
}
|
||||||
bytes
|
bytes
|
||||||
@@ -121,6 +129,7 @@ pub fn without_terminator(bytes: &[u8], line_term: LineTerminator) -> &[u8] {
|
|||||||
/// of bytes.
|
/// of bytes.
|
||||||
///
|
///
|
||||||
/// Line terminators are considered part of the line they terminate.
|
/// Line terminators are considered part of the line they terminate.
|
||||||
|
#[inline(always)]
|
||||||
pub fn locate(
|
pub fn locate(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
line_term: u8,
|
line_term: u8,
|
||||||
@@ -167,7 +176,7 @@ fn preceding_by_pos(
|
|||||||
) -> usize {
|
) -> usize {
|
||||||
if pos == 0 {
|
if pos == 0 {
|
||||||
return 0;
|
return 0;
|
||||||
} else if bytes[pos - 1] == b'\n' {
|
} else if bytes[pos - 1] == line_term {
|
||||||
pos -= 1;
|
pos -= 1;
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
|
@@ -290,12 +290,14 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
} else if let Some(line) = self.find_by_line_fast(buf)? {
|
} else if let Some(line) = self.find_by_line_fast(buf)? {
|
||||||
|
if self.config.max_context() > 0 {
|
||||||
if !self.after_context_by_line(buf, line.start())? {
|
if !self.after_context_by_line(buf, line.start())? {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
if !self.before_context_by_line(buf, line.start())? {
|
if !self.before_context_by_line(buf, line.start())? {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.set_pos(line.end());
|
self.set_pos(line.end());
|
||||||
if !self.sink_matched(buf, &line)? {
|
if !self.sink_matched(buf, &line)? {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
@@ -311,6 +313,7 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn match_by_line_fast_invert(
|
fn match_by_line_fast_invert(
|
||||||
&mut self,
|
&mut self,
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
@@ -351,6 +354,7 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn find_by_line_fast(
|
fn find_by_line_fast(
|
||||||
&self,
|
&self,
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
@@ -406,6 +410,7 @@ impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn sink_matched(
|
fn sink_matched(
|
||||||
&mut self,
|
&mut self,
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
@@ -419,11 +424,21 @@ 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 =
|
||||||
|
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 {
|
||||||
line_term: self.config.line_term,
|
line_term: self.config.line_term,
|
||||||
bytes: &buf[*range],
|
bytes: linebuf,
|
||||||
absolute_byte_offset: offset,
|
absolute_byte_offset: offset,
|
||||||
line_number: self.line_number,
|
line_number: self.line_number,
|
||||||
},
|
},
|
||||||
|
@@ -76,9 +76,9 @@ impl MmapChoice {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// SAFETY: This is acceptable because the only way `MmapChoiceImpl` can
|
// SAFETY: This is acceptable because the only way `MmapChoiceImpl` can
|
||||||
// be `Auto` is if the caller invoked the `auto` constructor. Thus,
|
// be `Auto` is if the caller invoked the `auto` constructor, which
|
||||||
// this is a propagation of the caller's assertion that using memory
|
// is itself not safe. Thus, this is a propagation of the caller's
|
||||||
// maps is safe.
|
// assertion that using memory maps is safe.
|
||||||
match unsafe { Mmap::map(file) } {
|
match unsafe { Mmap::map(file) } {
|
||||||
Ok(mmap) => Some(mmap),
|
Ok(mmap) => Some(mmap),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@@ -296,7 +296,7 @@ impl SearcherBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builder a searcher with the given matcher.
|
/// Build a searcher with the given matcher.
|
||||||
pub fn build(&self) -> Searcher {
|
pub fn build(&self) -> Searcher {
|
||||||
let mut config = self.config.clone();
|
let mut config = self.config.clone();
|
||||||
if config.passthru {
|
if config.passthru {
|
||||||
@@ -306,7 +306,8 @@ impl SearcherBuilder {
|
|||||||
let mut decode_builder = DecodeReaderBytesBuilder::new();
|
let mut decode_builder = DecodeReaderBytesBuilder::new();
|
||||||
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)
|
||||||
|
.bom_override(true);
|
||||||
Searcher {
|
Searcher {
|
||||||
config: config,
|
config: config,
|
||||||
decode_builder: decode_builder,
|
decode_builder: decode_builder,
|
||||||
@@ -318,7 +319,7 @@ impl SearcherBuilder {
|
|||||||
|
|
||||||
/// Set the line terminator that is used by the searcher.
|
/// Set the line terminator that is used by the searcher.
|
||||||
///
|
///
|
||||||
/// When building a searcher, if the matcher provided has a line terminator
|
/// When using a searcher, if the matcher provided has a line terminator
|
||||||
/// set, then it must be the same as this one. If they aren't, building
|
/// set, then it must be the same as this one. If they aren't, building
|
||||||
/// a searcher will return an error.
|
/// a searcher will return an error.
|
||||||
///
|
///
|
||||||
@@ -453,12 +454,25 @@ impl SearcherBuilder {
|
|||||||
/// enabled, then the entire contents will be read on to the heap before
|
/// enabled, then the entire contents will be read on to the heap before
|
||||||
/// searching begins.
|
/// searching begins.
|
||||||
///
|
///
|
||||||
/// The default behavior is **never**. Generally speaking, command line
|
/// The default behavior is **never**. Generally speaking, and perhaps
|
||||||
/// programs probably want to enable memory maps. The only reason to keep
|
/// against conventional wisdom, memory maps don't necessarily enable
|
||||||
/// memory maps disabled is if there are concerns using them. For example,
|
/// faster searching. For example, depending on the platform, using memory
|
||||||
/// if your process is searching a file backed memory map at the same time
|
/// maps while searching a large directory can actually be quite a bit
|
||||||
/// that file is truncated, then it's possible for the process to terminate
|
/// slower than using normal read calls because of the overhead of managing
|
||||||
/// with a bus error.
|
/// the memory maps.
|
||||||
|
///
|
||||||
|
/// Memory maps can be faster in some cases however. On some platforms,
|
||||||
|
/// when searching a very large file that *is already in memory*, it can
|
||||||
|
/// be slightly faster to search it as a memory map instead of using
|
||||||
|
/// normal read calls.
|
||||||
|
///
|
||||||
|
/// Finally, memory maps have a somewhat complicated safety story in Rust.
|
||||||
|
/// If you aren't sure whether enabling memory maps is worth it, then just
|
||||||
|
/// don't bother with it.
|
||||||
|
///
|
||||||
|
/// **WARNING**: If your process is searching a file backed memory map
|
||||||
|
/// at the same time that file is truncated, then it's possible for the
|
||||||
|
/// process to terminate with a bus error.
|
||||||
pub fn memory_map(
|
pub fn memory_map(
|
||||||
&mut self,
|
&mut self,
|
||||||
strategy: MmapChoice,
|
strategy: MmapChoice,
|
||||||
@@ -486,7 +500,8 @@ impl SearcherBuilder {
|
|||||||
/// Set the encoding used to read the source data before searching.
|
/// Set the encoding used to read the source data before searching.
|
||||||
///
|
///
|
||||||
/// When an encoding is provided, then the source data is _unconditionally_
|
/// When an encoding is provided, then the source data is _unconditionally_
|
||||||
/// transcoded using the encoding. This will disable BOM sniffing. If the
|
/// transcoded using the encoding, unless a BOM is present. If a BOM is
|
||||||
|
/// present, then the encoding indicated by the BOM is used instead. If the
|
||||||
/// transcoding process encounters an error, then bytes are replaced with
|
/// transcoding process encounters an error, then bytes are replaced with
|
||||||
/// the Unicode replacement codepoint.
|
/// the Unicode replacement codepoint.
|
||||||
///
|
///
|
||||||
@@ -732,6 +747,7 @@ impl Searcher {
|
|||||||
/// where the output may be tailored based on how the searcher is configured.
|
/// where the output may be tailored based on how the searcher is configured.
|
||||||
impl Searcher {
|
impl Searcher {
|
||||||
/// Returns the line terminator used by this searcher.
|
/// Returns the line terminator used by this searcher.
|
||||||
|
#[inline]
|
||||||
pub fn line_terminator(&self) -> LineTerminator {
|
pub fn line_terminator(&self) -> LineTerminator {
|
||||||
self.config.line_term
|
self.config.line_term
|
||||||
}
|
}
|
||||||
@@ -739,18 +755,21 @@ impl Searcher {
|
|||||||
/// Returns true if and only if this searcher is configured to invert its
|
/// Returns true if and only if this searcher is configured to invert its
|
||||||
/// search results. That is, matching lines are lines that do **not** match
|
/// search results. That is, matching lines are lines that do **not** match
|
||||||
/// the searcher's matcher.
|
/// the searcher's matcher.
|
||||||
|
#[inline]
|
||||||
pub fn invert_match(&self) -> bool {
|
pub fn invert_match(&self) -> bool {
|
||||||
self.config.invert_match
|
self.config.invert_match
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this searcher is configured to count line
|
/// Returns true if and only if this searcher is configured to count line
|
||||||
/// numbers.
|
/// numbers.
|
||||||
|
#[inline]
|
||||||
pub fn line_number(&self) -> bool {
|
pub fn line_number(&self) -> bool {
|
||||||
self.config.line_number
|
self.config.line_number
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if this searcher is configured to perform
|
/// Returns true if and only if this searcher is configured to perform
|
||||||
/// multi line search.
|
/// multi line search.
|
||||||
|
#[inline]
|
||||||
pub fn multi_line(&self) -> bool {
|
pub fn multi_line(&self) -> bool {
|
||||||
self.config.multi_line
|
self.config.multi_line
|
||||||
}
|
}
|
||||||
@@ -785,17 +804,20 @@ impl Searcher {
|
|||||||
|
|
||||||
/// Returns the number of "after" context lines to report. When context
|
/// Returns the number of "after" context lines to report. When context
|
||||||
/// reporting is not enabled, this returns `0`.
|
/// reporting is not enabled, this returns `0`.
|
||||||
|
#[inline]
|
||||||
pub fn after_context(&self) -> usize {
|
pub fn after_context(&self) -> usize {
|
||||||
self.config.after_context
|
self.config.after_context
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of "before" context lines to report. When context
|
/// Returns the number of "before" context lines to report. When context
|
||||||
/// reporting is not enabled, this returns `0`.
|
/// reporting is not enabled, this returns `0`.
|
||||||
|
#[inline]
|
||||||
pub fn before_context(&self) -> usize {
|
pub fn before_context(&self) -> usize {
|
||||||
self.config.before_context
|
self.config.before_context
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if the searcher has "passthru" mode enabled.
|
/// Returns true if and only if the searcher has "passthru" mode enabled.
|
||||||
|
#[inline]
|
||||||
pub fn passthru(&self) -> bool {
|
pub fn passthru(&self) -> bool {
|
||||||
self.config.passthru
|
self.config.passthru
|
||||||
}
|
}
|
||||||
|
@@ -69,7 +69,7 @@ impl SinkError for Box<::std::error::Error> {
|
|||||||
/// an implementation of this trait to a searcher, and the searcher is then
|
/// an implementation of this trait to a searcher, and the searcher is then
|
||||||
/// responsible for calling the methods on this trait.
|
/// responsible for calling the methods on this trait.
|
||||||
///
|
///
|
||||||
/// This trait defines five behaviors:
|
/// This trait defines several behaviors:
|
||||||
///
|
///
|
||||||
/// * What to do when a match is found. Callers must provide this.
|
/// * What to do when a match is found. Callers must provide this.
|
||||||
/// * What to do when an error occurs. Callers must provide this via the
|
/// * What to do when an error occurs. Callers must provide this via the
|
||||||
|
@@ -13,11 +13,18 @@ keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
|||||||
license = "Unlicense/MIT"
|
license = "Unlicense/MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
grep-matcher = { version = "0.0.1", path = "../grep-matcher" }
|
grep-matcher = { version = "0.1.0", path = "../grep-matcher" }
|
||||||
grep-printer = { version = "0.0.1", path = "../grep-printer" }
|
grep-pcre2 = { version = "0.1.0", path = "../grep-pcre2", optional = true }
|
||||||
grep-regex = { version = "0.0.1", path = "../grep-regex" }
|
grep-printer = { version = "0.1.0", path = "../grep-printer" }
|
||||||
grep-searcher = { version = "0.0.1", path = "../grep-searcher" }
|
grep-regex = { version = "0.1.0", path = "../grep-regex" }
|
||||||
|
grep-searcher = { version = "0.1.0", path = "../grep-searcher" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
atty = "0.2.11"
|
||||||
|
termcolor = "1"
|
||||||
|
walkdir = "2.2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
avx-accel = ["grep-searcher/avx-accel"]
|
avx-accel = ["grep-searcher/avx-accel"]
|
||||||
simd-accel = ["grep-searcher/simd-accel"]
|
simd-accel = ["grep-searcher/simd-accel"]
|
||||||
|
pcre2 = ["grep-pcre2"]
|
||||||
|
@@ -1,4 +1,41 @@
|
|||||||
grep
|
grep
|
||||||
----
|
----
|
||||||
This is a *library* that provides grep-style line-by-line regex searching (with
|
ripgrep, as a library.
|
||||||
comparable performance to `grep` itself).
|
|
||||||
|
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||||
|
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||||
|
[](https://crates.io/crates/grep)
|
||||||
|
|
||||||
|
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||||
|
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
[https://docs.rs/grep](https://docs.rs/grep)
|
||||||
|
|
||||||
|
NOTE: This crate isn't ready for wide use yet. Ambitious individuals can
|
||||||
|
probably piece together the parts, but there is no high level documentation
|
||||||
|
describing how all of the pieces fit together.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Add this to your `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
grep = "0.2"
|
||||||
|
```
|
||||||
|
|
||||||
|
and this to your crate root:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate grep;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
This crate provides a `pcre2` feature (disabled by default) which, when
|
||||||
|
enabled, re-exports the `grep-pcre2` crate as an alternative `Matcher`
|
||||||
|
implementation to the standard `grep-regex` implementation.
|
||||||
|
107
grep/examples/simplegrep.rs
Normal file
107
grep/examples/simplegrep.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
extern crate atty;
|
||||||
|
extern crate grep;
|
||||||
|
extern crate termcolor;
|
||||||
|
extern crate walkdir;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::error;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process;
|
||||||
|
use std::result;
|
||||||
|
|
||||||
|
use grep::printer::{ColorSpecs, StandardBuilder};
|
||||||
|
use grep::regex::RegexMatcher;
|
||||||
|
use grep::searcher::{BinaryDetection, SearcherBuilder};
|
||||||
|
use termcolor::{ColorChoice, StandardStream};
|
||||||
|
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() {
|
||||||
|
if let Err(err) = try_main() {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_main() -> Result<()> {
|
||||||
|
let mut args: Vec<OsString> = env::args_os().collect();
|
||||||
|
if args.len() < 2 {
|
||||||
|
fail!("Usage: simplegrep <pattern> [<path> ...]");
|
||||||
|
}
|
||||||
|
if args.len() == 2 {
|
||||||
|
args.push(OsString::from("./"));
|
||||||
|
}
|
||||||
|
let pattern = match args[1].clone().into_string() {
|
||||||
|
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<()> {
|
||||||
|
let matcher = RegexMatcher::new_line_matcher(&pattern)?;
|
||||||
|
let mut searcher = SearcherBuilder::new()
|
||||||
|
.binary_detection(BinaryDetection::quit(b'\x00'))
|
||||||
|
.build();
|
||||||
|
let mut printer = StandardBuilder::new()
|
||||||
|
.color_specs(colors())
|
||||||
|
.build(StandardStream::stdout(color_choice()));
|
||||||
|
|
||||||
|
for path in paths {
|
||||||
|
for result in WalkDir::new(path) {
|
||||||
|
let dent = match result {
|
||||||
|
Ok(dent) => dent,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"{}: {}",
|
||||||
|
err.path().unwrap_or(Path::new("error")).display(),
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !dent.file_type().is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let result = searcher.search_path(
|
||||||
|
&matcher,
|
||||||
|
dent.path(),
|
||||||
|
printer.sink_with_path(&matcher, dent.path()),
|
||||||
|
);
|
||||||
|
if let Err(err) = result {
|
||||||
|
eprintln!("{}: {}", dent.path().display(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(),
|
||||||
|
])
|
||||||
|
}
|
@@ -1,10 +1,22 @@
|
|||||||
/*!
|
/*!
|
||||||
TODO.
|
ripgrep, as a library.
|
||||||
|
|
||||||
|
This library is intended to provide a high level facade to the crates that
|
||||||
|
make up ripgrep's core searching routines. However, there is no high level
|
||||||
|
documentation available yet guiding users on how to fit all of the pieces
|
||||||
|
together.
|
||||||
|
|
||||||
|
Every public API item in the constituent crates is documented, but examples
|
||||||
|
are sparse.
|
||||||
|
|
||||||
|
A cookbook and a guide are planned.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
pub extern crate grep_matcher as matcher;
|
pub extern crate grep_matcher as matcher;
|
||||||
|
#[cfg(feature = "pcre2")]
|
||||||
|
pub extern crate grep_pcre2 as pcre2;
|
||||||
pub extern crate grep_printer as printer;
|
pub extern crate grep_printer as printer;
|
||||||
pub extern crate grep_regex as regex;
|
pub extern crate grep_regex as regex;
|
||||||
pub extern crate grep_searcher as searcher;
|
pub extern crate grep_searcher as searcher;
|
||||||
|
@@ -26,7 +26,7 @@ memchr = "2"
|
|||||||
regex = "1"
|
regex = "1"
|
||||||
same-file = "1"
|
same-file = "1"
|
||||||
thread_local = "0.3.2"
|
thread_local = "0.3.2"
|
||||||
walkdir = "2"
|
walkdir = "2.2.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.winapi]
|
[target.'cfg(windows)'.dependencies.winapi]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
|
@@ -4,7 +4,7 @@ The ignore crate provides a fast recursive directory iterator that respects
|
|||||||
various filters such as globs, file types and `.gitignore` files. This crate
|
various filters such as globs, file types and `.gitignore` files. This crate
|
||||||
also provides lower level direct access to gitignore and file type matchers.
|
also provides lower level direct access to gitignore and file type matchers.
|
||||||
|
|
||||||
[](https://travis-ci.org/BurntSushi/ripgrep)
|
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||||
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||||
[](https://crates.io/crates/ignore)
|
[](https://crates.io/crates/ignore)
|
||||||
|
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
#![allow(dead_code, unused_imports, unused_mut, unused_variables)]
|
|
||||||
|
|
||||||
extern crate crossbeam;
|
extern crate crossbeam;
|
||||||
extern crate ignore;
|
extern crate ignore;
|
||||||
extern crate walkdir;
|
extern crate walkdir;
|
||||||
@@ -8,7 +6,6 @@ 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::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use crossbeam::sync::MsQueue;
|
use crossbeam::sync::MsQueue;
|
||||||
@@ -48,13 +45,11 @@ fn main() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
} else if simple {
|
} else if simple {
|
||||||
let mut stdout = io::BufWriter::new(io::stdout());
|
|
||||||
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())));
|
queue.push(Some(DirEntry::X(result.unwrap())));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut stdout = io::BufWriter::new(io::stdout());
|
|
||||||
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())));
|
queue.push(Some(DirEntry::Y(result.unwrap())));
|
||||||
|
@@ -98,6 +98,7 @@ use {Error, Match};
|
|||||||
|
|
||||||
const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
||||||
("agda", &["*.agda", "*.lagda"]),
|
("agda", &["*.agda", "*.lagda"]),
|
||||||
|
("ats", &["*.ats", "*.dats", "*.sats", "*.hats"]),
|
||||||
("aidl", &["*.aidl"]),
|
("aidl", &["*.aidl"]),
|
||||||
("amake", &["*.mk", "*.bp"]),
|
("amake", &["*.mk", "*.bp"]),
|
||||||
("asciidoc", &["*.adoc", "*.asc", "*.asciidoc"]),
|
("asciidoc", &["*.adoc", "*.asc", "*.asciidoc"]),
|
||||||
@@ -107,7 +108,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("bazel", &["*.bzl", "WORKSPACE", "BUILD"]),
|
("bazel", &["*.bzl", "WORKSPACE", "BUILD"]),
|
||||||
("bitbake", &["*.bb", "*.bbappend", "*.bbclass", "*.conf", "*.inc"]),
|
("bitbake", &["*.bb", "*.bbappend", "*.bbclass", "*.conf", "*.inc"]),
|
||||||
("bzip2", &["*.bz2"]),
|
("bzip2", &["*.bz2"]),
|
||||||
("c", &["*.c", "*.h", "*.H"]),
|
("c", &["*.c", "*.h", "*.H", "*.cats"]),
|
||||||
("cabal", &["*.cabal"]),
|
("cabal", &["*.cabal"]),
|
||||||
("cbor", &["*.cbor"]),
|
("cbor", &["*.cbor"]),
|
||||||
("ceylon", &["*.ceylon"]),
|
("ceylon", &["*.ceylon"]),
|
||||||
@@ -129,6 +130,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("cython", &["*.pyx"]),
|
("cython", &["*.pyx"]),
|
||||||
("dart", &["*.dart"]),
|
("dart", &["*.dart"]),
|
||||||
("d", &["*.d"]),
|
("d", &["*.d"]),
|
||||||
|
("dhall", &["*.dhall"]),
|
||||||
("docker", &["*Dockerfile*"]),
|
("docker", &["*Dockerfile*"]),
|
||||||
("elisp", &["*.el"]),
|
("elisp", &["*.el"]),
|
||||||
("elixir", &["*.ex", "*.eex", "*.exs"]),
|
("elixir", &["*.ex", "*.eex", "*.exs"]),
|
||||||
@@ -147,9 +149,10 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("groovy", &["*.groovy", "*.gradle"]),
|
("groovy", &["*.groovy", "*.gradle"]),
|
||||||
("h", &["*.h", "*.hpp"]),
|
("h", &["*.h", "*.hpp"]),
|
||||||
("hbs", &["*.hbs"]),
|
("hbs", &["*.hbs"]),
|
||||||
("haskell", &["*.hs", "*.lhs"]),
|
("haskell", &["*.hs", "*.lhs", "*.cpphs", "*.c2hs", "*.hsc"]),
|
||||||
("hs", &["*.hs", "*.lhs"]),
|
("hs", &["*.hs", "*.lhs"]),
|
||||||
("html", &["*.htm", "*.html", "*.ejs"]),
|
("html", &["*.htm", "*.html", "*.ejs"]),
|
||||||
|
("idris", &["*.idr", "*.lidr"]),
|
||||||
("java", &["*.java", "*.jsp"]),
|
("java", &["*.java", "*.jsp"]),
|
||||||
("jinja", &["*.j2", "*.jinja", "*.jinja2"]),
|
("jinja", &["*.j2", "*.jinja", "*.jinja2"]),
|
||||||
("js", &[
|
("js", &[
|
||||||
@@ -200,6 +203,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
"makefile", "Makefile",
|
"makefile", "Makefile",
|
||||||
"*.mk", "*.mak"
|
"*.mk", "*.mak"
|
||||||
]),
|
]),
|
||||||
|
("mako", &["*.mako", "*.mao"]),
|
||||||
("markdown", &["*.markdown", "*.md", "*.mdown", "*.mkdn"]),
|
("markdown", &["*.markdown", "*.md", "*.mdown", "*.mkdn"]),
|
||||||
("md", &["*.markdown", "*.md", "*.mdown", "*.mkdn"]),
|
("md", &["*.markdown", "*.md", "*.mdown", "*.mkdn"]),
|
||||||
("man", &["*.[0-9lnpx]", "*.[0-9][cEFMmpSx]"]),
|
("man", &["*.[0-9lnpx]", "*.[0-9][cEFMmpSx]"]),
|
||||||
@@ -232,7 +236,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("ruby", &["Gemfile", "*.gemspec", ".irbrc", "Rakefile", "*.rb"]),
|
("ruby", &["Gemfile", "*.gemspec", ".irbrc", "Rakefile", "*.rb"]),
|
||||||
("rust", &["*.rs"]),
|
("rust", &["*.rs"]),
|
||||||
("sass", &["*.sass", "*.scss"]),
|
("sass", &["*.sass", "*.scss"]),
|
||||||
("scala", &["*.scala"]),
|
("scala", &["*.scala", "*.sbt"]),
|
||||||
("sh", &[
|
("sh", &[
|
||||||
// Portable/misc. init files
|
// Portable/misc. init files
|
||||||
".login", ".logout", ".profile", "profile",
|
".login", ".logout", ".profile", "profile",
|
||||||
|
271
src/app.rs
271
src/app.rs
@@ -82,7 +82,34 @@ pub fn app() -> App<'static, 'static> {
|
|||||||
/// the RIPGREP_BUILD_GIT_HASH env var is inspect for it. If that isn't set,
|
/// the RIPGREP_BUILD_GIT_HASH env var is inspect 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 {
|
||||||
// Let's say whether faster CPU instructions are enabled or not.
|
// Do we have a git hash?
|
||||||
|
// (Yes, if ripgrep was built on a machine with `git` installed.)
|
||||||
|
let hash = match revision_hash.or(option_env!("RIPGREP_BUILD_GIT_HASH")) {
|
||||||
|
None => String::new(),
|
||||||
|
Some(githash) => format!(" (rev {})", githash),
|
||||||
|
};
|
||||||
|
// Put everything together.
|
||||||
|
let runtime = runtime_cpu_features();
|
||||||
|
if runtime.is_empty() {
|
||||||
|
format!(
|
||||||
|
"{}{}\n{} (compiled)",
|
||||||
|
crate_version!(),
|
||||||
|
hash,
|
||||||
|
compile_cpu_features().join(" ")
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{}{}\n{} (compiled)\n{} (runtime)",
|
||||||
|
crate_version!(),
|
||||||
|
hash,
|
||||||
|
compile_cpu_features().join(" "),
|
||||||
|
runtime.join(" ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the relevant CPU features enabled at compile time.
|
||||||
|
fn compile_cpu_features() -> Vec<&'static str> {
|
||||||
let mut features = vec![];
|
let mut features = vec![];
|
||||||
if cfg!(feature = "simd-accel") {
|
if cfg!(feature = "simd-accel") {
|
||||||
features.push("+SIMD");
|
features.push("+SIMD");
|
||||||
@@ -94,14 +121,33 @@ pub fn long_version(revision_hash: Option<&str>) -> String {
|
|||||||
} else {
|
} else {
|
||||||
features.push("-AVX");
|
features.push("-AVX");
|
||||||
}
|
}
|
||||||
// Do we have a git hash?
|
features
|
||||||
// (Yes, if ripgrep was built on a machine with `git` installed.)
|
}
|
||||||
let hash = match revision_hash.or(option_env!("RIPGREP_BUILD_GIT_HASH")) {
|
|
||||||
None => String::new(),
|
/// Returns the relevant CPU features enabled at runtime.
|
||||||
Some(githash) => format!(" (rev {})", githash),
|
#[cfg(all(ripgrep_runtime_cpu, target_arch = "x86_64"))]
|
||||||
};
|
fn runtime_cpu_features() -> Vec<&'static str> {
|
||||||
// Put everything together.
|
// This is kind of a dirty violation of abstraction, since it assumes
|
||||||
format!("{}{}\n{}", crate_version!(), hash, features.join(" "))
|
// knowledge about what specific SIMD features are being used.
|
||||||
|
|
||||||
|
let mut features = vec![];
|
||||||
|
if is_x86_feature_detected!("ssse3") {
|
||||||
|
features.push("+SIMD");
|
||||||
|
} else {
|
||||||
|
features.push("-SIMD");
|
||||||
|
}
|
||||||
|
if is_x86_feature_detected!("avx2") {
|
||||||
|
features.push("+AVX");
|
||||||
|
} else {
|
||||||
|
features.push("-AVX");
|
||||||
|
}
|
||||||
|
features
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the relevant CPU features enabled at runtime.
|
||||||
|
#[cfg(not(all(ripgrep_runtime_cpu, target_arch = "x86_64")))]
|
||||||
|
fn runtime_cpu_features() -> Vec<&'static str> {
|
||||||
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Arg is a light alias for a clap::Arg that is specialized to compile time
|
/// Arg is a light alias for a clap::Arg that is specialized to compile time
|
||||||
@@ -502,6 +548,7 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
|||||||
flag_context_separator(&mut args);
|
flag_context_separator(&mut args);
|
||||||
flag_count(&mut args);
|
flag_count(&mut args);
|
||||||
flag_count_matches(&mut args);
|
flag_count_matches(&mut args);
|
||||||
|
flag_crlf(&mut args);
|
||||||
flag_debug(&mut args);
|
flag_debug(&mut args);
|
||||||
flag_dfa_size_limit(&mut args);
|
flag_dfa_size_limit(&mut args);
|
||||||
flag_encoding(&mut args);
|
flag_encoding(&mut args);
|
||||||
@@ -527,6 +574,7 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
|||||||
flag_max_filesize(&mut args);
|
flag_max_filesize(&mut args);
|
||||||
flag_mmap(&mut args);
|
flag_mmap(&mut args);
|
||||||
flag_multiline(&mut args);
|
flag_multiline(&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_global(&mut args);
|
flag_no_ignore_global(&mut args);
|
||||||
@@ -535,9 +583,12 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
|||||||
flag_no_ignore_vcs(&mut args);
|
flag_no_ignore_vcs(&mut args);
|
||||||
flag_no_messages(&mut args);
|
flag_no_messages(&mut args);
|
||||||
flag_null(&mut args);
|
flag_null(&mut args);
|
||||||
|
flag_null_data(&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_unicode(&mut args);
|
||||||
flag_pre(&mut args);
|
flag_pre(&mut args);
|
||||||
flag_pretty(&mut args);
|
flag_pretty(&mut args);
|
||||||
flag_quiet(&mut args);
|
flag_quiet(&mut args);
|
||||||
@@ -812,6 +863,32 @@ This overrides the --count flag. Note that when --count is combined with
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flag_crlf(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str = "Support CRLF line terminators (useful on Windows).";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
When enabled, ripgrep will treat CRLF ('\\r\\n') as a line terminator instead
|
||||||
|
of just '\\n'.
|
||||||
|
|
||||||
|
Principally, this permits '$' in regex patterns to match just before CRLF
|
||||||
|
instead of just before LF. The underlying regex engine may not support this
|
||||||
|
natively, so ripgrep will translate all instances of '$' to '(?:\\r??$)'. This
|
||||||
|
may produce slightly different than desired match offsets. It is intended as a
|
||||||
|
work-around until the regex engine supports this natively.
|
||||||
|
|
||||||
|
CRLF support can be disabled with --no-crlf.
|
||||||
|
");
|
||||||
|
let arg = RGArg::switch("crlf")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.overrides("no-crlf")
|
||||||
|
.overrides("null-data");
|
||||||
|
args.push(arg);
|
||||||
|
|
||||||
|
let arg = RGArg::switch("no-crlf")
|
||||||
|
.hidden()
|
||||||
|
.overrides("crlf");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
fn flag_debug(args: &mut Vec<RGArg>) {
|
fn flag_debug(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Show debug messages.";
|
const SHORT: &str = "Show debug messages.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
@@ -858,10 +935,17 @@ 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. Other supported values can be found
|
||||||
in the list of labels here:
|
in the list of labels here:
|
||||||
https://encoding.spec.whatwg.org/#concept-encoding-get
|
https://encoding.spec.whatwg.org/#concept-encoding-get
|
||||||
|
|
||||||
|
This flag can be disabled with --no-encoding.
|
||||||
");
|
");
|
||||||
let arg = RGArg::flag("encoding", "ENCODING").short("E")
|
let arg = RGArg::flag("encoding", "ENCODING").short("E")
|
||||||
.help(SHORT).long_help(LONG);
|
.help(SHORT).long_help(LONG);
|
||||||
args.push(arg);
|
args.push(arg);
|
||||||
|
|
||||||
|
let arg = RGArg::switch("no-encoding")
|
||||||
|
.hidden()
|
||||||
|
.overrides("encoding");
|
||||||
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flag_file(args: &mut Vec<RGArg>) {
|
fn flag_file(args: &mut Vec<RGArg>) {
|
||||||
@@ -1112,10 +1196,10 @@ was inverted.
|
|||||||
statistics about the search across all files.
|
statistics about the search across all files.
|
||||||
|
|
||||||
Since file paths or the contents of files are not guaranteed to be valid UTF-8
|
Since file paths or the contents of files are not guaranteed to be valid UTF-8
|
||||||
and JSON itself must be valid UTF-8, ripgrep will emit all data elements as
|
and JSON itself must be representable by a Unicode encoding, ripgrep will emit
|
||||||
objects with one of two keys: 'text' or 'bytes'. 'text' is a normal JSON string
|
all data elements as objects with one of two keys: 'text' or 'bytes'. 'text' is
|
||||||
the data is valid UTF-8 while 'bytes' is the base64 encoded contents of the
|
a normal JSON string when the data is valid UTF-8 while 'bytes' is the base64
|
||||||
bytes.
|
encoded contents of the data.
|
||||||
|
|
||||||
The JSON Lines format is only supported for showing search results. It cannot
|
The JSON Lines format is only supported for showing search results. It cannot
|
||||||
be used with other flags that emit other types of output, such as --files,
|
be used with other flags that emit other types of output, such as --files,
|
||||||
@@ -1127,6 +1211,9 @@ Other flags that control aspects of the standard output such as
|
|||||||
--only-matching, --heading, --replace, --max-columns, etc., have no effect
|
--only-matching, --heading, --replace, --max-columns, etc., have no effect
|
||||||
when --json is set.
|
when --json is set.
|
||||||
|
|
||||||
|
A more complete description of the JSON format used can be found here:
|
||||||
|
https://docs.rs/grep-printer/*/grep_printer/struct.JSON.html
|
||||||
|
|
||||||
The JSON Lines format can be disabled with --no-json.
|
The JSON Lines format can be disabled with --no-json.
|
||||||
");
|
");
|
||||||
let arg = RGArg::switch("json")
|
let arg = RGArg::switch("json")
|
||||||
@@ -1276,6 +1363,38 @@ fn flag_multiline(args: &mut Vec<RGArg>) {
|
|||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
Enable matching across multiple lines.
|
Enable matching across multiple lines.
|
||||||
|
|
||||||
|
When multiline mode is enabled, ripgrep will lift the restriction that a match
|
||||||
|
cannot include a line terminator. For example, when multiline mode is not
|
||||||
|
enabled (the default), then the regex '\\p{any}' will match any Unicode
|
||||||
|
codepoint other than '\\n'. Similarly, the regex '\\n' is explicitly forbidden,
|
||||||
|
and if you try to use it, ripgrep will return an error. However, when multiline
|
||||||
|
mode is enabled, '\\p{any}' will match any Unicode codepoint, including '\\n',
|
||||||
|
and regexes like '\\n' are permitted.
|
||||||
|
|
||||||
|
An important caveat is that multiline mode does not change the match semantics
|
||||||
|
of '.'. Namely, in most regex matchers, a '.' will by default match any
|
||||||
|
character other than '\\n', and this is true in ripgrep as well. In order to
|
||||||
|
make '.' match '\\n', you must enable the \"dot all\" flag inside the regex.
|
||||||
|
For example, both '(?s).' and '(?s:.)' have the same semantics, where '.' will
|
||||||
|
match any character, including '\\n'. Alternatively, the '--multiline-dotall'
|
||||||
|
flag may be passed to make the \"dot all\" behavior the default. This flag only
|
||||||
|
applies when multiline search is enabled.
|
||||||
|
|
||||||
|
There is no limit on the number of the lines that a single match can span.
|
||||||
|
|
||||||
|
**WARNING**: Because of how the underlying regex engine works, multiline
|
||||||
|
searches may be slower than normal line-oriented searches, and they may also
|
||||||
|
use more memory. In particular, when multiline mode is enabled, ripgrep
|
||||||
|
requires that each file it searches is laid out contiguously in memory
|
||||||
|
(either by reading it onto the heap or by memory-mapping it). Things that
|
||||||
|
cannot be memory-mapped (such as stdin) will be consumed until EOF before
|
||||||
|
searching can begin. In general, ripgrep will only do these things when
|
||||||
|
necessary. Specifically, if the --multiline flag is provided but the regex
|
||||||
|
does not contain patterns that would match '\\n' characters, then ripgrep
|
||||||
|
will automatically avoid reading each file into memory before searching it.
|
||||||
|
Nevertheless, if you only care about matches spanning at most one line, then it
|
||||||
|
is always better to disable multiline mode.
|
||||||
|
|
||||||
This flag can be disabled with --no-multiline.
|
This flag can be disabled with --no-multiline.
|
||||||
");
|
");
|
||||||
let arg = RGArg::switch("multiline").short("U")
|
let arg = RGArg::switch("multiline").short("U")
|
||||||
@@ -1289,6 +1408,37 @@ This flag can be disabled with --no-multiline.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flag_multiline_dotall(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str = "Make '.' match new lines when multiline is enabled.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
This flag enables \"dot all\" in your regex pattern, which causes '.' to match
|
||||||
|
newlines when multiline searching is enabled. This flag has no effect if
|
||||||
|
multiline searching isn't enabled with the --multiline flag.
|
||||||
|
|
||||||
|
Normally, a '.' will match any character except newlines. While this behavior
|
||||||
|
typically isn't relevant for line-oriented matching (since matches can span at
|
||||||
|
most one line), this can be useful when searching with the -U/--multiline flag.
|
||||||
|
By default, the multiline mode runs without this flag.
|
||||||
|
|
||||||
|
This flag is generally intended to be used in an alias or your ripgrep config
|
||||||
|
file if you prefer \"dot all\" semantics by default. Note that regardless of
|
||||||
|
whether this flag is used, \"dot all\" semantics can still be controlled via
|
||||||
|
inline flags in the regex pattern itself, e.g., '(?s:.)' always enables \"dot
|
||||||
|
all\" whereas '(?-s:.)' always disables \"dot all\".
|
||||||
|
|
||||||
|
This flag can be disabled with --no-multiline-dotall.
|
||||||
|
");
|
||||||
|
let arg = RGArg::switch("multiline-dotall")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.overrides("no-multiline-dotall");
|
||||||
|
args.push(arg);
|
||||||
|
|
||||||
|
let arg = RGArg::switch("no-multiline-dotall")
|
||||||
|
.hidden()
|
||||||
|
.overrides("multiline-dotall");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
fn flag_no_config(args: &mut Vec<RGArg>) {
|
fn flag_no_config(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Never read configuration files.";
|
const SHORT: &str = "Never read configuration files.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
@@ -1327,7 +1477,7 @@ fn flag_no_ignore_global(args: &mut Vec<RGArg>) {
|
|||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
Don't respect ignore files that come from \"global\" sources such as git's
|
Don't respect ignore files that come from \"global\" sources such as git's
|
||||||
`core.excludesFile` configuration option (which defaults to
|
`core.excludesFile` configuration option (which defaults to
|
||||||
`$HOME/.config/git/ignore).
|
`$HOME/.config/git/ignore`).
|
||||||
|
|
||||||
This flag can be disabled with the --ignore-global flag.
|
This flag can be disabled with the --ignore-global flag.
|
||||||
");
|
");
|
||||||
@@ -1431,6 +1581,29 @@ for use with xargs.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flag_null_data(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str = "Use NUL as a line terminator instead of \\n.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
Enabling this option causes ripgrep to use NUL as a line terminator instead of
|
||||||
|
the default of '\\n'.
|
||||||
|
|
||||||
|
This is useful when searching large binary files that would otherwise have very
|
||||||
|
long lines if '\\n' were used as the line terminator. In particular, ripgrep
|
||||||
|
requires that, at a minimum, each line must fit into memory. Using NUL instead
|
||||||
|
can be a useful stopgap to keep memory requirements low and avoid OOM (out of
|
||||||
|
memory) conditions.
|
||||||
|
|
||||||
|
This is also useful for processing NUL delimited data, such as that emitted
|
||||||
|
when using ripgrep's -0/--null flag or find's --print0 flag.
|
||||||
|
|
||||||
|
Using this flag implies -a/--text.
|
||||||
|
");
|
||||||
|
let arg = RGArg::switch("null-data")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.overrides("crlf");
|
||||||
|
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!("\
|
||||||
@@ -1472,6 +1645,72 @@ without needing to modify the pattern.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flag_pcre2(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str = "Enable PCRE2 matching.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
When this flag is present, ripgrep will use the PCRE2 regex engine instead of
|
||||||
|
its default regex engine.
|
||||||
|
|
||||||
|
This is generally useful when you want to use features such as look-around
|
||||||
|
or backreferences.
|
||||||
|
|
||||||
|
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
|
||||||
|
an error message and exiting.
|
||||||
|
|
||||||
|
This flag can be disabled with --no-pcre2.
|
||||||
|
");
|
||||||
|
let arg = RGArg::switch("pcre2").short("P")
|
||||||
|
.help(SHORT).long_help(LONG)
|
||||||
|
.overrides("no-pcre2");
|
||||||
|
args.push(arg);
|
||||||
|
|
||||||
|
let arg = RGArg::switch("no-pcre2")
|
||||||
|
.hidden()
|
||||||
|
.overrides("pcre2");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flag_pcre2_unicode(args: &mut Vec<RGArg>) {
|
||||||
|
const SHORT: &str = "Enable Unicode mode for PCRE2 matching.";
|
||||||
|
const LONG: &str = long!("\
|
||||||
|
When PCRE2 matching is enabled, this flag will enable Unicode mode. If PCRE2
|
||||||
|
matching is not enabled, then this flag has no effect.
|
||||||
|
|
||||||
|
This flag is enabled by default when PCRE2 matching is enabled.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
This flag can be disabled with --no-pcre2-unicode.
|
||||||
|
");
|
||||||
|
let arg = RGArg::switch("pcre2-unicode")
|
||||||
|
.help(SHORT).long_help(LONG);
|
||||||
|
args.push(arg);
|
||||||
|
|
||||||
|
let arg = RGArg::switch("no-pcre2-unicode")
|
||||||
|
.hidden()
|
||||||
|
.overrides("pcre2-unicode");
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
fn flag_pretty(args: &mut Vec<RGArg>) {
|
fn flag_pretty(args: &mut Vec<RGArg>) {
|
||||||
const SHORT: &str = "Alias for --color always --heading --line-number.";
|
const SHORT: &str = "Alias for --color always --heading --line-number.";
|
||||||
const LONG: &str = long!("\
|
const LONG: &str = long!("\
|
||||||
@@ -1690,7 +1929,7 @@ This flag can be disabled with --no-stats.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
|
|
||||||
let arg = RGArg::switch("no-stats")
|
let arg = RGArg::switch("no-stats")
|
||||||
.help(SHORT).long_help(LONG)
|
.hidden()
|
||||||
.overrides("stats");
|
.overrides("stats");
|
||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
@@ -1748,7 +1987,7 @@ This flag can be disabled with --no-trim.
|
|||||||
args.push(arg);
|
args.push(arg);
|
||||||
|
|
||||||
let arg = RGArg::switch("no-trim")
|
let arg = RGArg::switch("no-trim")
|
||||||
.help(SHORT).long_help(LONG)
|
.hidden()
|
||||||
.overrides("trim");
|
.overrides("trim");
|
||||||
args.push(arg);
|
args.push(arg);
|
||||||
}
|
}
|
||||||
|
156
src/args.rs
156
src/args.rs
@@ -9,8 +9,10 @@ use std::sync::Arc;
|
|||||||
use atty;
|
use atty;
|
||||||
use clap;
|
use clap;
|
||||||
use grep::matcher::LineTerminator;
|
use grep::matcher::LineTerminator;
|
||||||
use grep::searcher::{
|
#[cfg(feature = "pcre2")]
|
||||||
BinaryDetection, Encoding, MmapChoice, Searcher, SearcherBuilder,
|
use grep::pcre2::{
|
||||||
|
RegexMatcher as PCRE2RegexMatcher,
|
||||||
|
RegexMatcherBuilder as PCRE2RegexMatcherBuilder,
|
||||||
};
|
};
|
||||||
use grep::printer::{
|
use grep::printer::{
|
||||||
ColorSpecs, Stats,
|
ColorSpecs, Stats,
|
||||||
@@ -18,7 +20,13 @@ use grep::printer::{
|
|||||||
Standard, StandardBuilder,
|
Standard, StandardBuilder,
|
||||||
Summary, SummaryBuilder, SummaryKind,
|
Summary, SummaryBuilder, SummaryKind,
|
||||||
};
|
};
|
||||||
use grep::regex::{RegexMatcher, RegexMatcherBuilder};
|
use grep::regex::{
|
||||||
|
RegexMatcher as RustRegexMatcher,
|
||||||
|
RegexMatcherBuilder as RustRegexMatcherBuilder,
|
||||||
|
};
|
||||||
|
use grep::searcher::{
|
||||||
|
BinaryDetection, Encoding, MmapChoice, Searcher, SearcherBuilder,
|
||||||
|
};
|
||||||
use ignore::overrides::{Override, OverrideBuilder};
|
use ignore::overrides::{Override, OverrideBuilder};
|
||||||
use ignore::types::{FileTypeDef, Types, TypesBuilder};
|
use ignore::types::{FileTypeDef, Types, TypesBuilder};
|
||||||
use ignore::{Walk, WalkBuilder, WalkParallel};
|
use ignore::{Walk, WalkBuilder, WalkParallel};
|
||||||
@@ -419,7 +427,33 @@ impl ArgMatches {
|
|||||||
///
|
///
|
||||||
/// If there was a problem building the matcher (e.g., a syntax error),
|
/// If there was a problem building the matcher (e.g., a syntax error),
|
||||||
/// then this returns an error.
|
/// then this returns an error.
|
||||||
|
#[cfg(feature = "pcre2")]
|
||||||
fn matcher(&self, patterns: &[String]) -> Result<PatternMatcher> {
|
fn matcher(&self, patterns: &[String]) -> Result<PatternMatcher> {
|
||||||
|
if self.is_present("pcre2") {
|
||||||
|
let matcher = self.matcher_pcre2(patterns)?;
|
||||||
|
Ok(PatternMatcher::PCRE2(matcher))
|
||||||
|
} else {
|
||||||
|
let matcher = match self.matcher_rust(patterns) {
|
||||||
|
Ok(matcher) => matcher,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(From::from(suggest_pcre2(err.to_string())));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(PatternMatcher::RustRegex(matcher))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the matcher that should be used for searching.
|
||||||
|
///
|
||||||
|
/// If there was a problem building the matcher (e.g., a syntax error),
|
||||||
|
/// then this returns an error.
|
||||||
|
#[cfg(not(feature = "pcre2"))]
|
||||||
|
fn matcher(&self, patterns: &[String]) -> Result<PatternMatcher> {
|
||||||
|
if self.is_present("pcre2") {
|
||||||
|
return Err(From::from(
|
||||||
|
"PCRE2 is not available in this build of ripgrep",
|
||||||
|
));
|
||||||
|
}
|
||||||
let matcher = self.matcher_rust(patterns)?;
|
let matcher = self.matcher_rust(patterns)?;
|
||||||
Ok(PatternMatcher::RustRegex(matcher))
|
Ok(PatternMatcher::RustRegex(matcher))
|
||||||
}
|
}
|
||||||
@@ -428,18 +462,37 @@ impl ArgMatches {
|
|||||||
///
|
///
|
||||||
/// If there was a problem building the matcher (such as a regex syntax
|
/// If there was a problem building the matcher (such as a regex syntax
|
||||||
/// error), then an error is returned.
|
/// error), then an error is returned.
|
||||||
fn matcher_rust(&self, patterns: &[String]) -> Result<RegexMatcher> {
|
fn matcher_rust(&self, patterns: &[String]) -> Result<RustRegexMatcher> {
|
||||||
let mut builder = RegexMatcherBuilder::new();
|
let mut builder = RustRegexMatcherBuilder::new();
|
||||||
builder
|
builder
|
||||||
.case_smart(self.case_smart())
|
.case_smart(self.case_smart())
|
||||||
.case_insensitive(self.case_insensitive())
|
.case_insensitive(self.case_insensitive())
|
||||||
.multi_line(true)
|
.multi_line(true)
|
||||||
.dot_matches_new_line(false)
|
|
||||||
.unicode(true)
|
.unicode(true)
|
||||||
.octal(false)
|
.octal(false)
|
||||||
.word(self.is_present("word-regexp"));
|
.word(self.is_present("word-regexp"));
|
||||||
if !self.is_present("multiline") {
|
if self.is_present("multiline") {
|
||||||
builder.line_terminator(Some(b'\n'));
|
builder.dot_matches_new_line(self.is_present("multiline-dotall"));
|
||||||
|
if self.is_present("crlf") {
|
||||||
|
builder
|
||||||
|
.crlf(true)
|
||||||
|
.line_terminator(None);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
.line_terminator(Some(b'\n'))
|
||||||
|
.dot_matches_new_line(false);
|
||||||
|
if self.is_present("crlf") {
|
||||||
|
builder.crlf(true);
|
||||||
|
}
|
||||||
|
// We don't need to set this in multiline mode since mulitline
|
||||||
|
// matchers don't use optimizations related to line terminators.
|
||||||
|
// Moreover, a mulitline regex used with --null-data should
|
||||||
|
// be allowed to match NUL bytes explicitly, which this would
|
||||||
|
// otherwise forbid.
|
||||||
|
if self.is_present("null-data") {
|
||||||
|
builder.line_terminator(Some(b'\x00'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(limit) = self.regex_size_limit()? {
|
if let Some(limit) = self.regex_size_limit()? {
|
||||||
builder.size_limit(limit);
|
builder.size_limit(limit);
|
||||||
@@ -450,6 +503,43 @@ impl ArgMatches {
|
|||||||
Ok(builder.build(&patterns.join("|"))?)
|
Ok(builder.build(&patterns.join("|"))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a matcher using PCRE2.
|
||||||
|
///
|
||||||
|
/// If there was a problem building the matcher (such as a regex syntax
|
||||||
|
/// error), then an error is returned.
|
||||||
|
#[cfg(feature = "pcre2")]
|
||||||
|
fn matcher_pcre2(&self, patterns: &[String]) -> Result<PCRE2RegexMatcher> {
|
||||||
|
let mut builder = PCRE2RegexMatcherBuilder::new();
|
||||||
|
builder
|
||||||
|
.case_smart(self.case_smart())
|
||||||
|
.caseless(self.case_insensitive())
|
||||||
|
.multi_line(true)
|
||||||
|
.word(self.is_present("word-regexp"));
|
||||||
|
// For whatever reason, the JIT craps out during compilation with a
|
||||||
|
// "no more memory" error on 32 bit systems. So don't use it there.
|
||||||
|
if !cfg!(target_pointer_width = "32") {
|
||||||
|
builder.jit(true);
|
||||||
|
}
|
||||||
|
if self.pcre2_unicode() {
|
||||||
|
builder.utf(true).ucp(true);
|
||||||
|
if self.encoding()?.is_some() {
|
||||||
|
// SAFETY: If an encoding was specified, then we're guaranteed
|
||||||
|
// to get valid UTF-8, so we can disable PCRE2's UTF checking.
|
||||||
|
// (Feeding invalid UTF-8 to PCRE2 is UB.)
|
||||||
|
unsafe {
|
||||||
|
builder.disable_utf_check();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.is_present("multiline") {
|
||||||
|
builder.dotall(self.is_present("multiline-dotall"));
|
||||||
|
}
|
||||||
|
if self.is_present("crlf") {
|
||||||
|
builder.crlf(true);
|
||||||
|
}
|
||||||
|
Ok(builder.build(&patterns.join("|"))?)
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a JSON printer that writes results to the given writer.
|
/// Build a JSON printer that writes results to the given writer.
|
||||||
fn printer_json<W: io::Write>(&self, wtr: W) -> Result<JSON<W>> {
|
fn printer_json<W: io::Write>(&self, wtr: W) -> Result<JSON<W>> {
|
||||||
let mut builder = JSONBuilder::new();
|
let mut builder = JSONBuilder::new();
|
||||||
@@ -530,9 +620,17 @@ impl ArgMatches {
|
|||||||
/// Build a searcher from the command line parameters.
|
/// Build a searcher from the command line parameters.
|
||||||
fn searcher(&self, paths: &[PathBuf]) -> Result<Searcher> {
|
fn searcher(&self, paths: &[PathBuf]) -> Result<Searcher> {
|
||||||
let (ctx_before, ctx_after) = self.contexts()?;
|
let (ctx_before, ctx_after) = self.contexts()?;
|
||||||
|
let line_term =
|
||||||
|
if self.is_present("crlf") {
|
||||||
|
LineTerminator::crlf()
|
||||||
|
} else if self.is_present("null-data") {
|
||||||
|
LineTerminator::byte(b'\x00')
|
||||||
|
} else {
|
||||||
|
LineTerminator::byte(b'\n')
|
||||||
|
};
|
||||||
let mut builder = SearcherBuilder::new();
|
let mut builder = SearcherBuilder::new();
|
||||||
builder
|
builder
|
||||||
.line_terminator(LineTerminator::byte(b'\n'))
|
.line_terminator(line_term)
|
||||||
.invert_match(self.is_present("invert-match"))
|
.invert_match(self.is_present("invert-match"))
|
||||||
.line_number(self.line_number(paths))
|
.line_number(self.line_number(paths))
|
||||||
.multi_line(self.is_present("multiline"))
|
.multi_line(self.is_present("multiline"))
|
||||||
@@ -593,7 +691,11 @@ impl ArgMatches {
|
|||||||
impl ArgMatches {
|
impl ArgMatches {
|
||||||
/// Returns the form of binary detection to perform.
|
/// Returns the form of binary detection to perform.
|
||||||
fn binary_detection(&self) -> BinaryDetection {
|
fn binary_detection(&self) -> BinaryDetection {
|
||||||
if self.is_present("text") || self.unrestricted_count() >= 3 {
|
let none =
|
||||||
|
self.is_present("text")
|
||||||
|
|| self.unrestricted_count() >= 3
|
||||||
|
|| self.is_present("null-data");
|
||||||
|
if none {
|
||||||
BinaryDetection::none()
|
BinaryDetection::none()
|
||||||
} else {
|
} else {
|
||||||
BinaryDetection::quit(b'\x00')
|
BinaryDetection::quit(b'\x00')
|
||||||
@@ -736,7 +838,11 @@ impl ArgMatches {
|
|||||||
/// encoding is present, the Searcher will still do BOM sniffing for UTF-16
|
/// encoding is present, the Searcher will still do BOM sniffing for UTF-16
|
||||||
/// and transcode seamlessly.
|
/// and transcode seamlessly.
|
||||||
fn encoding(&self) -> Result<Option<Encoding>> {
|
fn encoding(&self) -> Result<Option<Encoding>> {
|
||||||
|
if self.is_present("no-encoding") {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
let label = match self.value_of_lossy("encoding") {
|
let label = match self.value_of_lossy("encoding") {
|
||||||
|
None if self.pcre2_unicode() => "utf-8".to_string(),
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
Some(label) => label,
|
Some(label) => label,
|
||||||
};
|
};
|
||||||
@@ -763,7 +869,6 @@ impl ArgMatches {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Returns true if and only if matches should be grouped with file name
|
/// Returns true if and only if matches should be grouped with file name
|
||||||
/// headings.
|
/// headings.
|
||||||
fn heading(&self) -> bool {
|
fn heading(&self) -> bool {
|
||||||
@@ -814,6 +919,9 @@ impl ArgMatches {
|
|||||||
if self.is_present("no-line-number") {
|
if self.is_present("no-line-number") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if self.output_kind() == OutputKind::JSON {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// A few things can imply counting line numbers. In particular, we
|
// A few things can imply counting line numbers. In particular, we
|
||||||
// generally want to show line numbers by default when printing to a
|
// generally want to show line numbers by default when printing to a
|
||||||
@@ -852,7 +960,7 @@ impl ArgMatches {
|
|||||||
// in a data structure that depends on immutability. Generally
|
// in a data structure that depends on immutability. Generally
|
||||||
// speaking, the worst thing that can happen is a SIGBUS (if the
|
// speaking, the worst thing that can happen is a SIGBUS (if the
|
||||||
// underlying file is truncated while reading it), which will cause
|
// underlying file is truncated while reading it), which will cause
|
||||||
// ripgrep to abort.
|
// ripgrep to abort. This reasoning should be treated as suspect.
|
||||||
let maybe = unsafe { MmapChoice::auto() };
|
let maybe = unsafe { MmapChoice::auto() };
|
||||||
let never = MmapChoice::never();
|
let never = MmapChoice::never();
|
||||||
if self.is_present("no-mmap") {
|
if self.is_present("no-mmap") {
|
||||||
@@ -1215,6 +1323,13 @@ impl ArgMatches {
|
|||||||
self.occurrences_of("unrestricted")
|
self.occurrences_of("unrestricted")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if PCRE2's Unicode mode should be enabled.
|
||||||
|
fn pcre2_unicode(&self) -> bool {
|
||||||
|
// PCRE2 Unicode is enabled by default, so only disable it when told
|
||||||
|
// to do so explicitly.
|
||||||
|
self.is_present("pcre2") && !self.is_present("no-pcre2-unicode")
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if and only if file names containing each match should
|
/// Returns true if and only if file names containing each match should
|
||||||
/// be emitted.
|
/// be emitted.
|
||||||
fn with_filename(&self, paths: &[PathBuf]) -> bool {
|
fn with_filename(&self, paths: &[PathBuf]) -> bool {
|
||||||
@@ -1353,6 +1468,21 @@ fn pattern_to_str(s: &OsStr) -> Result<&str> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// add a message to suggest the use of -P/--pcre2.
|
||||||
|
#[cfg(feature = "pcre2")]
|
||||||
|
fn suggest_pcre2(msg: String) -> String {
|
||||||
|
if !msg.contains("backreferences") && !msg.contains("look-around") {
|
||||||
|
msg
|
||||||
|
} else {
|
||||||
|
format!("{}
|
||||||
|
|
||||||
|
Consider enabling PCRE2 with the --pcre2 flag, which can handle backreferences
|
||||||
|
and look-around.", 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(
|
||||||
@@ -1396,6 +1526,8 @@ fn stdin_is_readable() -> bool {
|
|||||||
Ok(handle) => handle,
|
Ok(handle) => handle,
|
||||||
};
|
};
|
||||||
let raw_handle = handle.as_raw_handle();
|
let raw_handle = handle.as_raw_handle();
|
||||||
|
// SAFETY: As far as I can tell, it's not possible to use GetFileType in
|
||||||
|
// a way that violates safety. We give it a handle and we get an integer.
|
||||||
let ft = unsafe { GetFileType(raw_handle) };
|
let ft = unsafe { GetFileType(raw_handle) };
|
||||||
ft == FILE_TYPE_DISK || ft == FILE_TYPE_PIPE
|
ft == FILE_TYPE_DISK || ft == FILE_TYPE_PIPE
|
||||||
}
|
}
|
||||||
|
@@ -41,10 +41,10 @@ mod search;
|
|||||||
mod subject;
|
mod subject;
|
||||||
mod unescape;
|
mod unescape;
|
||||||
|
|
||||||
pub type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>;
|
type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>;
|
||||||
|
|
||||||
pub fn main() {
|
fn main() {
|
||||||
match Args::parse().and_then(run) {
|
match Args::parse().and_then(try_main) {
|
||||||
Ok(true) => process::exit(0),
|
Ok(true) => process::exit(0),
|
||||||
Ok(false) => process::exit(1),
|
Ok(false) => process::exit(1),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -54,7 +54,7 @@ pub fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(args: Args) -> Result<bool> {
|
fn try_main(args: Args) -> Result<bool> {
|
||||||
use args::Command::*;
|
use args::Command::*;
|
||||||
|
|
||||||
match args.command()? {
|
match args.command()? {
|
||||||
|
@@ -3,8 +3,10 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use grep::matcher::Matcher;
|
use grep::matcher::Matcher;
|
||||||
|
#[cfg(feature = "pcre2")]
|
||||||
|
use grep::pcre2::{RegexMatcher as PCRE2RegexMatcher};
|
||||||
use grep::printer::{JSON, Standard, Summary, Stats};
|
use grep::printer::{JSON, Standard, Summary, Stats};
|
||||||
use grep::regex::RegexMatcher;
|
use grep::regex::{RegexMatcher as RustRegexMatcher};
|
||||||
use grep::searcher::Searcher;
|
use grep::searcher::Searcher;
|
||||||
use serde_json as json;
|
use serde_json as json;
|
||||||
use termcolor::WriteColor;
|
use termcolor::WriteColor;
|
||||||
@@ -131,7 +133,9 @@ impl SearchResult {
|
|||||||
/// The pattern matcher used by a search worker.
|
/// The pattern matcher used by a search worker.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PatternMatcher {
|
pub enum PatternMatcher {
|
||||||
RustRegex(RegexMatcher),
|
RustRegex(RustRegexMatcher),
|
||||||
|
#[cfg(feature = "pcre2")]
|
||||||
|
PCRE2(PCRE2RegexMatcher),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The printer used by a search worker.
|
/// The printer used by a search worker.
|
||||||
@@ -295,6 +299,8 @@ impl<W: WriteColor> SearchWorker<W> {
|
|||||||
let (searcher, printer) = (&mut self.searcher, &mut self.printer);
|
let (searcher, printer) = (&mut self.searcher, &mut self.printer);
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
RustRegex(ref m) => search_path(m, searcher, printer, path),
|
RustRegex(ref m) => search_path(m, searcher, printer, path),
|
||||||
|
#[cfg(feature = "pcre2")]
|
||||||
|
PCRE2(ref m) => search_path(m, searcher, printer, path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,6 +323,8 @@ impl<W: WriteColor> SearchWorker<W> {
|
|||||||
let (searcher, printer) = (&mut self.searcher, &mut self.printer);
|
let (searcher, printer) = (&mut self.searcher, &mut self.printer);
|
||||||
match self.matcher {
|
match self.matcher {
|
||||||
RustRegex(ref m) => search_reader(m, searcher, printer, path, rdr),
|
RustRegex(ref m) => search_reader(m, searcher, printer, path, rdr),
|
||||||
|
#[cfg(feature = "pcre2")]
|
||||||
|
PCRE2(ref m) => search_reader(m, searcher, printer, path, rdr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
631
tests/feature.rs
Normal file
631
tests/feature.rs
Normal file
@@ -0,0 +1,631 @@
|
|||||||
|
use hay::{SHERLOCK, SHERLOCK_CRLF};
|
||||||
|
use util::{Dir, TestCommand, sort_lines};
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||||
|
rgtest!(f1_sjis, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_bytes(
|
||||||
|
"foo",
|
||||||
|
b"\x84Y\x84u\x84\x82\x84|\x84\x80\x84{ \x84V\x84\x80\x84|\x84}\x84\x83"
|
||||||
|
);
|
||||||
|
cmd.arg("-Esjis").arg("Шерлок Холмс");
|
||||||
|
eqnice!("foo:Шерлок Холмс\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||||
|
rgtest!(f1_utf16_auto, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_bytes(
|
||||||
|
"foo",
|
||||||
|
b"\xff\xfe(\x045\x04@\x04;\x04>\x04:\x04 \x00%\x04>\x04;\x04<\x04A\x04"
|
||||||
|
);
|
||||||
|
cmd.arg("Шерлок Холмс");
|
||||||
|
eqnice!("foo:Шерлок Холмс\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||||
|
rgtest!(f1_utf16_explicit, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_bytes(
|
||||||
|
"foo",
|
||||||
|
b"\xff\xfe(\x045\x04@\x04;\x04>\x04:\x04 \x00%\x04>\x04;\x04<\x04A\x04"
|
||||||
|
);
|
||||||
|
cmd.arg("-Eutf-16le").arg("Шерлок Холмс");
|
||||||
|
eqnice!("foo:Шерлок Холмс\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||||
|
rgtest!(f1_eucjp, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_bytes(
|
||||||
|
"foo",
|
||||||
|
b"\xa7\xba\xa7\xd6\xa7\xe2\xa7\xdd\xa7\xe0\xa7\xdc \xa7\xb7\xa7\xe0\xa7\xdd\xa7\xde\xa7\xe3"
|
||||||
|
);
|
||||||
|
cmd.arg("-Eeuc-jp").arg("Шерлок Холмс");
|
||||||
|
eqnice!("foo:Шерлок Холмс\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||||
|
rgtest!(f1_unknown_encoding, |_: Dir, mut cmd: TestCommand| {
|
||||||
|
cmd.arg("-Efoobar").assert_non_empty_stderr();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||||
|
rgtest!(f1_replacement_encoding, |_: Dir, mut cmd: TestCommand| {
|
||||||
|
cmd.arg("-Ecsiso2022kr").assert_non_empty_stderr();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/7
|
||||||
|
rgtest!(f7, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("pat", "Sherlock\nHolmes");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
Holmeses, success in the province of detective work must always
|
||||||
|
be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.arg("-fpat").arg("sherlock").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/7
|
||||||
|
rgtest!(f7_stdin, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
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!(expected, cmd.arg("-f-").pipe("Sherlock"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/20
|
||||||
|
rgtest!(f20_no_filename, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--no-filename");
|
||||||
|
|
||||||
|
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.arg("--no-filename").arg("Sherlock").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/34
|
||||||
|
rgtest!(f34_only_matching, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
sherlock:Sherlock
|
||||||
|
sherlock:Sherlock
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.arg("-o").arg("Sherlock").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/34
|
||||||
|
rgtest!(f34_only_matching_line_column, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
sherlock:1:57:Sherlock
|
||||||
|
sherlock:3:49:Sherlock
|
||||||
|
";
|
||||||
|
cmd.arg("-o").arg("--column").arg("-n").arg("Sherlock");
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/45
|
||||||
|
rgtest!(f45_relative_cwd, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create(".not-an-ignore", "foo\n/bar");
|
||||||
|
dir.create_dir("bar");
|
||||||
|
dir.create_dir("baz/bar");
|
||||||
|
dir.create_dir("baz/baz/bar");
|
||||||
|
dir.create("bar/test", "test");
|
||||||
|
dir.create("baz/bar/test", "test");
|
||||||
|
dir.create("baz/baz/bar/test", "test");
|
||||||
|
dir.create("baz/foo", "test");
|
||||||
|
dir.create("baz/test", "test");
|
||||||
|
dir.create("foo", "test");
|
||||||
|
dir.create("test", "test");
|
||||||
|
|
||||||
|
cmd.arg("-l").arg("test");
|
||||||
|
|
||||||
|
// First, get a baseline without applying ignore rules.
|
||||||
|
let expected = "
|
||||||
|
bar/test
|
||||||
|
baz/bar/test
|
||||||
|
baz/baz/bar/test
|
||||||
|
baz/foo
|
||||||
|
baz/test
|
||||||
|
foo
|
||||||
|
test
|
||||||
|
";
|
||||||
|
eqnice!(sort_lines(expected), sort_lines(&cmd.stdout()));
|
||||||
|
|
||||||
|
// Now try again with the ignore file activated.
|
||||||
|
cmd.arg("--ignore-file").arg(".not-an-ignore");
|
||||||
|
let expected = "
|
||||||
|
baz/bar/test
|
||||||
|
baz/baz/bar/test
|
||||||
|
baz/test
|
||||||
|
test
|
||||||
|
";
|
||||||
|
eqnice!(sort_lines(expected), sort_lines(&cmd.stdout()));
|
||||||
|
|
||||||
|
// Now do it again, but inside the baz directory. Since the ignore file
|
||||||
|
// is interpreted relative to the CWD, this will cause the /bar anchored
|
||||||
|
// pattern to filter out baz/bar, which is a subtle difference between true
|
||||||
|
// parent ignore files and manually specified ignore files.
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.args(&["--ignore-file", "../.not-an-ignore", "-l", "test"]);
|
||||||
|
cmd.current_dir(dir.path().join("baz"));
|
||||||
|
let expected = "
|
||||||
|
baz/bar/test
|
||||||
|
test
|
||||||
|
";
|
||||||
|
eqnice!(sort_lines(expected), sort_lines(&cmd.stdout()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/45
|
||||||
|
rgtest!(f45_precedence_with_others, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create(".not-an-ignore", "*.log");
|
||||||
|
dir.create(".ignore", "!imp.log");
|
||||||
|
dir.create("imp.log", "test");
|
||||||
|
dir.create("wat.log", "test");
|
||||||
|
|
||||||
|
cmd.arg("--ignore-file").arg(".not-an-ignore").arg("test");
|
||||||
|
eqnice!("imp.log:test\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/45
|
||||||
|
rgtest!(f45_precedence_internal, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create(".not-an-ignore1", "*.log");
|
||||||
|
dir.create(".not-an-ignore2", "!imp.log");
|
||||||
|
dir.create("imp.log", "test");
|
||||||
|
dir.create("wat.log", "test");
|
||||||
|
|
||||||
|
cmd.args(&[
|
||||||
|
"--ignore-file", ".not-an-ignore1",
|
||||||
|
"--ignore-file", ".not-an-ignore2",
|
||||||
|
"test",
|
||||||
|
]);
|
||||||
|
eqnice!("imp.log:test\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/68
|
||||||
|
rgtest!(f68_no_ignore_vcs, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "foo");
|
||||||
|
dir.create(".ignore", "bar");
|
||||||
|
dir.create("foo", "test");
|
||||||
|
dir.create("bar", "test");
|
||||||
|
|
||||||
|
eqnice!("foo:test\n", cmd.arg("--no-ignore-vcs").arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/70
|
||||||
|
rgtest!(f70_smart_case, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
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!(expected, cmd.arg("-S").arg("sherlock").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||||
|
rgtest!(f89_files_with_matches, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
cmd.arg("--null").arg("--files-with-matches").arg("Sherlock");
|
||||||
|
eqnice!("sherlock\x00", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||||
|
rgtest!(f89_files_without_match, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file.py", "foo");
|
||||||
|
|
||||||
|
cmd.arg("--null").arg("--files-without-match").arg("Sherlock");
|
||||||
|
eqnice!("file.py\x00", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||||
|
rgtest!(f89_count, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
cmd.arg("--null").arg("--count").arg("Sherlock");
|
||||||
|
eqnice!("sherlock\x002\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||||
|
rgtest!(f89_files, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
eqnice!("sherlock\x00", cmd.arg("--null").arg("--files").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||||
|
rgtest!(f89_match, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
sherlock\x00For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
sherlock\x00Holmeses, success in the province of detective work must always
|
||||||
|
sherlock\x00be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
sherlock\x00can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.arg("--null").arg("-C1").arg("Sherlock").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/109
|
||||||
|
rgtest!(f109_max_depth, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir("one");
|
||||||
|
dir.create("one/pass", "far");
|
||||||
|
dir.create_dir("one/too");
|
||||||
|
dir.create("one/too/many", "far");
|
||||||
|
|
||||||
|
cmd.arg("--maxdepth").arg("2").arg("far");
|
||||||
|
eqnice!("one/pass:far\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/124
|
||||||
|
rgtest!(f109_case_sensitive_part1, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "tEsT");
|
||||||
|
|
||||||
|
cmd.arg("--smart-case").arg("--case-sensitive").arg("test").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/124
|
||||||
|
rgtest!(f109_case_sensitive_part2, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "tEsT");
|
||||||
|
cmd.arg("--ignore-case").arg("--case-sensitive").arg("test").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/129
|
||||||
|
rgtest!(f129_matches, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "test\ntest abcdefghijklmnopqrstuvwxyz test");
|
||||||
|
|
||||||
|
let expected = "foo:test\nfoo:[Omitted long matching line]\n";
|
||||||
|
eqnice!(expected, cmd.arg("-M26").arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/129
|
||||||
|
rgtest!(f129_context, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "test\nabcdefghijklmnopqrstuvwxyz");
|
||||||
|
|
||||||
|
let expected = "foo:test\nfoo-[Omitted long context line]\n";
|
||||||
|
eqnice!(expected, cmd.arg("-M20").arg("-C1").arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/129
|
||||||
|
rgtest!(f129_replace, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "test\ntest abcdefghijklmnopqrstuvwxyz test");
|
||||||
|
|
||||||
|
let expected = "foo:foo\nfoo:[Omitted long line with 2 matches]\n";
|
||||||
|
eqnice!(expected, cmd.arg("-M26").arg("-rfoo").arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/159
|
||||||
|
rgtest!(f159_max_count, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "test\ntest");
|
||||||
|
|
||||||
|
eqnice!("foo:test\n", cmd.arg("-m1").arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/159
|
||||||
|
rgtest!(f159_max_count_zero, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "test\ntest");
|
||||||
|
|
||||||
|
cmd.arg("-m0").arg("test").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/196
|
||||||
|
rgtest!(f196_persistent_config, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
// Make sure we get no matches by default.
|
||||||
|
cmd.assert_err();
|
||||||
|
|
||||||
|
// Now add our config file, and make sure it impacts ripgrep.
|
||||||
|
dir.create(".ripgreprc", "--ignore-case");
|
||||||
|
cmd.cmd().env("RIPGREP_CONFIG_PATH", ".ripgreprc");
|
||||||
|
|
||||||
|
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());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/243
|
||||||
|
rgtest!(f243_column_line, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "test");
|
||||||
|
|
||||||
|
eqnice!("foo:1:1:test\n", cmd.arg("--column").arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/263
|
||||||
|
rgtest!(f263_sort_files, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "test");
|
||||||
|
dir.create("abc", "test");
|
||||||
|
dir.create("zoo", "test");
|
||||||
|
dir.create("bar", "test");
|
||||||
|
|
||||||
|
let expected = "abc:test\nbar:test\nfoo:test\nzoo:test\n";
|
||||||
|
eqnice!(expected, cmd.arg("--sort-files").arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/275
|
||||||
|
rgtest!(f275_pathsep, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create("foo/bar", "test");
|
||||||
|
|
||||||
|
cmd.arg("test").arg("--path-separator").arg("Z");
|
||||||
|
eqnice!("fooZbar:test\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/362
|
||||||
|
rgtest!(f362_dfa_size_limit, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
// This should fall back to the nfa engine but should still produce the
|
||||||
|
// expected result.
|
||||||
|
cmd.arg("--dfa-size-limit").arg("10").arg(r"For\s").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/362
|
||||||
|
rgtest!(f362_exceeds_regex_size_limit, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
// --regex-size-limit doesn't apply to PCRE2.
|
||||||
|
if dir.is_pcre2() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cmd.arg("--regex-size-limit").arg("10K").arg(r"[0-9]\w+").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/362
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
rgtest!(f362_u64_to_narrow_usize_overflow, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
// --dfa-size-limit doesn't apply to PCRE2.
|
||||||
|
if dir.is_pcre2() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dir.create_size("foo", 1000000);
|
||||||
|
|
||||||
|
// 2^35 * 2^20 is ok for u64, but not for usize
|
||||||
|
cmd.arg("--dfa-size-limit").arg("34359738368M").arg("--files");
|
||||||
|
cmd.assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/411
|
||||||
|
rgtest!(f411_single_threaded_search_stats, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
let lines = cmd.arg("--stats").arg("Sherlock").stdout();
|
||||||
|
assert!(lines.contains("2 matched lines"));
|
||||||
|
assert!(lines.contains("1 files contained matches"));
|
||||||
|
assert!(lines.contains("1 files searched"));
|
||||||
|
assert!(lines.contains("seconds"));
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(f411_parallel_search_stats, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock_1", SHERLOCK);
|
||||||
|
dir.create("sherlock_2", SHERLOCK);
|
||||||
|
|
||||||
|
let lines = cmd.arg("--stats").arg("Sherlock").stdout();
|
||||||
|
assert!(lines.contains("4 matched lines"));
|
||||||
|
assert!(lines.contains("2 files contained matches"));
|
||||||
|
assert!(lines.contains("2 files searched"));
|
||||||
|
assert!(lines.contains("seconds"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/416
|
||||||
|
rgtest!(f416_crlf, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK_CRLF);
|
||||||
|
cmd.arg("--crlf").arg(r"Sherlock$").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock\r
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/416
|
||||||
|
rgtest!(f416_crlf_multiline, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK_CRLF);
|
||||||
|
cmd.arg("--crlf").arg("-U").arg(r"Sherlock$").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock\r
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/416
|
||||||
|
rgtest!(f416_crlf_only_matching, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK_CRLF);
|
||||||
|
cmd.arg("--crlf").arg("-o").arg(r"Sherlock$").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
Sherlock\r
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/419
|
||||||
|
rgtest!(f419_zero_as_shortcut_for_null, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
cmd.arg("-0").arg("--count").arg("Sherlock");
|
||||||
|
eqnice!("sherlock\x002\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(f740_passthru, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("file", "\nfoo\nbar\nfoobar\n\nbaz\n");
|
||||||
|
dir.create("patterns", "foo\nbar\n");
|
||||||
|
|
||||||
|
// We can't assume that the way colour specs are translated to ANSI
|
||||||
|
// sequences will remain stable, and --replace doesn't currently work with
|
||||||
|
// pass-through, so for now we don't actually test the match sub-strings
|
||||||
|
let common_args = &["-n", "--passthru"];
|
||||||
|
let foo_expected = "\
|
||||||
|
1-
|
||||||
|
2:foo
|
||||||
|
3-bar
|
||||||
|
4:foobar
|
||||||
|
5-
|
||||||
|
6-baz
|
||||||
|
";
|
||||||
|
|
||||||
|
// With single pattern
|
||||||
|
cmd.args(common_args).arg("foo").arg("file");
|
||||||
|
eqnice!(foo_expected, cmd.stdout());
|
||||||
|
|
||||||
|
let foo_bar_expected = "\
|
||||||
|
1-
|
||||||
|
2:foo
|
||||||
|
3:bar
|
||||||
|
4:foobar
|
||||||
|
5-
|
||||||
|
6-baz
|
||||||
|
";
|
||||||
|
|
||||||
|
// With multiple -e patterns
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.args(common_args);
|
||||||
|
cmd.args(&["-e", "foo", "-e", "bar", "file"]);
|
||||||
|
eqnice!(foo_bar_expected, cmd.stdout());
|
||||||
|
|
||||||
|
// With multiple -f patterns
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.args(common_args);
|
||||||
|
cmd.args(&["-f", "patterns", "file"]);
|
||||||
|
eqnice!(foo_bar_expected, cmd.stdout());
|
||||||
|
|
||||||
|
// -c should override
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.args(common_args);
|
||||||
|
cmd.args(&["-c", "foo", "file"]);
|
||||||
|
eqnice!("2\n", cmd.stdout());
|
||||||
|
|
||||||
|
let only_foo_expected = "\
|
||||||
|
1-
|
||||||
|
2:foo
|
||||||
|
3-bar
|
||||||
|
4:foo
|
||||||
|
5-
|
||||||
|
6-baz
|
||||||
|
";
|
||||||
|
|
||||||
|
// -o should work
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.args(common_args);
|
||||||
|
cmd.args(&["-o", "foo", "file"]);
|
||||||
|
eqnice!(only_foo_expected, cmd.stdout());
|
||||||
|
|
||||||
|
let replace_foo_expected = "\
|
||||||
|
1-
|
||||||
|
2:wat
|
||||||
|
3-bar
|
||||||
|
4:watbar
|
||||||
|
5-
|
||||||
|
6-baz
|
||||||
|
";
|
||||||
|
|
||||||
|
// -r should work
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.args(common_args);
|
||||||
|
cmd.args(&["-r", "wat", "foo", "file"]);
|
||||||
|
eqnice!(replace_foo_expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/948
|
||||||
|
rgtest!(f948_exit_code_match, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg(".");
|
||||||
|
|
||||||
|
cmd.assert_exit_code(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/948
|
||||||
|
rgtest!(f948_exit_code_no_match, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("NADA");
|
||||||
|
|
||||||
|
cmd.assert_exit_code(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/948
|
||||||
|
rgtest!(f948_exit_code_error, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("*");
|
||||||
|
|
||||||
|
cmd.assert_exit_code(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/917
|
||||||
|
rgtest!(f917_trim, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
const SHERLOCK: &'static str = "\
|
||||||
|
zzz
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
Holmeses, success in the province of detective work must always
|
||||||
|
\tbe, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||||
|
but Doctor Watson has to have it taken out for him and dusted,
|
||||||
|
and exhibited clearly, with a label attached.
|
||||||
|
";
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.args(&[
|
||||||
|
"-n", "-B1", "-A2", "--trim", "Holmeses", "sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
2-For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
3:Holmeses, success in the province of detective work must always
|
||||||
|
4-be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
5-can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/917
|
||||||
|
//
|
||||||
|
// This is like f917_trim, except this tests that trimming occurs even when the
|
||||||
|
// whitespace is part of a match.
|
||||||
|
rgtest!(f917_trim_match, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
const SHERLOCK: &'static str = "\
|
||||||
|
zzz
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
Holmeses, success in the province of detective work must always
|
||||||
|
\tbe, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||||
|
but Doctor Watson has to have it taken out for him and dusted,
|
||||||
|
and exhibited clearly, with a label attached.
|
||||||
|
";
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.args(&[
|
||||||
|
"-n", "-B1", "-A2", "--trim", r"\s+Holmeses", "sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
2-For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
3:Holmeses, success in the province of detective work must always
|
||||||
|
4-be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
5-can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/993
|
||||||
|
rgtest!(f993_null_data, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("test", "foo\x00bar\x00\x00\x00baz\x00");
|
||||||
|
cmd.arg("--null-data").arg(r".+").arg("test");
|
||||||
|
|
||||||
|
// If we just used -a instead of --null-data, then the result would include
|
||||||
|
// all NUL bytes.
|
||||||
|
let expected = "foo\x00bar\x00baz\x00";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
@@ -6,3 +6,12 @@ can extract a clew from a wisp of straw or a flake of cigar ash;
|
|||||||
but Doctor Watson has to have it taken out for him and dusted,
|
but Doctor Watson has to have it taken out for him and dusted,
|
||||||
and exhibited clearly, with a label attached.
|
and exhibited clearly, with a label attached.
|
||||||
";
|
";
|
||||||
|
|
||||||
|
pub const SHERLOCK_CRLF: &'static str = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock\r
|
||||||
|
Holmeses, success in the province of detective work must always\r
|
||||||
|
be, to a very large extent, the result of luck. Sherlock Holmes\r
|
||||||
|
can extract a clew from a wisp of straw or a flake of cigar ash;\r
|
||||||
|
but Doctor Watson has to have it taken out for him and dusted,\r
|
||||||
|
and exhibited clearly, with a label attached.\r
|
||||||
|
";
|
||||||
|
263
tests/json.rs
Normal file
263
tests/json.rs
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
use std::time;
|
||||||
|
|
||||||
|
use serde_json as json;
|
||||||
|
|
||||||
|
use hay::{SHERLOCK, SHERLOCK_CRLF};
|
||||||
|
use util::{Dir, TestCommand};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(tag = "type", content = "data")]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
enum Message {
|
||||||
|
Begin(Begin),
|
||||||
|
End(End),
|
||||||
|
Match(Match),
|
||||||
|
Context(Context),
|
||||||
|
Summary(Summary),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message {
|
||||||
|
fn unwrap_begin(&self) -> Begin {
|
||||||
|
match *self {
|
||||||
|
Message::Begin(ref x) => x.clone(),
|
||||||
|
ref x => panic!("expected Message::Begin but got {:?}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_end(&self) -> End {
|
||||||
|
match *self {
|
||||||
|
Message::End(ref x) => x.clone(),
|
||||||
|
ref x => panic!("expected Message::End but got {:?}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_match(&self) -> Match {
|
||||||
|
match *self {
|
||||||
|
Message::Match(ref x) => x.clone(),
|
||||||
|
ref x => panic!("expected Message::Match but got {:?}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_context(&self) -> Context {
|
||||||
|
match *self {
|
||||||
|
Message::Context(ref x) => x.clone(),
|
||||||
|
ref x => panic!("expected Message::Context but got {:?}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_summary(&self) -> Summary {
|
||||||
|
match *self {
|
||||||
|
Message::Summary(ref x) => x.clone(),
|
||||||
|
ref x => panic!("expected Message::Summary but got {:?}", x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
struct Begin {
|
||||||
|
path: Option<Data>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
struct End {
|
||||||
|
path: Option<Data>,
|
||||||
|
binary_offset: Option<u64>,
|
||||||
|
stats: Stats,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
struct Summary {
|
||||||
|
elapsed_total: Duration,
|
||||||
|
stats: Stats,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
struct Match {
|
||||||
|
path: Option<Data>,
|
||||||
|
lines: Data,
|
||||||
|
line_number: Option<u64>,
|
||||||
|
absolute_offset: u64,
|
||||||
|
submatches: Vec<SubMatch>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
struct Context {
|
||||||
|
path: Option<Data>,
|
||||||
|
lines: Data,
|
||||||
|
line_number: Option<u64>,
|
||||||
|
absolute_offset: u64,
|
||||||
|
submatches: Vec<SubMatch>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
struct SubMatch {
|
||||||
|
#[serde(rename = "match")]
|
||||||
|
m: Data,
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum Data {
|
||||||
|
Text { text: String },
|
||||||
|
// This variant is used when the data isn't valid UTF-8. The bytes are
|
||||||
|
// base64 encoded, so using a String here is OK.
|
||||||
|
Bytes { bytes: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Data {
|
||||||
|
fn text(s: &str) -> Data { Data::Text { text: s.to_string() } }
|
||||||
|
fn bytes(s: &str) -> Data { Data::Bytes { bytes: s.to_string() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
struct Stats {
|
||||||
|
elapsed: Duration,
|
||||||
|
searches: u64,
|
||||||
|
searches_with_match: u64,
|
||||||
|
bytes_searched: u64,
|
||||||
|
bytes_printed: u64,
|
||||||
|
matched_lines: u64,
|
||||||
|
matches: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||||
|
struct Duration {
|
||||||
|
#[serde(flatten)]
|
||||||
|
duration: time::Duration,
|
||||||
|
human: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode JSON Lines into a Vec<Message>. If there was an error decoding,
|
||||||
|
/// this function panics.
|
||||||
|
fn json_decode(jsonlines: &str) -> Vec<Message> {
|
||||||
|
json::Deserializer::from_str(jsonlines)
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Result<Vec<Message>, _>>()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
rgtest!(basic, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--json").arg("-B1").arg("Sherlock Holmes").arg("sherlock");
|
||||||
|
|
||||||
|
let msgs = json_decode(&cmd.stdout());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
msgs[0].unwrap_begin(),
|
||||||
|
Begin { path: Some(Data::text("sherlock")) }
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
msgs[1].unwrap_context(),
|
||||||
|
Context {
|
||||||
|
path: Some(Data::text("sherlock")),
|
||||||
|
lines: Data::text("Holmeses, success in the province of detective work must always\n"),
|
||||||
|
line_number: Some(2),
|
||||||
|
absolute_offset: 65,
|
||||||
|
submatches: vec![],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
msgs[2].unwrap_match(),
|
||||||
|
Match {
|
||||||
|
path: Some(Data::text("sherlock")),
|
||||||
|
lines: Data::text("be, to a very large extent, the result of luck. Sherlock Holmes\n"),
|
||||||
|
line_number: Some(3),
|
||||||
|
absolute_offset: 129,
|
||||||
|
submatches: vec![
|
||||||
|
SubMatch {
|
||||||
|
m: Data::text("Sherlock Holmes"),
|
||||||
|
start: 48,
|
||||||
|
end: 63,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
msgs[3].unwrap_end().path,
|
||||||
|
Some(Data::text("sherlock"))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
msgs[3].unwrap_end().binary_offset,
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
msgs[4].unwrap_summary().stats.searches_with_match,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
msgs[4].unwrap_summary().stats.bytes_printed,
|
||||||
|
494
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
rgtest!(notutf8, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
|
// This test does not work with PCRE2 because PCRE2 does not support the
|
||||||
|
// `u` flag.
|
||||||
|
if dir.is_pcre2() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// macOS doesn't like this either... sigh.
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = &b"foo\xFFbar"[..];
|
||||||
|
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::from_bytes(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::bytes("Zm9v/2Jhcg==")) }
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
msgs[1].unwrap_match(),
|
||||||
|
Match {
|
||||||
|
path: Some(Data::bytes("Zm9v/2Jhcg==")),
|
||||||
|
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
|
||||||
|
//
|
||||||
|
// This test in particular checks that our match does _not_ include the `\r`
|
||||||
|
// even though the '$' may be rewritten as '(?:\r??$)' and could thus include
|
||||||
|
// `\r` in the match.
|
||||||
|
rgtest!(crlf, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK_CRLF);
|
||||||
|
cmd.arg("--json").arg("--crlf").arg(r"Sherlock$").arg("sherlock");
|
||||||
|
|
||||||
|
let msgs = json_decode(&cmd.stdout());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
msgs[1].unwrap_match().submatches[0].clone(),
|
||||||
|
SubMatch {
|
||||||
|
m: Data::text("Sherlock"),
|
||||||
|
start: 56,
|
||||||
|
end: 64,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
@@ -1,5 +1,21 @@
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_eq_nice {
|
macro_rules! rgtest {
|
||||||
|
($name:ident, $fun:expr) => {
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
let (dir, cmd) = ::util::setup(stringify!($name));
|
||||||
|
$fun(dir, cmd);
|
||||||
|
|
||||||
|
if cfg!(feature = "pcre2") {
|
||||||
|
let (dir, cmd) = ::util::setup_pcre2(stringify!($name));
|
||||||
|
$fun(dir, cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! eqnice {
|
||||||
($expected:expr, $got:expr) => {
|
($expected:expr, $got:expr) => {
|
||||||
let expected = &*$expected;
|
let expected = &*$expected;
|
||||||
let got = &*$got;
|
let got = &*$got;
|
||||||
@@ -20,3 +36,26 @@ got:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! eqnice_repr {
|
||||||
|
($expected:expr, $got:expr) => {
|
||||||
|
let expected = &*$expected;
|
||||||
|
let got = &*$got;
|
||||||
|
if expected != got {
|
||||||
|
panic!("
|
||||||
|
printed outputs differ!
|
||||||
|
|
||||||
|
expected:
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
{:?}
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
got:
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
{:?}
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
", expected, got);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
948
tests/misc.rs
Normal file
948
tests/misc.rs
Normal file
@@ -0,0 +1,948 @@
|
|||||||
|
use hay::SHERLOCK;
|
||||||
|
use util::{Dir, TestCommand, cmd_exists, sort_lines};
|
||||||
|
|
||||||
|
// This file contains "miscellaneous" tests that were either written before
|
||||||
|
// features were tracked more explicitly, or were simply written without
|
||||||
|
// linking them to a specific issue number. We should try to minimize the
|
||||||
|
// addition of more tests in this file and instead add them to either the
|
||||||
|
// regression test suite or the feature test suite (found in regression.rs and
|
||||||
|
// feature.rs, respectively).
|
||||||
|
|
||||||
|
rgtest!(single_file, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
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.arg("Sherlock").arg("sherlock").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(dir, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
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!(expected, cmd.arg("Sherlock").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(line_numbers, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
1:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
3:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.arg("-n").arg("Sherlock").arg("sherlock").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(columns, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--column").arg("Sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
1:57:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
3:49:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(with_filename, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-H").arg("Sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
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!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(with_heading, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.args(&[
|
||||||
|
// This forces the issue since --with-filename is disabled by default
|
||||||
|
// when searching one file.
|
||||||
|
"--with-filename", "--heading",
|
||||||
|
"Sherlock", "sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
sherlock
|
||||||
|
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!(with_heading_default, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
// Search two or more and get --with-filename enabled by default.
|
||||||
|
// Use -j1 to get deterministic results.
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("foo", "Sherlock Holmes lives on Baker Street.");
|
||||||
|
cmd.arg("-j1").arg("--heading").arg("Sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
foo
|
||||||
|
Sherlock Holmes lives on Baker Street.
|
||||||
|
|
||||||
|
sherlock
|
||||||
|
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!(sort_lines(expected), sort_lines(&cmd.stdout()));
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(inverted, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-v").arg("Sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
Holmeses, success in the province of detective work must always
|
||||||
|
can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||||
|
but Doctor Watson has to have it taken out for him and dusted,
|
||||||
|
and exhibited clearly, with a label attached.
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(inverted_line_numbers, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-n").arg("-v").arg("Sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
2:Holmeses, success in the province of detective work must always
|
||||||
|
4:can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||||
|
5:but Doctor Watson has to have it taken out for him and dusted,
|
||||||
|
6:and exhibited clearly, with a label attached.
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(case_insensitive, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-i").arg("sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
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!(word, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-w").arg("as").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(line, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.args(&[
|
||||||
|
"-x",
|
||||||
|
"Watson|and exhibited clearly, with a label attached.",
|
||||||
|
"sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
and exhibited clearly, with a label attached.
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(literal, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file", "blib\n()\nblab\n");
|
||||||
|
cmd.arg("-F").arg("()").arg("file");
|
||||||
|
|
||||||
|
eqnice!("()\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(quiet, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-q").arg("Sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
assert!(cmd.stdout().is_empty());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(replace, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-r").arg("FooBar").arg("Sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the FooBar
|
||||||
|
be, to a very large extent, the result of luck. FooBar Holmes
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(replace_groups, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.args(&[
|
||||||
|
"-r", "$2, $1", "([A-Z][a-z]+) ([A-Z][a-z]+)", "sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Watsons, Doctor of this world, as opposed to the Sherlock
|
||||||
|
be, to a very large extent, the result of luck. Holmes, Sherlock
|
||||||
|
but Watson, Doctor has to have it taken out for him and dusted,
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(replace_named_groups, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.args(&[
|
||||||
|
"-r", "$last, $first",
|
||||||
|
"(?P<first>[A-Z][a-z]+) (?P<last>[A-Z][a-z]+)",
|
||||||
|
"sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Watsons, Doctor of this world, as opposed to the Sherlock
|
||||||
|
be, to a very large extent, the result of luck. Holmes, Sherlock
|
||||||
|
but Watson, Doctor has to have it taken out for him and dusted,
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(replace_with_only_matching, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-o").arg("-r").arg("$1").arg(r"of (\w+)").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
this
|
||||||
|
detective
|
||||||
|
luck
|
||||||
|
straw
|
||||||
|
cigar
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(file_types, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file.py", "Sherlock");
|
||||||
|
dir.create("file.rs", "Sherlock");
|
||||||
|
cmd.arg("-t").arg("rust").arg("Sherlock");
|
||||||
|
|
||||||
|
eqnice!("file.rs:Sherlock\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(file_types_all, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file.py", "Sherlock");
|
||||||
|
cmd.arg("-t").arg("all").arg("Sherlock");
|
||||||
|
|
||||||
|
eqnice!("file.py:Sherlock\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(file_types_negate, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.remove("sherlock");
|
||||||
|
dir.create("file.py", "Sherlock");
|
||||||
|
dir.create("file.rs", "Sherlock");
|
||||||
|
cmd.arg("-T").arg("rust").arg("Sherlock");
|
||||||
|
|
||||||
|
eqnice!("file.py:Sherlock\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(file_types_negate_all, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file.py", "Sherlock");
|
||||||
|
cmd.arg("-T").arg("all").arg("Sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
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!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(file_type_clear, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file.py", "Sherlock");
|
||||||
|
dir.create("file.rs", "Sherlock");
|
||||||
|
cmd.arg("--type-clear").arg("rust").arg("-t").arg("rust").arg("Sherlock");
|
||||||
|
|
||||||
|
cmd.assert_non_empty_stderr();
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(file_type_add, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file.py", "Sherlock");
|
||||||
|
dir.create("file.rs", "Sherlock");
|
||||||
|
dir.create("file.wat", "Sherlock");
|
||||||
|
cmd.args(&[
|
||||||
|
"--type-add", "wat:*.wat", "-t", "wat", "Sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
eqnice!("file.wat:Sherlock\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(file_type_add_compose, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file.py", "Sherlock");
|
||||||
|
dir.create("file.rs", "Sherlock");
|
||||||
|
dir.create("file.wat", "Sherlock");
|
||||||
|
cmd.args(&[
|
||||||
|
"--type-add", "wat:*.wat",
|
||||||
|
"--type-add", "combo:include:wat,py",
|
||||||
|
"-t", "combo",
|
||||||
|
"Sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
file.py:Sherlock
|
||||||
|
file.wat:Sherlock
|
||||||
|
";
|
||||||
|
eqnice!(expected, sort_lines(&cmd.stdout()));
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(glob, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file.py", "Sherlock");
|
||||||
|
dir.create("file.rs", "Sherlock");
|
||||||
|
cmd.arg("-g").arg("*.rs").arg("Sherlock");
|
||||||
|
|
||||||
|
eqnice!("file.rs:Sherlock\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(glob_negate, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.remove("sherlock");
|
||||||
|
dir.create("file.py", "Sherlock");
|
||||||
|
dir.create("file.rs", "Sherlock");
|
||||||
|
cmd.arg("-g").arg("!*.rs").arg("Sherlock");
|
||||||
|
|
||||||
|
eqnice!("file.py:Sherlock\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(glob_case_insensitive, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file.HTML", "Sherlock");
|
||||||
|
cmd.arg("--iglob").arg("*.html").arg("Sherlock");
|
||||||
|
|
||||||
|
eqnice!("file.HTML:Sherlock\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(glob_case_sensitive, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file1.HTML", "Sherlock");
|
||||||
|
dir.create("file2.html", "Sherlock");
|
||||||
|
cmd.arg("--glob").arg("*.html").arg("Sherlock");
|
||||||
|
|
||||||
|
eqnice!("file2.html:Sherlock\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(byte_offset_only_matching, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-b").arg("-o").arg("Sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
sherlock:56:Sherlock
|
||||||
|
sherlock:177:Sherlock
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(count, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--count").arg("Sherlock");
|
||||||
|
|
||||||
|
let expected = "sherlock:2\n";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(count_matches, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--count-matches").arg("the");
|
||||||
|
|
||||||
|
let expected = "sherlock:4\n";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(count_matches_inverted, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--count-matches").arg("--invert-match").arg("Sherlock");
|
||||||
|
|
||||||
|
let expected = "sherlock:4\n";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(count_matches_via_only, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--count").arg("--only-matching").arg("the");
|
||||||
|
|
||||||
|
let expected = "sherlock:4\n";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(files_with_matches, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--files-with-matches").arg("Sherlock");
|
||||||
|
|
||||||
|
let expected = "sherlock\n";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(files_without_match, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file.py", "foo");
|
||||||
|
cmd.arg("--files-without-match").arg("Sherlock");
|
||||||
|
|
||||||
|
let expected = "file.py\n";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(after_context, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-A").arg("1").arg("Sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
Holmeses, success in the province of detective work must always
|
||||||
|
be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(after_context_line_numbers, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-A").arg("1").arg("-n").arg("Sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
1:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
2-Holmeses, success in the province of detective work must always
|
||||||
|
3:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
4-can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(before_context, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-B").arg("1").arg("Sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
Holmeses, success in the province of detective work must always
|
||||||
|
be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(before_context_line_numbers, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-B").arg("1").arg("-n").arg("Sherlock").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
1:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
2-Holmeses, success in the province of detective work must always
|
||||||
|
3:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(context, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-C").arg("1").arg("world|attached").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
Holmeses, success in the province of detective work must always
|
||||||
|
--
|
||||||
|
but Doctor Watson has to have it taken out for him and dusted,
|
||||||
|
and exhibited clearly, with a label attached.
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(context_line_numbers, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-C").arg("1").arg("-n").arg("world|attached").arg("sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
1:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
2-Holmeses, success in the province of detective work must always
|
||||||
|
--
|
||||||
|
5-but Doctor Watson has to have it taken out for him and dusted,
|
||||||
|
6:and exhibited clearly, with a label attached.
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(max_filesize_parse_errro_length, |_: Dir, mut cmd: TestCommand| {
|
||||||
|
cmd.arg("--max-filesize").arg("44444444444444444444");
|
||||||
|
cmd.assert_non_empty_stderr();
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(max_filesize_parse_error_suffix, |_: Dir, mut cmd: TestCommand| {
|
||||||
|
cmd.arg("--max-filesize").arg("45k");
|
||||||
|
cmd.assert_non_empty_stderr();
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(max_filesize_parse_no_suffix, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_size("foo", 40);
|
||||||
|
dir.create_size("bar", 60);
|
||||||
|
cmd.arg("--max-filesize").arg("50").arg("--files");
|
||||||
|
|
||||||
|
eqnice!("foo\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(max_filesize_parse_k_suffix, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_size("foo", 3048);
|
||||||
|
dir.create_size("bar", 4100);
|
||||||
|
cmd.arg("--max-filesize").arg("4K").arg("--files");
|
||||||
|
|
||||||
|
eqnice!("foo\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(max_filesize_parse_m_suffix, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_size("foo", 1000000);
|
||||||
|
dir.create_size("bar", 1400000);
|
||||||
|
cmd.arg("--max-filesize").arg("1M").arg("--files");
|
||||||
|
|
||||||
|
eqnice!("foo\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(max_filesize_suffix_overflow, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_size("foo", 1000000);
|
||||||
|
|
||||||
|
// 2^35 * 2^30 would otherwise overflow
|
||||||
|
cmd.arg("--max-filesize").arg("34359738368G").arg("--files");
|
||||||
|
cmd.assert_non_empty_stderr();
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(ignore_hidden, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create(".sherlock", SHERLOCK);
|
||||||
|
cmd.arg("Sherlock").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(no_ignore_hidden, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create(".sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--hidden").arg("Sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
.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!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(ignore_git, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "sherlock\n");
|
||||||
|
cmd.arg("Sherlock");
|
||||||
|
|
||||||
|
cmd.assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(ignore_generic, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create(".ignore", "sherlock\n");
|
||||||
|
cmd.arg("Sherlock");
|
||||||
|
|
||||||
|
cmd.assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(ignore_ripgrep, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create(".rgignore", "sherlock\n");
|
||||||
|
cmd.arg("Sherlock");
|
||||||
|
|
||||||
|
cmd.assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(no_ignore, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create(".gitignore", "sherlock\n");
|
||||||
|
cmd.arg("--no-ignore").arg("Sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
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!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(ignore_git_parent, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "sherlock\n");
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create("foo/sherlock", SHERLOCK);
|
||||||
|
cmd.arg("Sherlock");
|
||||||
|
|
||||||
|
// Even though we search in foo/, which has no .gitignore, ripgrep will
|
||||||
|
// traverse parent directories and respect the gitignore files found.
|
||||||
|
cmd.current_dir(dir.path().join("foo"));
|
||||||
|
cmd.assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(ignore_git_parent_stop, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
// This tests that searching parent directories for .gitignore files stops
|
||||||
|
// after it sees a .git directory. To test this, we create this directory
|
||||||
|
// hierarchy:
|
||||||
|
//
|
||||||
|
// .gitignore (contains `sherlock`)
|
||||||
|
// foo/
|
||||||
|
// .git/
|
||||||
|
// bar/
|
||||||
|
// sherlock
|
||||||
|
//
|
||||||
|
// And we perform the search inside `foo/bar/`. ripgrep will stop looking
|
||||||
|
// for .gitignore files after it sees `foo/.git/`, and therefore not
|
||||||
|
// respect the top-level `.gitignore` containing `sherlock`.
|
||||||
|
dir.create(".gitignore", "sherlock\n");
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create_dir("foo/.git");
|
||||||
|
dir.create_dir("foo/bar");
|
||||||
|
dir.create("foo/bar/sherlock", SHERLOCK);
|
||||||
|
cmd.arg("Sherlock");
|
||||||
|
cmd.current_dir(dir.path().join("foo").join("bar"));
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
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!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Like ignore_git_parent_stop, but with a .git file instead of a .git
|
||||||
|
// directory.
|
||||||
|
rgtest!(ignore_git_parent_stop_file, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
// This tests that searching parent directories for .gitignore files stops
|
||||||
|
// after it sees a .git *file*. A .git file is used for submodules. To test
|
||||||
|
// this, we create this directory hierarchy:
|
||||||
|
//
|
||||||
|
// .gitignore (contains `sherlock`)
|
||||||
|
// foo/
|
||||||
|
// .git
|
||||||
|
// bar/
|
||||||
|
// sherlock
|
||||||
|
//
|
||||||
|
// And we perform the search inside `foo/bar/`. ripgrep will stop looking
|
||||||
|
// for .gitignore files after it sees `foo/.git`, and therefore not
|
||||||
|
// respect the top-level `.gitignore` containing `sherlock`.
|
||||||
|
dir.create(".gitignore", "sherlock\n");
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create("foo/.git", "");
|
||||||
|
dir.create_dir("foo/bar");
|
||||||
|
dir.create("foo/bar/sherlock", SHERLOCK);
|
||||||
|
cmd.arg("Sherlock");
|
||||||
|
cmd.current_dir(dir.path().join("foo").join("bar"));
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
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!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(ignore_ripgrep_parent_no_stop, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
// This is like the `ignore_git_parent_stop` test, except it checks that
|
||||||
|
// ripgrep *doesn't* stop checking for .rgignore files.
|
||||||
|
dir.create(".rgignore", "sherlock\n");
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create_dir("foo/.git");
|
||||||
|
dir.create_dir("foo/bar");
|
||||||
|
dir.create("foo/bar/sherlock", SHERLOCK);
|
||||||
|
cmd.arg("Sherlock");
|
||||||
|
cmd.current_dir(dir.path().join("foo").join("bar"));
|
||||||
|
|
||||||
|
// The top-level .rgignore applies.
|
||||||
|
cmd.assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(no_parent_ignore_git, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
// Set up a directory hierarchy like this:
|
||||||
|
//
|
||||||
|
// .git/
|
||||||
|
// .gitignore
|
||||||
|
// foo/
|
||||||
|
// .gitignore
|
||||||
|
// sherlock
|
||||||
|
// watson
|
||||||
|
//
|
||||||
|
// Where `.gitignore` contains `sherlock` and `foo/.gitignore` contains
|
||||||
|
// `watson`.
|
||||||
|
//
|
||||||
|
// Now *do the search* from the foo directory. By default, ripgrep will
|
||||||
|
// search parent directories for .gitignore files. The --no-ignore-parent
|
||||||
|
// flag should prevent that. At the same time, the `foo/.gitignore` file
|
||||||
|
// will still be respected (since the search is happening in `foo/`).
|
||||||
|
//
|
||||||
|
// In other words, we should only see results from `sherlock`, not from
|
||||||
|
// `watson`.
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "sherlock\n");
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create("foo/.gitignore", "watson\n");
|
||||||
|
dir.create("foo/sherlock", SHERLOCK);
|
||||||
|
dir.create("foo/watson", SHERLOCK);
|
||||||
|
cmd.arg("--no-ignore-parent").arg("Sherlock");
|
||||||
|
cmd.current_dir(dir.path().join("foo"));
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
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!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(symlink_nofollow, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create_dir("foo/bar");
|
||||||
|
dir.link_dir("foo/baz", "foo/bar/baz");
|
||||||
|
dir.create_dir("foo/baz");
|
||||||
|
dir.create("foo/baz/sherlock", SHERLOCK);
|
||||||
|
cmd.arg("Sherlock");
|
||||||
|
cmd.current_dir(dir.path().join("foo/bar"));
|
||||||
|
|
||||||
|
cmd.assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
rgtest!(symlink_follow, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create_dir("foo/bar");
|
||||||
|
dir.create_dir("foo/baz");
|
||||||
|
dir.create("foo/baz/sherlock", SHERLOCK);
|
||||||
|
dir.link_dir("foo/baz", "foo/bar/baz");
|
||||||
|
cmd.arg("-L").arg("Sherlock");
|
||||||
|
cmd.current_dir(dir.path().join("foo/bar"));
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
baz/sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
baz/sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(unrestricted1, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create(".gitignore", "sherlock\n");
|
||||||
|
cmd.arg("-u").arg("Sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
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!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(unrestricted2, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create(".sherlock", SHERLOCK);
|
||||||
|
cmd.arg("-uu").arg("Sherlock");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
.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!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(unrestricted3, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("file", "foo\x00bar\nfoo\x00baz\n");
|
||||||
|
cmd.arg("-uuu").arg("foo");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
file:foo\x00bar
|
||||||
|
file:foo\x00baz
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(vimgrep, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--vimgrep").arg("Sherlock|Watson");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
sherlock:1:16:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
sherlock:1:57:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
sherlock:3:49:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
sherlock:5:12:but Doctor Watson has to have it taken out for him and dusted,
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(vimgrep_no_line, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--vimgrep").arg("-N").arg("Sherlock|Watson");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
sherlock:16:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
sherlock:57:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
sherlock:49:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
sherlock:12:but Doctor Watson has to have it taken out for him and dusted,
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(vimgrep_no_line_no_column, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.arg("--vimgrep").arg("-N").arg("--no-column").arg("Sherlock|Watson");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
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
|
||||||
|
sherlock:but Doctor Watson has to have it taken out for him and dusted,
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(preprocessing, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
if !cmd_exists("xzcat") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.create_bytes("sherlock.xz", include_bytes!("./data/sherlock.xz"));
|
||||||
|
cmd.arg("--pre").arg("xzcat").arg("Sherlock").arg("sherlock.xz");
|
||||||
|
|
||||||
|
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_gzip, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
if !cmd_exists("gzip") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.create_bytes("sherlock.gz", include_bytes!("./data/sherlock.gz"));
|
||||||
|
cmd.arg("-z").arg("Sherlock").arg("sherlock.gz");
|
||||||
|
|
||||||
|
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_bzip2, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
if !cmd_exists("bzip2") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.create_bytes("sherlock.bz2", include_bytes!("./data/sherlock.bz2"));
|
||||||
|
cmd.arg("-z").arg("Sherlock").arg("sherlock.bz2");
|
||||||
|
|
||||||
|
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_xz, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
if !cmd_exists("xz") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.create_bytes("sherlock.xz", include_bytes!("./data/sherlock.xz"));
|
||||||
|
cmd.arg("-z").arg("Sherlock").arg("sherlock.xz");
|
||||||
|
|
||||||
|
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_lz4, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
if !cmd_exists("lz4") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.create_bytes("sherlock.lz4", include_bytes!("./data/sherlock.lz4"));
|
||||||
|
cmd.arg("-z").arg("Sherlock").arg("sherlock.lz4");
|
||||||
|
|
||||||
|
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_lzma, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
if !cmd_exists("xz") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.create_bytes("sherlock.lzma", include_bytes!("./data/sherlock.lzma"));
|
||||||
|
cmd.arg("-z").arg("Sherlock").arg("sherlock.lzma");
|
||||||
|
|
||||||
|
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| {
|
||||||
|
if !cmd_exists("gzip") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.create("sherlock.gz", SHERLOCK);
|
||||||
|
cmd.arg("-z").arg("Sherlock").arg("sherlock.gz");
|
||||||
|
|
||||||
|
cmd.assert_non_empty_stderr();
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(binary_nosearch, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("file", "foo\x00bar\nfoo\x00baz\n");
|
||||||
|
cmd.arg("foo").arg("file");
|
||||||
|
|
||||||
|
cmd.assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// The following two tests show a discrepancy in search results between
|
||||||
|
// searching with memory mapped files and stream searching. Stream searching
|
||||||
|
// uses a heuristic (that GNU grep also uses) where NUL bytes are replaced with
|
||||||
|
// the EOL terminator, which tends to avoid allocating large amounts of memory
|
||||||
|
// for really long "lines." The memory map searcher has no need to worry about
|
||||||
|
// such things, and more than that, it would be pretty hard for it to match the
|
||||||
|
// semantics of streaming search in this case.
|
||||||
|
//
|
||||||
|
// Binary files with lots of NULs aren't really part of the use case of ripgrep
|
||||||
|
// (or any other grep-like tool for that matter), so we shouldn't feel too bad
|
||||||
|
// about it.
|
||||||
|
rgtest!(binary_search_mmap, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("file", "foo\x00bar\nfoo\x00baz\n");
|
||||||
|
cmd.arg("-a").arg("--mmap").arg("foo").arg("file");
|
||||||
|
eqnice!("foo\x00bar\nfoo\x00baz\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(binary_search_no_mmap, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("file", "foo\x00bar\nfoo\x00baz\n");
|
||||||
|
cmd.arg("-a").arg("--no-mmap").arg("foo").arg("file");
|
||||||
|
eqnice!("foo\x00bar\nfoo\x00baz\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(files, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("file", "");
|
||||||
|
dir.create_dir("dir");
|
||||||
|
dir.create("dir/file", "");
|
||||||
|
cmd.arg("--files");
|
||||||
|
|
||||||
|
eqnice!(sort_lines("file\ndir/file\n"), sort_lines(&cmd.stdout()));
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(type_list, |_: Dir, mut cmd: TestCommand| {
|
||||||
|
cmd.arg("--type-list");
|
||||||
|
// This can change over time, so just make sure we print something.
|
||||||
|
assert!(!cmd.stdout().is_empty());
|
||||||
|
});
|
109
tests/multiline.rs
Normal file
109
tests/multiline.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
use hay::SHERLOCK;
|
||||||
|
use util::{Dir, TestCommand};
|
||||||
|
|
||||||
|
// This tests that multiline matches that span multiple lines, but where
|
||||||
|
// multiple matches may begin and end on the same line work correctly.
|
||||||
|
rgtest!(overlap1, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("test", "xxx\nabc\ndefxxxabc\ndefxxx\nxxx");
|
||||||
|
cmd.arg("-n").arg("-U").arg("abc\ndef").arg("test");
|
||||||
|
eqnice!("2:abc\n3:defxxxabc\n4:defxxx\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Like overlap1, but tests the case where one match ends at precisely the same
|
||||||
|
// location at which the next match begins.
|
||||||
|
rgtest!(overlap2, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("test", "xxx\nabc\ndefabc\ndefxxx\nxxx");
|
||||||
|
cmd.arg("-n").arg("-U").arg("abc\ndef").arg("test");
|
||||||
|
eqnice!("2:abc\n3:defabc\n4:defxxx\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tests that even in a multiline search, a '.' does not match a newline.
|
||||||
|
rgtest!(dot_no_newline, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.args(&[
|
||||||
|
"-n", "-U", "of this world.+detective work", "sherlock",
|
||||||
|
]);
|
||||||
|
cmd.assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tests that the --multiline-dotall flag causes '.' to match a newline.
|
||||||
|
rgtest!(dot_all, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.args(&[
|
||||||
|
"-n", "-U", "--multiline-dotall",
|
||||||
|
"of this world.+detective work", "sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
1:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
2:Holmeses, success in the province of detective work must always
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tests that --only-matching works in multiline mode.
|
||||||
|
rgtest!(only_matching, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.args(&[
|
||||||
|
"-n", "-U", "--only-matching",
|
||||||
|
r"Watson|Sherlock\p{Any}+?Holmes", "sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
1:Watson
|
||||||
|
1:Sherlock
|
||||||
|
2:Holmes
|
||||||
|
3:Sherlock Holmes
|
||||||
|
5:Watson
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tests that --vimgrep works in multiline mode.
|
||||||
|
rgtest!(vimgrep, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.args(&[
|
||||||
|
"-n", "-U", "--vimgrep",
|
||||||
|
r"Watson|Sherlock\p{Any}+?Holmes", "sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
sherlock:1:16:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
sherlock:1:57:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
sherlock:2:57:Holmeses, success in the province of detective work must always
|
||||||
|
sherlock:3:49:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
sherlock:5:12:but Doctor Watson has to have it taken out for him and dusted,
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tests that multiline search works when reading from stdin. This is an
|
||||||
|
// important test because multiline search must read the entire contents of
|
||||||
|
// what it is searching into memory before executing the search.
|
||||||
|
rgtest!(stdin, |_: Dir, mut cmd: TestCommand| {
|
||||||
|
cmd.args(&[
|
||||||
|
"-n", "-U", r"of this world\p{Any}+?detective work",
|
||||||
|
]);
|
||||||
|
let expected = "\
|
||||||
|
1:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
2:Holmeses, success in the province of detective work must always
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.pipe(SHERLOCK));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test that multiline search and contextual matches work.
|
||||||
|
rgtest!(context, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
cmd.args(&[
|
||||||
|
"-n", "-U", "-C1",
|
||||||
|
r"detective work\p{Any}+?result of luck", "sherlock",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
1-For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
2:Holmeses, success in the province of detective work must always
|
||||||
|
3:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
4-can extract a clew from a wisp of straw or a flake of cigar ash;
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
@@ -1,36 +1,564 @@
|
|||||||
use hay::SHERLOCK;
|
use hay::SHERLOCK;
|
||||||
use workdir::WorkDir;
|
use util::{Dir, TestCommand, sort_lines};
|
||||||
|
|
||||||
// See: https://github.com/BurntSushi/ripgrep/issues/16
|
// See: https://github.com/BurntSushi/ripgrep/issues/16
|
||||||
#[test]
|
rgtest!(r16, |dir: Dir, mut cmd: TestCommand| {
|
||||||
fn r16() {
|
dir.create_dir(".git");
|
||||||
let (wd, mut cmd) = WorkDir::new_with("r16");
|
dir.create(".gitignore", "ghi/");
|
||||||
wd.create_dir(".git");
|
dir.create_dir("ghi");
|
||||||
wd.create(".gitignore", "ghi/");
|
dir.create_dir("def/ghi");
|
||||||
wd.create_dir("ghi");
|
dir.create("ghi/toplevel.txt", "xyz");
|
||||||
wd.create_dir("def/ghi");
|
dir.create("def/ghi/subdir.txt", "xyz");
|
||||||
wd.create("ghi/toplevel.txt", "xyz");
|
|
||||||
wd.create("def/ghi/subdir.txt", "xyz");
|
|
||||||
|
|
||||||
cmd.arg("xyz");
|
cmd.arg("xyz").assert_err();
|
||||||
wd.assert_err(&mut cmd);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// See: https://github.com/BurntSushi/ripgrep/issues/25
|
// See: https://github.com/BurntSushi/ripgrep/issues/25
|
||||||
#[test]
|
rgtest!(r25, |dir: Dir, mut cmd: TestCommand| {
|
||||||
fn r25() {
|
dir.create_dir(".git");
|
||||||
let (wd, mut cmd) = WorkDir::new_with("r25");
|
dir.create(".gitignore", "/llvm/");
|
||||||
wd.create_dir(".git");
|
dir.create_dir("src/llvm");
|
||||||
wd.create(".gitignore", "/llvm/");
|
dir.create("src/llvm/foo", "test");
|
||||||
wd.create_dir("src/llvm");
|
|
||||||
wd.create("src/llvm/foo", "test");
|
|
||||||
|
|
||||||
cmd.arg("test");
|
cmd.arg("test");
|
||||||
|
eqnice!("src/llvm/foo:test\n", cmd.stdout());
|
||||||
|
|
||||||
let lines: String = wd.stdout(&mut cmd);
|
cmd.current_dir(dir.path().join("src"));
|
||||||
assert_eq_nice!("src/llvm/foo:test\n", lines);
|
eqnice!("llvm/foo:test\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
cmd.current_dir(wd.path().join("src"));
|
// See: https://github.com/BurntSushi/ripgrep/issues/30
|
||||||
let lines: String = wd.stdout(&mut cmd);
|
rgtest!(r30, |dir: Dir, mut cmd: TestCommand| {
|
||||||
assert_eq_nice!("llvm/foo:test\n", lines);
|
dir.create(".gitignore", "vendor/**\n!vendor/manifest");
|
||||||
}
|
dir.create_dir("vendor");
|
||||||
|
dir.create("vendor/manifest", "test");
|
||||||
|
|
||||||
|
eqnice!("vendor/manifest:test\n", cmd.arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/49
|
||||||
|
rgtest!(r49, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create(".gitignore", "foo/bar");
|
||||||
|
dir.create_dir("test/foo/bar");
|
||||||
|
dir.create("test/foo/bar/baz", "test");
|
||||||
|
|
||||||
|
cmd.arg("xyz").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/50
|
||||||
|
rgtest!(r50, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create(".gitignore", "XXX/YYY/");
|
||||||
|
dir.create_dir("abc/def/XXX/YYY");
|
||||||
|
dir.create_dir("ghi/XXX/YYY");
|
||||||
|
dir.create("abc/def/XXX/YYY/bar", "test");
|
||||||
|
dir.create("ghi/XXX/YYY/bar", "test");
|
||||||
|
|
||||||
|
cmd.arg("xyz").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/64
|
||||||
|
rgtest!(r64, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir("dir");
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create("dir/abc", "");
|
||||||
|
dir.create("foo/abc", "");
|
||||||
|
|
||||||
|
eqnice!("foo/abc\n", cmd.arg("--files").arg("foo").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/65
|
||||||
|
rgtest!(r65, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "a/");
|
||||||
|
dir.create_dir("a");
|
||||||
|
dir.create("a/foo", "xyz");
|
||||||
|
dir.create("a/bar", "xyz");
|
||||||
|
|
||||||
|
cmd.arg("xyz").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/67
|
||||||
|
rgtest!(r67, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "/*\n!/dir");
|
||||||
|
dir.create_dir("dir");
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create("foo/bar", "test");
|
||||||
|
dir.create("dir/bar", "test");
|
||||||
|
|
||||||
|
eqnice!("dir/bar:test\n", cmd.arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/87
|
||||||
|
rgtest!(r87, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "foo\n**no-vcs**");
|
||||||
|
dir.create("foo", "test");
|
||||||
|
|
||||||
|
cmd.arg("test").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/90
|
||||||
|
rgtest!(r90, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "!.foo");
|
||||||
|
dir.create(".foo", "test");
|
||||||
|
|
||||||
|
eqnice!(".foo:test\n", cmd.arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/93
|
||||||
|
rgtest!(r93, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "192.168.1.1");
|
||||||
|
|
||||||
|
eqnice!("foo:192.168.1.1\n", cmd.arg(r"(\d{1,3}\.){3}\d{1,3}").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/99
|
||||||
|
rgtest!(r99, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo1", "test");
|
||||||
|
dir.create("foo2", "zzz");
|
||||||
|
dir.create("bar", "test");
|
||||||
|
|
||||||
|
eqnice!(
|
||||||
|
sort_lines("bar\ntest\n\nfoo1\ntest\n"),
|
||||||
|
sort_lines(&cmd.arg("-j1").arg("--heading").arg("test").stdout())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/105
|
||||||
|
rgtest!(r105_part1, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "zztest");
|
||||||
|
|
||||||
|
eqnice!("foo:1:3:zztest\n", cmd.arg("--vimgrep").arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/105
|
||||||
|
rgtest!(r105_part2, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "zztest");
|
||||||
|
|
||||||
|
eqnice!("foo:1:3:zztest\n", cmd.arg("--column").arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/127
|
||||||
|
rgtest!(r127, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
// Set up a directory hierarchy like this:
|
||||||
|
//
|
||||||
|
// .gitignore
|
||||||
|
// foo/
|
||||||
|
// sherlock
|
||||||
|
// watson
|
||||||
|
//
|
||||||
|
// Where `.gitignore` contains `foo/sherlock`.
|
||||||
|
//
|
||||||
|
// ripgrep should ignore 'foo/sherlock' giving us results only from
|
||||||
|
// 'foo/watson' but on Windows ripgrep will include both 'foo/sherlock' and
|
||||||
|
// 'foo/watson' in the search results.
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "foo/sherlock\n");
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create("foo/sherlock", SHERLOCK);
|
||||||
|
dir.create("foo/watson", SHERLOCK);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
foo/watson:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
foo/watson:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
";
|
||||||
|
assert_eq!(expected, cmd.arg("Sherlock").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/128
|
||||||
|
rgtest!(r128, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_bytes("foo", b"01234567\x0b\n\x0b\n\x0b\n\x0b\nx");
|
||||||
|
|
||||||
|
eqnice!("foo:5:x\n", cmd.arg("-n").arg("x").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/131
|
||||||
|
//
|
||||||
|
// TODO(burntsushi): Darwin doesn't like this test for some reason. Probably
|
||||||
|
// due to the weird file path.
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
rgtest!(r131, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", "TopÑapa");
|
||||||
|
dir.create("TopÑapa", "test");
|
||||||
|
|
||||||
|
cmd.arg("test").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/137
|
||||||
|
//
|
||||||
|
// TODO(burntsushi): Figure out how to make this test work on Windows. Right
|
||||||
|
// now it gives "access denied" errors when trying to create a file symlink.
|
||||||
|
// For now, disable test on Windows.
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
rgtest!(r137, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.link_file("sherlock", "sym1");
|
||||||
|
dir.link_file("sherlock", "sym2");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
./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
|
||||||
|
sym1:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
sym1:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
sym2:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
sym2:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||||
|
";
|
||||||
|
cmd.arg("-j1").arg("Sherlock").arg("./").arg("sym1").arg("sym2");
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/156
|
||||||
|
rgtest!(r156, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
let expected = r#"#parse('widgets/foo_bar_macros.vm')
|
||||||
|
#parse ( 'widgets/mobile/foo_bar_macros.vm' )
|
||||||
|
#parse ("widgets/foobarhiddenformfields.vm")
|
||||||
|
#parse ( "widgets/foo_bar_legal.vm" )
|
||||||
|
#include( 'widgets/foo_bar_tips.vm' )
|
||||||
|
#include('widgets/mobile/foo_bar_macros.vm')
|
||||||
|
#include ("widgets/mobile/foo_bar_resetpw.vm")
|
||||||
|
#parse('widgets/foo-bar-macros.vm')
|
||||||
|
#parse ( 'widgets/mobile/foo-bar-macros.vm' )
|
||||||
|
#parse ("widgets/foo-bar-hiddenformfields.vm")
|
||||||
|
#parse ( "widgets/foo-bar-legal.vm" )
|
||||||
|
#include( 'widgets/foo-bar-tips.vm' )
|
||||||
|
#include('widgets/mobile/foo-bar-macros.vm')
|
||||||
|
#include ("widgets/mobile/foo-bar-resetpw.vm")
|
||||||
|
"#;
|
||||||
|
dir.create("testcase.txt", expected);
|
||||||
|
|
||||||
|
cmd.arg("-N");
|
||||||
|
cmd.arg(r#"#(?:parse|include)\s*\(\s*(?:"|')[./A-Za-z_-]+(?:"|')"#);
|
||||||
|
cmd.arg("testcase.txt");
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/184
|
||||||
|
rgtest!(r184, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create(".gitignore", ".*");
|
||||||
|
dir.create_dir("foo/bar");
|
||||||
|
dir.create("foo/bar/baz", "test");
|
||||||
|
|
||||||
|
cmd.arg("test");
|
||||||
|
eqnice!("foo/bar/baz:test\n", cmd.stdout());
|
||||||
|
|
||||||
|
cmd.current_dir(dir.path().join("./foo/bar"));
|
||||||
|
eqnice!("baz:test\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/199
|
||||||
|
rgtest!(r199, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "tEsT");
|
||||||
|
|
||||||
|
eqnice!("foo:tEsT\n", cmd.arg("--smart-case").arg(r"\btest\b").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/206
|
||||||
|
rgtest!(r206, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir("foo");
|
||||||
|
dir.create("foo/bar.txt", "test");
|
||||||
|
|
||||||
|
cmd.arg("test").arg("-g").arg("*.txt");
|
||||||
|
eqnice!("foo/bar.txt:test\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/210
|
||||||
|
#[cfg(unix)]
|
||||||
|
rgtest!(r210, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
|
let badutf8 = OsStr::from_bytes(&b"foo\xffbar"[..]);
|
||||||
|
|
||||||
|
// APFS does not support creating files with invalid UTF-8 bytes.
|
||||||
|
// https://github.com/BurntSushi/ripgrep/issues/559
|
||||||
|
if dir.try_create(badutf8, "test").is_ok() {
|
||||||
|
cmd.arg("-H").arg("test").arg(badutf8);
|
||||||
|
assert_eq!(b"foo\xffbar:test\n".to_vec(), cmd.output().stdout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/228
|
||||||
|
rgtest!(r228, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir("foo");
|
||||||
|
|
||||||
|
cmd.arg("--ignore-file").arg("foo").arg("test").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/229
|
||||||
|
rgtest!(r229, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "economie");
|
||||||
|
|
||||||
|
cmd.arg("-S").arg("[E]conomie").assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/251
|
||||||
|
rgtest!(r251, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "привет\nПривет\nПрИвЕт");
|
||||||
|
|
||||||
|
let expected = "foo:привет\nfoo:Привет\nfoo:ПрИвЕт\n";
|
||||||
|
eqnice!(expected, cmd.arg("-i").arg("привет").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/256
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
rgtest!(r256, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir("bar");
|
||||||
|
dir.create("bar/baz", "test");
|
||||||
|
dir.link_dir("bar", "foo");
|
||||||
|
|
||||||
|
eqnice!("foo/baz:test\n", cmd.arg("test").arg("foo").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/256
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
rgtest!(r256_j1, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir("bar");
|
||||||
|
dir.create("bar/baz", "test");
|
||||||
|
dir.link_dir("bar", "foo");
|
||||||
|
|
||||||
|
eqnice!("foo/baz:test\n", cmd.arg("-j1").arg("test").arg("foo").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/270
|
||||||
|
rgtest!(r270, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "-test");
|
||||||
|
|
||||||
|
cmd.arg("-e").arg("-test");
|
||||||
|
eqnice!("foo:-test\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/279
|
||||||
|
rgtest!(r279, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "test");
|
||||||
|
|
||||||
|
eqnice!("", cmd.arg("-q").arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/391
|
||||||
|
rgtest!(r391, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create("lock", "");
|
||||||
|
dir.create("bar.py", "");
|
||||||
|
dir.create(".git/packed-refs", "");
|
||||||
|
dir.create(".git/description", "");
|
||||||
|
|
||||||
|
cmd.args(&[
|
||||||
|
"--no-ignore", "--hidden", "--follow", "--files",
|
||||||
|
"--glob",
|
||||||
|
"!{.git,node_modules,plugged}/**",
|
||||||
|
"--glob",
|
||||||
|
"*.{js,json,php,md,styl,scss,sass,pug,html,config,py,cpp,c,go,hs}",
|
||||||
|
]);
|
||||||
|
eqnice!("bar.py\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/405
|
||||||
|
rgtest!(r405, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir("foo/bar");
|
||||||
|
dir.create_dir("bar/foo");
|
||||||
|
dir.create("foo/bar/file1.txt", "test");
|
||||||
|
dir.create("bar/foo/file2.txt", "test");
|
||||||
|
|
||||||
|
cmd.arg("-g").arg("!/foo/**").arg("test");
|
||||||
|
eqnice!("bar/foo/file2.txt:test\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/428
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
rgtest!(r428_color_context_path, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", "foo\nbar");
|
||||||
|
cmd.args(&[
|
||||||
|
"-A1", "-H", "--no-heading", "-N",
|
||||||
|
"--colors=match:none", "--color=always",
|
||||||
|
"foo",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = format!(
|
||||||
|
"{colored_path}:foo\n{colored_path}-bar\n",
|
||||||
|
colored_path=
|
||||||
|
"\x1b\x5b\x30\x6d\x1b\x5b\x33\x35\x6dsherlock\x1b\x5b\x30\x6d"
|
||||||
|
);
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/428
|
||||||
|
rgtest!(r428_unrecognized_style, |_: Dir, mut cmd: TestCommand| {
|
||||||
|
cmd.arg("--colors=match:style:").arg("Sherlock");
|
||||||
|
cmd.assert_err();
|
||||||
|
|
||||||
|
let output = cmd.cmd().output().unwrap();
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
let expected = "\
|
||||||
|
unrecognized style attribute ''. Choose from: nobold, bold, nointense, \
|
||||||
|
intense, nounderline, underline.
|
||||||
|
";
|
||||||
|
eqnice!(expected, stderr);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/451
|
||||||
|
rgtest!(r451_only_matching_as_in_issue, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("digits.txt", "1 2 3\n");
|
||||||
|
cmd.arg("--only-matching").arg(r"[0-9]+").arg("digits.txt");
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/451
|
||||||
|
rgtest!(r451_only_matching, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("digits.txt", "1 2 3\n123\n");
|
||||||
|
cmd.args(&[
|
||||||
|
"--only-matching", "--column", r"[0-9]", "digits.txt",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
1:1:1
|
||||||
|
1:3:2
|
||||||
|
1:5:3
|
||||||
|
2:1:1
|
||||||
|
2:2:2
|
||||||
|
2:3:3
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/483
|
||||||
|
rgtest!(r483_matching_no_stdout, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("file.py", "");
|
||||||
|
cmd.arg("--quiet").arg("--files").arg("--glob").arg("*.py");
|
||||||
|
eqnice!("", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/483
|
||||||
|
rgtest!(r483_non_matching_exit_code, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("file.rs", "");
|
||||||
|
cmd.arg("--quiet").arg("--files").arg("--glob").arg("*.py");
|
||||||
|
cmd.assert_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/493
|
||||||
|
rgtest!(r493, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("input.txt", "peshwaship 're seminomata");
|
||||||
|
|
||||||
|
cmd.arg("-o").arg(r"\b 're \b").arg("input.txt");
|
||||||
|
assert_eq!(" 're \n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/506
|
||||||
|
rgtest!(r506_word_not_parenthesized, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("wb.txt", "min minimum amin\nmax maximum amax");
|
||||||
|
cmd.arg("-w").arg("-o").arg("min|max").arg("wb.txt");
|
||||||
|
eqnice!("min\nmax\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/553
|
||||||
|
rgtest!(r553_switch, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
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
|
||||||
|
";
|
||||||
|
cmd.arg("-i").arg("sherlock");
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
|
||||||
|
// Repeat the `i` flag to make sure everything still works.
|
||||||
|
eqnice!(expected, cmd.arg("-i").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
rgtest!(r553_flag, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
Holmeses, success in the province of detective work must always
|
||||||
|
--
|
||||||
|
but Doctor Watson has to have it taken out for him and dusted,
|
||||||
|
and exhibited clearly, with a label attached.
|
||||||
|
";
|
||||||
|
cmd.arg("-C").arg("1").arg(r"world|attached").arg("sherlock");
|
||||||
|
eqnice!(expected, cmd.stdout());
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
and exhibited clearly, with a label attached.
|
||||||
|
";
|
||||||
|
eqnice!(expected, cmd.arg("-C").arg("0").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/568
|
||||||
|
rgtest!(r568_leading_hyphen_option_args, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("file", "foo bar -baz\n");
|
||||||
|
cmd.arg("-e-baz").arg("-e").arg("-baz").arg("file");
|
||||||
|
eqnice!("foo bar -baz\n", cmd.stdout());
|
||||||
|
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.arg("-rni").arg("bar").arg("file");
|
||||||
|
eqnice!("foo ni -baz\n", cmd.stdout());
|
||||||
|
|
||||||
|
let mut cmd = dir.command();
|
||||||
|
cmd.arg("-r").arg("-n").arg("-i").arg("bar").arg("file");
|
||||||
|
eqnice!("foo -n -baz\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/599
|
||||||
|
//
|
||||||
|
// This test used to check that we emitted color escape sequences even for
|
||||||
|
// empty matches, but with the addition of the JSON output format, clients no
|
||||||
|
// longer need to rely on escape sequences to parse matches. Therefore, we no
|
||||||
|
// longer emit useless escape sequences.
|
||||||
|
rgtest!(r599, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("input.txt", "\n\ntest\n");
|
||||||
|
cmd.args(&[
|
||||||
|
"--color", "ansi",
|
||||||
|
"--colors", "path:none",
|
||||||
|
"--colors", "line:none",
|
||||||
|
"--colors", "match:fg:red",
|
||||||
|
"--colors", "match:style:nobold",
|
||||||
|
"--line-number",
|
||||||
|
r"^$",
|
||||||
|
"input.txt",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
[0m1[0m:
|
||||||
|
[0m2[0m:
|
||||||
|
";
|
||||||
|
eqnice_repr!(expected, cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/693
|
||||||
|
rgtest!(r693_context_in_contextless_mode, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("foo", "xyz\n");
|
||||||
|
dir.create("bar", "xyz\n");
|
||||||
|
|
||||||
|
cmd.arg("-C1").arg("-c").arg("--sort-files").arg("xyz");
|
||||||
|
eqnice!("bar:1\nfoo:1\n", cmd.stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/807
|
||||||
|
rgtest!(r807, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create_dir(".git");
|
||||||
|
dir.create(".gitignore", ".a/b");
|
||||||
|
dir.create_dir(".a/b");
|
||||||
|
dir.create_dir(".a/c");
|
||||||
|
dir.create(".a/b/file", "test");
|
||||||
|
dir.create(".a/c/file", "test");
|
||||||
|
|
||||||
|
eqnice!(".a/c/file:test\n", cmd.arg("--hidden").arg("test").stdout());
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/900
|
||||||
|
rgtest!(r900, |dir: Dir, mut cmd: TestCommand| {
|
||||||
|
dir.create("sherlock", SHERLOCK);
|
||||||
|
dir.create("pat", "");
|
||||||
|
|
||||||
|
cmd.arg("-fpat").arg("sherlock").assert_err();
|
||||||
|
});
|
||||||
|
2309
tests/tests.rs
2309
tests/tests.rs
File diff suppressed because it is too large
Load Diff
302
tests/util.rs
302
tests/util.rs
@@ -1,11 +1,10 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::ffi::OsStr;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{self, Command};
|
use std::process::{self, Command};
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
|
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -13,10 +12,44 @@ use std::time::Duration;
|
|||||||
static TEST_DIR: &'static str = "ripgrep-tests";
|
static TEST_DIR: &'static str = "ripgrep-tests";
|
||||||
static NEXT_ID: AtomicUsize = ATOMIC_USIZE_INIT;
|
static NEXT_ID: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||||
|
|
||||||
|
/// Setup an empty work directory and return a command pointing to the ripgrep
|
||||||
|
/// executable whose CWD is set to the work directory.
|
||||||
|
///
|
||||||
|
/// The name given will be used to create the directory. Generally, it should
|
||||||
|
/// correspond to the test name.
|
||||||
|
pub fn setup(test_name: &str) -> (Dir, TestCommand) {
|
||||||
|
let dir = Dir::new(test_name);
|
||||||
|
let cmd = dir.command();
|
||||||
|
(dir, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like `setup`, but uses PCRE2 as the underlying regex engine.
|
||||||
|
pub fn setup_pcre2(test_name: &str) -> (Dir, TestCommand) {
|
||||||
|
let mut dir = Dir::new(test_name);
|
||||||
|
dir.pcre2(true);
|
||||||
|
let cmd = dir.command();
|
||||||
|
(dir, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Break the given string into lines, sort them and then join them back
|
||||||
|
/// together. This is useful for testing output from ripgrep that may not
|
||||||
|
/// always be in the same order.
|
||||||
|
pub fn sort_lines(lines: &str) -> String {
|
||||||
|
let mut lines: Vec<&str> = lines.trim().lines().collect();
|
||||||
|
lines.sort();
|
||||||
|
format!("{}\n", lines.join("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if the given program can be successfully executed
|
||||||
|
/// with a `--help` flag.
|
||||||
|
pub fn cmd_exists(program: &str) -> bool {
|
||||||
|
Command::new(program).arg("--help").output().is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
/// Dir represents a directory in which tests should be run.
|
/// Dir represents a directory in which tests should be run.
|
||||||
///
|
///
|
||||||
/// Directories are created from a global atomic counter to avoid duplicates.
|
/// Directories are created from a global atomic counter to avoid duplicates.
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Dir {
|
pub struct Dir {
|
||||||
/// The directory in which this test executable is running.
|
/// The directory in which this test executable is running.
|
||||||
root: PathBuf,
|
root: PathBuf,
|
||||||
@@ -24,6 +57,8 @@ pub struct Dir {
|
|||||||
/// files, they should go in here. This directory is also used as the CWD
|
/// files, they should go in here. This directory is also used as the CWD
|
||||||
/// for any processes created by the test.
|
/// for any processes created by the test.
|
||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
|
/// Set to true when the test should use PCRE2 as the regex engine.
|
||||||
|
pcre2: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dir {
|
impl Dir {
|
||||||
@@ -45,9 +80,21 @@ impl Dir {
|
|||||||
Dir {
|
Dir {
|
||||||
root: root,
|
root: root,
|
||||||
dir: dir,
|
dir: dir,
|
||||||
|
pcre2: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use PCRE2 for this test.
|
||||||
|
pub fn pcre2(&mut self, yes: bool) {
|
||||||
|
self.pcre2 = yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if this test is configured to use PCRE2 as
|
||||||
|
/// the regex engine.
|
||||||
|
pub fn is_pcre2(&self) -> bool {
|
||||||
|
self.pcre2
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new file with the given name and contents in this directory,
|
/// Create a new file with the given name and contents in this directory,
|
||||||
/// or panic on error.
|
/// or panic on error.
|
||||||
pub fn create<P: AsRef<Path>>(&self, name: P, contents: &str) {
|
pub fn create<P: AsRef<Path>>(&self, name: P, contents: &str) {
|
||||||
@@ -75,18 +122,19 @@ impl Dir {
|
|||||||
/// Create a new file with the given name and contents in this directory,
|
/// Create a new file with the given name and contents in this directory,
|
||||||
/// or panic on error.
|
/// or panic on error.
|
||||||
pub fn create_bytes<P: AsRef<Path>>(&self, name: P, contents: &[u8]) {
|
pub fn create_bytes<P: AsRef<Path>>(&self, name: P, contents: &[u8]) {
|
||||||
let path = self.dir.join(name);
|
let path = self.dir.join(&name);
|
||||||
nice_err(&path, self.try_create_bytes(&path, contents));
|
nice_err(&path, self.try_create_bytes(name, contents));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
fn try_create_bytes<P: AsRef<Path>>(
|
pub fn try_create_bytes<P: AsRef<Path>>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
name: P,
|
||||||
contents: &[u8],
|
contents: &[u8],
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
let mut file = File::create(&path)?;
|
let path = self.dir.join(name);
|
||||||
|
let mut file = File::create(path)?;
|
||||||
file.write_all(contents)?;
|
file.write_all(contents)?;
|
||||||
file.flush()
|
file.flush()
|
||||||
}
|
}
|
||||||
@@ -113,12 +161,15 @@ impl Dir {
|
|||||||
/// * Sets the `--path-separator` to `/` so that paths have the same output
|
/// * Sets the `--path-separator` to `/` so that paths have the same output
|
||||||
/// on all systems. Tests that need to check `--path-separator` itself
|
/// on all systems. Tests that need to check `--path-separator` itself
|
||||||
/// can simply pass it again to override it.
|
/// can simply pass it again to override it.
|
||||||
pub fn command(&self) -> process::Command {
|
pub fn command(&self) -> TestCommand {
|
||||||
let mut cmd = process::Command::new(&self.bin());
|
let mut cmd = process::Command::new(&self.bin());
|
||||||
cmd.env_remove("RIPGREP_CONFIG_PATH");
|
cmd.env_remove("RIPGREP_CONFIG_PATH");
|
||||||
cmd.current_dir(&self.dir);
|
cmd.current_dir(&self.dir);
|
||||||
cmd.arg("--path-separator").arg("/");
|
cmd.arg("--path-separator").arg("/");
|
||||||
cmd
|
if self.is_pcre2() {
|
||||||
|
cmd.arg("--pcre2");
|
||||||
|
}
|
||||||
|
TestCommand { dir: self.clone(), cmd: cmd }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the path to the ripgrep executable.
|
/// Returns the path to the ripgrep executable.
|
||||||
@@ -182,16 +233,54 @@ impl Dir {
|
|||||||
let _ = fs::remove_file(&target);
|
let _ = fs::remove_file(&target);
|
||||||
nice_err(&target, symlink_file(&src, &target));
|
nice_err(&target, symlink_file(&src, &target));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simple wrapper around a process::Command with some conveniences.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TestCommand {
|
||||||
|
/// The dir used to launched this command.
|
||||||
|
dir: Dir,
|
||||||
|
/// The actual command we use to control the process.
|
||||||
|
cmd: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestCommand {
|
||||||
|
/// Returns a mutable reference to the underlying command.
|
||||||
|
pub fn cmd(&mut self) -> &mut Command {
|
||||||
|
&mut self.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an argument to pass to the command.
|
||||||
|
pub fn arg<A: AsRef<OsStr>>(&mut self, arg: A) -> &mut TestCommand {
|
||||||
|
self.cmd.arg(arg);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add any number of arguments to the command.
|
||||||
|
pub fn args<I, A>(
|
||||||
|
&mut self,
|
||||||
|
args: I,
|
||||||
|
) -> &mut TestCommand
|
||||||
|
where I: IntoIterator<Item=A>,
|
||||||
|
A: AsRef<OsStr>
|
||||||
|
{
|
||||||
|
self.cmd.args(args);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the working directory for this command.
|
||||||
|
///
|
||||||
|
/// Note that this does not need to be called normally, since the creation
|
||||||
|
/// of this TestCommand causes its working directory to be set to the
|
||||||
|
/// test's directory automatically.
|
||||||
|
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut TestCommand {
|
||||||
|
self.cmd.current_dir(dir);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs and captures the stdout of the given command.
|
/// Runs and captures the stdout of the given command.
|
||||||
///
|
pub fn stdout(&mut self) -> String {
|
||||||
/// If the return type could not be created from a string, then this
|
let o = self.output();
|
||||||
/// panics.
|
|
||||||
pub fn stdout<E: fmt::Debug, T: FromStr<Err=E>>(
|
|
||||||
&self,
|
|
||||||
cmd: &mut process::Command,
|
|
||||||
) -> T {
|
|
||||||
let o = self.output(cmd);
|
|
||||||
let stdout = String::from_utf8_lossy(&o.stdout);
|
let stdout = String::from_utf8_lossy(&o.stdout);
|
||||||
match stdout.parse() {
|
match stdout.parse() {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
@@ -205,23 +294,13 @@ impl Dir {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the output of a command. If the command failed, then this panics.
|
|
||||||
pub fn output(&self, cmd: &mut process::Command) -> process::Output {
|
|
||||||
let output = cmd.output().unwrap();
|
|
||||||
self.expect_success(cmd, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pipe `input` to a command, and collect the output.
|
/// Pipe `input` to a command, and collect the output.
|
||||||
pub fn pipe(
|
pub fn pipe(&mut self, input: &str) -> String {
|
||||||
&self,
|
self.cmd.stdin(process::Stdio::piped());
|
||||||
cmd: &mut process::Command,
|
self.cmd.stdout(process::Stdio::piped());
|
||||||
input: &str
|
self.cmd.stderr(process::Stdio::piped());
|
||||||
) -> process::Output {
|
|
||||||
cmd.stdin(process::Stdio::piped());
|
|
||||||
cmd.stdout(process::Stdio::piped());
|
|
||||||
cmd.stderr(process::Stdio::piped());
|
|
||||||
|
|
||||||
let mut child = cmd.spawn().unwrap();
|
let mut child = self.cmd.spawn().unwrap();
|
||||||
|
|
||||||
// Pipe input to child process using a separate thread to avoid
|
// Pipe input to child process using a separate thread to avoid
|
||||||
// risk of deadlock between parent and child process.
|
// risk of deadlock between parent and child process.
|
||||||
@@ -231,20 +310,86 @@ impl Dir {
|
|||||||
write!(stdin, "{}", input)
|
write!(stdin, "{}", input)
|
||||||
});
|
});
|
||||||
|
|
||||||
let output = self.expect_success(
|
let output = self.expect_success(child.wait_with_output().unwrap());
|
||||||
cmd,
|
|
||||||
child.wait_with_output().unwrap(),
|
|
||||||
);
|
|
||||||
worker.join().unwrap().unwrap();
|
worker.join().unwrap().unwrap();
|
||||||
output
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
match stdout.parse() {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(err) => {
|
||||||
|
panic!(
|
||||||
|
"could not convert from string: {:?}\n\n{}",
|
||||||
|
err,
|
||||||
|
stdout
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `o` is not the output of a successful process run
|
/// Gets the output of a command. If the command failed, then this panics.
|
||||||
fn expect_success(
|
pub fn output(&mut self) -> process::Output {
|
||||||
&self,
|
let output = self.cmd.output().unwrap();
|
||||||
cmd: &process::Command,
|
self.expect_success(output)
|
||||||
o: process::Output
|
}
|
||||||
) -> process::Output {
|
|
||||||
|
/// Runs the command and asserts that it resulted in an error exit code.
|
||||||
|
pub fn assert_err(&mut self) {
|
||||||
|
let o = self.cmd.output().unwrap();
|
||||||
|
if o.status.success() {
|
||||||
|
panic!(
|
||||||
|
"\n\n===== {:?} =====\n\
|
||||||
|
command succeeded but expected failure!\
|
||||||
|
\n\ncwd: {}\
|
||||||
|
\n\nstatus: {}\
|
||||||
|
\n\nstdout: {}\n\nstderr: {}\
|
||||||
|
\n\n=====\n",
|
||||||
|
self.cmd,
|
||||||
|
self.dir.dir.display(),
|
||||||
|
o.status,
|
||||||
|
String::from_utf8_lossy(&o.stdout),
|
||||||
|
String::from_utf8_lossy(&o.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the command and asserts that its exit code matches expected exit
|
||||||
|
/// code.
|
||||||
|
pub fn assert_exit_code(&mut self, expected_code: i32) {
|
||||||
|
let code = self.cmd.output().unwrap().status.code().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
expected_code, code,
|
||||||
|
"\n\n===== {:?} =====\n\
|
||||||
|
expected exit code did not match\
|
||||||
|
\n\nexpected: {}\
|
||||||
|
\n\nfound: {}\
|
||||||
|
\n\n=====\n",
|
||||||
|
self.cmd,
|
||||||
|
expected_code,
|
||||||
|
code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the command and asserts that something was printed to stderr.
|
||||||
|
pub fn assert_non_empty_stderr(&mut self) {
|
||||||
|
let o = self.cmd.output().unwrap();
|
||||||
|
if o.status.success() || o.stderr.is_empty() {
|
||||||
|
panic!(
|
||||||
|
"\n\n===== {:?} =====\n\
|
||||||
|
command succeeded but expected failure!\
|
||||||
|
\n\ncwd: {}\
|
||||||
|
\n\nstatus: {}\
|
||||||
|
\n\nstdout: {}\n\nstderr: {}\
|
||||||
|
\n\n=====\n",
|
||||||
|
self.cmd,
|
||||||
|
self.dir.dir.display(),
|
||||||
|
o.status,
|
||||||
|
String::from_utf8_lossy(&o.stdout),
|
||||||
|
String::from_utf8_lossy(&o.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_success(&self, o: process::Output) -> process::Output {
|
||||||
if !o.status.success() {
|
if !o.status.success() {
|
||||||
let suggest =
|
let suggest =
|
||||||
if o.stderr.is_empty() {
|
if o.stderr.is_empty() {
|
||||||
@@ -262,79 +407,12 @@ impl Dir {
|
|||||||
\n\nstdout: {}\
|
\n\nstdout: {}\
|
||||||
\n\nstderr: {}\
|
\n\nstderr: {}\
|
||||||
\n\n==========\n",
|
\n\n==========\n",
|
||||||
suggest, cmd, self.dir.display(), o.status,
|
suggest, self.cmd, self.dir.dir.display(), o.status,
|
||||||
String::from_utf8_lossy(&o.stdout),
|
String::from_utf8_lossy(&o.stdout),
|
||||||
String::from_utf8_lossy(&o.stderr));
|
String::from_utf8_lossy(&o.stderr));
|
||||||
}
|
}
|
||||||
o
|
o
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the given command and asserts that it resulted in an error exit
|
|
||||||
/// code.
|
|
||||||
pub fn assert_err(&self, cmd: &mut process::Command) {
|
|
||||||
let o = cmd.output().unwrap();
|
|
||||||
if o.status.success() {
|
|
||||||
panic!(
|
|
||||||
"\n\n===== {:?} =====\n\
|
|
||||||
command succeeded but expected failure!\
|
|
||||||
\n\ncwd: {}\
|
|
||||||
\n\nstatus: {}\
|
|
||||||
\n\nstdout: {}\n\nstderr: {}\
|
|
||||||
\n\n=====\n",
|
|
||||||
cmd,
|
|
||||||
self.dir.display(),
|
|
||||||
o.status,
|
|
||||||
String::from_utf8_lossy(&o.stdout),
|
|
||||||
String::from_utf8_lossy(&o.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the given command and asserts that its exit code matches expected
|
|
||||||
/// exit code.
|
|
||||||
pub fn assert_exit_code(
|
|
||||||
&self,
|
|
||||||
expected_code: i32,
|
|
||||||
cmd: &mut process::Command,
|
|
||||||
) {
|
|
||||||
let code = cmd.status().unwrap().code().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
expected_code, code,
|
|
||||||
"\n\n===== {:?} =====\n\
|
|
||||||
expected exit code did not match\
|
|
||||||
\n\nexpected: {}\
|
|
||||||
\n\nfound: {}\
|
|
||||||
\n\n=====\n",
|
|
||||||
cmd, expected_code, code
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the given command and asserts that something was printed to
|
|
||||||
/// stderr.
|
|
||||||
pub fn assert_non_empty_stderr(&self, cmd: &mut process::Command) {
|
|
||||||
let o = cmd.output().unwrap();
|
|
||||||
if o.status.success() || o.stderr.is_empty() {
|
|
||||||
panic!("\n\n===== {:?} =====\n\
|
|
||||||
command succeeded but expected failure!\
|
|
||||||
\n\ncwd: {}\
|
|
||||||
\n\nstatus: {}\
|
|
||||||
\n\nstdout: {}\n\nstderr: {}\
|
|
||||||
\n\n=====\n",
|
|
||||||
cmd, self.dir.display(), o.status,
|
|
||||||
String::from_utf8_lossy(&o.stdout),
|
|
||||||
String::from_utf8_lossy(&o.stderr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A simple wrapper around a process::Command with some conveniences.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TestCommand {
|
|
||||||
/// The dir used to launched this command.
|
|
||||||
dir: Dir,
|
|
||||||
/// The actual command we use to control the process.
|
|
||||||
cmd: Command,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nice_err<T, E: error::Error>(
|
fn nice_err<T, E: error::Error>(
|
||||||
|
363
tests/workdir.rs
363
tests/workdir.rs
@@ -1,363 +0,0 @@
|
|||||||
use std::env;
|
|
||||||
use std::error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::fs::{self, File};
|
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::process;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
static TEST_DIR: &'static str = "ripgrep-tests";
|
|
||||||
static NEXT_ID: AtomicUsize = ATOMIC_USIZE_INIT;
|
|
||||||
|
|
||||||
/// `WorkDir` represents a directory in which tests are run.
|
|
||||||
///
|
|
||||||
/// Directories are created from a global atomic counter to avoid duplicates.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct WorkDir {
|
|
||||||
/// The directory in which this test executable is running.
|
|
||||||
root: PathBuf,
|
|
||||||
/// The directory in which the test should run. If a test needs to create
|
|
||||||
/// files, they should go in here. This directory is also used as the CWD
|
|
||||||
/// for any processes created by the test.
|
|
||||||
dir: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorkDir {
|
|
||||||
/// Create a new test working directory with the given name. The name
|
|
||||||
/// does not need to be distinct for each invocation, but should correspond
|
|
||||||
/// to a logical grouping of tests.
|
|
||||||
pub fn new(name: &str) -> WorkDir {
|
|
||||||
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
|
|
||||||
let root = env::current_exe()
|
|
||||||
.unwrap()
|
|
||||||
.parent()
|
|
||||||
.expect("executable's directory")
|
|
||||||
.to_path_buf();
|
|
||||||
let dir = env::temp_dir()
|
|
||||||
.join(TEST_DIR)
|
|
||||||
.join(name)
|
|
||||||
.join(&format!("{}", id));
|
|
||||||
nice_err(&dir, repeat(|| fs::create_dir_all(&dir)));
|
|
||||||
WorkDir {
|
|
||||||
root: root,
|
|
||||||
dir: dir,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like `new`, but also returns a command that whose program is configured
|
|
||||||
/// to ripgrep's executable and has its current working directory set to
|
|
||||||
/// this work dir.
|
|
||||||
pub fn new_with(name: &str) -> (WorkDir, process::Command) {
|
|
||||||
let wd = WorkDir::new(name);
|
|
||||||
let command = wd.command();
|
|
||||||
(wd, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new file with the given name and contents in this directory,
|
|
||||||
/// or panic on error.
|
|
||||||
pub fn create<P: AsRef<Path>>(&self, name: P, contents: &str) {
|
|
||||||
self.create_bytes(name, contents.as_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to create a new file with the given name and contents in this
|
|
||||||
/// directory.
|
|
||||||
pub fn try_create<P: AsRef<Path>>(
|
|
||||||
&self,
|
|
||||||
name: P,
|
|
||||||
contents: &str,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
let path = self.dir.join(name);
|
|
||||||
self.try_create_bytes(path, contents.as_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new file with the given name and size.
|
|
||||||
pub fn create_size<P: AsRef<Path>>(&self, name: P, filesize: u64) {
|
|
||||||
let path = self.dir.join(name);
|
|
||||||
let file = nice_err(&path, File::create(&path));
|
|
||||||
nice_err(&path, file.set_len(filesize));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new file with the given name and contents in this directory,
|
|
||||||
/// or panic on error.
|
|
||||||
pub fn create_bytes<P: AsRef<Path>>(&self, name: P, contents: &[u8]) {
|
|
||||||
let path = self.dir.join(name);
|
|
||||||
nice_err(&path, self.try_create_bytes(&path, contents));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to create a new file with the given name and contents in this
|
|
||||||
/// directory.
|
|
||||||
fn try_create_bytes<P: AsRef<Path>>(
|
|
||||||
&self,
|
|
||||||
path: P,
|
|
||||||
contents: &[u8],
|
|
||||||
) -> io::Result<()> {
|
|
||||||
let mut file = File::create(&path)?;
|
|
||||||
file.write_all(contents)?;
|
|
||||||
file.flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a file with the given name from this directory.
|
|
||||||
pub fn remove<P: AsRef<Path>>(&self, name: P) {
|
|
||||||
let path = self.dir.join(name);
|
|
||||||
nice_err(&path, fs::remove_file(&path));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new directory with the given path (and any directories above
|
|
||||||
/// it) inside this directory.
|
|
||||||
pub fn create_dir<P: AsRef<Path>>(&self, path: P) {
|
|
||||||
let path = self.dir.join(path);
|
|
||||||
nice_err(&path, repeat(|| fs::create_dir_all(&path)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new command that is set to use the ripgrep executable in
|
|
||||||
/// this working directory.
|
|
||||||
///
|
|
||||||
/// This also:
|
|
||||||
///
|
|
||||||
/// * Unsets the `RIPGREP_CONFIG_PATH` environment variable.
|
|
||||||
/// * Sets the `--path-separator` to `/` so that paths have the same output
|
|
||||||
/// on all systems. Tests that need to check `--path-separator` itself
|
|
||||||
/// can simply pass it again to override it.
|
|
||||||
pub fn command(&self) -> process::Command {
|
|
||||||
let mut cmd = process::Command::new(&self.bin());
|
|
||||||
cmd.env_remove("RIPGREP_CONFIG_PATH");
|
|
||||||
cmd.current_dir(&self.dir);
|
|
||||||
cmd.arg("--path-separator").arg("/");
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the path to the ripgrep executable.
|
|
||||||
pub fn bin(&self) -> PathBuf {
|
|
||||||
if cfg!(windows) {
|
|
||||||
self.root.join("../rg.exe")
|
|
||||||
} else {
|
|
||||||
self.root.join("../rg")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the path to this directory.
|
|
||||||
pub fn path(&self) -> &Path {
|
|
||||||
&self.dir
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a directory symlink to the src with the given target name
|
|
||||||
/// in this directory.
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub fn link_dir<S: AsRef<Path>, T: AsRef<Path>>(&self, src: S, target: T) {
|
|
||||||
use std::os::unix::fs::symlink;
|
|
||||||
let src = self.dir.join(src);
|
|
||||||
let target = self.dir.join(target);
|
|
||||||
let _ = fs::remove_file(&target);
|
|
||||||
nice_err(&target, symlink(&src, &target));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a directory symlink to the src with the given target name
|
|
||||||
/// in this directory.
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn link_dir<S: AsRef<Path>, T: AsRef<Path>>(&self, src: S, target: T) {
|
|
||||||
use std::os::windows::fs::symlink_dir;
|
|
||||||
let src = self.dir.join(src);
|
|
||||||
let target = self.dir.join(target);
|
|
||||||
let _ = fs::remove_dir(&target);
|
|
||||||
nice_err(&target, symlink_dir(&src, &target));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a file symlink to the src with the given target name
|
|
||||||
/// in this directory.
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub fn link_file<S: AsRef<Path>, T: AsRef<Path>>(
|
|
||||||
&self,
|
|
||||||
src: S,
|
|
||||||
target: T,
|
|
||||||
) {
|
|
||||||
self.link_dir(src, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a file symlink to the src with the given target name
|
|
||||||
/// in this directory.
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn link_file<S: AsRef<Path>, T: AsRef<Path>>(
|
|
||||||
&self,
|
|
||||||
src: S,
|
|
||||||
target: T,
|
|
||||||
) {
|
|
||||||
use std::os::windows::fs::symlink_file;
|
|
||||||
let src = self.dir.join(src);
|
|
||||||
let target = self.dir.join(target);
|
|
||||||
let _ = fs::remove_file(&target);
|
|
||||||
nice_err(&target, symlink_file(&src, &target));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs and captures the stdout of the given command.
|
|
||||||
///
|
|
||||||
/// If the return type could not be created from a string, then this
|
|
||||||
/// panics.
|
|
||||||
pub fn stdout<E: fmt::Debug, T: FromStr<Err=E>>(
|
|
||||||
&self,
|
|
||||||
cmd: &mut process::Command,
|
|
||||||
) -> T {
|
|
||||||
let o = self.output(cmd);
|
|
||||||
let stdout = String::from_utf8_lossy(&o.stdout);
|
|
||||||
match stdout.parse() {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(err) => {
|
|
||||||
panic!(
|
|
||||||
"could not convert from string: {:?}\n\n{}",
|
|
||||||
err,
|
|
||||||
stdout
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the output of a command. If the command failed, then this panics.
|
|
||||||
pub fn output(&self, cmd: &mut process::Command) -> process::Output {
|
|
||||||
let output = cmd.output().unwrap();
|
|
||||||
self.expect_success(cmd, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pipe `input` to a command, and collect the output.
|
|
||||||
pub fn pipe(
|
|
||||||
&self,
|
|
||||||
cmd: &mut process::Command,
|
|
||||||
input: &str
|
|
||||||
) -> process::Output {
|
|
||||||
cmd.stdin(process::Stdio::piped());
|
|
||||||
cmd.stdout(process::Stdio::piped());
|
|
||||||
cmd.stderr(process::Stdio::piped());
|
|
||||||
|
|
||||||
let mut child = cmd.spawn().unwrap();
|
|
||||||
|
|
||||||
// Pipe input to child process using a separate thread to avoid
|
|
||||||
// risk of deadlock between parent and child process.
|
|
||||||
let mut stdin = child.stdin.take().expect("expected standard input");
|
|
||||||
let input = input.to_owned();
|
|
||||||
let worker = thread::spawn(move || {
|
|
||||||
write!(stdin, "{}", input)
|
|
||||||
});
|
|
||||||
|
|
||||||
let output = self.expect_success(
|
|
||||||
cmd,
|
|
||||||
child.wait_with_output().unwrap(),
|
|
||||||
);
|
|
||||||
worker.join().unwrap().unwrap();
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `o` is not the output of a successful process run
|
|
||||||
fn expect_success(
|
|
||||||
&self,
|
|
||||||
cmd: &process::Command,
|
|
||||||
o: process::Output
|
|
||||||
) -> process::Output {
|
|
||||||
if !o.status.success() {
|
|
||||||
let suggest =
|
|
||||||
if o.stderr.is_empty() {
|
|
||||||
"\n\nDid your search end up with no results?".to_string()
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
panic!("\n\n==========\n\
|
|
||||||
command failed but expected success!\
|
|
||||||
{}\
|
|
||||||
\n\ncommand: {:?}\
|
|
||||||
\ncwd: {}\
|
|
||||||
\n\nstatus: {}\
|
|
||||||
\n\nstdout: {}\
|
|
||||||
\n\nstderr: {}\
|
|
||||||
\n\n==========\n",
|
|
||||||
suggest, cmd, self.dir.display(), o.status,
|
|
||||||
String::from_utf8_lossy(&o.stdout),
|
|
||||||
String::from_utf8_lossy(&o.stderr));
|
|
||||||
}
|
|
||||||
o
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the given command and asserts that it resulted in an error exit
|
|
||||||
/// code.
|
|
||||||
pub fn assert_err(&self, cmd: &mut process::Command) {
|
|
||||||
let o = cmd.output().unwrap();
|
|
||||||
if o.status.success() {
|
|
||||||
panic!(
|
|
||||||
"\n\n===== {:?} =====\n\
|
|
||||||
command succeeded but expected failure!\
|
|
||||||
\n\ncwd: {}\
|
|
||||||
\n\nstatus: {}\
|
|
||||||
\n\nstdout: {}\n\nstderr: {}\
|
|
||||||
\n\n=====\n",
|
|
||||||
cmd,
|
|
||||||
self.dir.display(),
|
|
||||||
o.status,
|
|
||||||
String::from_utf8_lossy(&o.stdout),
|
|
||||||
String::from_utf8_lossy(&o.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the given command and asserts that its exit code matches expected
|
|
||||||
/// exit code.
|
|
||||||
pub fn assert_exit_code(
|
|
||||||
&self,
|
|
||||||
expected_code: i32,
|
|
||||||
cmd: &mut process::Command,
|
|
||||||
) {
|
|
||||||
let code = cmd.status().unwrap().code().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
expected_code, code,
|
|
||||||
"\n\n===== {:?} =====\n\
|
|
||||||
expected exit code did not match\
|
|
||||||
\n\nexpected: {}\
|
|
||||||
\n\nfound: {}\
|
|
||||||
\n\n=====\n",
|
|
||||||
cmd, expected_code, code
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the given command and asserts that something was printed to
|
|
||||||
/// stderr.
|
|
||||||
pub fn assert_non_empty_stderr(&self, cmd: &mut process::Command) {
|
|
||||||
let o = cmd.output().unwrap();
|
|
||||||
if o.status.success() || o.stderr.is_empty() {
|
|
||||||
panic!("\n\n===== {:?} =====\n\
|
|
||||||
command succeeded but expected failure!\
|
|
||||||
\n\ncwd: {}\
|
|
||||||
\n\nstatus: {}\
|
|
||||||
\n\nstdout: {}\n\nstderr: {}\
|
|
||||||
\n\n=====\n",
|
|
||||||
cmd, self.dir.display(), o.status,
|
|
||||||
String::from_utf8_lossy(&o.stdout),
|
|
||||||
String::from_utf8_lossy(&o.stderr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn nice_err<P: AsRef<Path>, T, E: error::Error>(
|
|
||||||
path: P,
|
|
||||||
res: Result<T, E>,
|
|
||||||
) -> T {
|
|
||||||
match res {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(err) => {
|
|
||||||
panic!("{}: {:?}", path.as_ref().display(), err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn repeat<F: FnMut() -> io::Result<()>>(mut f: F) -> io::Result<()> {
|
|
||||||
let mut last_err = None;
|
|
||||||
for _ in 0..10 {
|
|
||||||
if let Err(err) = f() {
|
|
||||||
last_err = Some(err);
|
|
||||||
thread::sleep(Duration::from_millis(500));
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(last_err.unwrap())
|
|
||||||
}
|
|
Reference in New Issue
Block a user