Compare commits
280 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a075a462fa | ||
|
|
24f753c306 | ||
|
|
1aae2759ad | ||
|
|
91646f6cca | ||
|
|
031ace209d | ||
|
|
942e9c4743 | ||
|
|
12c9656b18 | ||
|
|
8bf3760cdb | ||
|
|
c96623e66a | ||
|
|
36f949633b | ||
|
|
811fcc1fe8 | ||
|
|
79a8d0ab3f | ||
|
|
fbf8265cde | ||
|
|
d79add341b | ||
|
|
12b2b1f624 | ||
|
|
3aaf550ca5 | ||
|
|
d4876cd064 | ||
|
|
867a57e176 | ||
|
|
ec4904df33 | ||
|
|
c4ea157cb7 | ||
|
|
0156967f4c | ||
|
|
3238707b0b | ||
|
|
31fbae597f | ||
|
|
f2e1711781 | ||
|
|
94d600e6e1 | ||
|
|
b904c5d9dc | ||
|
|
5a29417796 | ||
|
|
f694800768 | ||
|
|
bd1c9e9499 | ||
|
|
f04b0dd95c | ||
|
|
5487dffefa | ||
|
|
1fc6787648 | ||
|
|
11e164aec9 | ||
|
|
1c1331d926 | ||
|
|
cbe94823d2 | ||
|
|
d8712daf27 | ||
|
|
7cbbef019f | ||
|
|
4d29d886e5 | ||
|
|
247a9398f4 | ||
|
|
4c3025ab1c | ||
|
|
4981991a6e | ||
|
|
51440f59cd | ||
|
|
7b8a8d77d0 | ||
|
|
4737326ed3 | ||
|
|
a3537aa32a | ||
|
|
d3e118a786 | ||
|
|
4e52059ad6 | ||
|
|
60c016c243 | ||
|
|
4665128f25 | ||
|
|
dde5bd5a80 | ||
|
|
762ad44f71 | ||
|
|
705386934d | ||
|
|
97bbc6ef11 | ||
|
|
27a980c1bc | ||
|
|
e8645dc8ae | ||
|
|
e96d93034a | ||
|
|
bc5accc035 | ||
|
|
c9d0ca8257 | ||
|
|
45fe4aab96 | ||
|
|
97f981fbcb | ||
|
|
59329dcc61 | ||
|
|
604da8eb86 | ||
|
|
1c964372ad | ||
|
|
50a961960e | ||
|
|
7481c5fe29 | ||
|
|
3ae37b0937 | ||
|
|
4ee6dbe422 | ||
|
|
cd4bdcf810 | ||
|
|
175406df01 | ||
|
|
89811d43d4 | ||
|
|
f0053682c0 | ||
|
|
35045d6105 | ||
|
|
95f552fc06 | ||
|
|
48353bea17 | ||
|
|
703d5b558e | ||
|
|
47efea234f | ||
|
|
ca0d8998a2 | ||
|
|
fdf24317ac | ||
|
|
b9d5f22a4d | ||
|
|
67bb4f040f | ||
|
|
cee2f09a6d | ||
|
|
ced777e91f | ||
|
|
e9d9083898 | ||
|
|
46dff8f4be | ||
|
|
7aa6e87952 | ||
|
|
925d0db9f0 | ||
|
|
316ffd87b3 | ||
|
|
5943b1effe | ||
|
|
c42f97b4da | ||
|
|
0d9bba7816 | ||
|
|
3550f2e29a | ||
|
|
babe80d498 | ||
|
|
3e892a7a80 | ||
|
|
1df3f0b793 | ||
|
|
b3935935cb | ||
|
|
67abbf6f22 | ||
|
|
7b9f7d7dc6 | ||
|
|
7ab29a91d0 | ||
|
|
9fa38c6232 | ||
|
|
de79be2db2 | ||
|
|
416b69bae5 | ||
|
|
3e78fce3a3 | ||
|
|
7a3fd1f23f | ||
|
|
d306403440 | ||
|
|
ebabe1df6a | ||
|
|
f27aa3ff6f | ||
|
|
20ccd441f2 | ||
|
|
104d740f76 | ||
|
|
2da0eab2b8 | ||
|
|
b8c7864a02 | ||
|
|
ec26995655 | ||
|
|
a41235a3b5 | ||
|
|
1a91b900e7 | ||
|
|
2b15832655 | ||
|
|
b1c52b52d6 | ||
|
|
109bc3f78e | ||
|
|
b62195b33f | ||
|
|
baebfd7add | ||
|
|
19e405e5c5 | ||
|
|
f85822266f | ||
|
|
b034b77798 | ||
|
|
278e1168bf | ||
|
|
6a8051b258 | ||
|
|
a13ac3e3d4 | ||
|
|
a72467996b | ||
|
|
9395076468 | ||
|
|
a12c63957b | ||
|
|
982265af70 | ||
|
|
ed94aedf27 | ||
|
|
fd5ae2f795 | ||
|
|
3d6a39be06 | ||
|
|
e7839f2200 | ||
|
|
9dc5464c84 | ||
|
|
95edcd4d3a | ||
|
|
d97f404970 | ||
|
|
b2bbd46178 | ||
|
|
82542df5cb | ||
|
|
e4329037aa | ||
|
|
ab0d1c1c79 | ||
|
|
2015c56e8d | ||
|
|
23ad8b989d | ||
|
|
a8f3d9e87e | ||
|
|
9f1aae64f8 | ||
|
|
1595f0faf5 | ||
|
|
8eeb0c0b60 | ||
|
|
423f2a1927 | ||
|
|
4b5e789a2a | ||
|
|
37b731a048 | ||
|
|
a44735aa87 | ||
|
|
6b2efd4d88 | ||
|
|
c8227e0cf3 | ||
|
|
b941c10b90 | ||
|
|
872a107658 | ||
|
|
71ad9bf393 | ||
|
|
f733e9ebe4 | ||
|
|
ce85df1d2e | ||
|
|
a6e3cab65a | ||
|
|
7b860affbe | ||
|
|
af4dc78537 | ||
|
|
9ce0484670 | ||
|
|
346bad7dfc | ||
|
|
56fe93d343 | ||
|
|
155676b474 | ||
|
|
a3fc4cdded | ||
|
|
3bec8f3f0a | ||
|
|
3b37f12ec0 | ||
|
|
a2ed677e03 | ||
|
|
2fb9c3c42c | ||
|
|
447e1ba0e2 | ||
|
|
3b45059212 | ||
|
|
f74078af5b | ||
|
|
5ff9b2f2a2 | ||
|
|
cc90511ab2 | ||
|
|
f5d60a80a8 | ||
|
|
6fa158f6d3 | ||
|
|
ef6dea40ff | ||
|
|
9035c6b7b3 | ||
|
|
f5eb36baac | ||
|
|
6367dd61ba | ||
|
|
98892de1c1 | ||
|
|
273c14a45a | ||
|
|
b33e9cba69 | ||
|
|
d5c045469b | ||
|
|
0ce82403d4 | ||
|
|
d2f95f6e59 | ||
|
|
25c259112b | ||
|
|
dfebed6cbe | ||
|
|
9981e7883a | ||
|
|
8d202e4bcf | ||
|
|
14c194dbe1 | ||
|
|
d547b92d76 | ||
|
|
e5a9cd1b64 | ||
|
|
2115774c6e | ||
|
|
9087154b74 | ||
|
|
bb4fd9ddce | ||
|
|
d772b21f3d | ||
|
|
1b14e245be | ||
|
|
49003e8488 | ||
|
|
80c362623a | ||
|
|
c1c484d1a7 | ||
|
|
263e2b012f | ||
|
|
b80a986721 | ||
|
|
8a91d3132f | ||
|
|
525d051172 | ||
|
|
5a9883d27c | ||
|
|
f462d092e7 | ||
|
|
fe84928c85 | ||
|
|
f7eaf67fc3 | ||
|
|
c1c92e4fee | ||
|
|
5644bbe43a | ||
|
|
aeb3a5ba0f | ||
|
|
24e14a0341 | ||
|
|
2a2b1506d4 | ||
|
|
4d6b3c727e | ||
|
|
c2bf9e3d45 | ||
|
|
dad73b92eb | ||
|
|
b0d8ff6f4a | ||
|
|
0263a401f6 | ||
|
|
4cb1b9ccc0 | ||
|
|
6f80e2e126 | ||
|
|
f9bff90842 | ||
|
|
5af4ec0056 | ||
|
|
9e2f10b893 | ||
|
|
69095cf5c3 | ||
|
|
7402db7b43 | ||
|
|
7698b60256 | ||
|
|
e7fb0fd267 | ||
|
|
29b59074c7 | ||
|
|
ee5eb2d659 | ||
|
|
bf8094344a | ||
|
|
a0819978aa | ||
|
|
5b7c17e2fb | ||
|
|
bf56b3bb8e | ||
|
|
9299d84d41 | ||
|
|
2cf1a08969 | ||
|
|
665b6016e3 | ||
|
|
33231622f3 | ||
|
|
919c5c7299 | ||
|
|
f9bf1e4a22 | ||
|
|
f7ee914dd3 | ||
|
|
0a63158a61 | ||
|
|
6cb604f38f | ||
|
|
bfbbfbf979 | ||
|
|
403ba5fdc8 | ||
|
|
8f87a4e8ac | ||
|
|
d27d3e675f | ||
|
|
bf5d873099 | ||
|
|
bc9d12c4c8 | ||
|
|
5a0c873f61 | ||
|
|
65fec147d6 | ||
|
|
7fbf2f014c | ||
|
|
d22a3ca3e5 | ||
|
|
e9ec52b7f9 | ||
|
|
0d14c74e63 | ||
|
|
1c5884b2f9 | ||
|
|
8203a80ac7 | ||
|
|
0e46171e3b | ||
|
|
f5c85827ce | ||
|
|
7cefc55238 | ||
|
|
92c918ebd9 | ||
|
|
c24f8fd50f | ||
|
|
73272cf8a6 | ||
|
|
4212a8b9cb | ||
|
|
983c7fd6f9 | ||
|
|
7cd02e9b7e | ||
|
|
5fdfae2f15 | ||
|
|
7057ee91de | ||
|
|
fdca74148d | ||
|
|
f11d9fb922 | ||
|
|
1115c23a4c | ||
|
|
8c5eaa40b2 | ||
|
|
3c05954c86 | ||
|
|
cf3a33cea7 | ||
|
|
8aa2ba3eb1 | ||
|
|
466cd70a8e | ||
|
|
954fbeb1d8 | ||
|
|
68fa50709e | ||
|
|
ab91e4b874 | ||
|
|
2b943eda47 | ||
|
|
37544c092f |
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,14 +1,5 @@
|
||||
.*.swp
|
||||
tags
|
||||
target
|
||||
*.lock
|
||||
tmp
|
||||
*.csv
|
||||
*.fst
|
||||
*-got
|
||||
*.csv.idx
|
||||
words
|
||||
98m*
|
||||
dict
|
||||
test
|
||||
months
|
||||
/grep/Cargo.lock
|
||||
/globset/Cargo.lock
|
||||
|
||||
@@ -15,12 +15,6 @@ matrix:
|
||||
- os: linux
|
||||
rust: nightly
|
||||
env: TARGET=x86_64-unknown-linux-musl
|
||||
- os: linux
|
||||
rust: nightly
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
- os: osx
|
||||
rust: nightly
|
||||
env: TARGET=i686-apple-darwin
|
||||
- os: osx
|
||||
rust: nightly
|
||||
env: TARGET=x86_64-apple-darwin
|
||||
|
||||
182
CHANGELOG.md
Normal file
182
CHANGELOG.md
Normal file
@@ -0,0 +1,182 @@
|
||||
0.2.4
|
||||
=====
|
||||
Feature enhancements:
|
||||
|
||||
* Added or improved file type filtering for Groovy, Handlebars, Tcl, zsh and
|
||||
Python.
|
||||
* [FEATURE #9](https://github.com/BurntSushi/ripgrep/issues/9):
|
||||
Support global gitignore config and `.git/info/exclude` files.
|
||||
* [FEATURE #45](https://github.com/BurntSushi/ripgrep/issues/45):
|
||||
Add --ignore-file flag for specifying additional ignore files.
|
||||
* [FEATURE #202](https://github.com/BurntSushi/ripgrep/pull/202):
|
||||
Introduce a new
|
||||
[`ignore`](https://github.com/BurntSushi/ripgrep/tree/master/ignore)
|
||||
crate that encapsulates all of ripgrep's gitignore matching logic.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* [BUG #44](https://github.com/BurntSushi/ripgrep/issues/44):
|
||||
ripgrep runs slowly when given lots of positional arguments that are
|
||||
directories.
|
||||
* [BUG #119](https://github.com/BurntSushi/ripgrep/issues/119):
|
||||
ripgrep didn't reset terminal colors if it was interrupted by `^C`.
|
||||
Fixed in [PR #187](https://github.com/BurntSushi/ripgrep/pull/187).
|
||||
* [BUG #184](https://github.com/BurntSushi/ripgrep/issues/184):
|
||||
Fixed a bug related to interpreting gitignore files in parent directories.
|
||||
|
||||
0.2.3
|
||||
=====
|
||||
Bug fixes:
|
||||
|
||||
* [BUG #164](https://github.com/BurntSushi/ripgrep/issues/164):
|
||||
Fixes a segfault on macos builds.
|
||||
* [BUG #167](https://github.com/BurntSushi/ripgrep/issues/167):
|
||||
Clarify documentation for --threads.
|
||||
|
||||
|
||||
0.2.2
|
||||
=====
|
||||
Packaging updates:
|
||||
|
||||
* `ripgrep` is now in homebrew-core. `brew install ripgrep` will do the trick
|
||||
on a Mac.
|
||||
* `ripgrep` is now in the Archlinux community repository.
|
||||
`pacman -S ripgrep` will do the trick on Archlinux.
|
||||
* Support has been discontinued for i686-darwin.
|
||||
* Glob matching has been moved out into its own crate:
|
||||
[`globset`](https://crates.io/crates/globset).
|
||||
|
||||
Feature enhancements:
|
||||
|
||||
* Added or improved file type filtering for CMake, config, Jinja, Markdown,
|
||||
Spark.
|
||||
* [FEATURE #109](https://github.com/BurntSushi/ripgrep/issues/109):
|
||||
Add a --max-depth flag for directory traversal.
|
||||
* [FEATURE #124](https://github.com/BurntSushi/ripgrep/issues/124):
|
||||
Add -s/--case-sensitive flag. Overrides --smart-case.
|
||||
* [FEATURE #139](https://github.com/BurntSushi/ripgrep/pull/139):
|
||||
The `ripgrep` repo is now a Homebrew tap. This is useful for installing
|
||||
SIMD accelerated binaries, which aren't available in homebrew-core.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* [BUG #87](https://github.com/BurntSushi/ripgrep/issues/87),
|
||||
[BUG #127](https://github.com/BurntSushi/ripgrep/issues/127),
|
||||
[BUG #131](https://github.com/BurntSushi/ripgrep/issues/131):
|
||||
Various issues related to glob matching.
|
||||
* [BUG #116](https://github.com/BurntSushi/ripgrep/issues/116):
|
||||
--quiet should stop search after first match.
|
||||
* [BUG #121](https://github.com/BurntSushi/ripgrep/pull/121):
|
||||
--color always should show colors, even when --vimgrep is used.
|
||||
* [BUG #122](https://github.com/BurntSushi/ripgrep/pull/122):
|
||||
Colorize file path at beginning of line.
|
||||
* [BUG #134](https://github.com/BurntSushi/ripgrep/issues/134):
|
||||
Processing a large ignore file (thousands of globs) was very slow.
|
||||
* [BUG #137](https://github.com/BurntSushi/ripgrep/issues/137):
|
||||
Always follow symlinks when given as an explicit argument.
|
||||
* [BUG #147](https://github.com/BurntSushi/ripgrep/issues/147):
|
||||
Clarify documentation for --replace.
|
||||
|
||||
|
||||
0.2.1
|
||||
=====
|
||||
Feature enhancements:
|
||||
|
||||
* Added or improved file type filtering for Clojure and SystemVerilog.
|
||||
* [FEATURE #89](https://github.com/BurntSushi/ripgrep/issues/89):
|
||||
Add a --null flag that outputs a NUL byte after every file path.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* [BUG #98](https://github.com/BurntSushi/ripgrep/issues/98):
|
||||
Fix a bug in single threaded mode when if opening a file failed, ripgrep
|
||||
quit instead of continuing the search.
|
||||
* [BUG #99](https://github.com/BurntSushi/ripgrep/issues/99):
|
||||
Fix another bug in single threaded mode where empty lines were being printed
|
||||
by mistake.
|
||||
* [BUG #105](https://github.com/BurntSushi/ripgrep/issues/105):
|
||||
Fix an off-by-one error with --column.
|
||||
* [BUG #106](https://github.com/BurntSushi/ripgrep/issues/106):
|
||||
Fix a bug where a whitespace only line in a gitignore file caused ripgrep
|
||||
to panic (i.e., crash).
|
||||
|
||||
|
||||
0.2.0
|
||||
=====
|
||||
Feature enhancements:
|
||||
|
||||
* Added or improved file type filtering for VB, R, F#, Swift, Nim, Javascript,
|
||||
TypeScript
|
||||
* [FEATURE #20](https://github.com/BurntSushi/ripgrep/issues/20):
|
||||
Adds a --no-filename flag.
|
||||
* [FEATURE #26](https://github.com/BurntSushi/ripgrep/issues/26):
|
||||
Adds --files-with-matches flag. Like --count, but only prints file paths
|
||||
and doesn't need to count every match.
|
||||
* [FEATURE #40](https://github.com/BurntSushi/ripgrep/issues/40):
|
||||
Switch from using `.rgignore` to `.ignore`. Note that `.rgignore` is
|
||||
still supported, but deprecated.
|
||||
* [FEATURE #68](https://github.com/BurntSushi/ripgrep/issues/68):
|
||||
Add --no-ignore-vcs flag that ignores .gitignore but not .ignore.
|
||||
* [FEATURE #70](https://github.com/BurntSushi/ripgrep/issues/70):
|
||||
Add -S/--smart-case flag (but is disabled by default).
|
||||
* [FEATURE #80](https://github.com/BurntSushi/ripgrep/issues/80):
|
||||
Add support for `{foo,bar}` globs.
|
||||
|
||||
Many many bug fixes. Thanks every for reporting these and helping make
|
||||
`ripgrep` better! (Note that I haven't captured every tracking issue here,
|
||||
some were closed as duplicates.)
|
||||
|
||||
* [BUG #8](https://github.com/BurntSushi/ripgrep/issues/8):
|
||||
Don't use an intermediate buffer when --threads=1. (Permits constant memory
|
||||
usage.)
|
||||
* [BUG #15](https://github.com/BurntSushi/ripgrep/issues/15):
|
||||
Improves the documentation for --type-add.
|
||||
* [BUG #16](https://github.com/BurntSushi/ripgrep/issues/16),
|
||||
[BUG #49](https://github.com/BurntSushi/ripgrep/issues/49),
|
||||
[BUG #50](https://github.com/BurntSushi/ripgrep/issues/50),
|
||||
[BUG #65](https://github.com/BurntSushi/ripgrep/issues/65):
|
||||
Some gitignore globs were being treated as anchored when they weren't.
|
||||
* [BUG #18](https://github.com/BurntSushi/ripgrep/issues/18):
|
||||
--vimgrep reported incorrect column number.
|
||||
* [BUG #19](https://github.com/BurntSushi/ripgrep/issues/19):
|
||||
ripgrep was hanging waiting on stdin in some Windows terminals. Note that
|
||||
this introduced a new bug:
|
||||
[#94](https://github.com/BurntSushi/ripgrep/issues/94).
|
||||
* [BUG #21](https://github.com/BurntSushi/ripgrep/issues/21):
|
||||
Removes leading `./` when printing file paths.
|
||||
* [BUG #22](https://github.com/BurntSushi/ripgrep/issues/22):
|
||||
Running `rg --help | echo` caused `rg` to panic.
|
||||
* [BUG #24](https://github.com/BurntSushi/ripgrep/issues/22):
|
||||
Clarify the central purpose of rg in its usage message.
|
||||
* [BUG #25](https://github.com/BurntSushi/ripgrep/issues/25):
|
||||
Anchored gitignore globs weren't applied in subdirectories correctly.
|
||||
* [BUG #30](https://github.com/BurntSushi/ripgrep/issues/30):
|
||||
Globs like `foo/**` should match contents of `foo`, but not `foo` itself.
|
||||
* [BUG #35](https://github.com/BurntSushi/ripgrep/issues/35),
|
||||
[BUG #81](https://github.com/BurntSushi/ripgrep/issues/81):
|
||||
When automatically detecting stdin, only read if it's a file or a fifo.
|
||||
i.e., ignore stdin in `rg foo < /dev/null`.
|
||||
* [BUG #36](https://github.com/BurntSushi/ripgrep/issues/36):
|
||||
Don't automatically pick memory maps on MacOS. Ever.
|
||||
* [BUG #38](https://github.com/BurntSushi/ripgrep/issues/38):
|
||||
Trailing whitespace in gitignore wasn't being ignored.
|
||||
* [BUG #43](https://github.com/BurntSushi/ripgrep/issues/43):
|
||||
--glob didn't work with directories.
|
||||
* [BUG #46](https://github.com/BurntSushi/ripgrep/issues/46):
|
||||
Use one fewer worker thread than what is provided on CLI.
|
||||
* [BUG #47](https://github.com/BurntSushi/ripgrep/issues/47):
|
||||
--help/--version now work even if other options are set.
|
||||
* [BUG #55](https://github.com/BurntSushi/ripgrep/issues/55):
|
||||
ripgrep was refusing to search /proc/cpuinfo. Fixed by disabling memory
|
||||
maps for files with zero size.
|
||||
* [BUG #64](https://github.com/BurntSushi/ripgrep/issues/64):
|
||||
The first path given with --files set was ignored.
|
||||
* [BUG #67](https://github.com/BurntSushi/ripgrep/issues/67):
|
||||
Sometimes whitelist globs like `!/dir` weren't interpreted as anchored.
|
||||
* [BUG #77](https://github.com/BurntSushi/ripgrep/issues/77):
|
||||
When -q/--quiet flag was passed, ripgrep kept searching even after a match
|
||||
was found.
|
||||
* [BUG #90](https://github.com/BurntSushi/ripgrep/issues/90):
|
||||
Permit whitelisting hidden files.
|
||||
* [BUG #93](https://github.com/BurntSushi/ripgrep/issues/93):
|
||||
ripgrep was extracting an erroneous inner literal from a repeated pattern.
|
||||
325
Cargo.lock
generated
Normal file
325
Cargo.lock
generated
Normal file
@@ -0,0 +1,325 @@
|
||||
[root]
|
||||
name = "ripgrep"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"ctrlc 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"grep 0.1.3",
|
||||
"ignore 0.1.1",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memmap 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deque"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docopt"
|
||||
version = "0.6.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grep"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memmap 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"globset 0.1.1",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memmap"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fs2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "0.1.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "simd"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "term"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread-id"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread-id"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
|
||||
"checksum ctrlc 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "77f98bb69e3fefadcc5ca80a1368a55251f70295168203e01165bcaecb270891"
|
||||
"checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf"
|
||||
"checksum docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)" = "4a7ef30445607f6fc8720f0a0a2c7442284b629cf0d049286860fae23e71c4d9"
|
||||
"checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f"
|
||||
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
|
||||
"checksum fs2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "640001e1bd865c7c32806292822445af576a6866175b5225aa2087ca5e3de551"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f"
|
||||
"checksum libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "044d1360593a78f5c8e5e710beccdc24ab71d1f01bc19a29bcacdba22e8475d8"
|
||||
"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054"
|
||||
"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
|
||||
"checksum memmap 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "065ce59af31c18ea2c419100bda6247dd4ec3099423202b12f0bd32e529fabd2"
|
||||
"checksum num_cpus 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8890e6084723d57d0df8d2720b0d60c6ee67d6c93e7169630e4371e88765dcad"
|
||||
"checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5"
|
||||
"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
|
||||
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
|
||||
"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b"
|
||||
"checksum simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63b5847c2d766ca7ce7227672850955802fabd779ba616aeabead4c2c3877023"
|
||||
"checksum strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "50c069df92e4b01425a8bf3576d5d417943a6a7272fbabaf5bd80b1aaa76442e"
|
||||
"checksum term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3deff8a2b3b6607d6d7cc32ac25c0b33709453ca9cceac006caac51e963cf94a"
|
||||
"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
|
||||
"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a"
|
||||
"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
|
||||
"checksum thread_local 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50057ca52c629a39aed52d8eb253800cb727875fa6fc7c4b1445f0ac3b50c27c"
|
||||
"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91"
|
||||
"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum walkdir 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "98da26f00240118fbb7a06fa29579d1b39d34cd6e0505ea5c125b26d5260a967"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
24
Cargo.toml
24
Cargo.toml
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
publish = false
|
||||
name = "ripgrep"
|
||||
version = "0.1.0" #:version
|
||||
version = "0.2.4" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Line oriented search tool using Rust's regex library.
|
||||
Line oriented search tool using Rust's regex library. Combines the raw
|
||||
performance of grep with the usability of the silver searcher.
|
||||
"""
|
||||
documentation = "https://github.com/BurntSushi/ripgrep"
|
||||
homepage = "https://github.com/BurntSushi/ripgrep"
|
||||
@@ -12,6 +12,7 @@ repository = "https://github.com/BurntSushi/ripgrep"
|
||||
readme = "README.md"
|
||||
keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
||||
license = "Unlicense/MIT"
|
||||
exclude = ["HomebrewFormula"]
|
||||
|
||||
[[bin]]
|
||||
bench = false
|
||||
@@ -23,22 +24,21 @@ name = "integration"
|
||||
path = "tests/tests.rs"
|
||||
|
||||
[dependencies]
|
||||
crossbeam = "0.2"
|
||||
ctrlc = "2.0"
|
||||
deque = "0.3"
|
||||
docopt = "0.6"
|
||||
env_logger = "0.3"
|
||||
grep = { version = "0.1", path = "grep" }
|
||||
grep = { version = "0.1.3", path = "grep" }
|
||||
ignore = { version = "0.1.1", path = "ignore" }
|
||||
lazy_static = "0.2"
|
||||
libc = "0.2"
|
||||
log = "0.3"
|
||||
memchr = "0.1"
|
||||
memmap = "0.2"
|
||||
memmap = "0.5"
|
||||
num_cpus = "1"
|
||||
regex = "0.1.75"
|
||||
regex-syntax = "0.3.5"
|
||||
regex = "0.1.77"
|
||||
rustc-serialize = "0.3"
|
||||
term = "0.4"
|
||||
thread_local = "0.2"
|
||||
walkdir = "0.1"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
kernel32-sys = "0.2"
|
||||
@@ -47,9 +47,5 @@ winapi = "0.2"
|
||||
[features]
|
||||
simd-accel = ["regex/simd-accel"]
|
||||
|
||||
[dev-dependencies]
|
||||
glob = "0.2"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
panic = "abort"
|
||||
|
||||
1
HomebrewFormula
Symbolic link
1
HomebrewFormula
Symbolic link
@@ -0,0 +1 @@
|
||||
pkg/brew
|
||||
14
Makefile
14
Makefile
@@ -1,14 +0,0 @@
|
||||
all:
|
||||
echo Nothing to do...
|
||||
|
||||
ctags:
|
||||
ctags --options=ctags.rust --languages=Rust src/*.rs src/*/*.rs
|
||||
|
||||
docs:
|
||||
cargo doc
|
||||
in-dir ./target/doc fix-perms
|
||||
rscp ./target/doc/* gopher:~/www/burntsushi.net/rustdoc/
|
||||
|
||||
push:
|
||||
git push origin master
|
||||
git push github master
|
||||
296
README.md
296
README.md
@@ -1,5 +1,291 @@
|
||||
rep
|
||||
---
|
||||
A faster `grep`, written in Rust using the
|
||||
[`regex`](https://github.com/rust-lang-nursery/regex)
|
||||
crate.
|
||||
ripgrep (rg)
|
||||
------------
|
||||
`ripgrep` is a command line search tool that combines the usability of The
|
||||
Silver Searcher (an `ack` clone) with the raw speed of GNU grep. `ripgrep` has
|
||||
first class support on Windows, Mac and Linux, with binary downloads available
|
||||
for [every release](https://github.com/BurntSushi/ripgrep/releases).
|
||||
|
||||
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||
[](https://crates.io/crates/ripgrep)
|
||||
|
||||
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||
|
||||
### Screenshot of search results
|
||||
|
||||
[](http://burntsushi.net/stuff/ripgrep1.png)
|
||||
|
||||
### Quick example comparing tools
|
||||
|
||||
This example searches the entire Linux kernel source tree (after running
|
||||
`make defconfig && make -j8`) for `[A-Z]+_SUSPEND`, where all matches must be
|
||||
words. Timings were collected on a system with an Intel i7-6900K 3.2 GHz.
|
||||
|
||||
Please remember that a single benchmark is never enough! See my
|
||||
[blog post on `ripgrep`](http://blog.burntsushi.net/ripgrep/)
|
||||
for a very detailed comparison with more benchmarks and analysis.
|
||||
|
||||
| Tool | Command | Line count | Time |
|
||||
| ---- | ------- | ---------- | ---- |
|
||||
| ripgrep | `rg -n -w '[A-Z]+_SUSPEND'` | 450 | **0.245s** |
|
||||
| [The Silver Searcher](https://github.com/ggreer/the_silver_searcher) | `ag -w '[A-Z]+_SUSPEND'` | 450 | 0.753s |
|
||||
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=C git grep -E -n -w '[A-Z]+_SUSPEND'` | 450 | 0.823s |
|
||||
| [git grep (Unicode)](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=en_US.UTF-8 git grep -E -n -w '[A-Z]+_SUSPEND'` | 450 | 2.880s |
|
||||
| [sift](https://github.com/svent/sift) | `sift --git -n -w '[A-Z]+_SUSPEND'` | 450 | 3.656s |
|
||||
| [The Platinum Searcher](https://github.com/monochromegane/the_platinum_searcher) | `pt -w -e '[A-Z]+_SUSPEND'` | 450 | 12.369s |
|
||||
| [ack](http://beyondgrep.com/) | `ack -w '[A-Z]+_SUSPEND'` | 1878 | 16.952s |
|
||||
|
||||
(Yes, `ack` [has](https://github.com/petdance/ack2/issues/445) a
|
||||
[bug](https://github.com/petdance/ack2/issues/14).)
|
||||
|
||||
### Why should I use `ripgrep`?
|
||||
|
||||
* It can replace both The Silver Searcher and GNU grep because it is faster
|
||||
than both. (N.B. It is not, strictly speaking, a "drop-in" replacement for
|
||||
both, but the feature sets are far more similar than different.)
|
||||
* Like The Silver Searcher, `ripgrep` defaults to recursive directory search
|
||||
and won't search files ignored by your `.gitignore` files. It also ignores
|
||||
hidden and binary files by default. `ripgrep` also implements full support
|
||||
for `.gitignore`, where as there are many bugs related to that functionality
|
||||
in The Silver Searcher.
|
||||
* `ripgrep` can search specific types of files. For example, `rg -tpy foo`
|
||||
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
|
||||
custom matching rules.
|
||||
* `ripgrep` supports many features found in `grep`, such as showing the context
|
||||
of search results, searching multiple patterns, highlighting matches with
|
||||
color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast while
|
||||
supporting Unicode (which is always on).
|
||||
|
||||
In other words, use `ripgrep` if you like speed, sane defaults, fewer bugs and
|
||||
Unicode.
|
||||
|
||||
### Is it really faster than everything else?
|
||||
|
||||
Yes. A large number of benchmarks with detailed analysis for each is
|
||||
[available on my blog](http://blog.burntsushi.net/ripgrep/).
|
||||
|
||||
Summarizing, `ripgrep` is fast because:
|
||||
|
||||
* It is built on top of
|
||||
[Rust's regex engine](https://github.com/rust-lang-nursery/regex).
|
||||
Rust's regex engine uses finite automata, SIMD and aggressive literal
|
||||
optimizations to make searching very fast.
|
||||
* Rust's regex library maintains performance with full Unicode support by
|
||||
building UTF-8 decoding directly into its deterministic finite automaton
|
||||
engine.
|
||||
* It supports searching with either memory maps or by searching incrementally
|
||||
with an intermediate buffer. The former is better for single files and the
|
||||
latter is better for large directories. `ripgrep` chooses the best searching
|
||||
strategy for you automatically.
|
||||
* Applies your ignore patterns in `.gitignore` files using a
|
||||
[`RegexSet`](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html).
|
||||
That means a single file path can be matched against multiple glob patterns
|
||||
simultaneously.
|
||||
* Uses a Chase-Lev work-stealing queue for quickly distributing work to
|
||||
multiple threads.
|
||||
|
||||
### Installation
|
||||
|
||||
The binary name for `ripgrep` is `rg`.
|
||||
|
||||
[Binaries for `ripgrep` are available for Windows, Mac and
|
||||
Linux.](https://github.com/BurntSushi/ripgrep/releases) Linux binaries are
|
||||
static executables. Windows binaries are available either as built with MinGW
|
||||
(GNU) or with Microsoft Visual C++ (MSVC). When possible, prefer MSVC over GNU,
|
||||
but you'll need to have the
|
||||
[Microsoft VC++ 2015 redistributable](https://www.microsoft.com/en-us/download/details.aspx?id=48145)
|
||||
installed.
|
||||
|
||||
If you're a **Mac OS X Homebrew** user, then you can install ripgrep either
|
||||
from homebrew-core, (compiled with rust stable, no SIMD):
|
||||
|
||||
```
|
||||
$ brew install ripgrep
|
||||
```
|
||||
|
||||
or you can install a binary compiled with rust nightly (including SIMD and all
|
||||
optimizations) by utilizing a custom tap:
|
||||
|
||||
```
|
||||
$ brew tap burntsushi/ripgrep https://github.com/BurntSushi/ripgrep.git
|
||||
$ brew install burntsushi/ripgrep/ripgrep-bin
|
||||
```
|
||||
|
||||
If you're an **Arch Linux** user, then you can install `ripgrep` from the official repos:
|
||||
|
||||
```
|
||||
$ pacman -S ripgrep
|
||||
```
|
||||
|
||||
If you're a **Fedora 24+** user, you can install `ripgrep` from [copr](https://copr.fedorainfracloud.org/coprs/carlgeorge/ripgrep/):
|
||||
|
||||
```
|
||||
$ dnf copr enable carlgeorge/ripgrep
|
||||
$ dnf install ripgrep
|
||||
```
|
||||
|
||||
If you're a **RHEL/CentOS 7** user, you can install `ripgrep` from [copr](https://copr.fedorainfracloud.org/coprs/carlgeorge/ripgrep/):
|
||||
|
||||
```
|
||||
$ yum-config-manager --add-repo=https://copr.fedorainfracloud.org/coprs/carlgeorge/ripgrep/repo/epel-7/carlgeorge-ripgrep-epel-7.repo
|
||||
$ yum install ripgrep
|
||||
```
|
||||
|
||||
If you're a **Nix** user, you can install `ripgrep` from
|
||||
[nixpkgs](https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/text/ripgrep/default.nix):
|
||||
|
||||
```
|
||||
$ nix-env --install ripgrep
|
||||
$ # (Or using the attribute name, which is also `ripgrep`.)
|
||||
```
|
||||
|
||||
If you're a **Rust programmer**, `ripgrep` can be installed with `cargo`:
|
||||
|
||||
```
|
||||
$ cargo install ripgrep
|
||||
```
|
||||
|
||||
`ripgrep` isn't currently in any other package repositories.
|
||||
[I'd like to change that](https://github.com/BurntSushi/ripgrep/issues/10).
|
||||
|
||||
### Whirlwind tour
|
||||
|
||||
The command line usage of `ripgrep` doesn't differ much from other tools that
|
||||
perform a similar function, so you probably already know how to use `ripgrep`.
|
||||
The full details can be found in `rg --help`, but let's go on a whirlwind tour.
|
||||
|
||||
`ripgrep` detects when its printing to a terminal, and will automatically
|
||||
colorize your output and show line numbers, just like The Silver Searcher.
|
||||
Coloring works on Windows too! Colors can be controlled more granularly with
|
||||
the `--color` flag.
|
||||
|
||||
One last thing before we get started: `ripgrep` assumes UTF-8 *everywhere*. It
|
||||
can still search files that are invalid UTF-8 (like, say, latin-1), but it will
|
||||
simply not work on UTF-16 encoded files or other more exotic encodings.
|
||||
[Support for other encodings may
|
||||
happen.](https://github.com/BurntSushi/ripgrep/issues/1)
|
||||
|
||||
To recursively search the current directory, while respecting all `.gitignore`
|
||||
files, ignore hidden files and directories and skip binary files:
|
||||
|
||||
```
|
||||
$ rg foobar
|
||||
```
|
||||
|
||||
The above command also respects all `.ignore` files, including in parent
|
||||
directories. `.ignore` files can be used when `.gitignore` files are
|
||||
insufficient. In all cases, `.ignore` patterns take precedence over
|
||||
`.gitignore`.
|
||||
|
||||
To ignore all ignore files, use `-u`. To additionally search hidden files
|
||||
and directories, use `-uu`. To additionally search binary files, use `-uuu`.
|
||||
(In other words, "search everything, dammit!") In particular, `rg -uuu` is
|
||||
similar to `grep -a -r`.
|
||||
|
||||
```
|
||||
$ rg -uu foobar # similar to `grep -r`
|
||||
$ rg -uuu foobar # similar to `grep -a -r`
|
||||
```
|
||||
|
||||
(Tip: If your ignore files aren't being adhered to like you expect, run your
|
||||
search with the `--debug` flag.)
|
||||
|
||||
Make the search case insensitive with `-i`, invert the search with `-v` or
|
||||
show the 2 lines before and after every search result with `-C2`.
|
||||
|
||||
Force all matches to be surrounded by word boundaries with `-w`.
|
||||
|
||||
Search and replace (find first and last names and swap them):
|
||||
|
||||
```
|
||||
$ rg '([A-Z][a-z]+)\s+([A-Z][a-z]+)' --replace '$2, $1'
|
||||
```
|
||||
|
||||
Named groups are supported:
|
||||
|
||||
```
|
||||
$ rg '(?P<first>[A-Z][a-z]+)\s+(?P<last>[A-Z][a-z]+)' --replace '$last, $first'
|
||||
```
|
||||
|
||||
Up the ante with full Unicode support, by matching any uppercase Unicode letter
|
||||
followed by any sequence of lowercase Unicode letters (good luck doing this
|
||||
with other search tools!):
|
||||
|
||||
```
|
||||
$ rg '(\p{Lu}\p{Ll}+)\s+(\p{Lu}\p{Ll}+)' --replace '$2, $1'
|
||||
```
|
||||
|
||||
Search only files matching a particular glob:
|
||||
|
||||
```
|
||||
$ rg foo -g 'README.*'
|
||||
```
|
||||
|
||||
<!--*-->
|
||||
|
||||
Or exclude files matching a particular glob:
|
||||
|
||||
```
|
||||
$ rg foo -g '!*.min.js'
|
||||
```
|
||||
|
||||
Search only HTML and CSS files:
|
||||
|
||||
```
|
||||
$ rg -thtml -tcss foobar
|
||||
```
|
||||
|
||||
Search everything except for Javascript files:
|
||||
|
||||
```
|
||||
$ rg -Tjs foobar
|
||||
```
|
||||
|
||||
To see a list of types supported, run `rg --type-list`. To add a new type, use
|
||||
`--type-add`, which must be accompanied by a pattern for searching (`rg` won't
|
||||
persist your type settings):
|
||||
|
||||
```
|
||||
$ rg --type-add 'foo:*.{foo,foobar}' -tfoo bar
|
||||
```
|
||||
|
||||
The type `foo` will now match any file ending with the `.foo` or `.foobar`
|
||||
extensions.
|
||||
|
||||
### Regex syntax
|
||||
|
||||
The syntax supported is
|
||||
[documented as part of Rust's regex library](https://doc.rust-lang.org/regex/regex/index.html#syntax).
|
||||
|
||||
### Building
|
||||
|
||||
`ripgrep` is written in Rust, so you'll need to grab a
|
||||
[Rust installation](https://www.rust-lang.org/) in order to compile it.
|
||||
`ripgrep` compiles with Rust 1.9 (stable) or newer. Building is easy:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/BurntSushi/ripgrep
|
||||
$ cd ripgrep
|
||||
$ cargo build --release
|
||||
$ ./target/release/rg --version
|
||||
0.1.3
|
||||
```
|
||||
|
||||
If you have a Rust nightly compiler, then you can enable optional SIMD
|
||||
acceleration like so:
|
||||
|
||||
```
|
||||
RUSTFLAGS="-C target-cpu=native" cargo build --release --features simd-accel
|
||||
```
|
||||
|
||||
### Running tests
|
||||
|
||||
`ripgrep` is relatively well tested, including both unit tests and integration
|
||||
tests. To run the full test suite, use:
|
||||
|
||||
```
|
||||
$ cargo test
|
||||
```
|
||||
|
||||
from the repository root.
|
||||
|
||||
35
appveyor.yml
35
appveyor.yml
@@ -2,27 +2,22 @@ environment:
|
||||
global:
|
||||
PROJECT_NAME: ripgrep
|
||||
matrix:
|
||||
# Nightly channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
CHANNEL: stable
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
CHANNEL: stable
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
CHANNEL: stable
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
CHANNEL: stable
|
||||
|
||||
# Install Rust and Cargo
|
||||
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
||||
install:
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/channel-rust-stable"
|
||||
- ps: $env:RUST_VERSION = Get-Content channel-rust-stable | select -first 1 | %{$_.split('-')[1]}
|
||||
- if NOT "%CHANNEL%" == "stable" set RUST_VERSION=%CHANNEL%
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:RUST_VERSION}-${env:TARGET}.exe"
|
||||
- rust-%RUST_VERSION%-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
|
||||
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
|
||||
- if "%TARGET%" == "i686-pc-windows-gnu" set PATH=%PATH%;C:\msys64\mingw32\bin
|
||||
- if "%TARGET%" == "x86_64-pc-windows-gnu" set PATH=%PATH%;C:\msys64\mingw64\bin
|
||||
- curl -sSf -o rustup-init.exe https://win.rustup.rs/
|
||||
- rustup-init.exe -y --default-host %TARGET%
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
- if defined MSYS2_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS2_BITS%\bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
@@ -33,6 +28,9 @@ build: false
|
||||
# TODO modify this phase as you see fit
|
||||
test_script:
|
||||
- cargo test --verbose
|
||||
- cargo test --verbose --manifest-path grep/Cargo.toml
|
||||
- cargo test --verbose --manifest-path globset/Cargo.toml
|
||||
- cargo test --verbose --manifest-path ignore/Cargo.toml
|
||||
|
||||
before_deploy:
|
||||
# Generate artifacts for release
|
||||
@@ -46,7 +44,7 @@ before_deploy:
|
||||
- appveyor PushArtifact ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip
|
||||
|
||||
deploy:
|
||||
description: 'Windows release'
|
||||
description: 'Automatically deployed release'
|
||||
# All the zipped artifacts will be deployed
|
||||
artifact: /.*\.zip/
|
||||
auth_token:
|
||||
@@ -57,12 +55,13 @@ deploy:
|
||||
# channel to use to produce the release artifacts
|
||||
# NOTE make sure you only release *once* per target
|
||||
# TODO you may want to pick a different channel
|
||||
CHANNEL: nightly
|
||||
CHANNEL: stable
|
||||
appveyor_repo_tag: true
|
||||
|
||||
branches:
|
||||
only:
|
||||
- appveyor
|
||||
- /\d+\.\d+\.\d+/
|
||||
except:
|
||||
- master
|
||||
# - appveyor
|
||||
# - /\d+\.\d+\.\d+/
|
||||
# except:
|
||||
# - master
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
'''
|
||||
benchsuite is a benchmark runner for comparing command line search tools.
|
||||
@@ -10,6 +10,7 @@ import os
|
||||
import os.path as path
|
||||
from multiprocessing import cpu_count
|
||||
import re
|
||||
import shutil
|
||||
import statistics
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -23,6 +24,7 @@ import time
|
||||
|
||||
SUBTITLES_DIR = 'subtitles'
|
||||
SUBTITLES_EN_NAME = 'OpenSubtitles2016.raw.en'
|
||||
SUBTITLES_EN_NAME_SAMPLE = 'OpenSubtitles2016.raw.sample.en'
|
||||
SUBTITLES_EN_NAME_GZ = '%s.gz' % SUBTITLES_EN_NAME
|
||||
SUBTITLES_EN_URL = 'http://opus.lingfil.uu.se/OpenSubtitles2016/mono/OpenSubtitles2016.raw.en.gz'
|
||||
SUBTITLES_RU_NAME = 'OpenSubtitles2016.raw.ru'
|
||||
@@ -32,13 +34,29 @@ SUBTITLES_RU_URL = 'http://opus.lingfil.uu.se/OpenSubtitles2016/mono/OpenSubtitl
|
||||
LINUX_DIR = 'linux'
|
||||
LINUX_CLONE = 'git://github.com/BurntSushi/linux'
|
||||
|
||||
# Grep takes locale settings from the environment. There is a *substantial*
|
||||
# performance impact for enabling Unicode, so we need to handle this explicitly
|
||||
# in our benchmarks.
|
||||
GREP_ASCII = {'LC_ALL': 'C'}
|
||||
GREP_UNICODE = {'LC_ALL': 'en_US.UTF-8'}
|
||||
|
||||
# Sift tries really hard to search everything by default. In our code search
|
||||
# benchmarks, we don't want that.
|
||||
SIFT = [
|
||||
'sift',
|
||||
'--binary-skip',
|
||||
'--exclude-files', '.*',
|
||||
'--exclude-files', '*.pdf',
|
||||
]
|
||||
|
||||
|
||||
def bench_linux_literal_default(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of a literal using *default* settings.
|
||||
|
||||
This is a purposefully unfair benchmark for use in performance
|
||||
analysis, but it is pedagogically useful.
|
||||
analysis, but it is pedagogically useful to demonstrate how
|
||||
default behaviors differ.
|
||||
'''
|
||||
require(suite_dir, 'linux')
|
||||
cwd = path.join(suite_dir, LINUX_DIR)
|
||||
@@ -48,8 +66,6 @@ def bench_linux_literal_default(suite_dir):
|
||||
kwargs['cwd'] = cwd
|
||||
return Command(*args, **kwargs)
|
||||
|
||||
# N.B. This is a purposefully unfair benchmark for illustrative purposes
|
||||
# of how the default modes for each search tool differ.
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
mkcmd('rg', ['rg', pat]),
|
||||
mkcmd('ag', ['ag', pat]),
|
||||
@@ -57,10 +73,12 @@ def bench_linux_literal_default(suite_dir):
|
||||
# doesn't read gitignore files. Instead, it has a file whitelist
|
||||
# that happens to match up exactly with the gitignores for this search.
|
||||
mkcmd('ucg', ['ucg', pat]),
|
||||
mkcmd('git grep', ['git', 'grep', pat], env={'LC_ALL': 'C'}),
|
||||
# I guess setting LC_ALL=en_US.UTF-8 probably isn't necessarily the
|
||||
# default, but I'd guess it to be on most desktop systems.
|
||||
mkcmd('pt', ['pt', pat]),
|
||||
# sift reports an extra line here for a binary file matched.
|
||||
mkcmd('sift', ['sift', pat]),
|
||||
mkcmd('git grep', ['git', 'grep', pat], env={'LC_ALL': 'en_US.UTF-8'}),
|
||||
])
|
||||
|
||||
|
||||
@@ -69,8 +87,9 @@ def bench_linux_literal(suite_dir):
|
||||
Benchmark the speed of a literal, attempting to be fair.
|
||||
|
||||
This tries to use the minimum set of options available in all tools
|
||||
to test how fast they are. For example, it makes sure there is no
|
||||
case insensitive matching and that line numbers are computed.
|
||||
to test how fast they are. For example, it makes sure there is
|
||||
no case insensitive matching and that line numbers are computed
|
||||
(because some tools don't permit disabling line numbers).
|
||||
'''
|
||||
require(suite_dir, 'linux')
|
||||
cwd = path.join(suite_dir, LINUX_DIR)
|
||||
@@ -81,19 +100,16 @@ def bench_linux_literal(suite_dir):
|
||||
return Command(*args, **kwargs)
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
mkcmd('rg', ['rg', '-n', pat]),
|
||||
mkcmd('rg-novcs', ['rg', '--no-ignore', '-n', pat]),
|
||||
mkcmd('rg-novcs-mmap', ['rg', '--mmap', '--no-ignore', '-n', pat]),
|
||||
mkcmd('ag', ['ag', '-s', pat]),
|
||||
mkcmd('ag-novcs', ['ag', '--skip-vcs-ignores', '-s', pat]),
|
||||
mkcmd('ucg', ['ucg', '--nosmart-case', pat]),
|
||||
mkcmd('git grep', [
|
||||
mkcmd('rg (ignore)', ['rg', '-n', pat]),
|
||||
mkcmd('rg (ignore) (mmap)', ['rg', '-n', '--mmap', pat]),
|
||||
mkcmd('ag (ignore) (mmap)', ['ag', '-s', pat]),
|
||||
mkcmd('pt (ignore)', ['pt', pat]),
|
||||
mkcmd('sift (ignore)', SIFT + ['-n', '--git', pat]),
|
||||
mkcmd('git grep (ignore)', [
|
||||
'git', 'grep', '-I', '-n', pat,
|
||||
], env={'LC_ALL': 'C'}),
|
||||
mkcmd('pt', ['pt', pat]),
|
||||
mkcmd('sift', [
|
||||
'sift', '-n', '--binary-skip', '--exclude-files', '.*', pat,
|
||||
]),
|
||||
mkcmd('rg (whitelist)', ['rg', '-n', '--no-ignore', '-tall', pat]),
|
||||
mkcmd('ucg (whitelist)', ['ucg', '--nosmart-case', pat]),
|
||||
])
|
||||
|
||||
|
||||
@@ -113,23 +129,22 @@ def bench_linux_literal_casei(suite_dir):
|
||||
return Command(*args, **kwargs)
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
mkcmd('rg', ['rg', '-n', '-i', pat]),
|
||||
mkcmd('rg-novcs', ['rg', '--no-ignore', '-n', '-i', pat]),
|
||||
mkcmd('rg-novcs-mmap', [
|
||||
'rg', '--mmap', '--no-ignore', '-n', '-i', pat,
|
||||
]),
|
||||
mkcmd('ag', ['ag', '-i', pat]),
|
||||
mkcmd('ag-novcs', ['ag', '--skip-vcs-ignores', '-i', pat]),
|
||||
mkcmd('ucg', ['ucg', '-i', pat]),
|
||||
mkcmd('git grep', [
|
||||
mkcmd('rg (ignore)', ['rg', '-n', '-i', pat]),
|
||||
mkcmd('rg (ignore) (mmap)', ['rg', '-n', '-i', '--mmap', pat]),
|
||||
mkcmd('ag (ignore) (mmap)', ['ag', '-i', pat]),
|
||||
mkcmd('pt (ignore)', ['pt', '-i', pat]),
|
||||
mkcmd('sift (ignore)', SIFT + ['-n', '-i', '--git', pat]),
|
||||
# It'd technically be more appropriate to set LC_ALL=en_US.UTF-8 here,
|
||||
# since that is certainly what ripgrep is doing, but this is for an
|
||||
# ASCII literal, so we should give `git grep` all the opportunity to
|
||||
# do its best.
|
||||
mkcmd('git grep (ignore)', [
|
||||
'git', 'grep', '-I', '-n', '-i', pat,
|
||||
], env={'LC_ALL': 'C'}),
|
||||
# sift yields more matches than it should here. Specifically, it gets
|
||||
# matches in Module.symvers and System.map in the repo root. Both of
|
||||
# those files show up in the repo root's .gitignore file.
|
||||
mkcmd('sift', [
|
||||
'sift', '-n', '--binary-skip', '--exclude-files', '.*', '-i', pat,
|
||||
mkcmd('rg (whitelist)', [
|
||||
'rg', '-n', '-i', '--no-ignore', '-tall', pat,
|
||||
]),
|
||||
mkcmd('ucg (whitelist)', ['ucg', '-i', pat]),
|
||||
])
|
||||
|
||||
|
||||
@@ -149,20 +164,17 @@ def bench_linux_re_literal_suffix(suite_dir):
|
||||
return Command(*args, **kwargs)
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
mkcmd('rg', ['rg', '-n', pat]),
|
||||
mkcmd('rg-novcs', ['rg', '--no-ignore', '-n', pat]),
|
||||
mkcmd('rg-novcs-mmap', ['rg', '--mmap', '--no-ignore', '-n', pat]),
|
||||
mkcmd('ag', ['ag', '-s', pat]),
|
||||
mkcmd('ag-novcs', ['ag', '--skip-vcs-ignores', '-s', pat]),
|
||||
mkcmd('ucg', ['ucg', '--nosmart-case', pat]),
|
||||
mkcmd('rg (ignore)', ['rg', '-n', pat]),
|
||||
mkcmd('ag (ignore)', ['ag', '-s', pat]),
|
||||
mkcmd('pt (ignore)', ['pt', '-e', pat]),
|
||||
mkcmd('sift (ignore)', SIFT + ['-n', '--git', pat]),
|
||||
mkcmd(
|
||||
'git grep',
|
||||
'git grep (ignore)',
|
||||
['git', 'grep', '-E', '-I', '-n', pat],
|
||||
env={'LC_ALL': 'C'},
|
||||
),
|
||||
mkcmd('sift', [
|
||||
'sift', '-n', '--binary-skip', '--exclude-files', '.*', pat,
|
||||
]),
|
||||
mkcmd('rg (whitelist)', ['rg', '-n', '--no-ignore', '-tall', pat]),
|
||||
mkcmd('ucg (whitelist)', ['ucg', '--nosmart-case', pat]),
|
||||
])
|
||||
|
||||
|
||||
@@ -182,22 +194,19 @@ def bench_linux_word(suite_dir):
|
||||
return Command(*args, **kwargs)
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
mkcmd('rg', ['rg', '-n', '-w', pat]),
|
||||
mkcmd('rg-novcs', ['rg', '--no-ignore', '-n', '-w', pat]),
|
||||
mkcmd('rg-novcs-mmap', [
|
||||
'rg', '--mmap', '--no-ignore', '-n', '-w', pat,
|
||||
]),
|
||||
mkcmd('ag', ['ag', '-s', '-w', pat]),
|
||||
mkcmd('ag-novcs', ['ag', '--skip-vcs-ignores', '-s', '-w', pat]),
|
||||
mkcmd('ucg', ['ucg', '--nosmart-case', '-w', pat]),
|
||||
mkcmd('rg (ignore)', ['rg', '-n', '-w', pat]),
|
||||
mkcmd('ag (ignore)', ['ag', '-s', '-w', pat]),
|
||||
mkcmd('pt (ignore)', ['pt', '-w', pat]),
|
||||
mkcmd('sift (ignore)', SIFT + ['-n', '-w', '--git', pat]),
|
||||
mkcmd(
|
||||
'git grep',
|
||||
'git grep (ignore)',
|
||||
['git', 'grep', '-E', '-I', '-n', '-w', pat],
|
||||
env={'LC_ALL': 'C'},
|
||||
),
|
||||
mkcmd('sift', [
|
||||
'sift', '-n', '--binary-skip', '--exclude-files', '.*', '-w', pat,
|
||||
mkcmd('rg (whitelist)', [
|
||||
'rg', '-n', '-w', '--no-ignore', '-tall', pat,
|
||||
]),
|
||||
mkcmd('ucg (whitelist)', ['ucg', '--nosmart-case', '-w', pat]),
|
||||
])
|
||||
|
||||
|
||||
@@ -205,7 +214,8 @@ def bench_linux_unicode_greek(suite_dir):
|
||||
'''
|
||||
Benchmark matching of a Unicode category.
|
||||
|
||||
Only three tools (ripgrep, sift and pt) support this.
|
||||
Only three tools (ripgrep, sift and pt) support this. We omit
|
||||
pt because it is too slow.
|
||||
'''
|
||||
require(suite_dir, 'linux')
|
||||
cwd = path.join(suite_dir, LINUX_DIR)
|
||||
@@ -217,15 +227,8 @@ def bench_linux_unicode_greek(suite_dir):
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
mkcmd('rg', ['rg', '-n', pat]),
|
||||
# sift tries to search a bunch of PDF files and clutters up the
|
||||
# results, even though --binary-skip is provided. They are excluded
|
||||
# here explicitly, but don't have a measurable impact on performance.
|
||||
mkcmd('sift', [
|
||||
'sift', '-n', '--binary-skip',
|
||||
'--exclude-files', '.*',
|
||||
'--exclude-files', '*.pdf',
|
||||
pat,
|
||||
]),
|
||||
mkcmd('pt', ['pt', '-e', pat]),
|
||||
mkcmd('sift', SIFT + ['-n', '--git', pat]),
|
||||
])
|
||||
|
||||
|
||||
@@ -245,15 +248,8 @@ def bench_linux_unicode_greek_casei(suite_dir):
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
mkcmd('rg', ['rg', '-n', '-i', pat]),
|
||||
# sift tries to search a bunch of PDF files and clutters up the
|
||||
# results, even though --binary-skip is provided. They are excluded
|
||||
# here explicitly, but don't have a measurable impact on performance.
|
||||
mkcmd('sift', [
|
||||
'sift', '-n', '--binary-skip',
|
||||
'--exclude-files', '.*',
|
||||
'--exclude-files', '*.pdf',
|
||||
pat,
|
||||
]),
|
||||
mkcmd('pt', ['pt', '-i', '-e', pat]),
|
||||
mkcmd('sift', SIFT + ['-n', '-i', '--git', pat]),
|
||||
])
|
||||
|
||||
|
||||
@@ -274,30 +270,26 @@ def bench_linux_unicode_word(suite_dir):
|
||||
return Command(*args, **kwargs)
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
mkcmd('rg', ['rg', '-n', pat]),
|
||||
mkcmd('rg (no Unicode)', ['rg', '-n', '(?-u)' + pat]),
|
||||
mkcmd('rg-novcs', ['rg', '--no-ignore', '-n', pat]),
|
||||
mkcmd('rg-novcs-mmap', [
|
||||
'rg', '--mmap', '--no-ignore', '-n', pat,
|
||||
]),
|
||||
mkcmd('ag (no Unicode)', ['ag', '-s', pat]),
|
||||
mkcmd('ag-novcs (no Unicode)', [
|
||||
'ag', '--skip-vcs-ignores', '-s', pat,
|
||||
]),
|
||||
mkcmd('ucg (no Unicode)', ['ucg', '--nosmart-case', pat]),
|
||||
mkcmd('rg (ignore)', ['rg', '-n', pat]),
|
||||
mkcmd('rg (ignore) (ASCII)', ['rg', '-n', '(?-u)' + pat]),
|
||||
mkcmd('ag (ignore) (ASCII)', ['ag', '-s', pat]),
|
||||
mkcmd('pt (ignore) (ASCII)', ['pt', '-e', pat]),
|
||||
mkcmd('sift (ignore) (ASCII)', SIFT + ['-n', '--git', pat]),
|
||||
mkcmd(
|
||||
'git grep',
|
||||
'git grep (ignore)',
|
||||
['git', 'grep', '-E', '-I', '-n', pat],
|
||||
env={'LC_ALL': 'en_US.UTF-8'},
|
||||
),
|
||||
mkcmd(
|
||||
'git grep (no Unicode)',
|
||||
'git grep (ignore) (ASCII)',
|
||||
['git', 'grep', '-E', '-I', '-n', pat],
|
||||
env={'LC_ALL': 'C'},
|
||||
),
|
||||
mkcmd('sift (no Unicode)', [
|
||||
'sift', '-n', '--binary-skip', '--exclude-files', '.*', pat,
|
||||
mkcmd('rg (whitelist)', ['rg', '-n', '--no-ignore', '-tall', pat]),
|
||||
mkcmd('rg (whitelist) (ASCII)', [
|
||||
'rg', '-n', '--no-ignore', '-tall', '(?-u)' + pat,
|
||||
]),
|
||||
mkcmd('ucg (ASCII)', ['ucg', '--nosmart-case', pat]),
|
||||
])
|
||||
|
||||
|
||||
@@ -319,30 +311,26 @@ def bench_linux_no_literal(suite_dir):
|
||||
return Command(*args, **kwargs)
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
mkcmd('rg', ['rg', '-n', pat]),
|
||||
mkcmd('rg-novcs', ['rg', '--no-ignore', '-n', pat]),
|
||||
mkcmd('rg (no Unicode)', ['rg', '-n', '(?-u)' + pat]),
|
||||
mkcmd('rg-novcs (no Unicode)', [
|
||||
'rg', '--no-ignore', '-n', '(?-u)' + pat,
|
||||
]),
|
||||
mkcmd('ag (no Unicode)', ['ag', '-s', pat]),
|
||||
mkcmd('ag-novcs (no Unicode)', [
|
||||
'ag', '--skip-vcs-ignores', '-s', pat,
|
||||
]),
|
||||
mkcmd('ucg (no Unicode)', ['ucg', '--nosmart-case', pat]),
|
||||
mkcmd('rg (ignore)', ['rg', '-n', pat]),
|
||||
mkcmd('rg (ignore) (ASCII)', ['rg', '-n', '(?-u)' + pat]),
|
||||
mkcmd('ag (ignore) (ASCII)', ['ag', '-s', pat]),
|
||||
mkcmd('pt (ignore) (ASCII)', ['pt', '-e', pat]),
|
||||
mkcmd('sift (ignore) (ASCII)', SIFT + ['-n', '--git', pat]),
|
||||
mkcmd(
|
||||
'git grep',
|
||||
'git grep (ignore)',
|
||||
['git', 'grep', '-E', '-I', '-n', pat],
|
||||
env={'LC_ALL': 'en_US.UTF-8'},
|
||||
),
|
||||
mkcmd(
|
||||
'git grep (no Unicode)',
|
||||
'git grep (ignore) (ASCII)',
|
||||
['git', 'grep', '-E', '-I', '-n', pat],
|
||||
env={'LC_ALL': 'C'},
|
||||
),
|
||||
mkcmd('sift (no Unicode)', [
|
||||
'sift', '-n', '--binary-skip', '--exclude-files', '.*', pat,
|
||||
mkcmd('rg (whitelist)', ['rg', '-n', '--no-ignore', '-tall', pat]),
|
||||
mkcmd('rg (whitelist) (ASCII)', [
|
||||
'rg', '-n', '--no-ignore', '-tall', '(?-u)' + pat,
|
||||
]),
|
||||
mkcmd('ucg (whitelist) (ASCII)', ['ucg', '--nosmart-case', pat]),
|
||||
])
|
||||
|
||||
|
||||
@@ -364,21 +352,15 @@ def bench_linux_alternates(suite_dir):
|
||||
return Command(*args, **kwargs)
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
mkcmd('rg', ['rg', '-n', pat]),
|
||||
mkcmd('rg-novcs', ['rg', '--no-ignore', '-n', pat]),
|
||||
mkcmd('rg-novcs-mmap', [
|
||||
'rg', '--mmap', '--no-ignore', '-n', pat,
|
||||
]),
|
||||
mkcmd('ag', ['ag', '-s', pat]),
|
||||
mkcmd('ag-novcs', [
|
||||
'ag', '--skip-vcs-ignores', '-s', pat,
|
||||
]),
|
||||
mkcmd('ucg', ['ucg', '--nosmart-case', pat]),
|
||||
mkcmd('rg (ignore)', ['rg', '-n', pat]),
|
||||
mkcmd('ag (ignore)', ['ag', '-s', pat]),
|
||||
mkcmd(
|
||||
'git grep',
|
||||
'git grep (ignore)',
|
||||
['git', 'grep', '-E', '-I', '-n', pat],
|
||||
env={'LC_ALL': 'C'},
|
||||
),
|
||||
mkcmd('rg (whitelist)', ['rg', '--no-ignore', '-n', pat]),
|
||||
mkcmd('ucg (whitelist)', ['ucg', '--nosmart-case', pat]),
|
||||
])
|
||||
|
||||
|
||||
@@ -393,36 +375,338 @@ def bench_linux_alternates_casei(suite_dir):
|
||||
return Command(*args, **kwargs)
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
mkcmd('rg', ['rg', '-n', '-i', pat]),
|
||||
mkcmd('rg-novcs', ['rg', '--no-ignore', '-n', '-i', pat]),
|
||||
mkcmd('rg-novcs-mmap', [
|
||||
'rg', '--mmap', '--no-ignore', '-n', '-i', pat,
|
||||
]),
|
||||
mkcmd('ag', ['ag', '-i', pat]),
|
||||
mkcmd('ag-novcs', [
|
||||
'ag', '--skip-vcs-ignores', '-i', pat,
|
||||
]),
|
||||
mkcmd('ucg', ['ucg', '-i', pat]),
|
||||
mkcmd('rg (ignore)', ['rg', '-n', '-i', pat]),
|
||||
mkcmd('ag (ignore)', ['ag', '-i', pat]),
|
||||
mkcmd(
|
||||
'git grep',
|
||||
'git grep (ignore)',
|
||||
['git', 'grep', '-E', '-I', '-n', '-i', pat],
|
||||
env={'LC_ALL': 'C'},
|
||||
),
|
||||
mkcmd('rg (whitelist)', ['rg', '--no-ignore', '-n', '-i', pat]),
|
||||
mkcmd('ucg (whitelist)', ['ucg', '-i', pat]),
|
||||
])
|
||||
|
||||
|
||||
# BREADCRUMBS(burntsushi): We should benchmark an alternation for `linux` as
|
||||
# well.
|
||||
|
||||
def bench_sherlock(suite_dir):
|
||||
'TODO: Fix this and add more single file benchmarks.'
|
||||
def bench_subtitles_en_literal(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of an ASCII string literal.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-en')
|
||||
en = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_EN_NAME)
|
||||
pat = 'Sherlock'
|
||||
en = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_EN_NAME_SAMPLE)
|
||||
pat = 'Sherlock Holmes'
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg', ['rg', pat, en]),
|
||||
Command('grep', ['grep', '-a', pat, en])
|
||||
Command('rg (no mmap)', ['rg', '--no-mmap', pat, en]),
|
||||
Command('pt', ['pt', '-N', pat, en]),
|
||||
Command('sift', ['sift', pat, en]),
|
||||
Command('grep', ['grep', '-a', pat, en], env=GREP_ASCII),
|
||||
Command('rg (lines)', ['rg', '-n', pat, en]),
|
||||
Command('ag (lines)', ['ag', '-s', pat, en]),
|
||||
Command('ucg (lines)', ['ucg', '--nosmart-case', pat, en]),
|
||||
Command('pt (lines)', ['pt', pat, en]),
|
||||
Command('sift (lines)', ['sift', '-n', pat, en]),
|
||||
Command('grep (lines)', ['grep', '-an', pat, en], env=GREP_ASCII),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_en_literal_casei(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of a Unicode-y string case insensitively.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-en')
|
||||
en = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_EN_NAME_SAMPLE)
|
||||
pat = 'Sherlock Holmes'
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg', ['rg', '-i', pat, en]),
|
||||
Command('grep', ['grep', '-ai', pat, en], env=GREP_UNICODE),
|
||||
Command('grep (ASCII)', [
|
||||
'grep', '-E', '-ai', pat, en,
|
||||
], env=GREP_ASCII),
|
||||
Command('rg (lines)', ['rg', '-n', '-i', pat, en]),
|
||||
Command('ag (lines) (ASCII)', ['ag', '-i', pat, en]),
|
||||
Command('ucg (lines) (ASCII)', ['ucg', '-i', pat, en]),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_en_literal_word(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of finding a literal inside word boundaries.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-en')
|
||||
en = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_EN_NAME_SAMPLE)
|
||||
pat = 'Sherlock Holmes'
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg (ASCII)', [
|
||||
'rg', '-n', r'(?-u:\b)' + pat + r'(?-u:\b)', en,
|
||||
]),
|
||||
Command('ag (ASCII)', ['ag', '-sw', pat, en]),
|
||||
Command('ucg (ASCII)', ['ucg', '--nosmart-case', pat, en]),
|
||||
Command('grep (ASCII)', [
|
||||
'grep', '-anw', pat, en,
|
||||
], env=GREP_ASCII),
|
||||
Command('rg', ['rg', '-nw', pat, en]),
|
||||
Command('grep', ['grep', '-anw', pat, en], env=GREP_UNICODE),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_en_alternate(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of a set of alternate literals.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-en')
|
||||
en = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_EN_NAME_SAMPLE)
|
||||
pat = '|'.join([
|
||||
'Sherlock Holmes',
|
||||
'John Watson',
|
||||
'Irene Adler',
|
||||
'Inspector Lestrade',
|
||||
'Professor Moriarty',
|
||||
])
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg (lines)', ['rg', '-n', pat, en]),
|
||||
Command('ag (lines)', ['ag', '-s', pat, en]),
|
||||
Command('ucg (lines)', ['ucg', '--nosmart-case', pat, en]),
|
||||
Command('grep (lines)', [
|
||||
'grep', '-E', '-an', pat, en,
|
||||
], env=GREP_ASCII),
|
||||
Command('rg', ['rg', pat, en]),
|
||||
Command('grep', [
|
||||
'grep', '-E', '-a', pat, en,
|
||||
], env=GREP_ASCII),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_en_alternate_casei(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of a set of alternate literals.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-en')
|
||||
en = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_EN_NAME_SAMPLE)
|
||||
pat = '|'.join([
|
||||
'Sherlock Holmes',
|
||||
'John Watson',
|
||||
'Irene Adler',
|
||||
'Inspector Lestrade',
|
||||
'Professor Moriarty',
|
||||
])
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('ag (ASCII)', ['ag', '-s', '-i', pat, en]),
|
||||
Command('ucg (ASCII)', ['ucg', '-i', pat, en]),
|
||||
Command('grep (ASCII)', [
|
||||
'grep', '-E', '-ani', pat, en,
|
||||
], env=GREP_ASCII),
|
||||
Command('rg', ['rg', '-n', '-i', pat, en]),
|
||||
Command('grep', ['grep', '-E', '-ani', pat, en], env=GREP_UNICODE),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_en_surrounding_words(suite_dir):
|
||||
'''
|
||||
Benchmark a more complex regex with an inner literal.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-en')
|
||||
en = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_EN_NAME_SAMPLE)
|
||||
pat = r'\w+\s+Holmes\s+\w+'
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg', ['rg', '-n', pat, en]),
|
||||
Command('grep', ['grep', '-E', '-an', pat, en], env=GREP_UNICODE),
|
||||
Command('rg (ASCII)', ['rg', '-n', '(?-u)' + pat, en]),
|
||||
Command('ag (ASCII)', ['ag', '-s', pat, en]),
|
||||
Command('ucg (ASCII)', ['ucg', '--nosmart-case', pat, en]),
|
||||
Command('grep (ASCII)', [
|
||||
'grep', '-E', '-an', pat, en,
|
||||
], env=GREP_ASCII),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_en_no_literal(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of a regex with no literals.
|
||||
|
||||
Note that we don't even try to run grep with Unicode support
|
||||
on this one. While it should eventually get the right answer,
|
||||
I killed it after it had already been running for two minutes
|
||||
and showed no signs of finishing soon.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-en')
|
||||
en = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_EN_NAME_SAMPLE)
|
||||
pat = r'\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}'
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg', ['rg', '-n', pat, en]),
|
||||
Command('rg (ASCII)', ['rg', '-n', '(?-u)' + pat, en]),
|
||||
Command('ag (ASCII)', ['ag', '-s', pat, en]),
|
||||
Command('ucg (ASCII)', ['ucg', '--nosmart-case', pat, en]),
|
||||
Command('grep (ASCII)', [
|
||||
'grep', '-E', '-an', pat, en,
|
||||
], env=GREP_ASCII),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_ru_literal(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of a Unicode-y string literal.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-ru')
|
||||
ru = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_RU_NAME)
|
||||
pat = 'Шерлок Холмс' # Sherlock Holmes
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg', ['rg', pat, ru]),
|
||||
Command('rg (no mmap)', ['rg', '--no-mmap', pat, ru]),
|
||||
Command('pt', ['pt', '-N', pat, ru]),
|
||||
Command('sift', ['sift', pat, ru]),
|
||||
Command('grep', ['grep', '-a', pat, ru], env=GREP_ASCII),
|
||||
Command('rg (lines)', ['rg', '-n', pat, ru]),
|
||||
Command('ag (lines)', ['ag', '-s', pat, ru]),
|
||||
Command('ucg (lines)', ['ucg', '--nosmart-case', pat, ru]),
|
||||
Command('pt (lines)', ['pt', pat, ru]),
|
||||
Command('sift (lines)', ['sift', '-n', pat, ru]),
|
||||
Command('grep (lines)', ['grep', '-an', pat, ru], env=GREP_ASCII),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_ru_literal_casei(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of a Unicode-y string case insensitively.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-ru')
|
||||
ru = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_RU_NAME)
|
||||
pat = 'Шерлок Холмс' # Sherlock Holmes
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg', ['rg', '-i', pat, ru]),
|
||||
Command('grep', ['grep', '-ai', pat, ru], env=GREP_UNICODE),
|
||||
Command('grep (ASCII)', [
|
||||
'grep', '-E', '-ai', pat, ru,
|
||||
], env=GREP_ASCII),
|
||||
Command('rg (lines)', ['rg', '-n', '-i', pat, ru]),
|
||||
Command('ag (lines) (ASCII)', ['ag', '-i', pat, ru]),
|
||||
Command('ucg (lines) (ASCII)', ['ucg', '-i', pat, ru]),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_ru_literal_word(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of finding a literal inside word boundaries.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-ru')
|
||||
ru = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_RU_NAME)
|
||||
pat = 'Шерлок Холмс' # Sherlock Holmes
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg (ASCII)', [
|
||||
'rg', '-n', r'(?-u:\b)' + pat + r'(?-u:\b)', ru,
|
||||
]),
|
||||
Command('ag (ASCII)', ['ag', '-sw', pat, ru]),
|
||||
Command('ucg (ASCII)', ['ucg', '--nosmart-case', pat, ru]),
|
||||
Command('grep (ASCII)', [
|
||||
'grep', '-anw', pat, ru,
|
||||
], env=GREP_ASCII),
|
||||
Command('rg', ['rg', '-nw', pat, ru]),
|
||||
Command('grep', ['grep', '-anw', pat, ru], env=GREP_UNICODE),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_ru_alternate(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of a set of alternate literals.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-ru')
|
||||
ru = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_RU_NAME)
|
||||
pat = '|'.join([
|
||||
'Шерлок Холмс', # Sherlock Holmes
|
||||
'Джон Уотсон', # John Watson
|
||||
'Ирен Адлер', # Irene Adler
|
||||
'инспектор Лестрейд', # Inspector Lestrade
|
||||
'профессор Мориарти', # Professor Moriarty
|
||||
])
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg (lines)', ['rg', '-n', pat, ru]),
|
||||
Command('ag (lines)', ['ag', '-s', pat, ru]),
|
||||
Command('ucg (lines)', ['ucg', '--nosmart-case', pat, ru]),
|
||||
Command('grep (lines)', [
|
||||
'grep', '-E', '-an', pat, ru,
|
||||
], env=GREP_ASCII),
|
||||
Command('rg', ['rg', pat, ru]),
|
||||
Command('grep', [
|
||||
'grep', '-E', '-a', pat, ru,
|
||||
], env=GREP_ASCII),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_ru_alternate_casei(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of a set of alternate literals.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-ru')
|
||||
ru = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_RU_NAME)
|
||||
pat = '|'.join([
|
||||
'Шерлок Холмс', # Sherlock Holmes
|
||||
'Джон Уотсон', # John Watson
|
||||
'Ирен Адлер', # Irene Adler
|
||||
'инспектор Лестрейд', # Inspector Lestrade
|
||||
'профессор Мориарти', # Professor Moriarty
|
||||
])
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('ag (ASCII)', ['ag', '-s', '-i', pat, ru]),
|
||||
Command('ucg (ASCII)', ['ucg', '-i', pat, ru]),
|
||||
Command('grep (ASCII)', [
|
||||
'grep', '-E', '-ani', pat, ru,
|
||||
], env=GREP_ASCII),
|
||||
Command('rg', ['rg', '-n', '-i', pat, ru]),
|
||||
Command('grep', ['grep', '-E', '-ani', pat, ru], env=GREP_UNICODE),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_ru_surrounding_words(suite_dir):
|
||||
'''
|
||||
Benchmark a more complex regex with an inner literal.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-en')
|
||||
ru = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_RU_NAME)
|
||||
pat = r'\w+\s+Холмс\s+\w+'
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg', ['rg', '-n', pat, ru]),
|
||||
Command('grep', ['grep', '-E', '-an', pat, ru], env=GREP_UNICODE),
|
||||
Command('ag (ASCII)', ['ag', '-s', pat, ru]),
|
||||
Command('ucg (ASCII)', ['ucg', '--nosmart-case', pat, ru]),
|
||||
Command('grep (ASCII)', [
|
||||
'grep', '-E', '-an', pat, ru,
|
||||
], env=GREP_ASCII),
|
||||
])
|
||||
|
||||
|
||||
def bench_subtitles_ru_no_literal(suite_dir):
|
||||
'''
|
||||
Benchmark the speed of a regex with no literals.
|
||||
|
||||
Note that we don't even try to run grep with Unicode support
|
||||
on this one. While it should eventually get the right answer,
|
||||
I killed it after it had already been running for two minutes
|
||||
and showed no signs of finishing soon.
|
||||
'''
|
||||
require(suite_dir, 'subtitles-ru')
|
||||
ru = path.join(suite_dir, SUBTITLES_DIR, SUBTITLES_RU_NAME)
|
||||
pat = r'\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}'
|
||||
|
||||
return Benchmark(pattern=pat, commands=[
|
||||
Command('rg', ['rg', '-n', pat, ru]),
|
||||
Command('rg (ASCII)', ['rg', '-n', '(?-u)' + pat, ru]),
|
||||
Command('ag (ASCII)', ['ag', '-s', pat, ru]),
|
||||
Command('ucg (ASCII)', ['ucg', '--nosmart-case', pat, ru]),
|
||||
Command('grep (ASCII)', [
|
||||
'grep', '-E', '-an', pat, ru,
|
||||
], env=GREP_ASCII),
|
||||
])
|
||||
|
||||
|
||||
@@ -444,6 +728,23 @@ class MissingDependencies(Exception):
|
||||
return 'MissingDependency(%s)' % repr(self.missing_names)
|
||||
|
||||
|
||||
class MissingCommands(Exception):
|
||||
'''
|
||||
A missing command exception.
|
||||
|
||||
This exception occurs when running a command in a benchmark
|
||||
where the command could not be found on the current system.
|
||||
|
||||
:ivar list(str) missing_names:
|
||||
The names of the command binaries that could not be found.
|
||||
'''
|
||||
def __init__(self, missing_names):
|
||||
self.missing_names = sorted(set(missing_names))
|
||||
|
||||
def __str__(self):
|
||||
return 'MissingCommands(%s)' % repr(self.missing_names)
|
||||
|
||||
|
||||
class Benchmark(object):
|
||||
'''
|
||||
A single benchmark corresponding to a grouping of commands.
|
||||
@@ -453,7 +754,8 @@ class Benchmark(object):
|
||||
'''
|
||||
|
||||
def __init__(self, name=None, pattern=None, commands=None,
|
||||
warmup_count=1, count=3, line_count=True):
|
||||
warmup_count=1, count=3, line_count=True,
|
||||
allow_missing_commands=False):
|
||||
'''
|
||||
Create a single benchmark.
|
||||
|
||||
@@ -491,15 +793,37 @@ class Benchmark(object):
|
||||
self.warmup_count = warmup_count
|
||||
self.count = count
|
||||
self.line_count = line_count
|
||||
self.allow_missing_commands = allow_missing_commands
|
||||
|
||||
def raise_if_missing(self):
|
||||
'''
|
||||
Raises a MissingCommands exception if applicable.
|
||||
|
||||
A MissingCommands exception is raised when the following
|
||||
criteria are met: 1) allow_missing_commands is False, and 2) at
|
||||
least one command in this benchmark could not be found on this
|
||||
system.
|
||||
'''
|
||||
missing_commands = \
|
||||
[c.binary_name for c in self.commands if not c.exists()]
|
||||
if not self.allow_missing_commands and len(missing_commands) > 0:
|
||||
raise MissingCommands(missing_commands)
|
||||
|
||||
def run(self):
|
||||
'''
|
||||
Runs this benchmark and returns the results.
|
||||
|
||||
:rtype: Result
|
||||
:raises:
|
||||
MissingCommands if any command doesn't exist.
|
||||
(Unless allow_missing_commands is enabled.)
|
||||
'''
|
||||
self.raise_if_missing()
|
||||
result = Result(self)
|
||||
for cmd in self.commands:
|
||||
if self.allow_missing_commands and not cmd.exists():
|
||||
# Skip this command if we're OK with it.
|
||||
continue
|
||||
# Do a warmup first.
|
||||
for _ in range(self.warmup_count):
|
||||
self.run_one(cmd)
|
||||
@@ -524,6 +848,8 @@ class Benchmark(object):
|
||||
it is the number of lines in the search output.
|
||||
:rtype: int
|
||||
'''
|
||||
if not cmd.exists():
|
||||
raise MissingCommand(cmd.cmd[0])
|
||||
cmd.kwargs['stderr'] = subprocess.DEVNULL
|
||||
if self.line_count:
|
||||
cmd.kwargs['stdout'] = subprocess.PIPE
|
||||
@@ -593,6 +919,8 @@ class Result(object):
|
||||
means = []
|
||||
for cmd in self.benchmark.commands:
|
||||
mean, _ = self.distribution_for(cmd)
|
||||
if mean is None:
|
||||
continue
|
||||
means.append((cmd, mean))
|
||||
return min(means, key=lambda tup: tup[1])[0]
|
||||
|
||||
@@ -615,16 +943,18 @@ class Result(object):
|
||||
'''
|
||||
Returns the distribution (mean +/- std) of the given command.
|
||||
|
||||
If there are no samples for this command (i.e., it was skipped),
|
||||
then return ``(None, None)``.
|
||||
|
||||
:rtype: (float, float)
|
||||
:returns:
|
||||
A tuple containing the mean and standard deviation, in that
|
||||
order.
|
||||
'''
|
||||
mean = statistics.mean(
|
||||
s['duration'] for s in self.samples_for(cmd))
|
||||
stdev = statistics.stdev(
|
||||
s['duration'] for s in self.samples_for(cmd))
|
||||
return mean, stdev
|
||||
samples = list(s['duration'] for s in self.samples_for(cmd))
|
||||
if len(samples) == 0:
|
||||
return None, None
|
||||
return statistics.mean(samples), statistics.stdev(samples)
|
||||
|
||||
|
||||
class Command(object):
|
||||
@@ -654,6 +984,15 @@ class Command(object):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def exists(self):
|
||||
'Returns true if and only if this command exists.'
|
||||
return shutil.which(self.binary_name) is not None
|
||||
|
||||
@property
|
||||
def binary_name(self):
|
||||
'Return the binary name of this command.'
|
||||
return self.cmd[0]
|
||||
|
||||
def run(self):
|
||||
'''
|
||||
Runs this command and returns its status.
|
||||
@@ -723,6 +1062,7 @@ def download_subtitles_en(suite_dir):
|
||||
subtitle_dir = path.join(suite_dir, SUBTITLES_DIR)
|
||||
en_path_gz = path.join(subtitle_dir, SUBTITLES_EN_NAME_GZ)
|
||||
en_path = path.join(subtitle_dir, SUBTITLES_EN_NAME)
|
||||
en_path_sample = path.join(subtitle_dir, SUBTITLES_EN_NAME_SAMPLE)
|
||||
|
||||
if not os.path.isdir(subtitle_dir):
|
||||
os.makedirs(subtitle_dir)
|
||||
@@ -730,12 +1070,19 @@ def download_subtitles_en(suite_dir):
|
||||
if not os.path.exists(en_path_gz):
|
||||
run_cmd(['curl', '-LO', SUBTITLES_EN_URL], cwd=subtitle_dir)
|
||||
run_cmd(['gunzip', en_path_gz], cwd=subtitle_dir)
|
||||
if not os.path.exists(en_path_sample):
|
||||
# Get a sample roughly the same size as the Russian corpus so that
|
||||
# benchmarks finish in a reasonable time.
|
||||
with open(path.join(subtitle_dir, en_path_sample), 'wb+') as f:
|
||||
run_cmd(
|
||||
['head', '-n', '32722372', en_path],
|
||||
cwd=subtitle_dir, stdout=f)
|
||||
|
||||
|
||||
def has_subtitles_en(suite_dir):
|
||||
'Returns true if English subtitles have been downloaded.'
|
||||
subtitle_dir = path.join(suite_dir, SUBTITLES_DIR)
|
||||
return path.exists(path.join(subtitle_dir, SUBTITLES_EN_NAME))
|
||||
return path.exists(path.join(subtitle_dir, SUBTITLES_EN_NAME_SAMPLE))
|
||||
|
||||
|
||||
def download_subtitles_ru(suite_dir):
|
||||
@@ -770,7 +1117,7 @@ def download(suite_dir, choices):
|
||||
A list of corpora to download. Available choices are:
|
||||
all, linux, subtitles-en, subtitles-ru.
|
||||
'''
|
||||
for choice in args.download:
|
||||
for choice in choices:
|
||||
if choice == 'linux':
|
||||
download_linux(suite_dir)
|
||||
elif choice == 'subtitles-en':
|
||||
@@ -786,7 +1133,9 @@ def download(suite_dir, choices):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def collect_benchmarks(suite_dir, filter_pat=None):
|
||||
def collect_benchmarks(suite_dir, filter_pat=None,
|
||||
allow_missing_commands=False,
|
||||
warmup_iter=1, bench_iter=3):
|
||||
'''
|
||||
Return an iterable of all runnable benchmarks.
|
||||
|
||||
@@ -808,6 +1157,11 @@ def collect_benchmarks(suite_dir, filter_pat=None):
|
||||
continue
|
||||
try:
|
||||
benchmark = globals()[fun](suite_dir)
|
||||
benchmark.name = name
|
||||
benchmark.warmup_count = warmup_iter
|
||||
benchmark.count = bench_iter
|
||||
benchmark.allow_missing_commands = allow_missing_commands
|
||||
benchmark.raise_if_missing()
|
||||
except MissingDependencies as e:
|
||||
eprint(
|
||||
'missing: %s, skipping benchmark %s (try running with: %s)' % (
|
||||
@@ -815,24 +1169,32 @@ def collect_benchmarks(suite_dir, filter_pat=None):
|
||||
name,
|
||||
' '.join(['--download %s' % n for n in e.missing_names]),
|
||||
))
|
||||
except MissingCommands as e:
|
||||
fmt = 'missing commands: %s, skipping benchmark %s ' \
|
||||
'(run with --allow-missing to run incomplete benchmarks)'
|
||||
eprint(fmt % (', '.join(e.missing_names), name))
|
||||
continue
|
||||
benchmark.name = name
|
||||
yield benchmark
|
||||
|
||||
|
||||
def main():
|
||||
download_choices = ['all', 'linux', 'subtitles-en', 'subtitles-ru']
|
||||
p = argparse.ArgumentParser('Command line search tool benchmark suite.')
|
||||
p.add_argument(
|
||||
'--dir', metavar='PATH', default=os.getcwd(),
|
||||
help='The directory in which to download data and perform searches.')
|
||||
p.add_argument(
|
||||
'--download', metavar='CORPUS', action='append',
|
||||
choices=['all', 'linux', 'subtitles-en', 'subtitles-ru'],
|
||||
choices=download_choices,
|
||||
help='Download and prepare corpus data, then exit without running '
|
||||
'any benchmarks. Note that this command is intended to be '
|
||||
'idempotent. WARNING: This downloads over a gigabyte of data, '
|
||||
'and also includes building the Linux kernel. If "all" is used '
|
||||
'then the total uncompressed size is around 13 GB.')
|
||||
'then the total uncompressed size is around 13 GB. '
|
||||
'Choices: %s' % ', '.join(download_choices))
|
||||
p.add_argument(
|
||||
'--allow-missing', action='store_true',
|
||||
help='Permit benchmarks to run even if some commands are missing.')
|
||||
p.add_argument(
|
||||
'-f', '--force', action='store_true',
|
||||
help='Overwrite existing files if there is a conflict.')
|
||||
@@ -843,13 +1205,29 @@ def main():
|
||||
'--raw', metavar='PATH',
|
||||
help='Dump raw data (all samples collected) in CSV format to the '
|
||||
'file path provided.')
|
||||
p.add_argument(
|
||||
'--warmup-iter', metavar='INTEGER', type=int, default=1,
|
||||
help='The number of iterations to run each command before '
|
||||
'recording measurements.')
|
||||
p.add_argument(
|
||||
'--bench-iter', metavar='INTEGER', type=int, default=3,
|
||||
help='The number of iterations to run each command while '
|
||||
'recording measurements.')
|
||||
p.add_argument(
|
||||
'bench', metavar='PAT', nargs='?',
|
||||
help='A regex pattern that will only run benchmarks that match.')
|
||||
args = p.parse_args()
|
||||
|
||||
if args.list:
|
||||
benchmarks = collect_benchmarks(
|
||||
args.dir, filter_pat=args.bench,
|
||||
allow_missing_commands=args.allow_missing,
|
||||
warmup_iter=args.warmup_iter, bench_iter=args.bench_iter)
|
||||
for b in benchmarks:
|
||||
print(b.name)
|
||||
sys.exit(0)
|
||||
if args.download is not None and len(args.download) > 0:
|
||||
download(args.dir, args.choices)
|
||||
download(args.dir, args.download)
|
||||
sys.exit(0)
|
||||
|
||||
if not path.isdir(args.dir):
|
||||
@@ -867,7 +1245,10 @@ def main():
|
||||
raw_csv_wtr = csv.DictWriter(raw_handle, fields)
|
||||
raw_csv_wtr.writerow({x: x for x in fields})
|
||||
|
||||
benchmarks = collect_benchmarks(args.dir, filter_pat=args.bench)
|
||||
benchmarks = collect_benchmarks(
|
||||
args.dir, filter_pat=args.bench,
|
||||
allow_missing_commands=args.allow_missing,
|
||||
warmup_iter=args.warmup_iter, bench_iter=args.bench_iter)
|
||||
for i, b in enumerate(benchmarks):
|
||||
result = b.run()
|
||||
fastest_cmd = result.fastest_cmd()
|
||||
@@ -881,6 +1262,12 @@ def main():
|
||||
for cmd in b.commands:
|
||||
name = cmd.name
|
||||
mean, stdev = result.distribution_for(cmd)
|
||||
if mean is None:
|
||||
# If we couldn't get a distribution for this command then
|
||||
# it was skipped.
|
||||
print('{name:{pad}} SKIPPED'.format(
|
||||
name=name, pad=max_name_len + 2))
|
||||
continue
|
||||
line_counts = result.line_counts_for(cmd)
|
||||
show_fast_cmd, show_line_counts = '', ''
|
||||
if fastest_cmd.name == cmd.name:
|
||||
93
benchsuite/runs/2016-09-17-ubuntu1604-ec2/README.SETUP
Normal file
93
benchsuite/runs/2016-09-17-ubuntu1604-ec2/README.SETUP
Normal file
@@ -0,0 +1,93 @@
|
||||
Ubuntu 16.04 HVM AMI
|
||||
c3.2xlarge, Xeon E5-2680, 2.8 GHz, 8 CPUs, 16 GB memory, 80 GB SSD
|
||||
|
||||
# Generic system setup
|
||||
|
||||
mkfs.ext4 /dev/xvdb
|
||||
sudo mount /dev/xvdb /mnt
|
||||
sudo chown ubuntu /mnt
|
||||
sudo apt-get update
|
||||
sudo apt-get install \ # for building Linux kernel
|
||||
make gcc bc
|
||||
sudo apt-get install \ # for the silver searcher
|
||||
automake pkg-config zlib1g-dev liblzma-dev libpcre3 libpcre3-dev
|
||||
sudo apt-get install \ # for Universal Code Grep
|
||||
libtool libpcre2-8-0 libpcre2-dev
|
||||
sudo apt-get install \ # for sift and the platinum searcher
|
||||
go
|
||||
|
||||
# Get benchmark corpora
|
||||
|
||||
cd /mnt
|
||||
mkdir /mnt/bench
|
||||
git clone git://github.com/BurntSushi/ripgrep
|
||||
cd ripgrep/benchsuite
|
||||
./benchsuite --dir /mnt/bench/ --download all # takes around 15 minutes
|
||||
|
||||
# Install search tools
|
||||
mkdir /mnt/bin/
|
||||
|
||||
## ripgrep
|
||||
|
||||
cd /mnt
|
||||
mkdir ripgrep-bin
|
||||
cd ripgrep-bin
|
||||
curl -LO 'https://github.com/BurntSushi/ripgrep/releases/download/0.1.2/ripgrep-0.1.2-x86_64-unknown-linux-musl.tar.gz'
|
||||
cp ripgrep-0.1.2-x86_64-unknown-linux-musl/rg /mnt/bin/
|
||||
|
||||
## The Silver Searcher
|
||||
|
||||
cd /mnt
|
||||
git clone git://github.com/ggreer/the_silver_searcher
|
||||
cd the_silver_searcher
|
||||
git checkout cda635
|
||||
./build.sh
|
||||
cp ag /mnt/bin/
|
||||
|
||||
## Universal Code Grep
|
||||
|
||||
cd /mnt
|
||||
git clone git://github.com/gvansickle/ucg
|
||||
cd ucg
|
||||
git checkout 487bfb
|
||||
autoreconf -i
|
||||
./configure
|
||||
make
|
||||
cp ucg /mnt/bin/
|
||||
|
||||
## The Platinum Searcher
|
||||
|
||||
export GOPATH=/mnt/go
|
||||
go get github.com/monochromegane/the_platinum_searcher
|
||||
cd /mnt/go/src/github.com/monochromegane/the_platinum_searcher
|
||||
git checkout 509368
|
||||
go install github.com/monochromegane/the_platinum_searcher/cmd/...
|
||||
cp /mnt/go/bin/pt /mnt/bin/
|
||||
|
||||
## Sift
|
||||
|
||||
export GOPATH=/mnt/go
|
||||
go get github.com/svent/sift
|
||||
cd /mnt/go/src/github.com/svent/sift
|
||||
git checkout 2d175c
|
||||
go install
|
||||
cp /mnt/go/bin/sift /mnt/bin/
|
||||
|
||||
## 'git grep' and GNU grep
|
||||
|
||||
They are part of the standard Ubuntu install, and are pretty recent (as of
|
||||
September 2016).
|
||||
|
||||
$ git --version
|
||||
git version 2.7.4
|
||||
$ grep --version
|
||||
grep (GNU grep) 2.25
|
||||
|
||||
|
||||
# Running benchmarks
|
||||
|
||||
export PATH="/mnt/bin:$PATH"
|
||||
cd /mnt/ripgrep/benchsuite
|
||||
./benchsuite --dir /mnt/bench/ --raw /mnt/bench/raw.csv \
|
||||
| tee /mnt/bench/summary
|
||||
# The above took around 30 minutes to run to completion.
|
||||
457
benchsuite/runs/2016-09-17-ubuntu1604-ec2/raw.csv
Normal file
457
benchsuite/runs/2016-09-17-ubuntu1604-ec2/raw.csv
Normal file
@@ -0,0 +1,457 @@
|
||||
benchmark,warmup_iter,iter,name,command,duration,lines,env
|
||||
linux_alternates,1,3,rg (ignore),rg -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.30042552947998047,68,
|
||||
linux_alternates,1,3,rg (ignore),rg -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.3699159622192383,68,
|
||||
linux_alternates,1,3,rg (ignore),rg -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.2950403690338135,68,
|
||||
linux_alternates,1,3,ag (ignore),ag -s ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,1.7572166919708252,68,
|
||||
linux_alternates,1,3,ag (ignore),ag -s ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,1.7568728923797607,68,
|
||||
linux_alternates,1,3,ag (ignore),ag -s ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,1.7468714714050293,68,
|
||||
linux_alternates,1,3,git grep (ignore),git grep -E -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.5062770843505859,68,LC_ALL=C
|
||||
linux_alternates,1,3,git grep (ignore),git grep -E -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.49932026863098145,68,LC_ALL=C
|
||||
linux_alternates,1,3,git grep (ignore),git grep -E -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.5008277893066406,68,LC_ALL=C
|
||||
linux_alternates,1,3,rg (whitelist),rg --no-ignore -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.19775605201721191,68,
|
||||
linux_alternates,1,3,rg (whitelist),rg --no-ignore -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.19699668884277344,68,
|
||||
linux_alternates,1,3,rg (whitelist),rg --no-ignore -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.19636178016662598,68,
|
||||
linux_alternates,1,3,ucg (whitelist),ucg --nosmart-case ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.21344256401062012,68,
|
||||
linux_alternates,1,3,ucg (whitelist),ucg --nosmart-case ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.21743154525756836,68,
|
||||
linux_alternates,1,3,ucg (whitelist),ucg --nosmart-case ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.22065043449401855,68,
|
||||
linux_alternates_casei,1,3,rg (ignore),rg -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.31632304191589355,160,
|
||||
linux_alternates_casei,1,3,rg (ignore),rg -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.3168807029724121,160,
|
||||
linux_alternates_casei,1,3,rg (ignore),rg -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.4834277629852295,160,
|
||||
linux_alternates_casei,1,3,ag (ignore),ag -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,1.9558978080749512,160,
|
||||
linux_alternates_casei,1,3,ag (ignore),ag -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,1.9736671447753906,160,
|
||||
linux_alternates_casei,1,3,ag (ignore),ag -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,1.9648356437683105,160,
|
||||
linux_alternates_casei,1,3,git grep (ignore),git grep -E -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,2.0229439735412598,160,LC_ALL=C
|
||||
linux_alternates_casei,1,3,git grep (ignore),git grep -E -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,2.0214922428131104,160,LC_ALL=C
|
||||
linux_alternates_casei,1,3,git grep (ignore),git grep -E -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,2.022616147994995,160,LC_ALL=C
|
||||
linux_alternates_casei,1,3,rg (whitelist),rg --no-ignore -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.22146987915039062,160,
|
||||
linux_alternates_casei,1,3,rg (whitelist),rg --no-ignore -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.22275280952453613,160,
|
||||
linux_alternates_casei,1,3,rg (whitelist),rg --no-ignore -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.22226715087890625,160,
|
||||
linux_alternates_casei,1,3,ucg (whitelist),ucg -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.5290391445159912,160,
|
||||
linux_alternates_casei,1,3,ucg (whitelist),ucg -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.5196049213409424,160,
|
||||
linux_alternates_casei,1,3,ucg (whitelist),ucg -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.5219125747680664,160,
|
||||
linux_literal,1,3,rg (ignore),rg -n PM_RESUME,0.2871882915496826,16,
|
||||
linux_literal,1,3,rg (ignore),rg -n PM_RESUME,0.38920140266418457,16,
|
||||
linux_literal,1,3,rg (ignore),rg -n PM_RESUME,0.3668382167816162,16,
|
||||
linux_literal,1,3,rg (ignore) (mmap),rg -n --mmap PM_RESUME,1.6073203086853027,16,
|
||||
linux_literal,1,3,rg (ignore) (mmap),rg -n --mmap PM_RESUME,1.6012177467346191,16,
|
||||
linux_literal,1,3,rg (ignore) (mmap),rg -n --mmap PM_RESUME,1.5818352699279785,16,
|
||||
linux_literal,1,3,ag (ignore) (mmap),ag -s PM_RESUME,1.592775583267212,16,
|
||||
linux_literal,1,3,ag (ignore) (mmap),ag -s PM_RESUME,1.5901448726654053,16,
|
||||
linux_literal,1,3,ag (ignore) (mmap),ag -s PM_RESUME,1.586061716079712,16,
|
||||
linux_literal,1,3,pt (ignore),pt PM_RESUME,0.45094990730285645,16,
|
||||
linux_literal,1,3,pt (ignore),pt PM_RESUME,0.47065186500549316,16,
|
||||
linux_literal,1,3,pt (ignore),pt PM_RESUME,0.4422800540924072,16,
|
||||
linux_literal,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git PM_RESUME,0.6293809413909912,16,
|
||||
linux_literal,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git PM_RESUME,0.6314799785614014,16,
|
||||
linux_literal,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git PM_RESUME,0.6289358139038086,16,
|
||||
linux_literal,1,3,git grep (ignore),git grep -I -n PM_RESUME,0.347550630569458,16,LC_ALL=C
|
||||
linux_literal,1,3,git grep (ignore),git grep -I -n PM_RESUME,0.345928430557251,16,LC_ALL=C
|
||||
linux_literal,1,3,git grep (ignore),git grep -I -n PM_RESUME,0.33939385414123535,16,LC_ALL=C
|
||||
linux_literal,1,3,rg (whitelist),rg -n --no-ignore -tall PM_RESUME,0.20830345153808594,16,
|
||||
linux_literal,1,3,rg (whitelist),rg -n --no-ignore -tall PM_RESUME,0.34220385551452637,16,
|
||||
linux_literal,1,3,rg (whitelist),rg -n --no-ignore -tall PM_RESUME,0.2077772617340088,16,
|
||||
linux_literal,1,3,ucg (whitelist),ucg --nosmart-case PM_RESUME,0.21996808052062988,16,
|
||||
linux_literal,1,3,ucg (whitelist),ucg --nosmart-case PM_RESUME,0.22822093963623047,16,
|
||||
linux_literal,1,3,ucg (whitelist),ucg --nosmart-case PM_RESUME,0.21923017501831055,16,
|
||||
linux_literal_casei,1,3,rg (ignore),rg -n -i PM_RESUME,0.2988588809967041,370,
|
||||
linux_literal_casei,1,3,rg (ignore),rg -n -i PM_RESUME,0.5339267253875732,370,
|
||||
linux_literal_casei,1,3,rg (ignore),rg -n -i PM_RESUME,0.43750762939453125,370,
|
||||
linux_literal_casei,1,3,rg (ignore) (mmap),rg -n -i --mmap PM_RESUME,1.603757381439209,370,
|
||||
linux_literal_casei,1,3,rg (ignore) (mmap),rg -n -i --mmap PM_RESUME,1.618077278137207,370,
|
||||
linux_literal_casei,1,3,rg (ignore) (mmap),rg -n -i --mmap PM_RESUME,1.6234121322631836,370,
|
||||
linux_literal_casei,1,3,ag (ignore) (mmap),ag -i PM_RESUME,1.621368646621704,370,
|
||||
linux_literal_casei,1,3,ag (ignore) (mmap),ag -i PM_RESUME,1.5698626041412354,370,
|
||||
linux_literal_casei,1,3,ag (ignore) (mmap),ag -i PM_RESUME,1.6105949878692627,370,
|
||||
linux_literal_casei,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git PM_RESUME,0.8005843162536621,370,
|
||||
linux_literal_casei,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git PM_RESUME,0.8044955730438232,370,
|
||||
linux_literal_casei,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git PM_RESUME,0.806922197341919,370,
|
||||
linux_literal_casei,1,3,git grep (ignore),git grep -I -n -i PM_RESUME,0.34161829948425293,370,LC_ALL=C
|
||||
linux_literal_casei,1,3,git grep (ignore),git grep -I -n -i PM_RESUME,0.3455958366394043,370,LC_ALL=C
|
||||
linux_literal_casei,1,3,git grep (ignore),git grep -I -n -i PM_RESUME,0.3493361473083496,370,LC_ALL=C
|
||||
linux_literal_casei,1,3,rg (whitelist),rg -n -i --no-ignore -tall PM_RESUME,0.2149522304534912,370,
|
||||
linux_literal_casei,1,3,rg (whitelist),rg -n -i --no-ignore -tall PM_RESUME,0.21602368354797363,370,
|
||||
linux_literal_casei,1,3,rg (whitelist),rg -n -i --no-ignore -tall PM_RESUME,0.27021098136901855,370,
|
||||
linux_literal_casei,1,3,ucg (whitelist),ucg -i PM_RESUME,0.22747421264648438,370,
|
||||
linux_literal_casei,1,3,ucg (whitelist),ucg -i PM_RESUME,0.22050881385803223,370,
|
||||
linux_literal_casei,1,3,ucg (whitelist),ucg -i PM_RESUME,0.21217584609985352,370,
|
||||
linux_literal_default,1,3,rg,rg PM_RESUME,0.27482175827026367,16,
|
||||
linux_literal_default,1,3,rg,rg PM_RESUME,0.2796294689178467,16,
|
||||
linux_literal_default,1,3,rg,rg PM_RESUME,0.27638936042785645,16,
|
||||
linux_literal_default,1,3,ag,ag PM_RESUME,1.598384141921997,16,
|
||||
linux_literal_default,1,3,ag,ag PM_RESUME,1.5849597454071045,16,
|
||||
linux_literal_default,1,3,ag,ag PM_RESUME,1.6000149250030518,16,
|
||||
linux_literal_default,1,3,ucg,ucg PM_RESUME,0.2263963222503662,16,
|
||||
linux_literal_default,1,3,ucg,ucg PM_RESUME,0.21367835998535156,16,
|
||||
linux_literal_default,1,3,ucg,ucg PM_RESUME,0.21764111518859863,16,
|
||||
linux_literal_default,1,3,pt,pt PM_RESUME,0.42263340950012207,16,
|
||||
linux_literal_default,1,3,pt,pt PM_RESUME,0.467041015625,16,
|
||||
linux_literal_default,1,3,pt,pt PM_RESUME,0.42820048332214355,16,
|
||||
linux_literal_default,1,3,sift,sift PM_RESUME,0.33421826362609863,16,
|
||||
linux_literal_default,1,3,sift,sift PM_RESUME,0.35932135581970215,16,
|
||||
linux_literal_default,1,3,sift,sift PM_RESUME,0.3372631072998047,16,
|
||||
linux_literal_default,1,3,git grep,git grep PM_RESUME,0.348984956741333,16,LC_ALL=en_US.UTF-8
|
||||
linux_literal_default,1,3,git grep,git grep PM_RESUME,0.34420299530029297,16,LC_ALL=en_US.UTF-8
|
||||
linux_literal_default,1,3,git grep,git grep PM_RESUME,0.33880615234375,16,LC_ALL=en_US.UTF-8
|
||||
linux_no_literal,1,3,rg (ignore),rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.5833027362823486,490,
|
||||
linux_no_literal,1,3,rg (ignore),rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.5783836841583252,490,
|
||||
linux_no_literal,1,3,rg (ignore),rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.5806806087493896,490,
|
||||
linux_no_literal,1,3,rg (ignore) (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.4163496494293213,490,
|
||||
linux_no_literal,1,3,rg (ignore) (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.4040029048919678,490,
|
||||
linux_no_literal,1,3,rg (ignore) (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.40265560150146484,490,
|
||||
linux_no_literal,1,3,ag (ignore) (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},2.3176910877227783,766,
|
||||
linux_no_literal,1,3,ag (ignore) (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},2.3504347801208496,766,
|
||||
linux_no_literal,1,3,ag (ignore) (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},2.317542552947998,766,
|
||||
linux_no_literal,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},25.821982383728027,491,
|
||||
linux_no_literal,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},25.82067561149597,491,
|
||||
linux_no_literal,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},25.626302242279053,491,
|
||||
linux_no_literal,1,3,git grep (ignore),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},26.349210023880005,490,LC_ALL=en_US.UTF-8
|
||||
linux_no_literal,1,3,git grep (ignore),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},26.34226369857788,490,LC_ALL=en_US.UTF-8
|
||||
linux_no_literal,1,3,git grep (ignore),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},26.340656995773315,490,LC_ALL=en_US.UTF-8
|
||||
linux_no_literal,1,3,git grep (ignore) (ASCII),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},4.14680290222168,490,LC_ALL=C
|
||||
linux_no_literal,1,3,git grep (ignore) (ASCII),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},4.153340578079224,490,LC_ALL=C
|
||||
linux_no_literal,1,3,git grep (ignore) (ASCII),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},4.154819011688232,490,LC_ALL=C
|
||||
linux_no_literal,1,3,rg (whitelist),rg -n --no-ignore -tall \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.49768829345703125,419,
|
||||
linux_no_literal,1,3,rg (whitelist),rg -n --no-ignore -tall \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.5121400356292725,419,
|
||||
linux_no_literal,1,3,rg (whitelist),rg -n --no-ignore -tall \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.49718427658081055,419,
|
||||
linux_no_literal,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.38550543785095215,419,
|
||||
linux_no_literal,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.32651591300964355,419,
|
||||
linux_no_literal,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.3246915340423584,419,
|
||||
linux_no_literal,1,3,ucg (whitelist) (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},1.128183126449585,416,
|
||||
linux_no_literal,1,3,ucg (whitelist) (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},1.1255216598510742,416,
|
||||
linux_no_literal,1,3,ucg (whitelist) (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},1.1268525123596191,416,
|
||||
linux_re_literal_suffix,1,3,rg (ignore),rg -n [A-Z]+_RESUME,0.36306214332580566,1652,
|
||||
linux_re_literal_suffix,1,3,rg (ignore),rg -n [A-Z]+_RESUME,0.45077037811279297,1652,
|
||||
linux_re_literal_suffix,1,3,rg (ignore),rg -n [A-Z]+_RESUME,0.3804624080657959,1652,
|
||||
linux_re_literal_suffix,1,3,ag (ignore),ag -s [A-Z]+_RESUME,1.8995850086212158,1652,
|
||||
linux_re_literal_suffix,1,3,ag (ignore),ag -s [A-Z]+_RESUME,1.8897662162780762,1652,
|
||||
linux_re_literal_suffix,1,3,ag (ignore),ag -s [A-Z]+_RESUME,1.878380537033081,1652,
|
||||
linux_re_literal_suffix,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git [A-Z]+_RESUME,10.48949646949768,1652,
|
||||
linux_re_literal_suffix,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git [A-Z]+_RESUME,10.289165735244751,1652,
|
||||
linux_re_literal_suffix,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git [A-Z]+_RESUME,10.000468254089355,1652,
|
||||
linux_re_literal_suffix,1,3,git grep (ignore),git grep -E -I -n [A-Z]+_RESUME,1.1080453395843506,1652,LC_ALL=C
|
||||
linux_re_literal_suffix,1,3,git grep (ignore),git grep -E -I -n [A-Z]+_RESUME,1.10821533203125,1652,LC_ALL=C
|
||||
linux_re_literal_suffix,1,3,git grep (ignore),git grep -E -I -n [A-Z]+_RESUME,1.105595588684082,1652,LC_ALL=C
|
||||
linux_re_literal_suffix,1,3,rg (whitelist),rg -n --no-ignore -tall [A-Z]+_RESUME,0.2073357105255127,1630,
|
||||
linux_re_literal_suffix,1,3,rg (whitelist),rg -n --no-ignore -tall [A-Z]+_RESUME,0.20552492141723633,1630,
|
||||
linux_re_literal_suffix,1,3,rg (whitelist),rg -n --no-ignore -tall [A-Z]+_RESUME,0.2061319351196289,1630,
|
||||
linux_re_literal_suffix,1,3,ucg (whitelist),ucg --nosmart-case [A-Z]+_RESUME,0.3013310432434082,1630,
|
||||
linux_re_literal_suffix,1,3,ucg (whitelist),ucg --nosmart-case [A-Z]+_RESUME,0.3005218505859375,1630,
|
||||
linux_re_literal_suffix,1,3,ucg (whitelist),ucg --nosmart-case [A-Z]+_RESUME,0.29984378814697266,1630,
|
||||
linux_unicode_greek,1,3,rg,rg -n \p{Greek},0.4162716865539551,23,
|
||||
linux_unicode_greek,1,3,rg,rg -n \p{Greek},0.47885966300964355,23,
|
||||
linux_unicode_greek,1,3,rg,rg -n \p{Greek},0.5135962963104248,23,
|
||||
linux_unicode_greek,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \p{Greek},7.570494651794434,23,
|
||||
linux_unicode_greek,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \p{Greek},7.790151596069336,23,
|
||||
linux_unicode_greek,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \p{Greek},7.783358812332153,23,
|
||||
linux_unicode_greek_casei,1,3,rg,rg -n -i \p{Greek},0.4370443820953369,103,
|
||||
linux_unicode_greek_casei,1,3,rg,rg -n -i \p{Greek},0.40680766105651855,103,
|
||||
linux_unicode_greek_casei,1,3,rg,rg -n -i \p{Greek},0.4011569023132324,103,
|
||||
linux_unicode_greek_casei,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git \p{Greek},0.002422809600830078,,
|
||||
linux_unicode_greek_casei,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git \p{Greek},0.002414226531982422,,
|
||||
linux_unicode_greek_casei,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git \p{Greek},0.002293109893798828,,
|
||||
linux_unicode_word,1,3,rg (ignore),rg -n \wAh,0.44771265983581543,186,
|
||||
linux_unicode_word,1,3,rg (ignore),rg -n \wAh,0.3006107807159424,186,
|
||||
linux_unicode_word,1,3,rg (ignore),rg -n \wAh,0.3026111125946045,186,
|
||||
linux_unicode_word,1,3,rg (ignore) (ASCII),rg -n (?-u)\wAh,0.43827080726623535,174,
|
||||
linux_unicode_word,1,3,rg (ignore) (ASCII),rg -n (?-u)\wAh,0.2931697368621826,174,
|
||||
linux_unicode_word,1,3,rg (ignore) (ASCII),rg -n (?-u)\wAh,0.33072638511657715,174,
|
||||
linux_unicode_word,1,3,ag (ignore) (ASCII),ag -s \wAh,1.7645132541656494,174,
|
||||
linux_unicode_word,1,3,ag (ignore) (ASCII),ag -s \wAh,1.7730333805084229,174,
|
||||
linux_unicode_word,1,3,ag (ignore) (ASCII),ag -s \wAh,1.773383378982544,174,
|
||||
linux_unicode_word,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n \wAh,10.872124671936035,174,
|
||||
linux_unicode_word,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n \wAh,11.147738456726074,174,
|
||||
linux_unicode_word,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n \wAh,11.029243469238281,174,
|
||||
linux_unicode_word,1,3,git grep (ignore),git grep -E -I -n \wAh,13.047154664993286,186,LC_ALL=en_US.UTF-8
|
||||
linux_unicode_word,1,3,git grep (ignore),git grep -E -I -n \wAh,13.025037288665771,186,LC_ALL=en_US.UTF-8
|
||||
linux_unicode_word,1,3,git grep (ignore),git grep -E -I -n \wAh,13.081012487411499,186,LC_ALL=en_US.UTF-8
|
||||
linux_unicode_word,1,3,git grep (ignore) (ASCII),git grep -E -I -n \wAh,2.98722767829895,174,LC_ALL=C
|
||||
linux_unicode_word,1,3,git grep (ignore) (ASCII),git grep -E -I -n \wAh,2.987990379333496,174,LC_ALL=C
|
||||
linux_unicode_word,1,3,git grep (ignore) (ASCII),git grep -E -I -n \wAh,2.988698959350586,174,LC_ALL=C
|
||||
linux_unicode_word,1,3,rg (whitelist),rg -n --no-ignore -tall \wAh,0.21961355209350586,180,
|
||||
linux_unicode_word,1,3,rg (whitelist),rg -n --no-ignore -tall \wAh,0.2884254455566406,180,
|
||||
linux_unicode_word,1,3,rg (whitelist),rg -n --no-ignore -tall \wAh,0.21905040740966797,180,
|
||||
linux_unicode_word,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\wAh,0.23966765403747559,168,
|
||||
linux_unicode_word,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\wAh,0.21801042556762695,168,
|
||||
linux_unicode_word,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\wAh,0.21909880638122559,168,
|
||||
linux_unicode_word,1,3,ucg (ASCII),ucg --nosmart-case \wAh,0.2259504795074463,168,
|
||||
linux_unicode_word,1,3,ucg (ASCII),ucg --nosmart-case \wAh,0.22269177436828613,168,
|
||||
linux_unicode_word,1,3,ucg (ASCII),ucg --nosmart-case \wAh,0.2259974479675293,168,
|
||||
linux_word,1,3,rg (ignore),rg -n -w PM_RESUME,0.34968090057373047,6,
|
||||
linux_word,1,3,rg (ignore),rg -n -w PM_RESUME,0.28850769996643066,6,
|
||||
linux_word,1,3,rg (ignore),rg -n -w PM_RESUME,0.34809160232543945,6,
|
||||
linux_word,1,3,ag (ignore),ag -s -w PM_RESUME,1.6134660243988037,6,
|
||||
linux_word,1,3,ag (ignore),ag -s -w PM_RESUME,1.59847092628479,6,
|
||||
linux_word,1,3,ag (ignore),ag -s -w PM_RESUME,1.581369161605835,6,
|
||||
linux_word,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -w --git PM_RESUME,7.644887208938599,6,
|
||||
linux_word,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -w --git PM_RESUME,7.8782219886779785,6,
|
||||
linux_word,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -w --git PM_RESUME,7.754150152206421,6,
|
||||
linux_word,1,3,git grep (ignore),git grep -E -I -n -w PM_RESUME,0.34302353858947754,6,LC_ALL=C
|
||||
linux_word,1,3,git grep (ignore),git grep -E -I -n -w PM_RESUME,0.33672142028808594,6,LC_ALL=C
|
||||
linux_word,1,3,git grep (ignore),git grep -E -I -n -w PM_RESUME,0.35193610191345215,6,LC_ALL=C
|
||||
linux_word,1,3,rg (whitelist),rg -n -w --no-ignore -tall PM_RESUME,0.33329272270202637,6,
|
||||
linux_word,1,3,rg (whitelist),rg -n -w --no-ignore -tall PM_RESUME,0.25751829147338867,6,
|
||||
linux_word,1,3,rg (whitelist),rg -n -w --no-ignore -tall PM_RESUME,0.21761608123779297,6,
|
||||
linux_word,1,3,ucg (whitelist),ucg --nosmart-case -w PM_RESUME,0.22014284133911133,6,
|
||||
linux_word,1,3,ucg (whitelist),ucg --nosmart-case -w PM_RESUME,0.22314929962158203,6,
|
||||
linux_word,1,3,ucg (whitelist),ucg --nosmart-case -w PM_RESUME,0.2202434539794922,6,
|
||||
subtitles_en_alternate,1,3,rg (lines),rg -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.622809886932373,848,
|
||||
subtitles_en_alternate,1,3,rg (lines),rg -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6181182861328125,848,
|
||||
subtitles_en_alternate,1,3,rg (lines),rg -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6205663681030273,848,
|
||||
subtitles_en_alternate,1,3,ag (lines),ag -s Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.7565691471099854,848,
|
||||
subtitles_en_alternate,1,3,ag (lines),ag -s Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.7561769485473633,848,
|
||||
subtitles_en_alternate,1,3,ag (lines),ag -s Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.7559969425201416,848,
|
||||
subtitles_en_alternate,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,1.4493463039398193,848,
|
||||
subtitles_en_alternate,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,1.44877028465271,848,
|
||||
subtitles_en_alternate,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,1.4433801174163818,848,
|
||||
subtitles_en_alternate,1,3,grep (lines),grep -E -an Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.4114174842834473,848,LC_ALL=C
|
||||
subtitles_en_alternate,1,3,grep (lines),grep -E -an Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.411625862121582,848,LC_ALL=C
|
||||
subtitles_en_alternate,1,3,grep (lines),grep -E -an Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.408623456954956,848,LC_ALL=C
|
||||
subtitles_en_alternate,1,3,rg,rg Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.2900726795196533,848,
|
||||
subtitles_en_alternate,1,3,rg,rg Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.2905848026275635,848,
|
||||
subtitles_en_alternate,1,3,rg,rg Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.2903940677642822,848,
|
||||
subtitles_en_alternate,1,3,grep,grep -E -a Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.9546515941619873,848,LC_ALL=C
|
||||
subtitles_en_alternate,1,3,grep,grep -E -a Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.955279588699341,848,LC_ALL=C
|
||||
subtitles_en_alternate,1,3,grep,grep -E -a Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.954089403152466,848,LC_ALL=C
|
||||
subtitles_en_alternate_casei,1,3,ag (ASCII),ag -s -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,5.169135332107544,862,
|
||||
subtitles_en_alternate_casei,1,3,ag (ASCII),ag -s -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,5.174601078033447,862,
|
||||
subtitles_en_alternate_casei,1,3,ag (ASCII),ag -s -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,5.1684675216674805,862,
|
||||
subtitles_en_alternate_casei,1,3,ucg (ASCII),ucg -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.4171736240386963,862,
|
||||
subtitles_en_alternate_casei,1,3,ucg (ASCII),ucg -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.4158167839050293,862,
|
||||
subtitles_en_alternate_casei,1,3,ucg (ASCII),ucg -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.4186513423919678,862,
|
||||
subtitles_en_alternate_casei,1,3,grep (ASCII),grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.531639575958252,862,LC_ALL=C
|
||||
subtitles_en_alternate_casei,1,3,grep (ASCII),grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.526550054550171,862,LC_ALL=C
|
||||
subtitles_en_alternate_casei,1,3,grep (ASCII),grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.528509140014648,862,LC_ALL=C
|
||||
subtitles_en_alternate_casei,1,3,rg,rg -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.722398281097412,862,
|
||||
subtitles_en_alternate_casei,1,3,rg,rg -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.7225935459136963,862,
|
||||
subtitles_en_alternate_casei,1,3,rg,rg -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.7222185134887695,862,
|
||||
subtitles_en_alternate_casei,1,3,grep,grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,5.12151837348938,862,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_alternate_casei,1,3,grep,grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,5.119963884353638,862,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_alternate_casei,1,3,grep,grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,5.121079683303833,862,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal,1,3,rg,rg Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.2684764862060547,629,
|
||||
subtitles_en_literal,1,3,rg,rg Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.26811957359313965,629,
|
||||
subtitles_en_literal,1,3,rg,rg Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.2684173583984375,629,
|
||||
subtitles_en_literal,1,3,pt,pt -N Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.435128688812256,629,
|
||||
subtitles_en_literal,1,3,pt,pt -N Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.4362881183624268,629,
|
||||
subtitles_en_literal,1,3,pt,pt -N Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.4399220943450928,629,
|
||||
subtitles_en_literal,1,3,sift,sift Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.32662391662597656,629,
|
||||
subtitles_en_literal,1,3,sift,sift Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.3277275562286377,629,
|
||||
subtitles_en_literal,1,3,sift,sift Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.32798290252685547,629,
|
||||
subtitles_en_literal,1,3,grep,grep -a Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.520500659942627,629,LC_ALL=C
|
||||
subtitles_en_literal,1,3,grep,grep -a Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.5191032886505127,629,LC_ALL=C
|
||||
subtitles_en_literal,1,3,grep,grep -a Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.5171597003936768,629,LC_ALL=C
|
||||
subtitles_en_literal,1,3,rg (lines),rg -n Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.595801830291748,629,
|
||||
subtitles_en_literal,1,3,rg (lines),rg -n Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.5954360961914062,629,
|
||||
subtitles_en_literal,1,3,rg (lines),rg -n Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.5945618152618408,629,
|
||||
subtitles_en_literal,1,3,ag (lines),ag -s Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.7291676998138428,629,
|
||||
subtitles_en_literal,1,3,ag (lines),ag -s Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.730966329574585,629,
|
||||
subtitles_en_literal,1,3,ag (lines),ag -s Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.728854179382324,629,
|
||||
subtitles_en_literal,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.775996208190918,629,
|
||||
subtitles_en_literal,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.7760195732116699,629,
|
||||
subtitles_en_literal,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.7763564586639404,629,
|
||||
subtitles_en_literal,1,3,pt (lines),pt Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.433366060256958,629,
|
||||
subtitles_en_literal,1,3,pt (lines),pt Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.4338581562042236,629,
|
||||
subtitles_en_literal,1,3,pt (lines),pt Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,3.435924768447876,629,
|
||||
subtitles_en_literal,1,3,sift (lines),sift -n Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.7586827278137207,629,
|
||||
subtitles_en_literal,1,3,sift (lines),sift -n Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.7590317726135254,629,
|
||||
subtitles_en_literal,1,3,sift (lines),sift -n Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.754432201385498,629,
|
||||
subtitles_en_literal,1,3,grep (lines),grep -an Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.9726488590240479,629,LC_ALL=C
|
||||
subtitles_en_literal,1,3,grep (lines),grep -an Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.9699857234954834,629,LC_ALL=C
|
||||
subtitles_en_literal,1,3,grep (lines),grep -an Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.9714112281799316,629,LC_ALL=C
|
||||
subtitles_en_literal_casei,1,3,rg,rg -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.36658453941345215,642,
|
||||
subtitles_en_literal_casei,1,3,rg,rg -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.3654778003692627,642,
|
||||
subtitles_en_literal_casei,1,3,rg,rg -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.36565113067626953,642,
|
||||
subtitles_en_literal_casei,1,3,grep,grep -ai Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.0890116691589355,642,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal_casei,1,3,grep,grep -ai Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.08124852180481,642,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal_casei,1,3,grep,grep -ai Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.0796849727630615,642,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal_casei,1,3,grep (ASCII),grep -E -ai Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6151647567749023,642,LC_ALL=C
|
||||
subtitles_en_literal_casei,1,3,grep (ASCII),grep -E -ai Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6141552925109863,642,LC_ALL=C
|
||||
subtitles_en_literal_casei,1,3,grep (ASCII),grep -E -ai Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6132988929748535,642,LC_ALL=C
|
||||
subtitles_en_literal_casei,1,3,rg (lines),rg -n -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6962459087371826,642,
|
||||
subtitles_en_literal_casei,1,3,rg (lines),rg -n -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6945579051971436,642,
|
||||
subtitles_en_literal_casei,1,3,rg (lines),rg -n -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.693866491317749,642,
|
||||
subtitles_en_literal_casei,1,3,ag (lines) (ASCII),ag -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.771639823913574,642,
|
||||
subtitles_en_literal_casei,1,3,ag (lines) (ASCII),ag -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.772296190261841,642,
|
||||
subtitles_en_literal_casei,1,3,ag (lines) (ASCII),ag -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.7712018489837646,642,
|
||||
subtitles_en_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.8068230152130127,642,
|
||||
subtitles_en_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.80411696434021,642,
|
||||
subtitles_en_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.8038516044616699,642,
|
||||
subtitles_en_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Sherlock Holmes(?-u:\b) /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.5951778888702393,629,
|
||||
subtitles_en_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Sherlock Holmes(?-u:\b) /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.5971941947937012,629,
|
||||
subtitles_en_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Sherlock Holmes(?-u:\b) /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.5952184200286865,629,
|
||||
subtitles_en_literal_word,1,3,ag (ASCII),ag -sw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.7292284965515137,629,
|
||||
subtitles_en_literal_word,1,3,ag (ASCII),ag -sw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.728681802749634,629,
|
||||
subtitles_en_literal_word,1,3,ag (ASCII),ag -sw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.7315311431884766,629,
|
||||
subtitles_en_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.784024715423584,629,
|
||||
subtitles_en_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.780540943145752,629,
|
||||
subtitles_en_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.7772648334503174,629,
|
||||
subtitles_en_literal_word,1,3,grep (ASCII),grep -anw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.9744353294372559,629,LC_ALL=C
|
||||
subtitles_en_literal_word,1,3,grep (ASCII),grep -anw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.9781327247619629,629,LC_ALL=C
|
||||
subtitles_en_literal_word,1,3,grep (ASCII),grep -anw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.9798097610473633,629,LC_ALL=C
|
||||
subtitles_en_literal_word,1,3,rg,rg -nw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.5965189933776855,629,
|
||||
subtitles_en_literal_word,1,3,rg,rg -nw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.5960156917572021,629,
|
||||
subtitles_en_literal_word,1,3,rg,rg -nw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.5975723266601562,629,
|
||||
subtitles_en_literal_word,1,3,grep,grep -anw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.9818971157073975,629,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal_word,1,3,grep,grep -anw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.9785251617431641,629,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal_word,1,3,grep,grep -anw Sherlock Holmes /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.977081298828125,629,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.7750890254974365,13,
|
||||
subtitles_en_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.774017333984375,13,
|
||||
subtitles_en_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.783200740814209,13,
|
||||
subtitles_en_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.5396409034729004,13,
|
||||
subtitles_en_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.5404820442199707,13,
|
||||
subtitles_en_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,2.5400590896606445,13,
|
||||
subtitles_en_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,10.072888612747192,48,
|
||||
subtitles_en_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,10.075153350830078,48,
|
||||
subtitles_en_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,10.0713791847229,48,
|
||||
subtitles_en_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,7.734791278839111,13,
|
||||
subtitles_en_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,7.7345263957977295,13,
|
||||
subtitles_en_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,7.73199462890625,13,
|
||||
subtitles_en_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.411367416381836,13,LC_ALL=C
|
||||
subtitles_en_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.41072940826416,13,LC_ALL=C
|
||||
subtitles_en_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.414730072021484,13,LC_ALL=C
|
||||
subtitles_en_surrounding_words,1,3,rg,rg -n \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6052529811859131,317,
|
||||
subtitles_en_surrounding_words,1,3,rg,rg -n \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6049232482910156,317,
|
||||
subtitles_en_surrounding_words,1,3,rg,rg -n \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6045565605163574,317,
|
||||
subtitles_en_surrounding_words,1,3,grep,grep -E -an \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,1.2849924564361572,317,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_surrounding_words,1,3,grep,grep -E -an \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,1.2837722301483154,317,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_surrounding_words,1,3,grep,grep -E -an \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,1.2847650051116943,317,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_surrounding_words,1,3,rg (ASCII),rg -n (?-u)\w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6005992889404297,317,
|
||||
subtitles_en_surrounding_words,1,3,rg (ASCII),rg -n (?-u)\w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.600543737411499,317,
|
||||
subtitles_en_surrounding_words,1,3,rg (ASCII),rg -n (?-u)\w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,0.6009273529052734,317,
|
||||
subtitles_en_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,11.664374113082886,323,
|
||||
subtitles_en_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,11.674306869506836,323,
|
||||
subtitles_en_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,11.657484769821167,323,
|
||||
subtitles_en_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.650290012359619,317,
|
||||
subtitles_en_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.651855707168579,317,
|
||||
subtitles_en_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,4.649921417236328,317,
|
||||
subtitles_en_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,1.2745037078857422,317,LC_ALL=C
|
||||
subtitles_en_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,1.275665521621704,317,LC_ALL=C
|
||||
subtitles_en_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Holmes\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.sample.en,1.273988962173462,317,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,rg (lines),rg -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.9031155109405518,691,
|
||||
subtitles_ru_alternate,1,3,rg (lines),rg -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.9021081924438477,691,
|
||||
subtitles_ru_alternate,1,3,rg (lines),rg -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.9028818607330322,691,
|
||||
subtitles_ru_alternate,1,3,ag (lines),ag -s Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,5.8895978927612305,691,
|
||||
subtitles_ru_alternate,1,3,ag (lines),ag -s Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,5.891357660293579,691,
|
||||
subtitles_ru_alternate,1,3,ag (lines),ag -s Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,5.892467975616455,691,
|
||||
subtitles_ru_alternate,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.7517757415771484,691,
|
||||
subtitles_ru_alternate,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.7550888061523438,691,
|
||||
subtitles_ru_alternate,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.7555651664733887,691,
|
||||
subtitles_ru_alternate,1,3,grep (lines),grep -E -an Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.51417875289917,691,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,grep (lines),grep -E -an Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.512972116470337,691,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,grep (lines),grep -E -an Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.507266521453857,691,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,rg,rg Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.300950288772583,691,
|
||||
subtitles_ru_alternate,1,3,rg,rg Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.2987852096557617,691,
|
||||
subtitles_ru_alternate,1,3,rg,rg Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.3024635314941406,691,
|
||||
subtitles_ru_alternate,1,3,grep,grep -E -a Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,7.983739137649536,691,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,grep,grep -E -a Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,7.988446950912476,691,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,grep,grep -E -a Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,7.979671478271484,691,LC_ALL=C
|
||||
subtitles_ru_alternate_casei,1,3,ag (ASCII),ag -s -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,5.889382362365723,691,
|
||||
subtitles_ru_alternate_casei,1,3,ag (ASCII),ag -s -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,5.902577877044678,691,
|
||||
subtitles_ru_alternate_casei,1,3,ag (ASCII),ag -s -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,5.890571355819702,691,
|
||||
subtitles_ru_alternate_casei,1,3,ucg (ASCII),ucg -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.7548162937164307,691,
|
||||
subtitles_ru_alternate_casei,1,3,ucg (ASCII),ucg -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.747791051864624,691,
|
||||
subtitles_ru_alternate_casei,1,3,ucg (ASCII),ucg -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.7396674156188965,691,
|
||||
subtitles_ru_alternate_casei,1,3,grep (ASCII),grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.58053183555603,691,LC_ALL=C
|
||||
subtitles_ru_alternate_casei,1,3,grep (ASCII),grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.594751596450806,691,LC_ALL=C
|
||||
subtitles_ru_alternate_casei,1,3,grep (ASCII),grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.561670541763306,691,LC_ALL=C
|
||||
subtitles_ru_alternate_casei,1,3,rg,rg -n -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,4.833597183227539,735,
|
||||
subtitles_ru_alternate_casei,1,3,rg,rg -n -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,4.831137180328369,735,
|
||||
subtitles_ru_alternate_casei,1,3,rg,rg -n -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,4.830361843109131,735,
|
||||
subtitles_ru_alternate_casei,1,3,grep,grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.723876953125,735,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_alternate_casei,1,3,grep,grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.723239183425903,735,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_alternate_casei,1,3,grep,grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.722000360488892,735,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal,1,3,rg,rg Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.3252851963043213,583,
|
||||
subtitles_ru_literal,1,3,rg,rg Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.3270294666290283,583,
|
||||
subtitles_ru_literal,1,3,rg,rg Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.32680773735046387,583,
|
||||
subtitles_ru_literal,1,3,pt,pt -N Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,12.932488679885864,583,
|
||||
subtitles_ru_literal,1,3,pt,pt -N Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,12.91330337524414,583,
|
||||
subtitles_ru_literal,1,3,pt,pt -N Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,12.911381006240845,583,
|
||||
subtitles_ru_literal,1,3,sift,sift Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,16.438587427139282,583,
|
||||
subtitles_ru_literal,1,3,sift,sift Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,16.412389755249023,583,
|
||||
subtitles_ru_literal,1,3,sift,sift Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,16.412444353103638,583,
|
||||
subtitles_ru_literal,1,3,grep,grep -a Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.7865939140319824,583,LC_ALL=C
|
||||
subtitles_ru_literal,1,3,grep,grep -a Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.7889957427978516,583,LC_ALL=C
|
||||
subtitles_ru_literal,1,3,grep,grep -a Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.7923679351806641,583,LC_ALL=C
|
||||
subtitles_ru_literal,1,3,rg (lines),rg -n Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.9255633354187012,583,
|
||||
subtitles_ru_literal,1,3,rg (lines),rg -n Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.9285938739776611,583,
|
||||
subtitles_ru_literal,1,3,rg (lines),rg -n Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.9274048805236816,583,
|
||||
subtitles_ru_literal,1,3,ag (lines),ag -s Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,4.4811036586761475,583,
|
||||
subtitles_ru_literal,1,3,ag (lines),ag -s Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,4.4785168170928955,583,
|
||||
subtitles_ru_literal,1,3,ag (lines),ag -s Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,4.484618186950684,583,
|
||||
subtitles_ru_literal,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.7743628025054932,583,
|
||||
subtitles_ru_literal,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.7731456756591797,583,
|
||||
subtitles_ru_literal,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.7749128341674805,583,
|
||||
subtitles_ru_literal,1,3,pt (lines),pt Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,12.931540250778198,583,
|
||||
subtitles_ru_literal,1,3,pt (lines),pt Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,12.935162782669067,583,
|
||||
subtitles_ru_literal,1,3,pt (lines),pt Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,12.949711561203003,583,
|
||||
subtitles_ru_literal,1,3,sift (lines),sift -n Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,17.183809995651245,583,
|
||||
subtitles_ru_literal,1,3,sift (lines),sift -n Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,17.19060492515564,583,
|
||||
subtitles_ru_literal,1,3,sift (lines),sift -n Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,17.17626404762268,583,
|
||||
subtitles_ru_literal,1,3,grep (lines),grep -an Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.3023960590362549,583,LC_ALL=C
|
||||
subtitles_ru_literal,1,3,grep (lines),grep -an Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.2992589473724365,583,LC_ALL=C
|
||||
subtitles_ru_literal,1,3,grep (lines),grep -an Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.299330711364746,583,LC_ALL=C
|
||||
subtitles_ru_literal_casei,1,3,rg,rg -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.1320264339447021,604,
|
||||
subtitles_ru_literal_casei,1,3,rg,rg -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.1323668956756592,604,
|
||||
subtitles_ru_literal_casei,1,3,rg,rg -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.1354515552520752,604,
|
||||
subtitles_ru_literal_casei,1,3,grep,grep -ai Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.194744348526001,604,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal_casei,1,3,grep,grep -ai Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.190656423568726,604,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal_casei,1,3,grep,grep -ai Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,8.18506383895874,604,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal_casei,1,3,grep (ASCII),grep -E -ai Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.7826528549194336,583,LC_ALL=C
|
||||
subtitles_ru_literal_casei,1,3,grep (ASCII),grep -E -ai Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.7846219539642334,583,LC_ALL=C
|
||||
subtitles_ru_literal_casei,1,3,grep (ASCII),grep -E -ai Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.7846999168395996,583,LC_ALL=C
|
||||
subtitles_ru_literal_casei,1,3,rg (lines),rg -n -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.734788179397583,604,
|
||||
subtitles_ru_literal_casei,1,3,rg (lines),rg -n -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.7333040237426758,604,
|
||||
subtitles_ru_literal_casei,1,3,rg (lines),rg -n -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.731860637664795,604,
|
||||
subtitles_ru_literal_casei,1,3,ag (lines) (ASCII),ag -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.7297205924987793,,
|
||||
subtitles_ru_literal_casei,1,3,ag (lines) (ASCII),ag -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.7314808368682861,,
|
||||
subtitles_ru_literal_casei,1,3,ag (lines) (ASCII),ag -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.7289514541625977,,
|
||||
subtitles_ru_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.7773914337158203,583,
|
||||
subtitles_ru_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.774536371231079,583,
|
||||
subtitles_ru_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.7740859985351562,583,
|
||||
subtitles_ru_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Шерлок Холмс(?-u:\b) /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.3252553939819336,,
|
||||
subtitles_ru_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Шерлок Холмс(?-u:\b) /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.32543301582336426,,
|
||||
subtitles_ru_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Шерлок Холмс(?-u:\b) /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.32512736320495605,,
|
||||
subtitles_ru_literal_word,1,3,ag (ASCII),ag -sw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.7519562244415283,,
|
||||
subtitles_ru_literal_word,1,3,ag (ASCII),ag -sw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.752692699432373,,
|
||||
subtitles_ru_literal_word,1,3,ag (ASCII),ag -sw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.7524521350860596,,
|
||||
subtitles_ru_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.7868170738220215,583,
|
||||
subtitles_ru_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.7786612510681152,583,
|
||||
subtitles_ru_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.776214838027954,583,
|
||||
subtitles_ru_literal_word,1,3,grep (ASCII),grep -anw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.303652286529541,583,LC_ALL=C
|
||||
subtitles_ru_literal_word,1,3,grep (ASCII),grep -anw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.3012151718139648,583,LC_ALL=C
|
||||
subtitles_ru_literal_word,1,3,grep (ASCII),grep -anw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.307457685470581,583,LC_ALL=C
|
||||
subtitles_ru_literal_word,1,3,rg,rg -nw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.9280951023101807,579,
|
||||
subtitles_ru_literal_word,1,3,rg,rg -nw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.9285900592803955,579,
|
||||
subtitles_ru_literal_word,1,3,rg,rg -nw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.9288535118103027,579,
|
||||
subtitles_ru_literal_word,1,3,grep,grep -anw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.3026466369628906,579,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal_word,1,3,grep,grep -anw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.3016819953918457,579,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal_word,1,3,grep,grep -anw Шерлок Холмс /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.3006587028503418,579,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,4.895586729049683,41,
|
||||
subtitles_ru_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,4.914353132247925,41,
|
||||
subtitles_ru_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,4.902980327606201,41,
|
||||
subtitles_ru_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,3.972637891769409,,
|
||||
subtitles_ru_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,3.9723849296569824,,
|
||||
subtitles_ru_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,3.9723057746887207,,
|
||||
subtitles_ru_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.3936586380004883,,
|
||||
subtitles_ru_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.3987748622894287,,
|
||||
subtitles_ru_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.393911361694336,,
|
||||
subtitles_ru_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.8932065963745117,,
|
||||
subtitles_ru_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.8977222442626953,,
|
||||
subtitles_ru_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.893040895462036,,
|
||||
subtitles_ru_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.4844722747802734,,LC_ALL=C
|
||||
subtitles_ru_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.485093116760254,,LC_ALL=C
|
||||
subtitles_ru_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.482459306716919,,LC_ALL=C
|
||||
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.9564735889434814,278,
|
||||
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.9561436176300049,278,
|
||||
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,0.9558789730072021,278,
|
||||
subtitles_ru_surrounding_words,1,3,grep,grep -E -an \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.6620113849639893,278,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_surrounding_words,1,3,grep,grep -E -an \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.6624438762664795,278,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_surrounding_words,1,3,grep,grep -E -an \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.6610260009765625,278,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.4094629287719727,,
|
||||
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.409822702407837,,
|
||||
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.4094691276550293,,
|
||||
subtitles_ru_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.866976261138916,,
|
||||
subtitles_ru_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.8666067123413086,,
|
||||
subtitles_ru_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,2.865297317504883,,
|
||||
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.602109670639038,,LC_ALL=C
|
||||
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.598329782485962,,LC_ALL=C
|
||||
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Холмс\s+\w+ /mnt/bench/subtitles/OpenSubtitles2016.raw.ru,1.5975282192230225,,LC_ALL=C
|
||||
|
226
benchsuite/runs/2016-09-17-ubuntu1604-ec2/summary
Normal file
226
benchsuite/runs/2016-09-17-ubuntu1604-ec2/summary
Normal file
@@ -0,0 +1,226 @@
|
||||
linux_alternates (pattern: ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT)
|
||||
-------------------------------------------------------------------------
|
||||
rg (ignore) 0.322 +/- 0.042 (lines: 68)
|
||||
ag (ignore) 1.754 +/- 0.006 (lines: 68)
|
||||
git grep (ignore) 0.502 +/- 0.004 (lines: 68)
|
||||
rg (whitelist)* 0.197 +/- 0.001 (lines: 68)*
|
||||
ucg (whitelist) 0.217 +/- 0.004 (lines: 68)
|
||||
|
||||
linux_alternates_casei (pattern: ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT)
|
||||
-------------------------------------------------------------------------------
|
||||
rg (ignore) 0.372 +/- 0.096 (lines: 160)
|
||||
ag (ignore) 1.965 +/- 0.009 (lines: 160)
|
||||
git grep (ignore) 2.022 +/- 0.001 (lines: 160)
|
||||
rg (whitelist)* 0.222 +/- 0.001 (lines: 160)*
|
||||
ucg (whitelist) 0.524 +/- 0.005 (lines: 160)
|
||||
|
||||
linux_literal (pattern: PM_RESUME)
|
||||
----------------------------------
|
||||
rg (ignore) 0.348 +/- 0.054 (lines: 16)
|
||||
rg (ignore) (mmap) 1.597 +/- 0.013 (lines: 16)
|
||||
ag (ignore) (mmap) 1.590 +/- 0.003 (lines: 16)
|
||||
pt (ignore) 0.455 +/- 0.015 (lines: 16)
|
||||
sift (ignore) 0.630 +/- 0.001 (lines: 16)
|
||||
git grep (ignore) 0.344 +/- 0.004 (lines: 16)
|
||||
rg (whitelist)* 0.253 +/- 0.077 (lines: 16)
|
||||
ucg (whitelist) 0.222 +/- 0.005 (lines: 16)*
|
||||
|
||||
linux_literal_casei (pattern: PM_RESUME)
|
||||
----------------------------------------
|
||||
rg (ignore) 0.423 +/- 0.118 (lines: 370)
|
||||
rg (ignore) (mmap) 1.615 +/- 0.010 (lines: 370)
|
||||
ag (ignore) (mmap) 1.601 +/- 0.027 (lines: 370)
|
||||
sift (ignore) 0.804 +/- 0.003 (lines: 370)
|
||||
git grep (ignore) 0.346 +/- 0.004 (lines: 370)
|
||||
rg (whitelist) 0.234 +/- 0.032 (lines: 370)
|
||||
ucg (whitelist)* 0.220 +/- 0.008 (lines: 370)*
|
||||
|
||||
linux_literal_default (pattern: PM_RESUME)
|
||||
------------------------------------------
|
||||
rg 0.277 +/- 0.002 (lines: 16)
|
||||
ag 1.594 +/- 0.008 (lines: 16)
|
||||
ucg* 0.219 +/- 0.007 (lines: 16)*
|
||||
pt 0.439 +/- 0.024 (lines: 16)
|
||||
sift 0.344 +/- 0.014 (lines: 16)
|
||||
git grep 0.344 +/- 0.005 (lines: 16)
|
||||
|
||||
linux_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||
-----------------------------------------------------------------
|
||||
rg (ignore) 0.581 +/- 0.002 (lines: 490)
|
||||
rg (ignore) (ASCII) 0.408 +/- 0.008 (lines: 490)
|
||||
ag (ignore) (ASCII) 2.329 +/- 0.019 (lines: 766)
|
||||
sift (ignore) (ASCII) 25.756 +/- 0.113 (lines: 491)
|
||||
git grep (ignore) 26.344 +/- 0.005 (lines: 490)
|
||||
git grep (ignore) (ASCII) 4.152 +/- 0.004 (lines: 490)
|
||||
rg (whitelist) 0.502 +/- 0.008 (lines: 419)
|
||||
rg (whitelist) (ASCII)* 0.346 +/- 0.035 (lines: 419)*
|
||||
ucg (whitelist) (ASCII) 1.127 +/- 0.001 (lines: 416)
|
||||
|
||||
linux_re_literal_suffix (pattern: [A-Z]+_RESUME)
|
||||
------------------------------------------------
|
||||
rg (ignore) 0.398 +/- 0.046 (lines: 1652)
|
||||
ag (ignore) 1.889 +/- 0.011 (lines: 1652)
|
||||
sift (ignore) 10.260 +/- 0.246 (lines: 1652)
|
||||
git grep (ignore) 1.107 +/- 0.001 (lines: 1652)
|
||||
rg (whitelist)* 0.206 +/- 0.001 (lines: 1630)*
|
||||
ucg (whitelist) 0.301 +/- 0.001 (lines: 1630)
|
||||
|
||||
linux_unicode_greek (pattern: \p{Greek})
|
||||
----------------------------------------
|
||||
rg* 0.470 +/- 0.049 (lines: 23)*
|
||||
sift 7.715 +/- 0.125 (lines: 23)
|
||||
|
||||
linux_unicode_greek_casei (pattern: \p{Greek})
|
||||
----------------------------------------------
|
||||
rg 0.415 +/- 0.019 (lines: 103)
|
||||
sift* 0.002 +/- 0.000 (lines: 0)*
|
||||
|
||||
linux_unicode_word (pattern: \wAh)
|
||||
----------------------------------
|
||||
rg (ignore) 0.350 +/- 0.084 (lines: 186)
|
||||
rg (ignore) (ASCII) 0.354 +/- 0.075 (lines: 174)
|
||||
ag (ignore) (ASCII) 1.770 +/- 0.005 (lines: 174)
|
||||
sift (ignore) (ASCII) 11.016 +/- 0.138 (lines: 174)
|
||||
git grep (ignore) 13.051 +/- 0.028 (lines: 186)
|
||||
git grep (ignore) (ASCII) 2.988 +/- 0.001 (lines: 174)
|
||||
rg (whitelist) 0.242 +/- 0.040 (lines: 180)
|
||||
rg (whitelist) (ASCII)* 0.226 +/- 0.012 (lines: 168)
|
||||
ucg (ASCII) 0.225 +/- 0.002 (lines: 168)*
|
||||
|
||||
linux_word (pattern: PM_RESUME)
|
||||
-------------------------------
|
||||
rg (ignore) 0.329 +/- 0.035 (lines: 6)
|
||||
ag (ignore) 1.598 +/- 0.016 (lines: 6)
|
||||
sift (ignore) 7.759 +/- 0.117 (lines: 6)
|
||||
git grep (ignore) 0.344 +/- 0.008 (lines: 6)
|
||||
rg (whitelist)* 0.269 +/- 0.059 (lines: 6)
|
||||
ucg (whitelist) 0.221 +/- 0.002 (lines: 6)*
|
||||
|
||||
subtitles_en_alternate (pattern: Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty)
|
||||
---------------------------------------------------------------------------------------------------------------
|
||||
rg (lines) 0.620 +/- 0.002 (lines: 848)
|
||||
ag (lines) 3.756 +/- 0.000 (lines: 848)
|
||||
ucg (lines) 1.447 +/- 0.003 (lines: 848)
|
||||
grep (lines) 3.411 +/- 0.002 (lines: 848)
|
||||
rg* 0.290 +/- 0.000 (lines: 848)*
|
||||
grep 2.955 +/- 0.001 (lines: 848)
|
||||
|
||||
subtitles_en_alternate_casei (pattern: Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty)
|
||||
---------------------------------------------------------------------------------------------------------------------
|
||||
ag (ASCII) 5.171 +/- 0.003 (lines: 862)
|
||||
ucg (ASCII) 3.417 +/- 0.001 (lines: 862)
|
||||
grep (ASCII) 4.529 +/- 0.003 (lines: 862)
|
||||
rg* 2.722 +/- 0.000 (lines: 862)*
|
||||
grep 5.121 +/- 0.001 (lines: 862)
|
||||
|
||||
subtitles_en_literal (pattern: Sherlock Holmes)
|
||||
-----------------------------------------------
|
||||
rg* 0.268 +/- 0.000 (lines: 629)*
|
||||
pt 3.437 +/- 0.003 (lines: 629)
|
||||
sift 0.327 +/- 0.001 (lines: 629)
|
||||
grep 0.519 +/- 0.002 (lines: 629)
|
||||
rg (lines) 0.595 +/- 0.001 (lines: 629)
|
||||
ag (lines) 2.730 +/- 0.001 (lines: 629)
|
||||
ucg (lines) 0.776 +/- 0.000 (lines: 629)
|
||||
pt (lines) 3.434 +/- 0.001 (lines: 629)
|
||||
sift (lines) 0.757 +/- 0.003 (lines: 629)
|
||||
grep (lines) 0.971 +/- 0.001 (lines: 629)
|
||||
|
||||
subtitles_en_literal_casei (pattern: Sherlock Holmes)
|
||||
-----------------------------------------------------
|
||||
rg* 0.366 +/- 0.001 (lines: 642)*
|
||||
grep 4.083 +/- 0.005 (lines: 642)
|
||||
grep (ASCII) 0.614 +/- 0.001 (lines: 642)
|
||||
rg (lines) 0.695 +/- 0.001 (lines: 642)
|
||||
ag (lines) (ASCII) 2.772 +/- 0.001 (lines: 642)
|
||||
ucg (lines) (ASCII) 0.805 +/- 0.002 (lines: 642)
|
||||
|
||||
subtitles_en_literal_word (pattern: Sherlock Holmes)
|
||||
----------------------------------------------------
|
||||
rg (ASCII)* 0.596 +/- 0.001 (lines: 629)*
|
||||
ag (ASCII) 2.730 +/- 0.002 (lines: 629)
|
||||
ucg (ASCII) 0.781 +/- 0.003 (lines: 629)
|
||||
grep (ASCII) 0.977 +/- 0.003 (lines: 629)
|
||||
rg 0.597 +/- 0.001 (lines: 629)
|
||||
grep 0.979 +/- 0.002 (lines: 629)
|
||||
|
||||
subtitles_en_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||
----------------------------------------------------------------------------------------
|
||||
rg 2.777 +/- 0.005 (lines: 13)
|
||||
rg (ASCII)* 2.540 +/- 0.000 (lines: 13)*
|
||||
ag (ASCII) 10.073 +/- 0.002 (lines: 48)
|
||||
ucg (ASCII) 7.734 +/- 0.002 (lines: 13)
|
||||
grep (ASCII) 4.412 +/- 0.002 (lines: 13)
|
||||
|
||||
subtitles_en_surrounding_words (pattern: \w+\s+Holmes\s+\w+)
|
||||
------------------------------------------------------------
|
||||
rg 0.605 +/- 0.000 (lines: 317)
|
||||
grep 1.285 +/- 0.001 (lines: 317)
|
||||
rg (ASCII)* 0.601 +/- 0.000 (lines: 317)*
|
||||
ag (ASCII) 11.665 +/- 0.008 (lines: 323)
|
||||
ucg (ASCII) 4.651 +/- 0.001 (lines: 317)
|
||||
grep (ASCII) 1.275 +/- 0.001 (lines: 317)
|
||||
|
||||
subtitles_ru_alternate (pattern: Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти)
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
rg (lines) 1.903 +/- 0.001 (lines: 691)
|
||||
ag (lines) 5.891 +/- 0.001 (lines: 691)
|
||||
ucg (lines) 2.754 +/- 0.002 (lines: 691)
|
||||
grep (lines) 8.511 +/- 0.004 (lines: 691)
|
||||
rg* 1.301 +/- 0.002 (lines: 691)*
|
||||
grep 7.984 +/- 0.004 (lines: 691)
|
||||
|
||||
subtitles_ru_alternate_casei (pattern: Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти)
|
||||
-----------------------------------------------------------------------------------------------------------------
|
||||
ag (ASCII) 5.894 +/- 0.007 (lines: 691)
|
||||
ucg (ASCII)* 2.747 +/- 0.008 (lines: 691)*
|
||||
grep (ASCII) 8.579 +/- 0.017 (lines: 691)
|
||||
rg 4.832 +/- 0.002 (lines: 735)
|
||||
grep 8.723 +/- 0.001 (lines: 735)
|
||||
|
||||
subtitles_ru_literal (pattern: Шерлок Холмс)
|
||||
--------------------------------------------
|
||||
rg* 0.326 +/- 0.001 (lines: 583)*
|
||||
pt 12.919 +/- 0.012 (lines: 583)
|
||||
sift 16.421 +/- 0.015 (lines: 583)
|
||||
grep 0.789 +/- 0.003 (lines: 583)
|
||||
rg (lines) 0.927 +/- 0.002 (lines: 583)
|
||||
ag (lines) 4.481 +/- 0.003 (lines: 583)
|
||||
ucg (lines) 1.774 +/- 0.001 (lines: 583)
|
||||
pt (lines) 12.939 +/- 0.010 (lines: 583)
|
||||
sift (lines) 17.184 +/- 0.007 (lines: 583)
|
||||
grep (lines) 1.300 +/- 0.002 (lines: 583)
|
||||
|
||||
subtitles_ru_literal_casei (pattern: Шерлок Холмс)
|
||||
--------------------------------------------------
|
||||
rg 1.133 +/- 0.002 (lines: 604)
|
||||
grep 8.190 +/- 0.005 (lines: 604)
|
||||
grep (ASCII) 0.784 +/- 0.001 (lines: 583)
|
||||
rg (lines) 1.733 +/- 0.001 (lines: 604)
|
||||
ag (lines) (ASCII)* 0.730 +/- 0.001 (lines: 0)*
|
||||
ucg (lines) (ASCII) 1.775 +/- 0.002 (lines: 583)
|
||||
|
||||
subtitles_ru_literal_word (pattern: Шерлок Холмс)
|
||||
-------------------------------------------------
|
||||
rg (ASCII)* 0.325 +/- 0.000 (lines: 0)*
|
||||
ag (ASCII) 0.752 +/- 0.000 (lines: 0)
|
||||
ucg (ASCII) 1.781 +/- 0.006 (lines: 583)
|
||||
grep (ASCII) 1.304 +/- 0.003 (lines: 583)
|
||||
rg 0.929 +/- 0.000 (lines: 579)
|
||||
grep 1.302 +/- 0.001 (lines: 579)
|
||||
|
||||
subtitles_ru_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||
----------------------------------------------------------------------------------------
|
||||
rg 4.904 +/- 0.009 (lines: 41)
|
||||
rg (ASCII) 3.972 +/- 0.000 (lines: 0)
|
||||
ag (ASCII)* 2.395 +/- 0.003 (lines: 0)*
|
||||
ucg (ASCII) 2.895 +/- 0.003 (lines: 0)
|
||||
grep (ASCII) 2.484 +/- 0.001 (lines: 0)
|
||||
|
||||
subtitles_ru_surrounding_words (pattern: \w+\s+Холмс\s+\w+)
|
||||
-----------------------------------------------------------
|
||||
rg* 0.956 +/- 0.000 (lines: 278)*
|
||||
grep 1.662 +/- 0.001 (lines: 278)
|
||||
ag (ASCII) 2.410 +/- 0.000 (lines: 0)
|
||||
ucg (ASCII) 2.866 +/- 0.001 (lines: 0)
|
||||
grep (ASCII) 1.599 +/- 0.002 (lines: 0)
|
||||
93
benchsuite/runs/2016-09-20-ubuntu1604-ec2/README.SETUP
Normal file
93
benchsuite/runs/2016-09-20-ubuntu1604-ec2/README.SETUP
Normal file
@@ -0,0 +1,93 @@
|
||||
Ubuntu 16.04 HVM AMI
|
||||
c3.2xlarge, Xeon E5-2680, 2.8 GHz, 8 CPUs, 16 GB memory, 80 GB SSD
|
||||
|
||||
# Generic system setup
|
||||
|
||||
mkfs.ext4 /dev/xvdb
|
||||
sudo mount /dev/xvdb /mnt
|
||||
sudo chown ubuntu /mnt
|
||||
sudo apt-get update
|
||||
sudo apt-get install \ # for building Linux kernel
|
||||
make gcc bc
|
||||
sudo apt-get install \ # for the silver searcher
|
||||
automake pkg-config zlib1g-dev liblzma-dev libpcre3 libpcre3-dev
|
||||
sudo apt-get install \ # for Universal Code Grep
|
||||
libtool libpcre2-8-0 libpcre2-dev
|
||||
sudo apt-get install \ # for sift and the platinum searcher
|
||||
go
|
||||
|
||||
# Get benchmark corpora
|
||||
|
||||
cd /mnt
|
||||
mkdir /mnt/bench
|
||||
git clone git://github.com/BurntSushi/ripgrep
|
||||
cd ripgrep/benchsuite
|
||||
./benchsuite --dir /mnt/bench/ --download all # takes around 15 minutes
|
||||
|
||||
# Install search tools
|
||||
mkdir /mnt/bin/
|
||||
|
||||
## ripgrep
|
||||
|
||||
cd /mnt
|
||||
mkdir ripgrep-bin
|
||||
cd ripgrep-bin
|
||||
curl -LO 'https://github.com/BurntSushi/ripgrep/releases/download/0.1.2/ripgrep-0.1.2-x86_64-unknown-linux-musl.tar.gz'
|
||||
cp ripgrep-0.1.2-x86_64-unknown-linux-musl/rg /mnt/bin/
|
||||
|
||||
## The Silver Searcher
|
||||
|
||||
cd /mnt
|
||||
git clone git://github.com/ggreer/the_silver_searcher
|
||||
cd the_silver_searcher
|
||||
git checkout cda635
|
||||
./build.sh
|
||||
cp ag /mnt/bin/
|
||||
|
||||
## Universal Code Grep
|
||||
|
||||
cd /mnt
|
||||
git clone git://github.com/gvansickle/ucg
|
||||
cd ucg
|
||||
git checkout 487bfb
|
||||
autoreconf -i
|
||||
./configure
|
||||
make
|
||||
cp ucg /mnt/bin/
|
||||
|
||||
## The Platinum Searcher
|
||||
|
||||
export GOPATH=/mnt/go
|
||||
go get github.com/monochromegane/the_platinum_searcher
|
||||
cd /mnt/go/src/github.com/monochromegane/the_platinum_searcher
|
||||
git checkout 509368
|
||||
go install github.com/monochromegane/the_platinum_searcher/cmd/...
|
||||
cp /mnt/go/bin/pt /mnt/bin/
|
||||
|
||||
## Sift
|
||||
|
||||
export GOPATH=/mnt/go
|
||||
go get github.com/svent/sift
|
||||
cd /mnt/go/src/github.com/svent/sift
|
||||
git checkout 2d175c
|
||||
go install
|
||||
cp /mnt/go/bin/sift /mnt/bin/
|
||||
|
||||
## 'git grep' and GNU grep
|
||||
|
||||
They are part of the standard Ubuntu install, and are pretty recent (as of
|
||||
September 2016).
|
||||
|
||||
$ git --version
|
||||
git version 2.7.4
|
||||
$ grep --version
|
||||
grep (GNU grep) 2.25
|
||||
|
||||
|
||||
# Running benchmarks
|
||||
|
||||
export PATH="/mnt/bin:$PATH"
|
||||
cd /mnt/ripgrep/benchsuite
|
||||
./benchsuite \
|
||||
--dir /mnt/bench/ --raw /mnt/bench/raw.csv --warmup-iter 3 --bench-iter 10
|
||||
# The above took around 120 minutes to run to completion.
|
||||
1611
benchsuite/runs/2016-09-20-ubuntu1604-ec2/raw.csv
Normal file
1611
benchsuite/runs/2016-09-20-ubuntu1604-ec2/raw.csv
Normal file
File diff suppressed because it is too large
Load Diff
235
benchsuite/runs/2016-09-20-ubuntu1604-ec2/summary
Normal file
235
benchsuite/runs/2016-09-20-ubuntu1604-ec2/summary
Normal file
@@ -0,0 +1,235 @@
|
||||
linux_alternates (pattern: ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT)
|
||||
-------------------------------------------------------------------------
|
||||
rg (ignore) 0.351 +/- 0.074 (lines: 68)
|
||||
ag (ignore) 1.747 +/- 0.005 (lines: 68)
|
||||
git grep (ignore) 0.501 +/- 0.003 (lines: 68)
|
||||
rg (whitelist)* 0.216 +/- 0.031 (lines: 68)
|
||||
ucg (whitelist) 0.214 +/- 0.008 (lines: 68)*
|
||||
|
||||
linux_alternates_casei (pattern: ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT)
|
||||
-------------------------------------------------------------------------------
|
||||
rg (ignore) 0.391 +/- 0.078 (lines: 160)
|
||||
ag (ignore) 1.968 +/- 0.009 (lines: 160)
|
||||
git grep (ignore) 2.018 +/- 0.006 (lines: 160)
|
||||
rg (whitelist)* 0.222 +/- 0.001 (lines: 160)*
|
||||
ucg (whitelist) 0.522 +/- 0.002 (lines: 160)
|
||||
|
||||
linux_literal (pattern: PM_RESUME)
|
||||
----------------------------------
|
||||
rg (ignore) 0.334 +/- 0.053 (lines: 16)
|
||||
rg (ignore) (mmap) 1.611 +/- 0.009 (lines: 16)
|
||||
ag (ignore) (mmap) 1.588 +/- 0.011 (lines: 16)
|
||||
pt (ignore) 0.456 +/- 0.025 (lines: 16)
|
||||
sift (ignore) 0.630 +/- 0.004 (lines: 16)
|
||||
git grep (ignore) 0.345 +/- 0.007 (lines: 16)
|
||||
rg (whitelist)* 0.228 +/- 0.042 (lines: 16)
|
||||
ucg (whitelist) 0.218 +/- 0.007 (lines: 16)*
|
||||
|
||||
linux_literal_casei (pattern: PM_RESUME)
|
||||
----------------------------------------
|
||||
rg (ignore) 0.345 +/- 0.073 (lines: 370)
|
||||
rg (ignore) (mmap) 1.612 +/- 0.011 (lines: 370)
|
||||
ag (ignore) (mmap) 1.609 +/- 0.015 (lines: 370)
|
||||
pt (ignore) 17.204 +/- 0.126 (lines: 370)
|
||||
sift (ignore) 0.805 +/- 0.005 (lines: 370)
|
||||
git grep (ignore) 0.343 +/- 0.007 (lines: 370)
|
||||
rg (whitelist)* 0.222 +/- 0.021 (lines: 370)
|
||||
ucg (whitelist) 0.217 +/- 0.006 (lines: 370)*
|
||||
|
||||
linux_literal_default (pattern: PM_RESUME)
|
||||
------------------------------------------
|
||||
rg 0.349 +/- 0.104 (lines: 16)
|
||||
ag 1.589 +/- 0.009 (lines: 16)
|
||||
ucg* 0.218 +/- 0.007 (lines: 16)*
|
||||
pt 0.462 +/- 0.012 (lines: 16)
|
||||
sift 0.352 +/- 0.018 (lines: 16)
|
||||
git grep 0.342 +/- 0.005 (lines: 16)
|
||||
|
||||
linux_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||
-----------------------------------------------------------------
|
||||
rg (ignore) 0.577 +/- 0.003 (lines: 490)
|
||||
rg (ignore) (ASCII) 0.416 +/- 0.025 (lines: 490)
|
||||
ag (ignore) (ASCII) 2.339 +/- 0.010 (lines: 766)
|
||||
pt (ignore) (ASCII) 22.066 +/- 0.057 (lines: 490)
|
||||
sift (ignore) (ASCII) 25.563 +/- 0.108 (lines: 490)
|
||||
git grep (ignore) 26.382 +/- 0.044 (lines: 490)
|
||||
git grep (ignore) (ASCII) 4.153 +/- 0.010 (lines: 490)
|
||||
rg (whitelist) 0.503 +/- 0.011 (lines: 419)
|
||||
rg (whitelist) (ASCII)* 0.343 +/- 0.038 (lines: 419)*
|
||||
ucg (whitelist) (ASCII) 1.130 +/- 0.003 (lines: 416)
|
||||
|
||||
linux_re_literal_suffix (pattern: [A-Z]+_RESUME)
|
||||
------------------------------------------------
|
||||
rg (ignore) 0.318 +/- 0.034 (lines: 1652)
|
||||
ag (ignore) 1.899 +/- 0.008 (lines: 1652)
|
||||
pt (ignore) 13.713 +/- 0.241 (lines: 1652)
|
||||
sift (ignore) 10.172 +/- 0.186 (lines: 1652)
|
||||
git grep (ignore) 1.108 +/- 0.004 (lines: 1652)
|
||||
rg (whitelist)* 0.221 +/- 0.022 (lines: 1630)*
|
||||
ucg (whitelist) 0.301 +/- 0.001 (lines: 1630)
|
||||
|
||||
linux_unicode_greek (pattern: \p{Greek})
|
||||
----------------------------------------
|
||||
rg* 0.414 +/- 0.021 (lines: 23)*
|
||||
pt 12.745 +/- 0.166 (lines: 23)
|
||||
sift 7.767 +/- 0.264 (lines: 23)
|
||||
|
||||
linux_unicode_greek_casei (pattern: \p{Greek})
|
||||
----------------------------------------------
|
||||
rg 0.425 +/- 0.027 (lines: 103)
|
||||
pt 12.612 +/- 0.217 (lines: 23)
|
||||
sift* 0.002 +/- 0.000 (lines: 0)*
|
||||
|
||||
linux_unicode_word (pattern: \wAh)
|
||||
----------------------------------
|
||||
rg (ignore) 0.355 +/- 0.073 (lines: 186)
|
||||
rg (ignore) (ASCII) 0.329 +/- 0.060 (lines: 174)
|
||||
ag (ignore) (ASCII) 1.774 +/- 0.011 (lines: 174)
|
||||
pt (ignore) (ASCII) 14.180 +/- 0.180 (lines: 174)
|
||||
sift (ignore) (ASCII) 11.087 +/- 0.108 (lines: 174)
|
||||
git grep (ignore) 13.045 +/- 0.008 (lines: 186)
|
||||
git grep (ignore) (ASCII) 2.991 +/- 0.004 (lines: 174)
|
||||
rg (whitelist) 0.235 +/- 0.031 (lines: 180)
|
||||
rg (whitelist) (ASCII)* 0.225 +/- 0.023 (lines: 168)*
|
||||
ucg (ASCII) 0.229 +/- 0.007 (lines: 168)
|
||||
|
||||
linux_word (pattern: PM_RESUME)
|
||||
-------------------------------
|
||||
rg (ignore) 0.362 +/- 0.080 (lines: 6)
|
||||
ag (ignore) 1.603 +/- 0.009 (lines: 6)
|
||||
pt (ignore) 14.417 +/- 0.144 (lines: 6)
|
||||
sift (ignore) 7.840 +/- 0.123 (lines: 6)
|
||||
git grep (ignore) 0.341 +/- 0.005 (lines: 6)
|
||||
rg (whitelist)* 0.220 +/- 0.026 (lines: 6)*
|
||||
ucg (whitelist) 0.221 +/- 0.007 (lines: 6)
|
||||
|
||||
subtitles_en_alternate (pattern: Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty)
|
||||
---------------------------------------------------------------------------------------------------------------
|
||||
rg (lines) 0.619 +/- 0.001 (lines: 848)
|
||||
ag (lines) 3.757 +/- 0.001 (lines: 848)
|
||||
ucg (lines) 1.479 +/- 0.002 (lines: 848)
|
||||
grep (lines) 3.412 +/- 0.004 (lines: 848)
|
||||
rg* 0.294 +/- 0.001 (lines: 848)*
|
||||
grep 2.955 +/- 0.003 (lines: 848)
|
||||
|
||||
subtitles_en_alternate_casei (pattern: Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty)
|
||||
---------------------------------------------------------------------------------------------------------------------
|
||||
ag (ASCII) 5.170 +/- 0.004 (lines: 862)
|
||||
ucg (ASCII) 3.453 +/- 0.005 (lines: 862)
|
||||
grep (ASCII) 4.537 +/- 0.025 (lines: 862)
|
||||
rg* 2.724 +/- 0.002 (lines: 862)*
|
||||
grep 5.125 +/- 0.006 (lines: 862)
|
||||
|
||||
subtitles_en_literal (pattern: Sherlock Holmes)
|
||||
-----------------------------------------------
|
||||
rg* 0.268 +/- 0.000 (lines: 629)*
|
||||
rg (no mmap) 0.336 +/- 0.001 (lines: 629)
|
||||
pt 3.433 +/- 0.002 (lines: 629)
|
||||
sift 0.326 +/- 0.002 (lines: 629)
|
||||
grep 0.516 +/- 0.001 (lines: 629)
|
||||
rg (lines) 0.595 +/- 0.001 (lines: 629)
|
||||
ag (lines) 2.730 +/- 0.003 (lines: 629)
|
||||
ucg (lines) 0.745 +/- 0.001 (lines: 629)
|
||||
pt (lines) 3.434 +/- 0.005 (lines: 629)
|
||||
sift (lines) 0.756 +/- 0.002 (lines: 629)
|
||||
grep (lines) 0.969 +/- 0.001 (lines: 629)
|
||||
|
||||
subtitles_en_literal_casei (pattern: Sherlock Holmes)
|
||||
-----------------------------------------------------
|
||||
rg* 0.366 +/- 0.001 (lines: 642)*
|
||||
grep 4.084 +/- 0.005 (lines: 642)
|
||||
grep (ASCII) 0.614 +/- 0.001 (lines: 642)
|
||||
rg (lines) 0.696 +/- 0.002 (lines: 642)
|
||||
ag (lines) (ASCII) 2.775 +/- 0.004 (lines: 642)
|
||||
ucg (lines) (ASCII) 0.841 +/- 0.002 (lines: 642)
|
||||
|
||||
subtitles_en_literal_word (pattern: Sherlock Holmes)
|
||||
----------------------------------------------------
|
||||
rg (ASCII) 0.596 +/- 0.001 (lines: 629)
|
||||
ag (ASCII) 2.729 +/- 0.001 (lines: 629)
|
||||
ucg (ASCII) 0.810 +/- 0.002 (lines: 629)
|
||||
grep (ASCII) 0.970 +/- 0.000 (lines: 629)
|
||||
rg* 0.596 +/- 0.001 (lines: 629)*
|
||||
grep 0.972 +/- 0.003 (lines: 629)
|
||||
|
||||
subtitles_en_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||
----------------------------------------------------------------------------------------
|
||||
rg 2.777 +/- 0.003 (lines: 13)
|
||||
rg (ASCII)* 2.541 +/- 0.005 (lines: 13)*
|
||||
ag (ASCII) 10.076 +/- 0.005 (lines: 48)
|
||||
ucg (ASCII) 7.771 +/- 0.004 (lines: 13)
|
||||
grep (ASCII) 4.411 +/- 0.004 (lines: 13)
|
||||
|
||||
subtitles_en_surrounding_words (pattern: \w+\s+Holmes\s+\w+)
|
||||
------------------------------------------------------------
|
||||
rg 0.605 +/- 0.000 (lines: 317)
|
||||
grep 1.286 +/- 0.002 (lines: 317)
|
||||
rg (ASCII)* 0.602 +/- 0.000 (lines: 317)*
|
||||
ag (ASCII) 11.663 +/- 0.008 (lines: 323)
|
||||
ucg (ASCII) 4.690 +/- 0.002 (lines: 317)
|
||||
grep (ASCII) 1.276 +/- 0.002 (lines: 317)
|
||||
|
||||
subtitles_ru_alternate (pattern: Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти)
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
rg (lines) 1.902 +/- 0.002 (lines: 691)
|
||||
ag (lines) 5.892 +/- 0.003 (lines: 691)
|
||||
ucg (lines) 2.864 +/- 0.006 (lines: 691)
|
||||
grep (lines) 8.511 +/- 0.005 (lines: 691)
|
||||
rg* 1.300 +/- 0.002 (lines: 691)*
|
||||
grep 7.994 +/- 0.017 (lines: 691)
|
||||
|
||||
subtitles_ru_alternate_casei (pattern: Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти)
|
||||
-----------------------------------------------------------------------------------------------------------------
|
||||
ag (ASCII) 5.891 +/- 0.001 (lines: 691)
|
||||
ucg (ASCII)* 2.868 +/- 0.005 (lines: 691)*
|
||||
grep (ASCII) 8.572 +/- 0.009 (lines: 691)
|
||||
rg 4.834 +/- 0.004 (lines: 735)
|
||||
grep 8.729 +/- 0.004 (lines: 735)
|
||||
|
||||
subtitles_ru_literal (pattern: Шерлок Холмс)
|
||||
--------------------------------------------
|
||||
rg* 0.325 +/- 0.001 (lines: 583)*
|
||||
rg (no mmap) 0.452 +/- 0.002 (lines: 583)
|
||||
pt 12.917 +/- 0.009 (lines: 583)
|
||||
sift 16.418 +/- 0.008 (lines: 583)
|
||||
grep 0.780 +/- 0.001 (lines: 583)
|
||||
rg (lines) 0.926 +/- 0.001 (lines: 583)
|
||||
ag (lines) 4.481 +/- 0.003 (lines: 583)
|
||||
ucg (lines) 1.889 +/- 0.004 (lines: 583)
|
||||
pt (lines) 12.935 +/- 0.011 (lines: 583)
|
||||
sift (lines) 17.177 +/- 0.010 (lines: 583)
|
||||
grep (lines) 1.300 +/- 0.003 (lines: 583)
|
||||
|
||||
subtitles_ru_literal_casei (pattern: Шерлок Холмс)
|
||||
--------------------------------------------------
|
||||
rg 1.131 +/- 0.001 (lines: 604)
|
||||
grep 8.187 +/- 0.006 (lines: 604)
|
||||
grep (ASCII) 0.785 +/- 0.001 (lines: 583)
|
||||
rg (lines) 1.733 +/- 0.002 (lines: 604)
|
||||
ag (lines) (ASCII)* 0.729 +/- 0.001 (lines: 0)*
|
||||
ucg (lines) (ASCII) 1.896 +/- 0.005 (lines: 583)
|
||||
|
||||
subtitles_ru_literal_word (pattern: Шерлок Холмс)
|
||||
-------------------------------------------------
|
||||
rg (ASCII)* 0.325 +/- 0.000 (lines: 0)*
|
||||
ag (ASCII) 0.753 +/- 0.001 (lines: 0)
|
||||
ucg (ASCII) 1.891 +/- 0.004 (lines: 583)
|
||||
grep (ASCII) 1.303 +/- 0.004 (lines: 583)
|
||||
rg 0.929 +/- 0.001 (lines: 579)
|
||||
grep 1.304 +/- 0.003 (lines: 579)
|
||||
|
||||
subtitles_ru_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||
----------------------------------------------------------------------------------------
|
||||
rg 4.905 +/- 0.003 (lines: 41)
|
||||
rg (ASCII) 3.973 +/- 0.002 (lines: 0)
|
||||
ag (ASCII)* 2.395 +/- 0.004 (lines: 0)*
|
||||
ucg (ASCII) 3.006 +/- 0.005 (lines: 0)
|
||||
grep (ASCII) 2.483 +/- 0.005 (lines: 0)
|
||||
|
||||
subtitles_ru_surrounding_words (pattern: \w+\s+Холмс\s+\w+)
|
||||
-----------------------------------------------------------
|
||||
rg* 0.957 +/- 0.001 (lines: 278)*
|
||||
grep 1.660 +/- 0.002 (lines: 278)
|
||||
ag (ASCII) 2.411 +/- 0.001 (lines: 0)
|
||||
ucg (ASCII) 2.980 +/- 0.002 (lines: 0)
|
||||
grep (ASCII) 1.596 +/- 0.003 (lines: 0)
|
||||
484
benchsuite/runs/2016-09-22-archlinux-cheetah/raw.csv
Normal file
484
benchsuite/runs/2016-09-22-archlinux-cheetah/raw.csv
Normal file
@@ -0,0 +1,484 @@
|
||||
benchmark,warmup_iter,iter,name,command,duration,lines,env
|
||||
linux_alternates,1,3,rg (ignore),rg -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.16187143325805664,68,
|
||||
linux_alternates,1,3,rg (ignore),rg -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.15862464904785156,68,
|
||||
linux_alternates,1,3,rg (ignore),rg -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.15711188316345215,68,
|
||||
linux_alternates,1,3,ag (ignore),ag -s ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.5682125091552734,68,
|
||||
linux_alternates,1,3,ag (ignore),ag -s ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.551994800567627,68,
|
||||
linux_alternates,1,3,ag (ignore),ag -s ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.5516140460968018,68,
|
||||
linux_alternates,1,3,git grep (ignore),git grep -E -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.24930572509765625,68,LC_ALL=C
|
||||
linux_alternates,1,3,git grep (ignore),git grep -E -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.25743985176086426,68,LC_ALL=C
|
||||
linux_alternates,1,3,git grep (ignore),git grep -E -I -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.24960589408874512,68,LC_ALL=C
|
||||
linux_alternates,1,3,rg (whitelist),rg --no-ignore -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.1039431095123291,68,
|
||||
linux_alternates,1,3,rg (whitelist),rg --no-ignore -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.10251975059509277,68,
|
||||
linux_alternates,1,3,rg (whitelist),rg --no-ignore -n ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.10156440734863281,68,
|
||||
linux_alternates,1,3,ucg (whitelist),ucg --nosmart-case ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.15678954124450684,68,
|
||||
linux_alternates,1,3,ucg (whitelist),ucg --nosmart-case ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.15236687660217285,68,
|
||||
linux_alternates,1,3,ucg (whitelist),ucg --nosmart-case ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.1554563045501709,68,
|
||||
linux_alternates_casei,1,3,rg (ignore),rg -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.16192197799682617,160,
|
||||
linux_alternates_casei,1,3,rg (ignore),rg -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.15988397598266602,160,
|
||||
linux_alternates_casei,1,3,rg (ignore),rg -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.1610555648803711,160,
|
||||
linux_alternates_casei,1,3,ag (ignore),ag -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.6026794910430908,160,
|
||||
linux_alternates_casei,1,3,ag (ignore),ag -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.6083211898803711,160,
|
||||
linux_alternates_casei,1,3,ag (ignore),ag -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.674222469329834,160,
|
||||
linux_alternates_casei,1,3,git grep (ignore),git grep -E -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,1.0635485649108887,160,LC_ALL=C
|
||||
linux_alternates_casei,1,3,git grep (ignore),git grep -E -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.8944556713104248,160,LC_ALL=C
|
||||
linux_alternates_casei,1,3,git grep (ignore),git grep -E -I -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.860692024230957,160,LC_ALL=C
|
||||
linux_alternates_casei,1,3,rg (whitelist),rg --no-ignore -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.11778616905212402,160,
|
||||
linux_alternates_casei,1,3,rg (whitelist),rg --no-ignore -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.11570453643798828,160,
|
||||
linux_alternates_casei,1,3,rg (whitelist),rg --no-ignore -n -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.13424015045166016,160,
|
||||
linux_alternates_casei,1,3,ucg (whitelist),ucg -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.2392585277557373,160,
|
||||
linux_alternates_casei,1,3,ucg (whitelist),ucg -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.2421557903289795,160,
|
||||
linux_alternates_casei,1,3,ucg (whitelist),ucg -i ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,0.2390902042388916,160,
|
||||
linux_literal,1,3,rg (ignore),rg -n PM_RESUME,0.14362168312072754,16,
|
||||
linux_literal,1,3,rg (ignore),rg -n PM_RESUME,0.16165685653686523,16,
|
||||
linux_literal,1,3,rg (ignore),rg -n PM_RESUME,0.1725757122039795,16,
|
||||
linux_literal,1,3,rg (ignore) (mmap),rg -n --mmap PM_RESUME,0.3785591125488281,16,
|
||||
linux_literal,1,3,rg (ignore) (mmap),rg -n --mmap PM_RESUME,0.3961794376373291,16,
|
||||
linux_literal,1,3,rg (ignore) (mmap),rg -n --mmap PM_RESUME,0.3924229145050049,16,
|
||||
linux_literal,1,3,ag (ignore) (mmap),ag -s PM_RESUME,0.5462315082550049,16,
|
||||
linux_literal,1,3,ag (ignore) (mmap),ag -s PM_RESUME,0.7240490913391113,16,
|
||||
linux_literal,1,3,ag (ignore) (mmap),ag -s PM_RESUME,0.4528837203979492,16,
|
||||
linux_literal,1,3,pt (ignore),pt PM_RESUME,0.16354584693908691,16,
|
||||
linux_literal,1,3,pt (ignore),pt PM_RESUME,0.15301966667175293,16,
|
||||
linux_literal,1,3,pt (ignore),pt PM_RESUME,0.15467524528503418,16,
|
||||
linux_literal,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git PM_RESUME,0.33901119232177734,16,
|
||||
linux_literal,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git PM_RESUME,0.34641242027282715,16,
|
||||
linux_literal,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git PM_RESUME,0.3532230854034424,16,
|
||||
linux_literal,1,3,git grep (ignore),git grep -I -n PM_RESUME,0.1970062255859375,16,LC_ALL=C
|
||||
linux_literal,1,3,git grep (ignore),git grep -I -n PM_RESUME,0.21334624290466309,16,LC_ALL=C
|
||||
linux_literal,1,3,git grep (ignore),git grep -I -n PM_RESUME,0.19323515892028809,16,LC_ALL=C
|
||||
linux_literal,1,3,rg (whitelist),rg -n --no-ignore -tall PM_RESUME,0.10708379745483398,16,
|
||||
linux_literal,1,3,rg (whitelist),rg -n --no-ignore -tall PM_RESUME,0.10089921951293945,16,
|
||||
linux_literal,1,3,rg (whitelist),rg -n --no-ignore -tall PM_RESUME,0.10404157638549805,16,
|
||||
linux_literal,1,3,ucg (whitelist),ucg --nosmart-case PM_RESUME,0.17191362380981445,16,
|
||||
linux_literal,1,3,ucg (whitelist),ucg --nosmart-case PM_RESUME,0.1670548915863037,16,
|
||||
linux_literal,1,3,ucg (whitelist),ucg --nosmart-case PM_RESUME,0.16875147819519043,16,
|
||||
linux_literal_casei,1,3,rg (ignore),rg -n -i PM_RESUME,0.16549420356750488,370,
|
||||
linux_literal_casei,1,3,rg (ignore),rg -n -i PM_RESUME,0.14867877960205078,370,
|
||||
linux_literal_casei,1,3,rg (ignore),rg -n -i PM_RESUME,0.1509239673614502,370,
|
||||
linux_literal_casei,1,3,rg (ignore) (mmap),rg -n -i --mmap PM_RESUME,0.38896727561950684,370,
|
||||
linux_literal_casei,1,3,rg (ignore) (mmap),rg -n -i --mmap PM_RESUME,0.39926719665527344,370,
|
||||
linux_literal_casei,1,3,rg (ignore) (mmap),rg -n -i --mmap PM_RESUME,0.3912012577056885,370,
|
||||
linux_literal_casei,1,3,ag (ignore) (mmap),ag -i PM_RESUME,0.48294734954833984,370,
|
||||
linux_literal_casei,1,3,ag (ignore) (mmap),ag -i PM_RESUME,0.4827127456665039,370,
|
||||
linux_literal_casei,1,3,ag (ignore) (mmap),ag -i PM_RESUME,0.4216601848602295,370,
|
||||
linux_literal_casei,1,3,pt (ignore),pt -i PM_RESUME,11.884198665618896,370,
|
||||
linux_literal_casei,1,3,pt (ignore),pt -i PM_RESUME,12.05774974822998,370,
|
||||
linux_literal_casei,1,3,pt (ignore),pt -i PM_RESUME,11.864668130874634,370,
|
||||
linux_literal_casei,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git PM_RESUME,0.3698842525482178,370,
|
||||
linux_literal_casei,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git PM_RESUME,0.3745250701904297,370,
|
||||
linux_literal_casei,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git PM_RESUME,0.3670051097869873,370,
|
||||
linux_literal_casei,1,3,git grep (ignore),git grep -I -n -i PM_RESUME,0.18896126747131348,370,LC_ALL=C
|
||||
linux_literal_casei,1,3,git grep (ignore),git grep -I -n -i PM_RESUME,0.19655537605285645,370,LC_ALL=C
|
||||
linux_literal_casei,1,3,git grep (ignore),git grep -I -n -i PM_RESUME,0.183945894241333,370,LC_ALL=C
|
||||
linux_literal_casei,1,3,rg (whitelist),rg -n -i --no-ignore -tall PM_RESUME,0.11336517333984375,370,
|
||||
linux_literal_casei,1,3,rg (whitelist),rg -n -i --no-ignore -tall PM_RESUME,0.10497522354125977,370,
|
||||
linux_literal_casei,1,3,rg (whitelist),rg -n -i --no-ignore -tall PM_RESUME,0.10771870613098145,370,
|
||||
linux_literal_casei,1,3,ucg (whitelist),ucg -i PM_RESUME,0.1626441478729248,370,
|
||||
linux_literal_casei,1,3,ucg (whitelist),ucg -i PM_RESUME,0.16270971298217773,370,
|
||||
linux_literal_casei,1,3,ucg (whitelist),ucg -i PM_RESUME,0.1596980094909668,370,
|
||||
linux_literal_default,1,3,rg,rg PM_RESUME,0.14002084732055664,16,
|
||||
linux_literal_default,1,3,rg,rg PM_RESUME,0.13782882690429688,16,
|
||||
linux_literal_default,1,3,rg,rg PM_RESUME,0.13794231414794922,16,
|
||||
linux_literal_default,1,3,ag,ag PM_RESUME,0.4623262882232666,16,
|
||||
linux_literal_default,1,3,ag,ag PM_RESUME,0.419907808303833,16,
|
||||
linux_literal_default,1,3,ag,ag PM_RESUME,0.44193005561828613,16,
|
||||
linux_literal_default,1,3,ucg,ucg PM_RESUME,0.1634502410888672,16,
|
||||
linux_literal_default,1,3,ucg,ucg PM_RESUME,0.17029428482055664,16,
|
||||
linux_literal_default,1,3,ucg,ucg PM_RESUME,0.16076922416687012,16,
|
||||
linux_literal_default,1,3,pt,pt PM_RESUME,0.15809178352355957,16,
|
||||
linux_literal_default,1,3,pt,pt PM_RESUME,0.1554114818572998,16,
|
||||
linux_literal_default,1,3,pt,pt PM_RESUME,0.15415430068969727,16,
|
||||
linux_literal_default,1,3,sift,sift PM_RESUME,0.1160738468170166,16,
|
||||
linux_literal_default,1,3,sift,sift PM_RESUME,0.11754369735717773,16,
|
||||
linux_literal_default,1,3,sift,sift PM_RESUME,0.11825299263000488,16,
|
||||
linux_literal_default,1,3,git grep,git grep PM_RESUME,0.18619132041931152,16,LC_ALL=en_US.UTF-8
|
||||
linux_literal_default,1,3,git grep,git grep PM_RESUME,0.16708111763000488,16,LC_ALL=en_US.UTF-8
|
||||
linux_literal_default,1,3,git grep,git grep PM_RESUME,0.20236515998840332,16,LC_ALL=en_US.UTF-8
|
||||
linux_no_literal,1,3,rg (ignore),rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.34000611305236816,490,
|
||||
linux_no_literal,1,3,rg (ignore),rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.36969494819641113,490,
|
||||
linux_no_literal,1,3,rg (ignore),rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.3622872829437256,490,
|
||||
linux_no_literal,1,3,rg (ignore) (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.2572669982910156,490,
|
||||
linux_no_literal,1,3,rg (ignore) (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.3148176670074463,490,
|
||||
linux_no_literal,1,3,rg (ignore) (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.3092081546783447,490,
|
||||
linux_no_literal,1,3,ag (ignore) (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.7651598453521729,766,
|
||||
linux_no_literal,1,3,ag (ignore) (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.7487096786499023,766,
|
||||
linux_no_literal,1,3,ag (ignore) (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.7506840229034424,766,
|
||||
linux_no_literal,1,3,pt (ignore) (ASCII),pt -e \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},12.426137685775757,490,
|
||||
linux_no_literal,1,3,pt (ignore) (ASCII),pt -e \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},12.454124450683594,490,
|
||||
linux_no_literal,1,3,pt (ignore) (ASCII),pt -e \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},12.573740243911743,490,
|
||||
linux_no_literal,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},8.734328746795654,490,
|
||||
linux_no_literal,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},8.95349931716919,490,
|
||||
linux_no_literal,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},8.97816801071167,490,
|
||||
linux_no_literal,1,3,git grep (ignore),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},8.792009830474854,490,LC_ALL=en_US.UTF-8
|
||||
linux_no_literal,1,3,git grep (ignore),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},8.744960308074951,490,LC_ALL=en_US.UTF-8
|
||||
linux_no_literal,1,3,git grep (ignore),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},9.04098892211914,490,LC_ALL=en_US.UTF-8
|
||||
linux_no_literal,1,3,git grep (ignore) (ASCII),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},1.8596012592315674,490,LC_ALL=C
|
||||
linux_no_literal,1,3,git grep (ignore) (ASCII),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},1.824751853942871,490,LC_ALL=C
|
||||
linux_no_literal,1,3,git grep (ignore) (ASCII),git grep -E -I -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},1.9543235301971436,490,LC_ALL=C
|
||||
linux_no_literal,1,3,rg (whitelist),rg -n --no-ignore -tall \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.31849026679992676,419,
|
||||
linux_no_literal,1,3,rg (whitelist),rg -n --no-ignore -tall \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.3028290271759033,419,
|
||||
linux_no_literal,1,3,rg (whitelist),rg -n --no-ignore -tall \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.31795716285705566,419,
|
||||
linux_no_literal,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.22214102745056152,419,
|
||||
linux_no_literal,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.2748534679412842,419,
|
||||
linux_no_literal,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.2098088264465332,419,
|
||||
linux_no_literal,1,3,ucg (whitelist) (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.4494349956512451,416,
|
||||
linux_no_literal,1,3,ucg (whitelist) (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.44551706314086914,416,
|
||||
linux_no_literal,1,3,ucg (whitelist) (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5},0.4425935745239258,416,
|
||||
linux_re_literal_suffix,1,3,rg (ignore),rg -n [A-Z]+_RESUME,0.19018173217773438,1652,
|
||||
linux_re_literal_suffix,1,3,rg (ignore),rg -n [A-Z]+_RESUME,0.1422746181488037,1652,
|
||||
linux_re_literal_suffix,1,3,rg (ignore),rg -n [A-Z]+_RESUME,0.14041566848754883,1652,
|
||||
linux_re_literal_suffix,1,3,ag (ignore),ag -s [A-Z]+_RESUME,0.5190994739532471,1652,
|
||||
linux_re_literal_suffix,1,3,ag (ignore),ag -s [A-Z]+_RESUME,0.5290501117706299,1652,
|
||||
linux_re_literal_suffix,1,3,ag (ignore),ag -s [A-Z]+_RESUME,0.5360753536224365,1652,
|
||||
linux_re_literal_suffix,1,3,pt (ignore),pt -e [A-Z]+_RESUME,11.900719404220581,1652,
|
||||
linux_re_literal_suffix,1,3,pt (ignore),pt -e [A-Z]+_RESUME,11.905181646347046,1652,
|
||||
linux_re_literal_suffix,1,3,pt (ignore),pt -e [A-Z]+_RESUME,11.878832817077637,1652,
|
||||
linux_re_literal_suffix,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git [A-Z]+_RESUME,3.9338936805725098,1652,
|
||||
linux_re_literal_suffix,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git [A-Z]+_RESUME,3.971776247024536,1652,
|
||||
linux_re_literal_suffix,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git [A-Z]+_RESUME,4.010965585708618,1652,
|
||||
linux_re_literal_suffix,1,3,git grep (ignore),git grep -E -I -n [A-Z]+_RESUME,0.553400993347168,1652,LC_ALL=C
|
||||
linux_re_literal_suffix,1,3,git grep (ignore),git grep -E -I -n [A-Z]+_RESUME,0.5757920742034912,1652,LC_ALL=C
|
||||
linux_re_literal_suffix,1,3,git grep (ignore),git grep -E -I -n [A-Z]+_RESUME,0.6472165584564209,1652,LC_ALL=C
|
||||
linux_re_literal_suffix,1,3,rg (whitelist),rg -n --no-ignore -tall [A-Z]+_RESUME,0.10109567642211914,1630,
|
||||
linux_re_literal_suffix,1,3,rg (whitelist),rg -n --no-ignore -tall [A-Z]+_RESUME,0.1010751724243164,1630,
|
||||
linux_re_literal_suffix,1,3,rg (whitelist),rg -n --no-ignore -tall [A-Z]+_RESUME,0.10259413719177246,1630,
|
||||
linux_re_literal_suffix,1,3,ucg (whitelist),ucg --nosmart-case [A-Z]+_RESUME,0.13558602333068848,1630,
|
||||
linux_re_literal_suffix,1,3,ucg (whitelist),ucg --nosmart-case [A-Z]+_RESUME,0.13765740394592285,1630,
|
||||
linux_re_literal_suffix,1,3,ucg (whitelist),ucg --nosmart-case [A-Z]+_RESUME,0.14018011093139648,1630,
|
||||
linux_unicode_greek,1,3,rg,rg -n \p{Greek},0.2655603885650635,23,
|
||||
linux_unicode_greek,1,3,rg,rg -n \p{Greek},0.2579922676086426,23,
|
||||
linux_unicode_greek,1,3,rg,rg -n \p{Greek},0.2389824390411377,23,
|
||||
linux_unicode_greek,1,3,pt,pt -e \p{Greek},11.789764881134033,23,
|
||||
linux_unicode_greek,1,3,pt,pt -e \p{Greek},11.717109680175781,23,
|
||||
linux_unicode_greek,1,3,pt,pt -e \p{Greek},11.714937448501587,23,
|
||||
linux_unicode_greek,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \p{Greek},2.732529401779175,23,
|
||||
linux_unicode_greek,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \p{Greek},2.782707452774048,23,
|
||||
linux_unicode_greek,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \p{Greek},2.8073642253875732,23,
|
||||
linux_unicode_greek_casei,1,3,rg,rg -n -i \p{Greek},0.29653406143188477,103,
|
||||
linux_unicode_greek_casei,1,3,rg,rg -n -i \p{Greek},0.24062681198120117,103,
|
||||
linux_unicode_greek_casei,1,3,rg,rg -n -i \p{Greek},0.24714946746826172,103,
|
||||
linux_unicode_greek_casei,1,3,pt,pt -i -e \p{Greek},11.754312515258789,23,
|
||||
linux_unicode_greek_casei,1,3,pt,pt -i -e \p{Greek},11.742995500564575,23,
|
||||
linux_unicode_greek_casei,1,3,pt,pt -i -e \p{Greek},11.763852596282959,23,
|
||||
linux_unicode_greek_casei,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git \p{Greek},0.0018308162689208984,,
|
||||
linux_unicode_greek_casei,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git \p{Greek},0.0017769336700439453,,
|
||||
linux_unicode_greek_casei,1,3,sift,sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -i --git \p{Greek},0.0018434524536132812,,
|
||||
linux_unicode_word,1,3,rg (ignore),rg -n \wAh,0.19392776489257812,186,
|
||||
linux_unicode_word,1,3,rg (ignore),rg -n \wAh,0.14665579795837402,186,
|
||||
linux_unicode_word,1,3,rg (ignore),rg -n \wAh,0.15625548362731934,186,
|
||||
linux_unicode_word,1,3,rg (ignore) (ASCII),rg -n (?-u)\wAh,0.14500045776367188,174,
|
||||
linux_unicode_word,1,3,rg (ignore) (ASCII),rg -n (?-u)\wAh,0.15199685096740723,174,
|
||||
linux_unicode_word,1,3,rg (ignore) (ASCII),rg -n (?-u)\wAh,0.14606475830078125,174,
|
||||
linux_unicode_word,1,3,ag (ignore) (ASCII),ag -s \wAh,0.47933006286621094,174,
|
||||
linux_unicode_word,1,3,ag (ignore) (ASCII),ag -s \wAh,0.4965095520019531,174,
|
||||
linux_unicode_word,1,3,ag (ignore) (ASCII),ag -s \wAh,0.4971892833709717,174,
|
||||
linux_unicode_word,1,3,pt (ignore) (ASCII),pt -e \wAh,12.043948650360107,174,
|
||||
linux_unicode_word,1,3,pt (ignore) (ASCII),pt -e \wAh,12.024799823760986,174,
|
||||
linux_unicode_word,1,3,pt (ignore) (ASCII),pt -e \wAh,12.032919883728027,174,
|
||||
linux_unicode_word,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \wAh,4.146677255630493,174,
|
||||
linux_unicode_word,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \wAh,4.05977988243103,174,
|
||||
linux_unicode_word,1,3,sift (ignore) (ASCII),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n --git \wAh,4.246629238128662,174,
|
||||
linux_unicode_word,1,3,git grep (ignore),git grep -E -I -n \wAh,4.401025772094727,186,LC_ALL=en_US.UTF-8
|
||||
linux_unicode_word,1,3,git grep (ignore),git grep -E -I -n \wAh,4.499484062194824,186,LC_ALL=en_US.UTF-8
|
||||
linux_unicode_word,1,3,git grep (ignore),git grep -E -I -n \wAh,4.530885934829712,186,LC_ALL=en_US.UTF-8
|
||||
linux_unicode_word,1,3,git grep (ignore) (ASCII),git grep -E -I -n \wAh,1.552142858505249,174,LC_ALL=C
|
||||
linux_unicode_word,1,3,git grep (ignore) (ASCII),git grep -E -I -n \wAh,1.4553732872009277,174,LC_ALL=C
|
||||
linux_unicode_word,1,3,git grep (ignore) (ASCII),git grep -E -I -n \wAh,1.3407535552978516,174,LC_ALL=C
|
||||
linux_unicode_word,1,3,rg (whitelist),rg -n --no-ignore -tall \wAh,0.10183477401733398,180,
|
||||
linux_unicode_word,1,3,rg (whitelist),rg -n --no-ignore -tall \wAh,0.10535907745361328,180,
|
||||
linux_unicode_word,1,3,rg (whitelist),rg -n --no-ignore -tall \wAh,0.10527634620666504,180,
|
||||
linux_unicode_word,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\wAh,0.10268187522888184,168,
|
||||
linux_unicode_word,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\wAh,0.10159945487976074,168,
|
||||
linux_unicode_word,1,3,rg (whitelist) (ASCII),rg -n --no-ignore -tall (?-u)\wAh,0.09854245185852051,168,
|
||||
linux_unicode_word,1,3,ucg (ASCII),ucg --nosmart-case \wAh,0.17232871055603027,168,
|
||||
linux_unicode_word,1,3,ucg (ASCII),ucg --nosmart-case \wAh,0.17245817184448242,168,
|
||||
linux_unicode_word,1,3,ucg (ASCII),ucg --nosmart-case \wAh,0.1736738681793213,168,
|
||||
linux_word,1,3,rg (ignore),rg -n -w PM_RESUME,0.18302679061889648,6,
|
||||
linux_word,1,3,rg (ignore),rg -n -w PM_RESUME,0.14654874801635742,6,
|
||||
linux_word,1,3,rg (ignore),rg -n -w PM_RESUME,0.14666008949279785,6,
|
||||
linux_word,1,3,ag (ignore),ag -s -w PM_RESUME,0.43340468406677246,6,
|
||||
linux_word,1,3,ag (ignore),ag -s -w PM_RESUME,0.4431777000427246,6,
|
||||
linux_word,1,3,ag (ignore),ag -s -w PM_RESUME,0.4939415454864502,6,
|
||||
linux_word,1,3,pt (ignore),pt -w PM_RESUME,12.12212347984314,6,
|
||||
linux_word,1,3,pt (ignore),pt -w PM_RESUME,12.106850862503052,6,
|
||||
linux_word,1,3,pt (ignore),pt -w PM_RESUME,12.119231224060059,6,
|
||||
linux_word,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -w --git PM_RESUME,3.3320345878601074,6,
|
||||
linux_word,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -w --git PM_RESUME,3.2089524269104004,6,
|
||||
linux_word,1,3,sift (ignore),sift --binary-skip --exclude-files .* --exclude-files *.pdf -n -w --git PM_RESUME,3.34165096282959,6,
|
||||
linux_word,1,3,git grep (ignore),git grep -E -I -n -w PM_RESUME,0.20112895965576172,6,LC_ALL=C
|
||||
linux_word,1,3,git grep (ignore),git grep -E -I -n -w PM_RESUME,0.16607999801635742,6,LC_ALL=C
|
||||
linux_word,1,3,git grep (ignore),git grep -E -I -n -w PM_RESUME,0.2049086093902588,6,LC_ALL=C
|
||||
linux_word,1,3,rg (whitelist),rg -n -w --no-ignore -tall PM_RESUME,0.10440325736999512,6,
|
||||
linux_word,1,3,rg (whitelist),rg -n -w --no-ignore -tall PM_RESUME,0.09853243827819824,6,
|
||||
linux_word,1,3,rg (whitelist),rg -n -w --no-ignore -tall PM_RESUME,0.09580087661743164,6,
|
||||
linux_word,1,3,ucg (whitelist),ucg --nosmart-case -w PM_RESUME,0.16874432563781738,6,
|
||||
linux_word,1,3,ucg (whitelist),ucg --nosmart-case -w PM_RESUME,0.16241073608398438,6,
|
||||
linux_word,1,3,ucg (whitelist),ucg --nosmart-case -w PM_RESUME,0.1621534824371338,6,
|
||||
subtitles_en_alternate,1,3,rg (lines),rg -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5109667778015137,848,
|
||||
subtitles_en_alternate,1,3,rg (lines),rg -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.662921667098999,848,
|
||||
subtitles_en_alternate,1,3,rg (lines),rg -n Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.627272367477417,848,
|
||||
subtitles_en_alternate,1,3,ag (lines),ag -s Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.4915170669555664,848,
|
||||
subtitles_en_alternate,1,3,ag (lines),ag -s Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.294611930847168,848,
|
||||
subtitles_en_alternate,1,3,ag (lines),ag -s Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.475243330001831,848,
|
||||
subtitles_en_alternate,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.450028419494629,848,
|
||||
subtitles_en_alternate,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.266982078552246,848,
|
||||
subtitles_en_alternate,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.429199457168579,848,
|
||||
subtitles_en_alternate,1,3,grep (lines),grep -E -an Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.81933856010437,848,LC_ALL=C
|
||||
subtitles_en_alternate,1,3,grep (lines),grep -E -an Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.829207420349121,848,LC_ALL=C
|
||||
subtitles_en_alternate,1,3,grep (lines),grep -E -an Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.795469284057617,848,LC_ALL=C
|
||||
subtitles_en_alternate,1,3,rg,rg Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.3135373592376709,848,
|
||||
subtitles_en_alternate,1,3,rg,rg Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.47072267532348633,848,
|
||||
subtitles_en_alternate,1,3,rg,rg Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.3777759075164795,848,
|
||||
subtitles_en_alternate,1,3,grep,grep -E -a Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.2277214527130127,848,LC_ALL=C
|
||||
subtitles_en_alternate,1,3,grep,grep -E -a Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.4641876220703125,848,LC_ALL=C
|
||||
subtitles_en_alternate,1,3,grep,grep -E -a Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.4201643466949463,848,LC_ALL=C
|
||||
subtitles_en_alternate_casei,1,3,ag (ASCII),ag -s -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.925377368927002,862,
|
||||
subtitles_en_alternate_casei,1,3,ag (ASCII),ag -s -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.7743782997131348,862,
|
||||
subtitles_en_alternate_casei,1,3,ag (ASCII),ag -s -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.9031031131744385,862,
|
||||
subtitles_en_alternate_casei,1,3,ucg (ASCII),ucg -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.902085542678833,862,
|
||||
subtitles_en_alternate_casei,1,3,ucg (ASCII),ucg -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.9104111194610596,862,
|
||||
subtitles_en_alternate_casei,1,3,ucg (ASCII),ucg -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.9407846927642822,862,
|
||||
subtitles_en_alternate_casei,1,3,grep (ASCII),grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.6935694217681885,862,LC_ALL=C
|
||||
subtitles_en_alternate_casei,1,3,grep (ASCII),grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.7745134830474854,862,LC_ALL=C
|
||||
subtitles_en_alternate_casei,1,3,grep (ASCII),grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.5532472133636475,862,LC_ALL=C
|
||||
subtitles_en_alternate_casei,1,3,rg,rg -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.3000056743621826,862,
|
||||
subtitles_en_alternate_casei,1,3,rg,rg -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.46085786819458,862,
|
||||
subtitles_en_alternate_casei,1,3,rg,rg -n -i Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.2774720191955566,862,
|
||||
subtitles_en_alternate_casei,1,3,grep,grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,4.183770418167114,862,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_alternate_casei,1,3,grep,grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,4.210017442703247,862,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_alternate_casei,1,3,grep,grep -E -ani Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,4.052750587463379,862,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal,1,3,rg,rg Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.2422008514404297,629,
|
||||
subtitles_en_literal,1,3,rg,rg Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.18678736686706543,629,
|
||||
subtitles_en_literal,1,3,rg,rg Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.20187044143676758,629,
|
||||
subtitles_en_literal,1,3,rg (no mmap),rg --no-mmap Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.21189618110656738,629,
|
||||
subtitles_en_literal,1,3,rg (no mmap),rg --no-mmap Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.21378588676452637,629,
|
||||
subtitles_en_literal,1,3,rg (no mmap),rg --no-mmap Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.2638204097747803,629,
|
||||
subtitles_en_literal,1,3,pt,pt -N Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.6680185794830322,629,
|
||||
subtitles_en_literal,1,3,pt,pt -N Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.7018795013427734,629,
|
||||
subtitles_en_literal,1,3,pt,pt -N Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.7135086059570312,629,
|
||||
subtitles_en_literal,1,3,sift,sift Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.33170270919799805,629,
|
||||
subtitles_en_literal,1,3,sift,sift Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.3204488754272461,629,
|
||||
subtitles_en_literal,1,3,sift,sift Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.3302597999572754,629,
|
||||
subtitles_en_literal,1,3,grep,grep -a Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5119576454162598,629,LC_ALL=C
|
||||
subtitles_en_literal,1,3,grep,grep -a Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.48076844215393066,629,LC_ALL=C
|
||||
subtitles_en_literal,1,3,grep,grep -a Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.6118988990783691,629,LC_ALL=C
|
||||
subtitles_en_literal,1,3,rg (lines),rg -n Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.6149063110351562,629,
|
||||
subtitles_en_literal,1,3,rg (lines),rg -n Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.547245979309082,629,
|
||||
subtitles_en_literal,1,3,rg (lines),rg -n Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5441193580627441,629,
|
||||
subtitles_en_literal,1,3,ag (lines),ag -s Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.990401268005371,629,
|
||||
subtitles_en_literal,1,3,ag (lines),ag -s Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.9924650192260742,629,
|
||||
subtitles_en_literal,1,3,ag (lines),ag -s Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.807952642440796,629,
|
||||
subtitles_en_literal,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.772294282913208,629,
|
||||
subtitles_en_literal,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.8758792877197266,629,
|
||||
subtitles_en_literal,1,3,ucg (lines),ucg --nosmart-case Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.8043625354766846,629,
|
||||
subtitles_en_literal,1,3,pt (lines),pt Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.5409111976623535,629,
|
||||
subtitles_en_literal,1,3,pt (lines),pt Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.535851001739502,629,
|
||||
subtitles_en_literal,1,3,pt (lines),pt Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.6951041221618652,629,
|
||||
subtitles_en_literal,1,3,sift (lines),sift -n Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.7138817310333252,629,
|
||||
subtitles_en_literal,1,3,sift (lines),sift -n Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.727524995803833,629,
|
||||
subtitles_en_literal,1,3,sift (lines),sift -n Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.7202486991882324,629,
|
||||
subtitles_en_literal,1,3,grep (lines),grep -an Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.0228428840637207,629,LC_ALL=C
|
||||
subtitles_en_literal,1,3,grep (lines),grep -an Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.9452800750732422,629,LC_ALL=C
|
||||
subtitles_en_literal,1,3,grep (lines),grep -an Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.0286569595336914,629,LC_ALL=C
|
||||
subtitles_en_literal_casei,1,3,rg,rg -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.36867737770080566,642,
|
||||
subtitles_en_literal_casei,1,3,rg,rg -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.391110897064209,642,
|
||||
subtitles_en_literal_casei,1,3,rg,rg -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.42432594299316406,642,
|
||||
subtitles_en_literal_casei,1,3,grep,grep -ai Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.2124955654144287,642,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal_casei,1,3,grep,grep -ai Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.288994789123535,642,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal_casei,1,3,grep,grep -ai Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.2279436588287354,642,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal_casei,1,3,grep (ASCII),grep -E -ai Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5713660717010498,642,LC_ALL=C
|
||||
subtitles_en_literal_casei,1,3,grep (ASCII),grep -E -ai Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5478014945983887,642,LC_ALL=C
|
||||
subtitles_en_literal_casei,1,3,grep (ASCII),grep -E -ai Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5502541065216064,642,LC_ALL=C
|
||||
subtitles_en_literal_casei,1,3,rg (lines),rg -n -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5866222381591797,642,
|
||||
subtitles_en_literal_casei,1,3,rg (lines),rg -n -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5829083919525146,642,
|
||||
subtitles_en_literal_casei,1,3,rg (lines),rg -n -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.7458250522613525,642,
|
||||
subtitles_en_literal_casei,1,3,ag (lines) (ASCII),ag -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.8324756622314453,642,
|
||||
subtitles_en_literal_casei,1,3,ag (lines) (ASCII),ag -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.8361680507659912,642,
|
||||
subtitles_en_literal_casei,1,3,ag (lines) (ASCII),ag -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.017822265625,642,
|
||||
subtitles_en_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.9228289127349854,642,
|
||||
subtitles_en_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.9356215000152588,642,
|
||||
subtitles_en_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.9355306625366211,642,
|
||||
subtitles_en_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Sherlock Holmes(?-u:\b) /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.46090102195739746,629,
|
||||
subtitles_en_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Sherlock Holmes(?-u:\b) /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.41375064849853516,629,
|
||||
subtitles_en_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Sherlock Holmes(?-u:\b) /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.4181649684906006,629,
|
||||
subtitles_en_literal_word,1,3,ag (ASCII),ag -sw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.8445067405700684,629,
|
||||
subtitles_en_literal_word,1,3,ag (ASCII),ag -sw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.805356740951538,629,
|
||||
subtitles_en_literal_word,1,3,ag (ASCII),ag -sw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.8274097442626953,629,
|
||||
subtitles_en_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.9036450386047363,629,
|
||||
subtitles_en_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.7601873874664307,629,
|
||||
subtitles_en_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.7341368198394775,629,
|
||||
subtitles_en_literal_word,1,3,grep (ASCII),grep -anw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.867140531539917,629,LC_ALL=C
|
||||
subtitles_en_literal_word,1,3,grep (ASCII),grep -anw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.8452246189117432,629,LC_ALL=C
|
||||
subtitles_en_literal_word,1,3,grep (ASCII),grep -anw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.8254234790802002,629,LC_ALL=C
|
||||
subtitles_en_literal_word,1,3,rg,rg -nw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.39222264289855957,629,
|
||||
subtitles_en_literal_word,1,3,rg,rg -nw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5510556697845459,629,
|
||||
subtitles_en_literal_word,1,3,rg,rg -nw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.439284086227417,629,
|
||||
subtitles_en_literal_word,1,3,grep,grep -anw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.8688523769378662,629,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal_word,1,3,grep,grep -anw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.0727341175079346,629,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_literal_word,1,3,grep,grep -anw Sherlock Holmes /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.8665497303009033,629,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.2950611114501953,13,
|
||||
subtitles_en_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.226304769515991,13,
|
||||
subtitles_en_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.3140387535095215,13,
|
||||
subtitles_en_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.055910587310791,13,
|
||||
subtitles_en_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.032794952392578,13,
|
||||
subtitles_en_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,2.0602478981018066,13,
|
||||
subtitles_en_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,7.566168308258057,48,
|
||||
subtitles_en_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,7.360717535018921,48,
|
||||
subtitles_en_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,7.30781364440918,48,
|
||||
subtitles_en_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,6.13957142829895,13,
|
||||
subtitles_en_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,6.132368564605713,13,
|
||||
subtitles_en_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,5.929611921310425,13,
|
||||
subtitles_en_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.432003974914551,13,LC_ALL=C
|
||||
subtitles_en_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.714038610458374,13,LC_ALL=C
|
||||
subtitles_en_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.6915385723114014,13,LC_ALL=C
|
||||
subtitles_en_surrounding_words,1,3,rg,rg -n \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.42601513862609863,317,
|
||||
subtitles_en_surrounding_words,1,3,rg,rg -n \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.561565637588501,317,
|
||||
subtitles_en_surrounding_words,1,3,rg,rg -n \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5141217708587646,317,
|
||||
subtitles_en_surrounding_words,1,3,grep,grep -E -an \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.3077969551086426,317,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_surrounding_words,1,3,grep,grep -E -an \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.1768431663513184,317,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_surrounding_words,1,3,grep,grep -E -an \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.0883615016937256,317,LC_ALL=en_US.UTF-8
|
||||
subtitles_en_surrounding_words,1,3,rg (ASCII),rg -n (?-u)\w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5061089992523193,317,
|
||||
subtitles_en_surrounding_words,1,3,rg (ASCII),rg -n (?-u)\w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.4004178047180176,317,
|
||||
subtitles_en_surrounding_words,1,3,rg (ASCII),rg -n (?-u)\w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,0.5536422729492188,317,
|
||||
subtitles_en_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,4.981522798538208,323,
|
||||
subtitles_en_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,4.83101487159729,323,
|
||||
subtitles_en_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,5.025806427001953,323,
|
||||
subtitles_en_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.906707286834717,317,
|
||||
subtitles_en_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.8826284408569336,317,
|
||||
subtitles_en_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,3.852736711502075,317,
|
||||
subtitles_en_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.262470006942749,317,LC_ALL=C
|
||||
subtitles_en_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.1661438941955566,317,LC_ALL=C
|
||||
subtitles_en_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Holmes\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.sample.en,1.1648437976837158,317,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,rg (lines),rg -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.79972243309021,691,
|
||||
subtitles_ru_alternate,1,3,rg (lines),rg -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.629694938659668,691,
|
||||
subtitles_ru_alternate,1,3,rg (lines),rg -n Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.8325402736663818,691,
|
||||
subtitles_ru_alternate,1,3,ag (lines),ag -s Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,4.124853849411011,691,
|
||||
subtitles_ru_alternate,1,3,ag (lines),ag -s Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,4.0995543003082275,691,
|
||||
subtitles_ru_alternate,1,3,ag (lines),ag -s Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,4.105457544326782,691,
|
||||
subtitles_ru_alternate,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.491947650909424,691,
|
||||
subtitles_ru_alternate,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.544341802597046,691,
|
||||
subtitles_ru_alternate,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.6295418739318848,691,
|
||||
subtitles_ru_alternate,1,3,grep (lines),grep -E -an Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.948693513870239,691,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,grep (lines),grep -E -an Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.991183280944824,691,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,grep (lines),grep -E -an Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,7.004202842712402,691,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,rg,rg Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.1708271503448486,691,
|
||||
subtitles_ru_alternate,1,3,rg,rg Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.3341143131256104,691,
|
||||
subtitles_ru_alternate,1,3,rg,rg Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.3589985370635986,691,
|
||||
subtitles_ru_alternate,1,3,grep,grep -E -a Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.3860955238342285,691,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,grep,grep -E -a Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.437295436859131,691,LC_ALL=C
|
||||
subtitles_ru_alternate,1,3,grep,grep -E -a Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.34372615814209,691,LC_ALL=C
|
||||
subtitles_ru_alternate_casei,1,3,ag (ASCII),ag -s -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,4.100299119949341,691,
|
||||
subtitles_ru_alternate_casei,1,3,ag (ASCII),ag -s -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,4.343926668167114,691,
|
||||
subtitles_ru_alternate_casei,1,3,ag (ASCII),ag -s -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,4.247299432754517,691,
|
||||
subtitles_ru_alternate_casei,1,3,ucg (ASCII),ucg -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.6179757118225098,691,
|
||||
subtitles_ru_alternate_casei,1,3,ucg (ASCII),ucg -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.438668727874756,691,
|
||||
subtitles_ru_alternate_casei,1,3,ucg (ASCII),ucg -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.6296229362487793,691,
|
||||
subtitles_ru_alternate_casei,1,3,grep (ASCII),grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.816138744354248,691,LC_ALL=C
|
||||
subtitles_ru_alternate_casei,1,3,grep (ASCII),grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,7.005900859832764,691,LC_ALL=C
|
||||
subtitles_ru_alternate_casei,1,3,grep (ASCII),grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.96263861656189,691,LC_ALL=C
|
||||
subtitles_ru_alternate_casei,1,3,rg,rg -n -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,4.222437858581543,735,
|
||||
subtitles_ru_alternate_casei,1,3,rg,rg -n -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,4.260664463043213,735,
|
||||
subtitles_ru_alternate_casei,1,3,rg,rg -n -i Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,4.040424823760986,735,
|
||||
subtitles_ru_alternate_casei,1,3,grep,grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,7.112156391143799,735,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_alternate_casei,1,3,grep,grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,7.066831350326538,735,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_alternate_casei,1,3,grep,grep -E -ani Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.983258962631226,735,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal,1,3,rg,rg Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.3009657859802246,583,
|
||||
subtitles_ru_literal,1,3,rg,rg Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.31114768981933594,583,
|
||||
subtitles_ru_literal,1,3,rg,rg Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.3145453929901123,583,
|
||||
subtitles_ru_literal,1,3,rg (no mmap),rg --no-mmap Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.3670217990875244,583,
|
||||
subtitles_ru_literal,1,3,rg (no mmap),rg --no-mmap Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.39557576179504395,583,
|
||||
subtitles_ru_literal,1,3,rg (no mmap),rg --no-mmap Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.38857460021972656,583,
|
||||
subtitles_ru_literal,1,3,pt,pt -N Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,5.653504133224487,583,
|
||||
subtitles_ru_literal,1,3,pt,pt -N Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,5.440903663635254,583,
|
||||
subtitles_ru_literal,1,3,pt,pt -N Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,5.604345321655273,583,
|
||||
subtitles_ru_literal,1,3,sift,sift Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.333267450332642,583,
|
||||
subtitles_ru_literal,1,3,sift,sift Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.3738181591033936,583,
|
||||
subtitles_ru_literal,1,3,sift,sift Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.604928970336914,583,
|
||||
subtitles_ru_literal,1,3,grep,grep -a Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.7382421493530273,583,LC_ALL=C
|
||||
subtitles_ru_literal,1,3,grep,grep -a Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.7205381393432617,583,LC_ALL=C
|
||||
subtitles_ru_literal,1,3,grep,grep -a Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.8509528636932373,583,LC_ALL=C
|
||||
subtitles_ru_literal,1,3,rg (lines),rg -n Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.6589868068695068,583,
|
||||
subtitles_ru_literal,1,3,rg (lines),rg -n Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.857933759689331,583,
|
||||
subtitles_ru_literal,1,3,rg (lines),rg -n Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.7283596992492676,583,
|
||||
subtitles_ru_literal,1,3,ag (lines),ag -s Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,3.111611843109131,583,
|
||||
subtitles_ru_literal,1,3,ag (lines),ag -s Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,3.1084604263305664,583,
|
||||
subtitles_ru_literal,1,3,ag (lines),ag -s Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.993370771408081,583,
|
||||
subtitles_ru_literal,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.8463020324707031,583,
|
||||
subtitles_ru_literal,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.8160367012023926,583,
|
||||
subtitles_ru_literal,1,3,ucg (lines),ucg --nosmart-case Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.9012954235076904,583,
|
||||
subtitles_ru_literal,1,3,pt (lines),pt Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,5.590208530426025,583,
|
||||
subtitles_ru_literal,1,3,pt (lines),pt Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,5.712487697601318,583,
|
||||
subtitles_ru_literal,1,3,pt (lines),pt Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,5.628024339675903,583,
|
||||
subtitles_ru_literal,1,3,sift (lines),sift -n Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.999194145202637,583,
|
||||
subtitles_ru_literal,1,3,sift (lines),sift -n Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.639494895935059,583,
|
||||
subtitles_ru_literal,1,3,sift (lines),sift -n Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.6554951667785645,583,
|
||||
subtitles_ru_literal,1,3,grep (lines),grep -an Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.3319404125213623,583,LC_ALL=C
|
||||
subtitles_ru_literal,1,3,grep (lines),grep -an Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.3712143898010254,583,LC_ALL=C
|
||||
subtitles_ru_literal,1,3,grep (lines),grep -an Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.2382845878601074,583,LC_ALL=C
|
||||
subtitles_ru_literal_casei,1,3,rg,rg -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.2365717887878418,604,
|
||||
subtitles_ru_literal_casei,1,3,rg,rg -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.2306108474731445,604,
|
||||
subtitles_ru_literal_casei,1,3,rg,rg -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.22041654586792,604,
|
||||
subtitles_ru_literal_casei,1,3,grep,grep -ai Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.636392116546631,604,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal_casei,1,3,grep,grep -ai Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.447664976119995,604,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal_casei,1,3,grep,grep -ai Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,6.47466516494751,604,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal_casei,1,3,grep (ASCII),grep -E -ai Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.8679373264312744,583,LC_ALL=C
|
||||
subtitles_ru_literal_casei,1,3,grep (ASCII),grep -E -ai Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.8400778770446777,583,LC_ALL=C
|
||||
subtitles_ru_literal_casei,1,3,grep (ASCII),grep -E -ai Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.8912734985351562,583,LC_ALL=C
|
||||
subtitles_ru_literal_casei,1,3,rg (lines),rg -n -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.6649067401885986,604,
|
||||
subtitles_ru_literal_casei,1,3,rg (lines),rg -n -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.472435712814331,604,
|
||||
subtitles_ru_literal_casei,1,3,rg (lines),rg -n -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.5518934726715088,604,
|
||||
subtitles_ru_literal_casei,1,3,ag (lines) (ASCII),ag -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.6329071521759033,,
|
||||
subtitles_ru_literal_casei,1,3,ag (lines) (ASCII),ag -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.6236186027526855,,
|
||||
subtitles_ru_literal_casei,1,3,ag (lines) (ASCII),ag -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.744124174118042,,
|
||||
subtitles_ru_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.842088222503662,583,
|
||||
subtitles_ru_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.6797516345977783,583,
|
||||
subtitles_ru_literal_casei,1,3,ucg (lines) (ASCII),ucg -i Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.86281418800354,583,
|
||||
subtitles_ru_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Шерлок Холмс(?-u:\b) /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.3059053421020508,,
|
||||
subtitles_ru_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Шерлок Холмс(?-u:\b) /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.3220036029815674,,
|
||||
subtitles_ru_literal_word,1,3,rg (ASCII),rg -n (?-u:\b)Шерлок Холмс(?-u:\b) /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.3317573070526123,,
|
||||
subtitles_ru_literal_word,1,3,ag (ASCII),ag -sw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.790125846862793,,
|
||||
subtitles_ru_literal_word,1,3,ag (ASCII),ag -sw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.8004007339477539,,
|
||||
subtitles_ru_literal_word,1,3,ag (ASCII),ag -sw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.7884745597839355,,
|
||||
subtitles_ru_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.8595643043518066,583,
|
||||
subtitles_ru_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.8338594436645508,583,
|
||||
subtitles_ru_literal_word,1,3,ucg (ASCII),ucg --nosmart-case Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.6697683334350586,583,
|
||||
subtitles_ru_literal_word,1,3,grep (ASCII),grep -anw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.1608448028564453,583,LC_ALL=C
|
||||
subtitles_ru_literal_word,1,3,grep (ASCII),grep -anw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.3229436874389648,583,LC_ALL=C
|
||||
subtitles_ru_literal_word,1,3,grep (ASCII),grep -anw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.3650012016296387,583,LC_ALL=C
|
||||
subtitles_ru_literal_word,1,3,rg,rg -nw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.840968132019043,579,
|
||||
subtitles_ru_literal_word,1,3,rg,rg -nw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.7012484073638916,579,
|
||||
subtitles_ru_literal_word,1,3,rg,rg -nw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.6782803535461426,579,
|
||||
subtitles_ru_literal_word,1,3,grep,grep -anw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.3308250904083252,579,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal_word,1,3,grep,grep -anw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.2687301635742188,579,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_literal_word,1,3,grep,grep -anw Шерлок Холмс /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.317870855331421,579,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,3.8553390502929688,41,
|
||||
subtitles_ru_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,3.6567201614379883,41,
|
||||
subtitles_ru_no_literal,1,3,rg,rg -n \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,3.6484830379486084,41,
|
||||
subtitles_ru_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.968876838684082,,
|
||||
subtitles_ru_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,3.0976366996765137,,
|
||||
subtitles_ru_no_literal,1,3,rg (ASCII),rg -n (?-u)\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.9159646034240723,,
|
||||
subtitles_ru_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.8425374031066895,,
|
||||
subtitles_ru_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.0483176708221436,,
|
||||
subtitles_ru_no_literal,1,3,ag (ASCII),ag -s \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.0838112831115723,,
|
||||
subtitles_ru_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.510307550430298,,
|
||||
subtitles_ru_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.542471408843994,,
|
||||
subtitles_ru_no_literal,1,3,ucg (ASCII),ucg --nosmart-case \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.5069386959075928,,
|
||||
subtitles_ru_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.0888967514038086,,LC_ALL=C
|
||||
subtitles_ru_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.119218349456787,,LC_ALL=C
|
||||
subtitles_ru_no_literal,1,3,grep (ASCII),grep -E -an \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5} /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.0531933307647705,,LC_ALL=C
|
||||
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.792813777923584,278,
|
||||
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.8660504817962646,278,
|
||||
subtitles_ru_surrounding_words,1,3,rg,rg -n \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,0.8515620231628418,278,
|
||||
subtitles_ru_surrounding_words,1,3,grep,grep -E -an \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.4405500888824463,278,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_surrounding_words,1,3,grep,grep -E -an \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.6386635303497314,278,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_surrounding_words,1,3,grep,grep -E -an \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.5895426273345947,278,LC_ALL=en_US.UTF-8
|
||||
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.032708168029785,,
|
||||
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.927478551864624,,
|
||||
subtitles_ru_surrounding_words,1,3,ag (ASCII),ag -s \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.019101619720459,,
|
||||
subtitles_ru_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.6226632595062256,,
|
||||
subtitles_ru_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.5062124729156494,,
|
||||
subtitles_ru_surrounding_words,1,3,ucg (ASCII),ucg --nosmart-case \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,2.5160763263702393,,
|
||||
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.613976240158081,,LC_ALL=C
|
||||
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.5636706352233887,,LC_ALL=C
|
||||
subtitles_ru_surrounding_words,1,3,grep (ASCII),grep -E -an \w+\s+Холмс\s+\w+ /data/benchsuite/subtitles/OpenSubtitles2016.raw.ru,1.4378304481506348,,LC_ALL=C
|
||||
|
235
benchsuite/runs/2016-09-22-archlinux-cheetah/summary
Normal file
235
benchsuite/runs/2016-09-22-archlinux-cheetah/summary
Normal file
@@ -0,0 +1,235 @@
|
||||
linux_alternates (pattern: ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT)
|
||||
-------------------------------------------------------------------------
|
||||
rg (ignore) 0.159 +/- 0.002 (lines: 68)
|
||||
ag (ignore) 0.557 +/- 0.009 (lines: 68)
|
||||
git grep (ignore) 0.252 +/- 0.005 (lines: 68)
|
||||
rg (whitelist)* 0.103 +/- 0.001 (lines: 68)*
|
||||
ucg (whitelist) 0.155 +/- 0.002 (lines: 68)
|
||||
|
||||
linux_alternates_casei (pattern: ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT)
|
||||
-------------------------------------------------------------------------------
|
||||
rg (ignore) 0.161 +/- 0.001 (lines: 160)
|
||||
ag (ignore) 0.628 +/- 0.040 (lines: 160)
|
||||
git grep (ignore) 0.940 +/- 0.109 (lines: 160)
|
||||
rg (whitelist)* 0.123 +/- 0.010 (lines: 160)*
|
||||
ucg (whitelist) 0.240 +/- 0.002 (lines: 160)
|
||||
|
||||
linux_literal (pattern: PM_RESUME)
|
||||
----------------------------------
|
||||
rg (ignore) 0.159 +/- 0.015 (lines: 16)
|
||||
rg (ignore) (mmap) 0.389 +/- 0.009 (lines: 16)
|
||||
ag (ignore) (mmap) 0.574 +/- 0.138 (lines: 16)
|
||||
pt (ignore) 0.157 +/- 0.006 (lines: 16)
|
||||
sift (ignore) 0.346 +/- 0.007 (lines: 16)
|
||||
git grep (ignore) 0.201 +/- 0.011 (lines: 16)
|
||||
rg (whitelist)* 0.104 +/- 0.003 (lines: 16)*
|
||||
ucg (whitelist) 0.169 +/- 0.002 (lines: 16)
|
||||
|
||||
linux_literal_casei (pattern: PM_RESUME)
|
||||
----------------------------------------
|
||||
rg (ignore) 0.155 +/- 0.009 (lines: 370)
|
||||
rg (ignore) (mmap) 0.393 +/- 0.005 (lines: 370)
|
||||
ag (ignore) (mmap) 0.462 +/- 0.035 (lines: 370)
|
||||
pt (ignore) 11.936 +/- 0.106 (lines: 370)
|
||||
sift (ignore) 0.370 +/- 0.004 (lines: 370)
|
||||
git grep (ignore) 0.190 +/- 0.006 (lines: 370)
|
||||
rg (whitelist)* 0.109 +/- 0.004 (lines: 370)*
|
||||
ucg (whitelist) 0.162 +/- 0.002 (lines: 370)
|
||||
|
||||
linux_literal_default (pattern: PM_RESUME)
|
||||
------------------------------------------
|
||||
rg 0.139 +/- 0.001 (lines: 16)
|
||||
ag 0.441 +/- 0.021 (lines: 16)
|
||||
ucg 0.165 +/- 0.005 (lines: 16)
|
||||
pt 0.156 +/- 0.002 (lines: 16)
|
||||
sift* 0.117 +/- 0.001 (lines: 16)*
|
||||
git grep 0.185 +/- 0.018 (lines: 16)
|
||||
|
||||
linux_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||
-----------------------------------------------------------------
|
||||
rg (ignore) 0.357 +/- 0.015 (lines: 490)
|
||||
rg (ignore) (ASCII) 0.294 +/- 0.032 (lines: 490)
|
||||
ag (ignore) (ASCII) 0.755 +/- 0.009 (lines: 766)
|
||||
pt (ignore) (ASCII) 12.485 +/- 0.078 (lines: 490)
|
||||
sift (ignore) (ASCII) 8.889 +/- 0.134 (lines: 490)
|
||||
git grep (ignore) 8.859 +/- 0.159 (lines: 490)
|
||||
git grep (ignore) (ASCII) 1.880 +/- 0.067 (lines: 490)
|
||||
rg (whitelist) 0.313 +/- 0.009 (lines: 419)
|
||||
rg (whitelist) (ASCII)* 0.236 +/- 0.035 (lines: 419)*
|
||||
ucg (whitelist) (ASCII) 0.446 +/- 0.003 (lines: 416)
|
||||
|
||||
linux_re_literal_suffix (pattern: [A-Z]+_RESUME)
|
||||
------------------------------------------------
|
||||
rg (ignore) 0.158 +/- 0.028 (lines: 1652)
|
||||
ag (ignore) 0.528 +/- 0.009 (lines: 1652)
|
||||
pt (ignore) 11.895 +/- 0.014 (lines: 1652)
|
||||
sift (ignore) 3.972 +/- 0.039 (lines: 1652)
|
||||
git grep (ignore) 0.592 +/- 0.049 (lines: 1652)
|
||||
rg (whitelist)* 0.102 +/- 0.001 (lines: 1630)*
|
||||
ucg (whitelist) 0.138 +/- 0.002 (lines: 1630)
|
||||
|
||||
linux_unicode_greek (pattern: \p{Greek})
|
||||
----------------------------------------
|
||||
rg* 0.254 +/- 0.014 (lines: 23)*
|
||||
pt 11.741 +/- 0.043 (lines: 23)
|
||||
sift 2.774 +/- 0.038 (lines: 23)
|
||||
|
||||
linux_unicode_greek_casei (pattern: \p{Greek})
|
||||
----------------------------------------------
|
||||
rg 0.261 +/- 0.031 (lines: 103)
|
||||
pt 11.754 +/- 0.010 (lines: 23)
|
||||
sift* 0.002 +/- 0.000 (lines: 0)*
|
||||
|
||||
linux_unicode_word (pattern: \wAh)
|
||||
----------------------------------
|
||||
rg (ignore) 0.166 +/- 0.025 (lines: 186)
|
||||
rg (ignore) (ASCII) 0.148 +/- 0.004 (lines: 174)
|
||||
ag (ignore) (ASCII) 0.491 +/- 0.010 (lines: 174)
|
||||
pt (ignore) (ASCII) 12.034 +/- 0.010 (lines: 174)
|
||||
sift (ignore) (ASCII) 4.151 +/- 0.094 (lines: 174)
|
||||
git grep (ignore) 4.477 +/- 0.068 (lines: 186)
|
||||
git grep (ignore) (ASCII) 1.449 +/- 0.106 (lines: 174)
|
||||
rg (whitelist) 0.104 +/- 0.002 (lines: 180)
|
||||
rg (whitelist) (ASCII)* 0.101 +/- 0.002 (lines: 168)*
|
||||
ucg (ASCII) 0.173 +/- 0.001 (lines: 168)
|
||||
|
||||
linux_word (pattern: PM_RESUME)
|
||||
-------------------------------
|
||||
rg (ignore) 0.159 +/- 0.021 (lines: 6)
|
||||
ag (ignore) 0.457 +/- 0.032 (lines: 6)
|
||||
pt (ignore) 12.116 +/- 0.008 (lines: 6)
|
||||
sift (ignore) 3.294 +/- 0.074 (lines: 6)
|
||||
git grep (ignore) 0.191 +/- 0.021 (lines: 6)
|
||||
rg (whitelist)* 0.100 +/- 0.004 (lines: 6)*
|
||||
ucg (whitelist) 0.164 +/- 0.004 (lines: 6)
|
||||
|
||||
subtitles_en_alternate (pattern: Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty)
|
||||
---------------------------------------------------------------------------------------------------------------
|
||||
rg (lines) 0.600 +/- 0.079 (lines: 848)
|
||||
ag (lines) 2.420 +/- 0.109 (lines: 848)
|
||||
ucg (lines) 1.382 +/- 0.100 (lines: 848)
|
||||
grep (lines) 2.815 +/- 0.017 (lines: 848)
|
||||
rg* 0.387 +/- 0.079 (lines: 848)*
|
||||
grep 2.371 +/- 0.126 (lines: 848)
|
||||
|
||||
subtitles_en_alternate_casei (pattern: Sherlock Holmes|John Watson|Irene Adler|Inspector Lestrade|Professor Moriarty)
|
||||
---------------------------------------------------------------------------------------------------------------------
|
||||
ag (ASCII) 3.868 +/- 0.082 (lines: 862)
|
||||
ucg (ASCII) 2.918 +/- 0.020 (lines: 862)
|
||||
grep (ASCII) 3.674 +/- 0.112 (lines: 862)
|
||||
rg* 2.346 +/- 0.100 (lines: 862)*
|
||||
grep 4.149 +/- 0.084 (lines: 862)
|
||||
|
||||
subtitles_en_literal (pattern: Sherlock Holmes)
|
||||
-----------------------------------------------
|
||||
rg* 0.210 +/- 0.029 (lines: 629)*
|
||||
rg (no mmap) 0.230 +/- 0.029 (lines: 629)
|
||||
pt 1.694 +/- 0.024 (lines: 629)
|
||||
sift 0.327 +/- 0.006 (lines: 629)
|
||||
grep 0.535 +/- 0.069 (lines: 629)
|
||||
rg (lines) 0.569 +/- 0.040 (lines: 629)
|
||||
ag (lines) 1.930 +/- 0.106 (lines: 629)
|
||||
ucg (lines) 0.818 +/- 0.053 (lines: 629)
|
||||
pt (lines) 1.591 +/- 0.091 (lines: 629)
|
||||
sift (lines) 0.721 +/- 0.007 (lines: 629)
|
||||
grep (lines) 0.999 +/- 0.047 (lines: 629)
|
||||
|
||||
subtitles_en_literal_casei (pattern: Sherlock Holmes)
|
||||
-----------------------------------------------------
|
||||
rg* 0.395 +/- 0.028 (lines: 642)*
|
||||
grep 3.243 +/- 0.040 (lines: 642)
|
||||
grep (ASCII) 0.556 +/- 0.013 (lines: 642)
|
||||
rg (lines) 0.638 +/- 0.093 (lines: 642)
|
||||
ag (lines) (ASCII) 1.895 +/- 0.106 (lines: 642)
|
||||
ucg (lines) (ASCII) 0.931 +/- 0.007 (lines: 642)
|
||||
|
||||
subtitles_en_literal_word (pattern: Sherlock Holmes)
|
||||
----------------------------------------------------
|
||||
rg (ASCII) 0.431 +/- 0.026 (lines: 629)*
|
||||
ag (ASCII) 1.826 +/- 0.020 (lines: 629)
|
||||
ucg (ASCII) 0.799 +/- 0.091 (lines: 629)
|
||||
grep (ASCII) 0.846 +/- 0.021 (lines: 629)
|
||||
rg* 0.461 +/- 0.082 (lines: 629)
|
||||
grep 0.936 +/- 0.118 (lines: 629)
|
||||
|
||||
subtitles_en_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||
----------------------------------------------------------------------------------------
|
||||
rg 2.278 +/- 0.046 (lines: 13)
|
||||
rg (ASCII)* 2.050 +/- 0.015 (lines: 13)*
|
||||
ag (ASCII) 7.412 +/- 0.136 (lines: 48)
|
||||
ucg (ASCII) 6.067 +/- 0.119 (lines: 13)
|
||||
grep (ASCII) 3.613 +/- 0.157 (lines: 13)
|
||||
|
||||
subtitles_en_surrounding_words (pattern: \w+\s+Holmes\s+\w+)
|
||||
------------------------------------------------------------
|
||||
rg 0.501 +/- 0.069 (lines: 317)
|
||||
grep 1.191 +/- 0.110 (lines: 317)
|
||||
rg (ASCII)* 0.487 +/- 0.078 (lines: 317)*
|
||||
ag (ASCII) 4.946 +/- 0.102 (lines: 323)
|
||||
ucg (ASCII) 3.881 +/- 0.027 (lines: 317)
|
||||
grep (ASCII) 1.198 +/- 0.056 (lines: 317)
|
||||
|
||||
subtitles_ru_alternate (pattern: Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти)
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
rg (lines) 1.754 +/- 0.109 (lines: 691)
|
||||
ag (lines) 4.110 +/- 0.013 (lines: 691)
|
||||
ucg (lines) 2.555 +/- 0.069 (lines: 691)
|
||||
grep (lines) 6.981 +/- 0.029 (lines: 691)
|
||||
rg* 1.288 +/- 0.102 (lines: 691)*
|
||||
grep 6.389 +/- 0.047 (lines: 691)
|
||||
|
||||
subtitles_ru_alternate_casei (pattern: Шерлок Холмс|Джон Уотсон|Ирен Адлер|инспектор Лестрейд|профессор Мориарти)
|
||||
-----------------------------------------------------------------------------------------------------------------
|
||||
ag (ASCII) 4.231 +/- 0.123 (lines: 691)
|
||||
ucg (ASCII)* 2.562 +/- 0.107 (lines: 691)*
|
||||
grep (ASCII) 6.928 +/- 0.099 (lines: 691)
|
||||
rg 4.175 +/- 0.118 (lines: 735)
|
||||
grep 7.054 +/- 0.065 (lines: 735)
|
||||
|
||||
subtitles_ru_literal (pattern: Шерлок Холмс)
|
||||
--------------------------------------------
|
||||
rg* 0.309 +/- 0.007 (lines: 583)*
|
||||
rg (no mmap) 0.384 +/- 0.015 (lines: 583)
|
||||
pt 5.566 +/- 0.111 (lines: 583)
|
||||
sift 6.437 +/- 0.147 (lines: 583)
|
||||
grep 0.770 +/- 0.071 (lines: 583)
|
||||
rg (lines) 0.748 +/- 0.101 (lines: 583)
|
||||
ag (lines) 3.071 +/- 0.067 (lines: 583)
|
||||
ucg (lines) 1.855 +/- 0.043 (lines: 583)
|
||||
pt (lines) 5.644 +/- 0.063 (lines: 583)
|
||||
sift (lines) 6.765 +/- 0.203 (lines: 583)
|
||||
grep (lines) 1.314 +/- 0.068 (lines: 583)
|
||||
|
||||
subtitles_ru_literal_casei (pattern: Шерлок Холмс)
|
||||
--------------------------------------------------
|
||||
rg 1.229 +/- 0.008 (lines: 604)
|
||||
grep 6.520 +/- 0.102 (lines: 604)
|
||||
grep (ASCII) 0.866 +/- 0.026 (lines: 583)
|
||||
rg (lines) 1.563 +/- 0.097 (lines: 604)
|
||||
ag (lines) (ASCII)* 0.667 +/- 0.067 (lines: 0)*
|
||||
ucg (lines) (ASCII) 1.795 +/- 0.100 (lines: 583)
|
||||
|
||||
subtitles_ru_literal_word (pattern: Шерлок Холмс)
|
||||
-------------------------------------------------
|
||||
rg (ASCII)* 0.320 +/- 0.013 (lines: 0)*
|
||||
ag (ASCII) 0.793 +/- 0.006 (lines: 0)
|
||||
ucg (ASCII) 1.788 +/- 0.103 (lines: 583)
|
||||
grep (ASCII) 1.283 +/- 0.108 (lines: 583)
|
||||
rg 0.740 +/- 0.088 (lines: 579)
|
||||
grep 1.306 +/- 0.033 (lines: 579)
|
||||
|
||||
subtitles_ru_no_literal (pattern: \w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5}\s+\w{5})
|
||||
----------------------------------------------------------------------------------------
|
||||
rg 3.720 +/- 0.117 (lines: 41)
|
||||
rg (ASCII) 2.994 +/- 0.093 (lines: 0)
|
||||
ag (ASCII)* 1.992 +/- 0.130 (lines: 0)*
|
||||
ucg (ASCII) 2.520 +/- 0.020 (lines: 0)
|
||||
grep (ASCII) 2.087 +/- 0.033 (lines: 0)
|
||||
|
||||
subtitles_ru_surrounding_words (pattern: \w+\s+Холмс\s+\w+)
|
||||
-----------------------------------------------------------
|
||||
rg* 0.837 +/- 0.039 (lines: 278)*
|
||||
grep 1.556 +/- 0.103 (lines: 278)
|
||||
ag (ASCII) 1.993 +/- 0.057 (lines: 0)
|
||||
ucg (ASCII) 2.548 +/- 0.065 (lines: 0)
|
||||
grep (ASCII) 1.538 +/- 0.091 (lines: 0)
|
||||
@@ -18,7 +18,7 @@ mk_tarball() {
|
||||
mkdir "$td/$name"
|
||||
|
||||
cp target/$TARGET/release/rg "$td/$name/"
|
||||
cp {README.md,UNLICENSE,COPYING,LICENSE-MIT} "$td/$name/"
|
||||
cp {doc/rg.1,README.md,UNLICENSE,COPYING,LICENSE-MIT} "$td/$name/"
|
||||
|
||||
pushd $td
|
||||
tar czf "$out_dir/$name.tar.gz" *
|
||||
|
||||
@@ -16,8 +16,15 @@ disable_cross_doctests() {
|
||||
}
|
||||
|
||||
run_test_suite() {
|
||||
cargo clean --target $TARGET --verbose
|
||||
cargo build --target $TARGET --verbose
|
||||
cargo test --target $TARGET --verbose
|
||||
cargo build --target $TARGET --verbose --manifest-path grep/Cargo.toml
|
||||
cargo test --target $TARGET --verbose --manifest-path grep/Cargo.toml
|
||||
cargo build --target $TARGET --verbose --manifest-path globset/Cargo.toml
|
||||
cargo test --target $TARGET --verbose --manifest-path globset/Cargo.toml
|
||||
cargo build --target $TARGET --verbose --manifest-path ignore/Cargo.toml
|
||||
cargo test --target $TARGET --verbose --manifest-path ignore/Cargo.toml
|
||||
|
||||
# sanity check the file type
|
||||
file target/$TARGET/debug/rg
|
||||
|
||||
25
ci/sha256.sh
Normal file
25
ci/sha256.sh
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
if [ $# != 1 ]; then
|
||||
echo "Usage: $(basename $0) version" >&2
|
||||
exit 1
|
||||
fi
|
||||
version="$1"
|
||||
|
||||
# Linux and Darwin builds.
|
||||
for arch in i686 x86_64; do
|
||||
for target in apple-darwin unknown-linux-musl; do
|
||||
url="https://github.com/BurntSushi/ripgrep/releases/download/$version/ripgrep-$version-$arch-$target.tar.gz"
|
||||
sha=$(curl -sfSL "$url" | sha256sum)
|
||||
echo "$version-$arch-$target $sha"
|
||||
done
|
||||
done
|
||||
|
||||
# Source.
|
||||
for ext in zip tar.gz; do
|
||||
url="https://github.com/BurntSushi/ripgrep/archive/$version.$ext"
|
||||
sha=$(curl -sfSL "$url" | sha256sum)
|
||||
echo "source.$ext $sha"
|
||||
done
|
||||
11
ctags.rust
11
ctags.rust
@@ -1,11 +0,0 @@
|
||||
--langdef=Rust
|
||||
--langmap=Rust:.rs
|
||||
--regex-Rust=/^[ \t]*(#\[[^\]]\][ \t]*)*(pub[ \t]+)?(extern[ \t]+)?("[^"]+"[ \t]+)?(unsafe[ \t]+)?fn[ \t]+([a-zA-Z0-9_]+)/\6/f,functions,function definitions/
|
||||
--regex-Rust=/^[ \t]*(pub[ \t]+)?type[ \t]+([a-zA-Z0-9_]+)/\2/T,types,type definitions/
|
||||
--regex-Rust=/^[ \t]*(pub[ \t]+)?enum[ \t]+([a-zA-Z0-9_]+)/\2/g,enum,enumeration names/
|
||||
--regex-Rust=/^[ \t]*(pub[ \t]+)?struct[ \t]+([a-zA-Z0-9_]+)/\2/s,structure names/
|
||||
--regex-Rust=/^[ \t]*(pub[ \t]+)?mod[ \t]+([a-zA-Z0-9_]+)/\2/m,modules,module names/
|
||||
--regex-Rust=/^[ \t]*(pub[ \t]+)?static[ \t]+([a-zA-Z0-9_]+)/\2/c,consts,static constants/
|
||||
--regex-Rust=/^[ \t]*(pub[ \t]+)?trait[ \t]+([a-zA-Z0-9_]+)/\2/t,traits,traits/
|
||||
--regex-Rust=/^[ \t]*(pub[ \t]+)?impl([ \t\n]+<.*>)?[ \t]+([a-zA-Z0-9_]+)/\3/i,impls,trait implementations/
|
||||
--regex-Rust=/^[ \t]*macro_rules![ \t]+([a-zA-Z0-9_]+)/\1/d,macros,macro definitions/
|
||||
4
doc/convert-to-man
Executable file
4
doc/convert-to-man
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
pandoc -s -t man rg.1.md -o rg.1
|
||||
sed -i 's/\.TH.*/.TH "rg" "1"/g' rg.1
|
||||
324
doc/rg.1
Normal file
324
doc/rg.1
Normal file
@@ -0,0 +1,324 @@
|
||||
.\" Automatically generated by Pandoc 1.17.2
|
||||
.\"
|
||||
.TH "rg" "1"
|
||||
.hy
|
||||
.SH NAME
|
||||
.PP
|
||||
rg \- recursively search current directory for lines matching a pattern
|
||||
.SH SYNOPSIS
|
||||
.PP
|
||||
rg [\f[I]options\f[]] \-e PATTERN ...
|
||||
[\f[I]<\f[]path\f[I]> ...\f[]]
|
||||
.PP
|
||||
rg [\f[I]options\f[]] <\f[I]pattern\f[]> [\f[I]<\f[]path\f[I]> ...\f[]]
|
||||
.PP
|
||||
rg [\f[I]options\f[]] \-\-files [\f[I]<\f[]path\f[I]> ...\f[]]
|
||||
.PP
|
||||
rg [\f[I]options\f[]] \-\-type\-list
|
||||
.PP
|
||||
rg [\f[I]options\f[]] \-\-help
|
||||
.PP
|
||||
rg [\f[I]options\f[]] \-\-version
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
rg (ripgrep) combines the usability of The Silver Searcher (an ack
|
||||
clone) with the raw speed of grep.
|
||||
.SH COMMON OPTIONS
|
||||
.TP
|
||||
.B \-a, \-\-text
|
||||
Search binary files as if they were text.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-c, \-\-count
|
||||
Only show count of line matches for each file.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-color \f[I]WHEN\f[]
|
||||
Whether to use coloring in match.
|
||||
Valid values are never, always or auto.
|
||||
[default: auto]
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-e, \-\-regexp \f[I]PATTERN\f[] ...
|
||||
Use PATTERN to search.
|
||||
This option can be provided multiple times, where all patterns given are
|
||||
searched.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-F, \-\-fixed\-strings
|
||||
Treat the pattern as a literal string instead of a regular expression.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-g, \-\-glob \f[I]GLOB\f[] ...
|
||||
Include or exclude files for searching that match the given glob.
|
||||
This always overrides any other ignore logic.
|
||||
Multiple glob flags may be used.
|
||||
Globbing rules match .gitignore globs.
|
||||
Precede a glob with a \[aq]!\[aq] to exclude it.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Show this usage message.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-i, \-\-ignore\-case
|
||||
Case insensitive search.
|
||||
Overridden by \-\-case\-sensitive.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-n, \-\-line\-number
|
||||
Show line numbers (1\-based).
|
||||
This is enabled by default at a tty.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-N, \-\-no\-line\-number
|
||||
Suppress line numbers.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-q, \-\-quiet
|
||||
Do not print anything to stdout.
|
||||
If a match is found in a file, stop searching that file.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-t, \-\-type \f[I]TYPE\f[] ...
|
||||
Only search files matching TYPE.
|
||||
Multiple type flags may be provided.
|
||||
Use the \-\-type\-list flag to list all available types.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-T, \-\-type\-not \f[I]TYPE\f[] ...
|
||||
Do not search files matching TYPE.
|
||||
Multiple not\-type flags may be provided.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-u, \-\-unrestricted ...
|
||||
Reduce the level of \[aq]smart\[aq] searching.
|
||||
A single \-u doesn\[aq]t respect .gitignore (etc.) files.
|
||||
Two \-u flags will search hidden files and directories.
|
||||
Three \-u flags will search binary files.
|
||||
\-uu is equivalent to grep \-r, and \-uuu is equivalent to grep \-a \-r.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-v, \-\-invert\-match
|
||||
Invert matching.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-w, \-\-word\-regexp
|
||||
Only show matches surrounded by word boundaries.
|
||||
This is equivalent to putting \\b before and after the search pattern.
|
||||
.RS
|
||||
.RE
|
||||
.SH LESS COMMON OPTIONS
|
||||
.TP
|
||||
.B \-A, \-\-after\-context \f[I]NUM\f[]
|
||||
Show NUM lines after each match.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-B, \-\-before\-context \f[I]NUM\f[]
|
||||
Show NUM lines before each match.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-C, \-\-context \f[I]NUM\f[]
|
||||
Show NUM lines before and after each match.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-column
|
||||
Show column numbers (1 based) in output.
|
||||
This only shows the column numbers for the first match on each line.
|
||||
Note that this doesn\[aq]t try to account for Unicode.
|
||||
One byte is equal to one column.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-context\-separator \f[I]ARG\f[]
|
||||
The string to use when separating non\-continuous context lines.
|
||||
Escape sequences may be used.
|
||||
[default: \-\-]
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-debug
|
||||
Show debug messages.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-files
|
||||
Print each file that would be searched (but don\[aq]t search).
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-l, \-\-files\-with\-matches
|
||||
Only show path of each file with matches.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-H, \-\-with\-filename
|
||||
Prefix each match with the file name that contains it.
|
||||
This is the default when more than one file is searched.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-no\-filename
|
||||
Never show the filename for a match.
|
||||
This is the default when one file is searched.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-heading
|
||||
Show the file name above clusters of matches from each file.
|
||||
This is the default mode at a tty.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-no\-heading
|
||||
Don\[aq]t show any file name heading.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-hidden
|
||||
Search hidden directories and files.
|
||||
(Hidden directories and files are skipped by default.)
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-L, \-\-follow
|
||||
Follow symlinks.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-maxdepth \f[I]NUM\f[]
|
||||
Descend at most NUM directories below the command line arguments.
|
||||
A value of zero searches only the starting\-points themselves.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-mmap
|
||||
Search using memory maps when possible.
|
||||
This is enabled by default when ripgrep thinks it will be faster.
|
||||
(Note that mmap searching doesn\[aq]t currently support the various
|
||||
context related options.)
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-no\-mmap
|
||||
Never use memory maps, even when they might be faster.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-no\-ignore
|
||||
Don\[aq]t respect ignore files (.gitignore, .ignore, etc.) This implies
|
||||
\-\-no\-ignore\-parent.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-no\-ignore\-parent
|
||||
Don\[aq]t respect ignore files in parent directories.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-no\-ignore\-vcs
|
||||
Don\[aq]t respect version control ignore files (e.g., .gitignore).
|
||||
Note that .ignore files will continue to be respected.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-null
|
||||
Whenever a file name is printed, follow it with a NUL byte.
|
||||
This includes printing filenames before matches, and when printing a
|
||||
list of matching files such as with \-\-count, \-\-files\-with\-matches
|
||||
and \-\-files.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-p, \-\-pretty
|
||||
Alias for \-\-color=always \-\-heading \-n.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-r, \-\-replace \f[I]ARG\f[]
|
||||
Replace every match with the string given when printing search results.
|
||||
Neither this flag nor any other flag will modify your files.
|
||||
.RS
|
||||
.PP
|
||||
Capture group indices (e.g., $5) and names (e.g., $foo) are supported in
|
||||
the replacement string.
|
||||
.RE
|
||||
.TP
|
||||
.B \-s, \-\-case\-sensitive
|
||||
Search case sensitively.
|
||||
This overrides \-\-ignore\-case and \-\-smart\-case.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-S, \-\-smart\-case
|
||||
Search case insensitively if the pattern is all lowercase.
|
||||
Search case sensitively otherwise.
|
||||
This is overridden by either \-\-case\-sensitive or \-\-ignore\-case.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-j, \-\-threads \f[I]ARG\f[]
|
||||
The number of threads to use.
|
||||
0 means use the number of logical CPUs (capped at 6).
|
||||
[default: 0]
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-version
|
||||
Show the version number of ripgrep and exit.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-vimgrep
|
||||
Show results with every match on its own line, including line numbers
|
||||
and column numbers.
|
||||
(With this option, a line with more than one match of the regex will be
|
||||
printed more than once.)
|
||||
.RS
|
||||
.RE
|
||||
.SH FILE TYPE MANAGEMENT OPTIONS
|
||||
.TP
|
||||
.B \-\-type\-list
|
||||
Show all supported file types and their associated globs.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-type\-add \f[I]ARG\f[] ...
|
||||
Add a new glob for a particular file type.
|
||||
Only one glob can be added at a time.
|
||||
Multiple \-\-type\-add flags can be provided.
|
||||
Unless \-\-type\-clear is used, globs are added to any existing globs
|
||||
inside of ripgrep.
|
||||
Note that this must be passed to every invocation of rg.
|
||||
Type settings are NOT persisted.
|
||||
.RS
|
||||
.PP
|
||||
Example:
|
||||
\f[C]rg\ \-\-type\-add\ \[aq]foo:*.foo\[aq]\ \-tfoo\ PATTERN\f[]
|
||||
.RE
|
||||
.TP
|
||||
.B \-\-type\-clear \f[I]TYPE\f[] ...
|
||||
Clear the file type globs previously defined for TYPE.
|
||||
This only clears the default type definitions that are found inside of
|
||||
ripgrep.
|
||||
Note that this must be passed to every invocation of rg.
|
||||
.RS
|
||||
.RE
|
||||
211
doc/rg.1.md
Normal file
211
doc/rg.1.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# NAME
|
||||
|
||||
rg - recursively search current directory for lines matching a pattern
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
rg [*options*] -e PATTERN ... [*<*path*> ...*]
|
||||
|
||||
rg [*options*] <*pattern*> [*<*path*> ...*]
|
||||
|
||||
rg [*options*] --files [*<*path*> ...*]
|
||||
|
||||
rg [*options*] --type-list
|
||||
|
||||
rg [*options*] --help
|
||||
|
||||
rg [*options*] --version
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
rg (ripgrep) combines the usability of The Silver Searcher (an ack clone) with
|
||||
the raw speed of grep.
|
||||
|
||||
# COMMON OPTIONS
|
||||
|
||||
-a, --text
|
||||
: Search binary files as if they were text.
|
||||
|
||||
-c, --count
|
||||
: Only show count of line matches for each file.
|
||||
|
||||
--color *WHEN*
|
||||
: Whether to use coloring in match. Valid values are never, always or auto.
|
||||
[default: auto]
|
||||
|
||||
-e, --regexp *PATTERN* ...
|
||||
: Use PATTERN to search. This option can be provided multiple times, where all
|
||||
patterns given are searched.
|
||||
|
||||
-F, --fixed-strings
|
||||
: Treat the pattern as a literal string instead of a regular expression.
|
||||
|
||||
-g, --glob *GLOB* ...
|
||||
: Include or exclude files for searching that match the given glob. This always
|
||||
overrides any other ignore logic. Multiple glob flags may be used. Globbing
|
||||
rules match .gitignore globs. Precede a glob with a '!' to exclude it.
|
||||
|
||||
-h, --help
|
||||
: Show this usage message.
|
||||
|
||||
-i, --ignore-case
|
||||
: Case insensitive search. Overridden by --case-sensitive.
|
||||
|
||||
-n, --line-number
|
||||
: Show line numbers (1-based). This is enabled by default at a tty.
|
||||
|
||||
-N, --no-line-number
|
||||
: Suppress line numbers.
|
||||
|
||||
-q, --quiet
|
||||
: Do not print anything to stdout. If a match is found in a file, stop
|
||||
searching that file.
|
||||
|
||||
-t, --type *TYPE* ...
|
||||
: Only search files matching TYPE. Multiple type flags may be provided. Use the
|
||||
--type-list flag to list all available types.
|
||||
|
||||
-T, --type-not *TYPE* ...
|
||||
: Do not search files matching TYPE. Multiple not-type flags may be provided.
|
||||
|
||||
-u, --unrestricted ...
|
||||
: Reduce the level of 'smart' searching. A single -u doesn't respect .gitignore
|
||||
(etc.) files. Two -u flags will search hidden files and directories. Three
|
||||
-u flags will search binary files. -uu is equivalent to grep -r, and -uuu is
|
||||
equivalent to grep -a -r.
|
||||
|
||||
-v, --invert-match
|
||||
: Invert matching.
|
||||
|
||||
-w, --word-regexp
|
||||
: Only show matches surrounded by word boundaries. This is equivalent to
|
||||
putting \\b before and after the search pattern.
|
||||
|
||||
# LESS COMMON OPTIONS
|
||||
|
||||
-A, --after-context *NUM*
|
||||
: Show NUM lines after each match.
|
||||
|
||||
-B, --before-context *NUM*
|
||||
: Show NUM lines before each match.
|
||||
|
||||
-C, --context *NUM*
|
||||
: Show NUM lines before and after each match.
|
||||
|
||||
--column
|
||||
: Show column numbers (1 based) in output. This only shows the column
|
||||
numbers for the first match on each line. Note that this doesn't try
|
||||
to account for Unicode. One byte is equal to one column.
|
||||
|
||||
--context-separator *ARG*
|
||||
: The string to use when separating non-continuous context lines. Escape
|
||||
sequences may be used. [default: --]
|
||||
|
||||
--debug
|
||||
: Show debug messages.
|
||||
|
||||
--files
|
||||
: Print each file that would be searched (but don't search).
|
||||
|
||||
-l, --files-with-matches
|
||||
: Only show path of each file with matches.
|
||||
|
||||
-H, --with-filename
|
||||
: Prefix each match with the file name that contains it. This is the
|
||||
default when more than one file is searched.
|
||||
|
||||
--no-filename
|
||||
: Never show the filename for a match. This is the default when
|
||||
one file is searched.
|
||||
|
||||
--heading
|
||||
: Show the file name above clusters of matches from each file.
|
||||
This is the default mode at a tty.
|
||||
|
||||
--no-heading
|
||||
: Don't show any file name heading.
|
||||
|
||||
--hidden
|
||||
: Search hidden directories and files. (Hidden directories and files are
|
||||
skipped by default.)
|
||||
|
||||
-L, --follow
|
||||
: Follow symlinks.
|
||||
|
||||
--maxdepth *NUM*
|
||||
: Descend at most NUM directories below the command line arguments.
|
||||
A value of zero searches only the starting-points themselves.
|
||||
|
||||
--mmap
|
||||
: Search using memory maps when possible. This is enabled by default
|
||||
when ripgrep thinks it will be faster. (Note that mmap searching
|
||||
doesn't currently support the various context related options.)
|
||||
|
||||
--no-mmap
|
||||
: Never use memory maps, even when they might be faster.
|
||||
|
||||
--no-ignore
|
||||
: Don't respect ignore files (.gitignore, .ignore, etc.)
|
||||
This implies --no-ignore-parent.
|
||||
|
||||
--no-ignore-parent
|
||||
: Don't respect ignore files in parent directories.
|
||||
|
||||
--no-ignore-vcs
|
||||
: Don't respect version control ignore files (e.g., .gitignore).
|
||||
Note that .ignore files will continue to be respected.
|
||||
|
||||
--null
|
||||
: Whenever a file name is printed, follow it with a NUL byte.
|
||||
This includes printing filenames before matches, and when printing
|
||||
a list of matching files such as with --count, --files-with-matches
|
||||
and --files.
|
||||
|
||||
-p, --pretty
|
||||
: Alias for --color=always --heading -n.
|
||||
|
||||
-r, --replace *ARG*
|
||||
: Replace every match with the string given when printing search results.
|
||||
Neither this flag nor any other flag will modify your files.
|
||||
|
||||
Capture group indices (e.g., $5) and names (e.g., $foo) are supported
|
||||
in the replacement string.
|
||||
|
||||
-s, --case-sensitive
|
||||
: Search case sensitively. This overrides --ignore-case and --smart-case.
|
||||
|
||||
-S, --smart-case
|
||||
: Search case insensitively if the pattern is all lowercase.
|
||||
Search case sensitively otherwise. This is overridden by either
|
||||
--case-sensitive or --ignore-case.
|
||||
|
||||
-j, --threads *ARG*
|
||||
: The number of threads to use. 0 means use the number of logical CPUs
|
||||
(capped at 6). [default: 0]
|
||||
|
||||
--version
|
||||
: Show the version number of ripgrep and exit.
|
||||
|
||||
--vimgrep
|
||||
: Show results with every match on its own line, including line
|
||||
numbers and column numbers. (With this option, a line with more
|
||||
than one match of the regex will be printed more than once.)
|
||||
|
||||
# FILE TYPE MANAGEMENT OPTIONS
|
||||
|
||||
--type-list
|
||||
: Show all supported file types and their associated globs.
|
||||
|
||||
--type-add *ARG* ...
|
||||
: Add a new glob for a particular file type. Only one glob can be added
|
||||
at a time. Multiple --type-add flags can be provided. Unless --type-clear
|
||||
is used, globs are added to any existing globs inside of ripgrep. Note that
|
||||
this must be passed to every invocation of rg. Type settings are NOT
|
||||
persisted.
|
||||
|
||||
Example: `rg --type-add 'foo:*.foo' -tfoo PATTERN`
|
||||
|
||||
--type-clear *TYPE* ...
|
||||
: Clear the file type globs previously defined for TYPE. This only clears
|
||||
the default type definitions that are found inside of ripgrep. Note
|
||||
that this must be passed to every invocation of rg.
|
||||
33
globset/Cargo.toml
Normal file
33
globset/Cargo.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "globset"
|
||||
version = "0.1.1" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
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
|
||||
simultaneously, and returning all of the globs that matched.
|
||||
"""
|
||||
documentation = "https://docs.rs/globset"
|
||||
homepage = "https://github.com/BurntSushi/ripgrep/tree/master/globset"
|
||||
repository = "https://github.com/BurntSushi/ripgrep/tree/master/globset"
|
||||
readme = "README.md"
|
||||
keywords = ["regex", "glob", "multiple", "set", "pattern"]
|
||||
license = "Unlicense/MIT"
|
||||
|
||||
[lib]
|
||||
name = "globset"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
aho-corasick = "0.5.3"
|
||||
fnv = "1.0"
|
||||
lazy_static = "0.2"
|
||||
log = "0.3"
|
||||
memchr = "0.1"
|
||||
regex = "0.1.77"
|
||||
|
||||
[dev-dependencies]
|
||||
glob = "0.2"
|
||||
|
||||
[features]
|
||||
simd-accel = ["regex/simd-accel"]
|
||||
122
globset/README.md
Normal file
122
globset/README.md
Normal file
@@ -0,0 +1,122 @@
|
||||
globset
|
||||
=======
|
||||
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
|
||||
simultaneously, and returning all of the globs that matched.
|
||||
|
||||
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||
[](https://crates.io/crates/globset)
|
||||
|
||||
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||
|
||||
### Documentation
|
||||
|
||||
[https://docs.rs/globset](https://docs.rs/globset)
|
||||
|
||||
### Usage
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
globset = "0.1"
|
||||
```
|
||||
|
||||
and this to your crate root:
|
||||
|
||||
```rust
|
||||
extern crate globset;
|
||||
```
|
||||
|
||||
### Example: one glob
|
||||
|
||||
This example shows how to match a single glob against a single file path.
|
||||
|
||||
```rust
|
||||
use globset::Glob;
|
||||
|
||||
let glob = try!(Glob::new("*.rs")).compile_matcher();
|
||||
|
||||
assert!(glob.is_match("foo.rs"));
|
||||
assert!(glob.is_match("foo/bar.rs"));
|
||||
assert!(!glob.is_match("Cargo.toml"));
|
||||
```
|
||||
|
||||
### Example: configuring a glob matcher
|
||||
|
||||
This example shows how to use a `GlobBuilder` to configure aspects of match
|
||||
semantics. In this example, we prevent wildcards from matching path separators.
|
||||
|
||||
```rust
|
||||
use globset::GlobBuilder;
|
||||
|
||||
let glob = try!(GlobBuilder::new("*.rs")
|
||||
.literal_separator(true).build()).compile_matcher();
|
||||
|
||||
assert!(glob.is_match("foo.rs"));
|
||||
assert!(!glob.is_match("foo/bar.rs")); // no longer matches
|
||||
assert!(!glob.is_match("Cargo.toml"));
|
||||
```
|
||||
|
||||
### Example: match multiple globs at once
|
||||
|
||||
This example shows how to match multiple glob patterns at once.
|
||||
|
||||
```rust
|
||||
use globset::{Glob, GlobSetBuilder};
|
||||
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
// A GlobBuilder can be used to configure each glob's match semantics
|
||||
// independently.
|
||||
builder.add(try!(Glob::new("*.rs")));
|
||||
builder.add(try!(Glob::new("src/lib.rs")));
|
||||
builder.add(try!(Glob::new("src/**/foo.rs")));
|
||||
let set = try!(builder.build());
|
||||
|
||||
assert_eq!(set.matches("src/bar/baz/foo.rs"), vec![0, 2]);
|
||||
```
|
||||
|
||||
### Performance
|
||||
|
||||
This crate implements globs by converting them to regular expressions, and
|
||||
executing them with the
|
||||
[`regex`](https://github.com/rust-lang-nursery/regex)
|
||||
crate.
|
||||
|
||||
For single glob matching, performance of this crate should be roughly on par
|
||||
with the performance of the
|
||||
[`glob`](https://github.com/rust-lang-nursery/glob)
|
||||
crate. (`*_regex` correspond to benchmarks for this library while `*_glob`
|
||||
correspond to benchmarks for the `glob` library.)
|
||||
Optimizations in the `regex` crate may propel this library past `glob`,
|
||||
particularly when matching longer paths.
|
||||
|
||||
```
|
||||
test ext_glob ... bench: 425 ns/iter (+/- 21)
|
||||
test ext_regex ... bench: 175 ns/iter (+/- 10)
|
||||
test long_glob ... bench: 182 ns/iter (+/- 11)
|
||||
test long_regex ... bench: 173 ns/iter (+/- 10)
|
||||
test short_glob ... bench: 69 ns/iter (+/- 4)
|
||||
test short_regex ... bench: 83 ns/iter (+/- 2)
|
||||
```
|
||||
|
||||
The primary performance advantage of this crate is when matching multiple
|
||||
globs against a single path. With the `glob` crate, one must match each glob
|
||||
synchronously, one after the other. In this crate, many can be matched
|
||||
simultaneously. For example:
|
||||
|
||||
```
|
||||
test many_short_glob ... bench: 1,063 ns/iter (+/- 47)
|
||||
test many_short_regex_set ... bench: 186 ns/iter (+/- 11)
|
||||
```
|
||||
|
||||
### Comparison with the [`glob`](https://github.com/rust-lang-nursery/glob) crate
|
||||
|
||||
* Supports alternate "or" globs, e.g., `*.{foo,bar}`.
|
||||
* Can match non-UTF-8 file paths correctly.
|
||||
* Supports matching multiple globs at once.
|
||||
* Doesn't provide a recursive directory iterator of matching file paths,
|
||||
although I believe this crate should grow one eventually.
|
||||
* Supports case insensitive and require-literal-separator match options, but
|
||||
**doesn't** support the require-literal-leading-dot option.
|
||||
@@ -5,37 +5,53 @@ tool itself, see the benchsuite directory.
|
||||
#![feature(test)]
|
||||
|
||||
extern crate glob;
|
||||
extern crate globset;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate regex;
|
||||
extern crate test;
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
use globset::{Candidate, Glob, GlobMatcher, GlobSet, GlobSetBuilder};
|
||||
|
||||
const EXT: &'static str = "some/a/bigger/path/to/the/crazy/needle.txt";
|
||||
const EXT_PAT: &'static str = "*.txt";
|
||||
|
||||
const SHORT: &'static str = "some/needle.txt";
|
||||
const SHORT_PAT: &'static str = "some/**/needle.txt";
|
||||
|
||||
const LONG: &'static str = "some/a/bigger/path/to/the/crazy/needle.txt";
|
||||
const LONG_PAT: &'static str = "some/**/needle.txt";
|
||||
|
||||
#[allow(dead_code, unused_variables)]
|
||||
#[path = "../src/glob.rs"]
|
||||
mod reglob;
|
||||
|
||||
fn new_glob(pat: &str) -> glob::Pattern {
|
||||
glob::Pattern::new(pat).unwrap()
|
||||
}
|
||||
|
||||
fn new_reglob(pat: &str) -> reglob::Set {
|
||||
let mut builder = reglob::SetBuilder::new();
|
||||
builder.add(pat).unwrap();
|
||||
fn new_reglob(pat: &str) -> GlobMatcher {
|
||||
Glob::new(pat).unwrap().compile_matcher()
|
||||
}
|
||||
|
||||
fn new_reglob_many(pats: &[&str]) -> GlobSet {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
for pat in pats {
|
||||
builder.add(Glob::new(pat).unwrap());
|
||||
}
|
||||
builder.build().unwrap()
|
||||
}
|
||||
|
||||
fn new_reglob_many(pats: &[&str]) -> reglob::Set {
|
||||
let mut builder = reglob::SetBuilder::new();
|
||||
for pat in pats {
|
||||
builder.add(pat).unwrap();
|
||||
}
|
||||
builder.build().unwrap()
|
||||
#[bench]
|
||||
fn ext_glob(b: &mut test::Bencher) {
|
||||
let pat = new_glob(EXT_PAT);
|
||||
b.iter(|| assert!(pat.matches(EXT)));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn ext_regex(b: &mut test::Bencher) {
|
||||
let set = new_reglob(EXT_PAT);
|
||||
let cand = Candidate::new(EXT);
|
||||
b.iter(|| assert!(set.is_match_candidate(&cand)));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
@@ -47,7 +63,8 @@ fn short_glob(b: &mut test::Bencher) {
|
||||
#[bench]
|
||||
fn short_regex(b: &mut test::Bencher) {
|
||||
let set = new_reglob(SHORT_PAT);
|
||||
b.iter(|| assert!(set.is_match(SHORT)));
|
||||
let cand = Candidate::new(SHORT);
|
||||
b.iter(|| assert!(set.is_match_candidate(&cand)));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
@@ -59,7 +76,8 @@ fn long_glob(b: &mut test::Bencher) {
|
||||
#[bench]
|
||||
fn long_regex(b: &mut test::Bencher) {
|
||||
let set = new_reglob(LONG_PAT);
|
||||
b.iter(|| assert!(set.is_match(LONG)));
|
||||
let cand = Candidate::new(LONG);
|
||||
b.iter(|| assert!(set.is_match_candidate(&cand)));
|
||||
}
|
||||
|
||||
const MANY_SHORT_GLOBS: &'static [&'static str] = &[
|
||||
@@ -101,26 +119,3 @@ fn many_short_regex_set(b: &mut test::Bencher) {
|
||||
let set = new_reglob_many(MANY_SHORT_GLOBS);
|
||||
b.iter(|| assert_eq!(2, set.matches(MANY_SHORT_SEARCH).iter().count()));
|
||||
}
|
||||
|
||||
// This is the fastest on my system (beating many_glob by about 2x). This
|
||||
// suggests that a RegexSet needs quite a few regexes (or a larger haystack)
|
||||
// in order for it to scale.
|
||||
//
|
||||
// TODO(burntsushi): come up with a benchmark that uses more complex patterns
|
||||
// or a longer haystack.
|
||||
#[bench]
|
||||
fn many_short_regex_pattern(b: &mut test::Bencher) {
|
||||
let pats: Vec<_> = MANY_SHORT_GLOBS.iter().map(|&s| {
|
||||
let pat = reglob::Pattern::new(s).unwrap();
|
||||
regex::Regex::new(&pat.to_regex()).unwrap()
|
||||
}).collect();
|
||||
b.iter(|| {
|
||||
let mut count = 0;
|
||||
for pat in &pats {
|
||||
if pat.is_match(MANY_SHORT_SEARCH) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(2, count);
|
||||
})
|
||||
}
|
||||
1300
globset/src/glob.rs
Normal file
1300
globset/src/glob.rs
Normal file
File diff suppressed because it is too large
Load Diff
798
globset/src/lib.rs
Normal file
798
globset/src/lib.rs
Normal file
@@ -0,0 +1,798 @@
|
||||
/*!
|
||||
The globset crate provides 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 simultaneously, and returning all of the globs that
|
||||
matched. For example, given this set of globs:
|
||||
|
||||
```ignore
|
||||
*.rs
|
||||
src/lib.rs
|
||||
src/**/foo.rs
|
||||
```
|
||||
|
||||
and a path `src/bar/baz/foo.rs`, then the set would report the first and third
|
||||
globs as matching.
|
||||
|
||||
Single glob matching is also provided and is done by converting globs to
|
||||
|
||||
# Example: one glob
|
||||
|
||||
This example shows how to match a single glob against a single file path.
|
||||
|
||||
```
|
||||
# fn example() -> Result<(), globset::Error> {
|
||||
use globset::Glob;
|
||||
|
||||
let glob = try!(Glob::new("*.rs")).compile_matcher();
|
||||
|
||||
assert!(glob.is_match("foo.rs"));
|
||||
assert!(glob.is_match("foo/bar.rs"));
|
||||
assert!(!glob.is_match("Cargo.toml"));
|
||||
# Ok(()) } example().unwrap();
|
||||
```
|
||||
|
||||
# Example: configuring a glob matcher
|
||||
|
||||
This example shows how to use a `GlobBuilder` to configure aspects of match
|
||||
semantics. In this example, we prevent wildcards from matching path separators.
|
||||
|
||||
```
|
||||
# fn example() -> Result<(), globset::Error> {
|
||||
use globset::GlobBuilder;
|
||||
|
||||
let glob = try!(GlobBuilder::new("*.rs")
|
||||
.literal_separator(true).build()).compile_matcher();
|
||||
|
||||
assert!(glob.is_match("foo.rs"));
|
||||
assert!(!glob.is_match("foo/bar.rs")); // no longer matches
|
||||
assert!(!glob.is_match("Cargo.toml"));
|
||||
# Ok(()) } example().unwrap();
|
||||
```
|
||||
|
||||
# Example: match multiple globs at once
|
||||
|
||||
This example shows how to match multiple glob patterns at once.
|
||||
|
||||
```
|
||||
# fn example() -> Result<(), globset::Error> {
|
||||
use globset::{Glob, GlobSetBuilder};
|
||||
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
// A GlobBuilder can be used to configure each glob's match semantics
|
||||
// independently.
|
||||
builder.add(try!(Glob::new("*.rs")));
|
||||
builder.add(try!(Glob::new("src/lib.rs")));
|
||||
builder.add(try!(Glob::new("src/**/foo.rs")));
|
||||
let set = try!(builder.build());
|
||||
|
||||
assert_eq!(set.matches("src/bar/baz/foo.rs"), vec![0, 2]);
|
||||
# Ok(()) } example().unwrap();
|
||||
```
|
||||
|
||||
# Syntax
|
||||
|
||||
Standard Unix-style glob syntax is supported:
|
||||
|
||||
* `?` matches any single character. (If the `literal_separator` option is
|
||||
enabled, then `?` can never match a path separator.)
|
||||
* `*` matches zero or more characters. (If the `literal_separator` option is
|
||||
enabled, then `*` can never match a path separator.)
|
||||
* `**` recursively matches directories but are only legal in three situations.
|
||||
First, if the glob starts with <code>\*\*/</code>, then it matches
|
||||
all directories. For example, <code>\*\*/foo</code> matches `foo`
|
||||
and `bar/foo` but not `foo/bar`. Secondly, if the glob ends with
|
||||
<code>/\*\*</code>, then it matches all sub-entries. For example,
|
||||
<code>foo/\*\*</code> matches `foo/a` and `foo/a/b`, but not `foo`.
|
||||
Thirdly, if the glob contains <code>/\*\*/</code> anywhere within
|
||||
the pattern, then it matches zero or more directories. Using `**` anywhere
|
||||
else is illegal (N.B. the glob `**` is allowed and means "match everything").
|
||||
* `{a,b}` matches `a` or `b` where `a` and `b` are arbitrary glob patterns.
|
||||
(N.B. Nesting `{...}` is not currently allowed.)
|
||||
* `[ab]` matches `a` or `b` where `a` and `b` are characters. Use
|
||||
`[!ab]` to match any character except for `a` and `b`.
|
||||
* Metacharacters such as `*` and `?` can be escaped with character class
|
||||
notation. e.g., `[*]` matches `*`.
|
||||
|
||||
A `GlobBuilder` can be used to prevent wildcards from matching path separators,
|
||||
or to enable case insensitive matching.
|
||||
*/
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
extern crate aho_corasick;
|
||||
extern crate fnv;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate memchr;
|
||||
extern crate regex;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::error::Error as StdError;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt;
|
||||
use std::hash;
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
|
||||
use aho_corasick::{Automaton, AcAutomaton, FullAcAutomaton};
|
||||
use regex::bytes::{Regex, RegexBuilder, RegexSet};
|
||||
|
||||
use pathutil::{
|
||||
file_name, file_name_ext, normalize_path, os_str_bytes, path_bytes,
|
||||
};
|
||||
use glob::MatchStrategy;
|
||||
pub use glob::{Glob, GlobBuilder, GlobMatcher};
|
||||
|
||||
mod glob;
|
||||
mod pathutil;
|
||||
|
||||
macro_rules! eprintln {
|
||||
($($tt:tt)*) => {{
|
||||
use std::io::Write;
|
||||
let _ = writeln!(&mut ::std::io::stderr(), $($tt)*);
|
||||
}}
|
||||
}
|
||||
|
||||
/// Represents an error that can occur when parsing a glob pattern.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Occurs when a use of `**` is invalid. Namely, `**` can only appear
|
||||
/// adjacent to a path separator, or the beginning/end of a glob.
|
||||
InvalidRecursive,
|
||||
/// Occurs when a character class (e.g., `[abc]`) is not closed.
|
||||
UnclosedClass,
|
||||
/// Occurs when a range in a character (e.g., `[a-z]`) is invalid. For
|
||||
/// example, if the range starts with a lexicographically larger character
|
||||
/// than it ends with.
|
||||
InvalidRange(char, char),
|
||||
/// Occurs when a `}` is found without a matching `{`.
|
||||
UnopenedAlternates,
|
||||
/// Occurs when a `{` is found without a matching `}`.
|
||||
UnclosedAlternates,
|
||||
/// Occurs when an alternating group is nested inside another alternating
|
||||
/// group, e.g., `{{a,b},{c,d}}`.
|
||||
NestedAlternates,
|
||||
/// An error associated with parsing or compiling a regex.
|
||||
Regex(String),
|
||||
}
|
||||
|
||||
impl StdError for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::InvalidRecursive => {
|
||||
"invalid use of **; must be one path component"
|
||||
}
|
||||
Error::UnclosedClass => {
|
||||
"unclosed character class; missing ']'"
|
||||
}
|
||||
Error::InvalidRange(_, _) => {
|
||||
"invalid character range"
|
||||
}
|
||||
Error::UnopenedAlternates => {
|
||||
"unopened alternate group; missing '{' \
|
||||
(maybe escape '}' with '[}]'?)"
|
||||
}
|
||||
Error::UnclosedAlternates => {
|
||||
"unclosed alternate group; missing '}' \
|
||||
(maybe escape '{' with '[{]'?)"
|
||||
}
|
||||
Error::NestedAlternates => {
|
||||
"nested alternate groups are not allowed"
|
||||
}
|
||||
Error::Regex(ref err) => err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::InvalidRecursive
|
||||
| Error::UnclosedClass
|
||||
| Error::UnopenedAlternates
|
||||
| Error::UnclosedAlternates
|
||||
| Error::NestedAlternates
|
||||
| Error::Regex(_) => {
|
||||
write!(f, "{}", self.description())
|
||||
}
|
||||
Error::InvalidRange(s, e) => {
|
||||
write!(f, "invalid range; '{}' > '{}'", s, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_regex(pat: &str) -> Result<Regex, Error> {
|
||||
RegexBuilder::new(pat)
|
||||
.dot_matches_new_line(true)
|
||||
.size_limit(10 * (1 << 20))
|
||||
.dfa_size_limit(10 * (1 << 20))
|
||||
.compile()
|
||||
.map_err(|err| Error::Regex(err.to_string()))
|
||||
}
|
||||
|
||||
fn new_regex_set<I, S>(pats: I) -> Result<RegexSet, Error>
|
||||
where S: AsRef<str>, I: IntoIterator<Item=S> {
|
||||
RegexSet::new(pats).map_err(|err| Error::Regex(err.to_string()))
|
||||
}
|
||||
|
||||
type Fnv = hash::BuildHasherDefault<fnv::FnvHasher>;
|
||||
|
||||
/// GlobSet represents a group of globs that can be matched together in a
|
||||
/// single pass.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GlobSet {
|
||||
len: usize,
|
||||
strats: Vec<GlobSetMatchStrategy>,
|
||||
}
|
||||
|
||||
impl GlobSet {
|
||||
/// Returns true if this set is empty, and therefore matches nothing.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
/// Returns the number of globs in this set.
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Returns true if any glob in this set matches the path given.
|
||||
pub fn is_match<P: AsRef<Path>>(&self, path: P) -> bool {
|
||||
self.is_match_candidate(&Candidate::new(path.as_ref()))
|
||||
}
|
||||
|
||||
/// Returns true if any glob in this set matches the path given.
|
||||
///
|
||||
/// This takes a Candidate as input, which can be used to amortize the
|
||||
/// cost of preparing a path for matching.
|
||||
pub fn is_match_candidate(&self, path: &Candidate) -> bool {
|
||||
if self.is_empty() {
|
||||
return false;
|
||||
}
|
||||
for strat in &self.strats {
|
||||
if strat.is_match(path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns the sequence number of every glob pattern that matches the
|
||||
/// given path.
|
||||
pub fn matches<P: AsRef<Path>>(&self, path: P) -> Vec<usize> {
|
||||
self.matches_candidate(&Candidate::new(path.as_ref()))
|
||||
}
|
||||
|
||||
/// Returns the sequence number of every glob pattern that matches the
|
||||
/// given path.
|
||||
///
|
||||
/// This takes a Candidate as input, which can be used to amortize the
|
||||
/// cost of preparing a path for matching.
|
||||
pub fn matches_candidate(&self, path: &Candidate) -> Vec<usize> {
|
||||
let mut into = vec![];
|
||||
if self.is_empty() {
|
||||
return into;
|
||||
}
|
||||
self.matches_candidate_into(path, &mut into);
|
||||
into
|
||||
}
|
||||
|
||||
/// Adds the sequence number of every glob pattern that matches the given
|
||||
/// path to the vec given.
|
||||
///
|
||||
/// `into` is is cleared before matching begins, and contains the set of
|
||||
/// sequence numbers (in ascending order) after matching ends. If no globs
|
||||
/// were matched, then `into` will be empty.
|
||||
pub fn matches_into<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
into: &mut Vec<usize>,
|
||||
) {
|
||||
self.matches_candidate_into(&Candidate::new(path.as_ref()), into);
|
||||
}
|
||||
|
||||
/// Adds the sequence number of every glob pattern that matches the given
|
||||
/// path to the vec given.
|
||||
///
|
||||
/// `into` is is cleared before matching begins, and contains the set of
|
||||
/// sequence numbers (in ascending order) after matching ends. If no globs
|
||||
/// were matched, then `into` will be empty.
|
||||
///
|
||||
/// This takes a Candidate as input, which can be used to amortize the
|
||||
/// cost of preparing a path for matching.
|
||||
pub fn matches_candidate_into(
|
||||
&self,
|
||||
path: &Candidate,
|
||||
into: &mut Vec<usize>,
|
||||
) {
|
||||
into.clear();
|
||||
if self.is_empty() {
|
||||
return;
|
||||
}
|
||||
for strat in &self.strats {
|
||||
strat.matches_into(path, into);
|
||||
}
|
||||
into.sort();
|
||||
into.dedup();
|
||||
}
|
||||
|
||||
fn new(pats: &[Glob]) -> Result<GlobSet, Error> {
|
||||
if pats.is_empty() {
|
||||
return Ok(GlobSet { len: 0, strats: vec![] });
|
||||
}
|
||||
let mut lits = LiteralStrategy::new();
|
||||
let mut base_lits = BasenameLiteralStrategy::new();
|
||||
let mut exts = ExtensionStrategy::new();
|
||||
let mut prefixes = MultiStrategyBuilder::new();
|
||||
let mut suffixes = MultiStrategyBuilder::new();
|
||||
let mut required_exts = RequiredExtensionStrategyBuilder::new();
|
||||
let mut regexes = MultiStrategyBuilder::new();
|
||||
for (i, p) in pats.iter().enumerate() {
|
||||
match MatchStrategy::new(p) {
|
||||
MatchStrategy::Literal(lit) => {
|
||||
lits.add(i, lit);
|
||||
}
|
||||
MatchStrategy::BasenameLiteral(lit) => {
|
||||
base_lits.add(i, lit);
|
||||
}
|
||||
MatchStrategy::Extension(ext) => {
|
||||
exts.add(i, ext);
|
||||
}
|
||||
MatchStrategy::Prefix(prefix) => {
|
||||
prefixes.add(i, prefix);
|
||||
}
|
||||
MatchStrategy::Suffix { suffix, component } => {
|
||||
if component {
|
||||
lits.add(i, suffix[1..].to_string());
|
||||
}
|
||||
suffixes.add(i, suffix);
|
||||
}
|
||||
MatchStrategy::RequiredExtension(ext) => {
|
||||
required_exts.add(i, ext, p.regex().to_owned());
|
||||
}
|
||||
MatchStrategy::Regex => {
|
||||
debug!("glob converted to regex: {:?}", p);
|
||||
regexes.add(i, p.regex().to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("built glob set; {} literals, {} basenames, {} extensions, \
|
||||
{} prefixes, {} suffixes, {} required extensions, {} regexes",
|
||||
lits.0.len(), base_lits.0.len(), exts.0.len(),
|
||||
prefixes.literals.len(), suffixes.literals.len(),
|
||||
required_exts.0.len(), regexes.literals.len());
|
||||
Ok(GlobSet {
|
||||
len: pats.len(),
|
||||
strats: vec![
|
||||
GlobSetMatchStrategy::Extension(exts),
|
||||
GlobSetMatchStrategy::BasenameLiteral(base_lits),
|
||||
GlobSetMatchStrategy::Literal(lits),
|
||||
GlobSetMatchStrategy::Suffix(suffixes.suffix()),
|
||||
GlobSetMatchStrategy::Prefix(prefixes.prefix()),
|
||||
GlobSetMatchStrategy::RequiredExtension(
|
||||
try!(required_exts.build())),
|
||||
GlobSetMatchStrategy::Regex(try!(regexes.regex_set())),
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// GlobSetBuilder builds a group of patterns that can be used to
|
||||
/// simultaneously match a file path.
|
||||
pub struct GlobSetBuilder {
|
||||
pats: Vec<Glob>,
|
||||
}
|
||||
|
||||
impl GlobSetBuilder {
|
||||
/// Create a new GlobSetBuilder. A GlobSetBuilder can be used to add new
|
||||
/// patterns. Once all patterns have been added, `build` should be called
|
||||
/// to produce a `GlobSet`, which can then be used for matching.
|
||||
pub fn new() -> GlobSetBuilder {
|
||||
GlobSetBuilder { pats: vec![] }
|
||||
}
|
||||
|
||||
/// Builds a new matcher from all of the glob patterns added so far.
|
||||
///
|
||||
/// Once a matcher is built, no new patterns can be added to it.
|
||||
pub fn build(&self) -> Result<GlobSet, Error> {
|
||||
GlobSet::new(&self.pats)
|
||||
}
|
||||
|
||||
/// Add a new pattern to this set.
|
||||
#[allow(dead_code)]
|
||||
pub fn add(&mut self, pat: Glob) -> &mut GlobSetBuilder {
|
||||
self.pats.push(pat);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A candidate path for matching.
|
||||
///
|
||||
/// All glob matching in this crate operates on `Candidate` values.
|
||||
/// Constructing candidates has a very small cost associated with it, so
|
||||
/// callers may find it beneficial to amortize that cost when matching a single
|
||||
/// path against multiple globs or sets of globs.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Candidate<'a> {
|
||||
path: Cow<'a, [u8]>,
|
||||
basename: Cow<'a, [u8]>,
|
||||
ext: &'a OsStr,
|
||||
}
|
||||
|
||||
impl<'a> Candidate<'a> {
|
||||
/// Create a new candidate for matching from the given path.
|
||||
pub fn new<P: AsRef<Path> + ?Sized>(path: &'a P) -> Candidate<'a> {
|
||||
let path = path.as_ref();
|
||||
let basename = file_name(path).unwrap_or(OsStr::new(""));
|
||||
Candidate {
|
||||
path: normalize_path(path_bytes(path)),
|
||||
basename: os_str_bytes(basename),
|
||||
ext: file_name_ext(basename).unwrap_or(OsStr::new("")),
|
||||
}
|
||||
}
|
||||
|
||||
fn path_prefix(&self, max: usize) -> &[u8] {
|
||||
if self.path.len() <= max {
|
||||
&*self.path
|
||||
} else {
|
||||
&self.path[..max]
|
||||
}
|
||||
}
|
||||
|
||||
fn path_suffix(&self, max: usize) -> &[u8] {
|
||||
if self.path.len() <= max {
|
||||
&*self.path
|
||||
} else {
|
||||
&self.path[self.path.len() - max..]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum GlobSetMatchStrategy {
|
||||
Literal(LiteralStrategy),
|
||||
BasenameLiteral(BasenameLiteralStrategy),
|
||||
Extension(ExtensionStrategy),
|
||||
Prefix(PrefixStrategy),
|
||||
Suffix(SuffixStrategy),
|
||||
RequiredExtension(RequiredExtensionStrategy),
|
||||
Regex(RegexSetStrategy),
|
||||
}
|
||||
|
||||
impl GlobSetMatchStrategy {
|
||||
fn is_match(&self, candidate: &Candidate) -> bool {
|
||||
use self::GlobSetMatchStrategy::*;
|
||||
match *self {
|
||||
Literal(ref s) => s.is_match(candidate),
|
||||
BasenameLiteral(ref s) => s.is_match(candidate),
|
||||
Extension(ref s) => s.is_match(candidate),
|
||||
Prefix(ref s) => s.is_match(candidate),
|
||||
Suffix(ref s) => s.is_match(candidate),
|
||||
RequiredExtension(ref s) => s.is_match(candidate),
|
||||
Regex(ref s) => s.is_match(candidate),
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
|
||||
use self::GlobSetMatchStrategy::*;
|
||||
match *self {
|
||||
Literal(ref s) => s.matches_into(candidate, matches),
|
||||
BasenameLiteral(ref s) => s.matches_into(candidate, matches),
|
||||
Extension(ref s) => s.matches_into(candidate, matches),
|
||||
Prefix(ref s) => s.matches_into(candidate, matches),
|
||||
Suffix(ref s) => s.matches_into(candidate, matches),
|
||||
RequiredExtension(ref s) => s.matches_into(candidate, matches),
|
||||
Regex(ref s) => s.matches_into(candidate, matches),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct LiteralStrategy(BTreeMap<Vec<u8>, Vec<usize>>);
|
||||
|
||||
impl LiteralStrategy {
|
||||
fn new() -> LiteralStrategy {
|
||||
LiteralStrategy(BTreeMap::new())
|
||||
}
|
||||
|
||||
fn add(&mut self, global_index: usize, lit: String) {
|
||||
self.0.entry(lit.into_bytes()).or_insert(vec![]).push(global_index);
|
||||
}
|
||||
|
||||
fn is_match(&self, candidate: &Candidate) -> bool {
|
||||
self.0.contains_key(&*candidate.path)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
|
||||
if let Some(hits) = self.0.get(&*candidate.path) {
|
||||
matches.extend(hits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct BasenameLiteralStrategy(BTreeMap<Vec<u8>, Vec<usize>>);
|
||||
|
||||
impl BasenameLiteralStrategy {
|
||||
fn new() -> BasenameLiteralStrategy {
|
||||
BasenameLiteralStrategy(BTreeMap::new())
|
||||
}
|
||||
|
||||
fn add(&mut self, global_index: usize, lit: String) {
|
||||
self.0.entry(lit.into_bytes()).or_insert(vec![]).push(global_index);
|
||||
}
|
||||
|
||||
fn is_match(&self, candidate: &Candidate) -> bool {
|
||||
if candidate.basename.is_empty() {
|
||||
return false;
|
||||
}
|
||||
self.0.contains_key(&*candidate.basename)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
|
||||
if candidate.basename.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Some(hits) = self.0.get(&*candidate.basename) {
|
||||
matches.extend(hits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ExtensionStrategy(HashMap<OsString, Vec<usize>, Fnv>);
|
||||
|
||||
impl ExtensionStrategy {
|
||||
fn new() -> ExtensionStrategy {
|
||||
ExtensionStrategy(HashMap::with_hasher(Fnv::default()))
|
||||
}
|
||||
|
||||
fn add(&mut self, global_index: usize, ext: OsString) {
|
||||
self.0.entry(ext).or_insert(vec![]).push(global_index);
|
||||
}
|
||||
|
||||
fn is_match(&self, candidate: &Candidate) -> bool {
|
||||
if candidate.ext.is_empty() {
|
||||
return false;
|
||||
}
|
||||
self.0.contains_key(candidate.ext)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
|
||||
if candidate.ext.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Some(hits) = self.0.get(candidate.ext) {
|
||||
matches.extend(hits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct PrefixStrategy {
|
||||
matcher: FullAcAutomaton<Vec<u8>>,
|
||||
map: Vec<usize>,
|
||||
longest: usize,
|
||||
}
|
||||
|
||||
impl PrefixStrategy {
|
||||
fn is_match(&self, candidate: &Candidate) -> bool {
|
||||
let path = candidate.path_prefix(self.longest);
|
||||
for m in self.matcher.find_overlapping(path) {
|
||||
if m.start == 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
|
||||
let path = candidate.path_prefix(self.longest);
|
||||
for m in self.matcher.find_overlapping(path) {
|
||||
if m.start == 0 {
|
||||
matches.push(self.map[m.pati]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct SuffixStrategy {
|
||||
matcher: FullAcAutomaton<Vec<u8>>,
|
||||
map: Vec<usize>,
|
||||
longest: usize,
|
||||
}
|
||||
|
||||
impl SuffixStrategy {
|
||||
fn is_match(&self, candidate: &Candidate) -> bool {
|
||||
let path = candidate.path_suffix(self.longest);
|
||||
for m in self.matcher.find_overlapping(path) {
|
||||
if m.end == path.len() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
|
||||
let path = candidate.path_suffix(self.longest);
|
||||
for m in self.matcher.find_overlapping(path) {
|
||||
if m.end == path.len() {
|
||||
matches.push(self.map[m.pati]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct RequiredExtensionStrategy(HashMap<OsString, Vec<(usize, Regex)>, Fnv>);
|
||||
|
||||
impl RequiredExtensionStrategy {
|
||||
fn is_match(&self, candidate: &Candidate) -> bool {
|
||||
if candidate.ext.is_empty() {
|
||||
return false;
|
||||
}
|
||||
match self.0.get(candidate.ext) {
|
||||
None => false,
|
||||
Some(regexes) => {
|
||||
for &(_, ref re) in regexes {
|
||||
if re.is_match(&*candidate.path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
|
||||
if candidate.ext.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Some(regexes) = self.0.get(candidate.ext) {
|
||||
for &(global_index, ref re) in regexes {
|
||||
if re.is_match(&*candidate.path) {
|
||||
matches.push(global_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct RegexSetStrategy {
|
||||
matcher: RegexSet,
|
||||
map: Vec<usize>,
|
||||
}
|
||||
|
||||
impl RegexSetStrategy {
|
||||
fn is_match(&self, candidate: &Candidate) -> bool {
|
||||
self.matcher.is_match(&*candidate.path)
|
||||
}
|
||||
|
||||
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
|
||||
for i in self.matcher.matches(&*candidate.path) {
|
||||
matches.push(self.map[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct MultiStrategyBuilder {
|
||||
literals: Vec<String>,
|
||||
map: Vec<usize>,
|
||||
longest: usize,
|
||||
}
|
||||
|
||||
impl MultiStrategyBuilder {
|
||||
fn new() -> MultiStrategyBuilder {
|
||||
MultiStrategyBuilder {
|
||||
literals: vec![],
|
||||
map: vec![],
|
||||
longest: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn add(&mut self, global_index: usize, literal: String) {
|
||||
if literal.len() > self.longest {
|
||||
self.longest = literal.len();
|
||||
}
|
||||
self.map.push(global_index);
|
||||
self.literals.push(literal);
|
||||
}
|
||||
|
||||
fn prefix(self) -> PrefixStrategy {
|
||||
let it = self.literals.into_iter().map(|s| s.into_bytes());
|
||||
PrefixStrategy {
|
||||
matcher: AcAutomaton::new(it).into_full(),
|
||||
map: self.map,
|
||||
longest: self.longest,
|
||||
}
|
||||
}
|
||||
|
||||
fn suffix(self) -> SuffixStrategy {
|
||||
let it = self.literals.into_iter().map(|s| s.into_bytes());
|
||||
SuffixStrategy {
|
||||
matcher: AcAutomaton::new(it).into_full(),
|
||||
map: self.map,
|
||||
longest: self.longest,
|
||||
}
|
||||
}
|
||||
|
||||
fn regex_set(self) -> Result<RegexSetStrategy, Error> {
|
||||
Ok(RegexSetStrategy {
|
||||
matcher: try!(new_regex_set(self.literals)),
|
||||
map: self.map,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct RequiredExtensionStrategyBuilder(
|
||||
HashMap<OsString, Vec<(usize, String)>>,
|
||||
);
|
||||
|
||||
impl RequiredExtensionStrategyBuilder {
|
||||
fn new() -> RequiredExtensionStrategyBuilder {
|
||||
RequiredExtensionStrategyBuilder(HashMap::new())
|
||||
}
|
||||
|
||||
fn add(&mut self, global_index: usize, ext: OsString, regex: String) {
|
||||
self.0.entry(ext).or_insert(vec![]).push((global_index, regex));
|
||||
}
|
||||
|
||||
fn build(self) -> Result<RequiredExtensionStrategy, Error> {
|
||||
let mut exts = HashMap::with_hasher(Fnv::default());
|
||||
for (ext, regexes) in self.0.into_iter() {
|
||||
exts.insert(ext.clone(), vec![]);
|
||||
for (global_index, regex) in regexes {
|
||||
let compiled = try!(new_regex(®ex));
|
||||
exts.get_mut(&ext).unwrap().push((global_index, compiled));
|
||||
}
|
||||
}
|
||||
Ok(RequiredExtensionStrategy(exts))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::GlobSetBuilder;
|
||||
use glob::Glob;
|
||||
|
||||
#[test]
|
||||
fn set_works() {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
builder.add(Glob::new("src/**/*.rs").unwrap());
|
||||
builder.add(Glob::new("*.c").unwrap());
|
||||
builder.add(Glob::new("src/lib.rs").unwrap());
|
||||
let set = builder.build().unwrap();
|
||||
|
||||
assert!(set.is_match("foo.c"));
|
||||
assert!(set.is_match("src/foo.c"));
|
||||
assert!(!set.is_match("foo.rs"));
|
||||
assert!(!set.is_match("tests/foo.rs"));
|
||||
assert!(set.is_match("src/foo.rs"));
|
||||
assert!(set.is_match("src/grep/src/main.rs"));
|
||||
|
||||
let matches = set.matches("src/lib.rs");
|
||||
assert_eq!(2, matches.len());
|
||||
assert_eq!(0, matches[0]);
|
||||
assert_eq!(2, matches[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_set_works() {
|
||||
let set = GlobSetBuilder::new().build().unwrap();
|
||||
assert!(!set.is_match(""));
|
||||
assert!(!set.is_match("a"));
|
||||
}
|
||||
}
|
||||
178
globset/src/pathutil.rs
Normal file
178
globset/src/pathutil.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
/// The final component of the path, if it is a normal file.
|
||||
///
|
||||
/// If the path terminates in ., .., or consists solely of a root of prefix,
|
||||
/// file_name will return None.
|
||||
#[cfg(unix)]
|
||||
pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
|
||||
path: &'a P,
|
||||
) -> Option<&'a OsStr> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use memchr::memrchr;
|
||||
|
||||
let path = path.as_ref().as_os_str().as_bytes();
|
||||
if path.is_empty() {
|
||||
return None;
|
||||
} else if path.len() == 1 && path[0] == b'.' {
|
||||
return None;
|
||||
} else if path.last() == Some(&b'.') {
|
||||
return None;
|
||||
} else if path.len() >= 2 && &path[path.len() - 2..] == &b".."[..] {
|
||||
return None;
|
||||
}
|
||||
let last_slash = memrchr(b'/', path).map(|i| i + 1).unwrap_or(0);
|
||||
Some(OsStr::from_bytes(&path[last_slash..]))
|
||||
}
|
||||
|
||||
/// The final component of the path, if it is a normal file.
|
||||
///
|
||||
/// If the path terminates in ., .., or consists solely of a root of prefix,
|
||||
/// file_name will return None.
|
||||
#[cfg(not(unix))]
|
||||
pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
|
||||
path: &'a P,
|
||||
) -> Option<&'a OsStr> {
|
||||
path.as_ref().file_name()
|
||||
}
|
||||
|
||||
/// Return a file extension given a path's file name.
|
||||
///
|
||||
/// Note that this does NOT match the semantics of std::path::Path::extension.
|
||||
/// Namely, the extension includes the `.` and matching is otherwise more
|
||||
/// liberal. Specifically, the extenion is:
|
||||
///
|
||||
/// * None, if the file name given is empty;
|
||||
/// * None, if there is no embedded `.`;
|
||||
/// * Otherwise, the portion of the file name starting with the final `.`.
|
||||
///
|
||||
/// e.g., A file name of `.rs` has an extension `.rs`.
|
||||
///
|
||||
/// N.B. This is done to make certain glob match optimizations easier. Namely,
|
||||
/// a pattern like `*.rs` is obviously trying to match files with a `rs`
|
||||
/// extension, but it also matches files like `.rs`, which doesn't have an
|
||||
/// extension according to std::path::Path::extension.
|
||||
pub fn file_name_ext(name: &OsStr) -> Option<&OsStr> {
|
||||
// Yes, these functions are awful, and yes, we are completely violating
|
||||
// the abstraction barrier of std::ffi. The barrier we're violating is
|
||||
// that an OsStr's encoding is *ASCII compatible*. While this is obviously
|
||||
// true on Unix systems, it's also true on Windows because an OsStr uses
|
||||
// WTF-8 internally: https://simonsapin.github.io/wtf-8/
|
||||
//
|
||||
// We should consider doing the same for the other path utility functions.
|
||||
// Right now, we don't break any barriers, but Windows users are paying
|
||||
// for it.
|
||||
//
|
||||
// Got any better ideas that don't cost anything? Hit me up. ---AG
|
||||
unsafe fn os_str_as_u8_slice(s: &OsStr) -> &[u8] {
|
||||
::std::mem::transmute(s)
|
||||
}
|
||||
unsafe fn u8_slice_as_os_str(s: &[u8]) -> &OsStr {
|
||||
::std::mem::transmute(s)
|
||||
}
|
||||
if name.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let name = unsafe { os_str_as_u8_slice(name) };
|
||||
for (i, &b) in name.iter().enumerate().rev() {
|
||||
if b == b'.' {
|
||||
return Some(unsafe { u8_slice_as_os_str(&name[i..]) });
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Return raw bytes of a path, transcoded to UTF-8 if necessary.
|
||||
pub fn path_bytes(path: &Path) -> Cow<[u8]> {
|
||||
os_str_bytes(path.as_os_str())
|
||||
}
|
||||
|
||||
/// Return the raw bytes of the given OS string, possibly transcoded to UTF-8.
|
||||
#[cfg(unix)]
|
||||
pub fn os_str_bytes(s: &OsStr) -> Cow<[u8]> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
Cow::Borrowed(s.as_bytes())
|
||||
}
|
||||
|
||||
/// Return the raw bytes of the given OS string, possibly transcoded to UTF-8.
|
||||
#[cfg(not(unix))]
|
||||
pub fn os_str_bytes(s: &OsStr) -> Cow<[u8]> {
|
||||
// TODO(burntsushi): On Windows, OS strings are WTF-8, which is a superset
|
||||
// of UTF-8, so even if we could get at the raw bytes, they wouldn't
|
||||
// be useful. We *must* convert to UTF-8 before doing path matching.
|
||||
// Unfortunate, but necessary.
|
||||
match s.to_string_lossy() {
|
||||
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
|
||||
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes a path to use `/` as a separator everywhere, even on platforms
|
||||
/// that recognize other characters as separators.
|
||||
#[cfg(unix)]
|
||||
pub fn normalize_path(path: Cow<[u8]>) -> Cow<[u8]> {
|
||||
// UNIX only uses /, so we're good.
|
||||
path
|
||||
}
|
||||
|
||||
/// Normalizes a path to use `/` as a separator everywhere, even on platforms
|
||||
/// that recognize other characters as separators.
|
||||
#[cfg(not(unix))]
|
||||
pub fn normalize_path(mut path: Cow<[u8]>) -> Cow<[u8]> {
|
||||
use std::path::is_separator;
|
||||
|
||||
for i in 0..path.len() {
|
||||
if path[i] == b'/' || !is_separator(path[i] as char) {
|
||||
continue;
|
||||
}
|
||||
path.to_mut()[i] = b'/';
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
|
||||
use super::{file_name_ext, normalize_path};
|
||||
|
||||
macro_rules! ext {
|
||||
($name:ident, $file_name:expr, $ext:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let got = file_name_ext(OsStr::new($file_name));
|
||||
assert_eq!($ext.map(OsStr::new), got);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ext!(ext1, "foo.rs", Some(".rs"));
|
||||
ext!(ext2, ".rs", Some(".rs"));
|
||||
ext!(ext3, "..rs", Some(".rs"));
|
||||
ext!(ext4, "", None::<&str>);
|
||||
ext!(ext5, "foo", None::<&str>);
|
||||
|
||||
macro_rules! normalize {
|
||||
($name:ident, $path:expr, $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let got = normalize_path(Cow::Owned($path.to_vec()));
|
||||
assert_eq!($expected.to_vec(), got.into_owned());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
normalize!(normal1, b"foo", b"foo");
|
||||
normalize!(normal2, b"foo/bar", b"foo/bar");
|
||||
#[cfg(unix)]
|
||||
normalize!(normal3, b"foo\\bar", b"foo\\bar");
|
||||
#[cfg(not(unix))]
|
||||
normalize!(normal3, b"foo\\bar", b"foo/bar");
|
||||
#[cfg(unix)]
|
||||
normalize!(normal4, b"foo\\bar/baz", b"foo\\bar/baz");
|
||||
#[cfg(not(unix))]
|
||||
normalize!(normal4, b"foo\\bar/baz", b"foo/bar/baz");
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
[package]
|
||||
publish = false
|
||||
name = "grep"
|
||||
version = "0.1.0" #:version
|
||||
version = "0.1.3" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Fast line oriented regex searching as a library.
|
||||
"""
|
||||
documentation = "https://github.com/BurntSushi/ripgrep"
|
||||
documentation = "http://burntsushi.net/rustdoc/grep/"
|
||||
homepage = "https://github.com/BurntSushi/ripgrep"
|
||||
repository = "https://github.com/BurntSushi/ripgrep"
|
||||
readme = "README.md"
|
||||
@@ -16,6 +15,6 @@ license = "Unlicense/MIT"
|
||||
[dependencies]
|
||||
log = "0.3"
|
||||
memchr = "0.1"
|
||||
memmap = "0.2"
|
||||
regex = "0.1.75"
|
||||
memmap = "0.5"
|
||||
regex = "0.1.77"
|
||||
regex-syntax = "0.3.5"
|
||||
|
||||
4
grep/README.md
Normal file
4
grep/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
grep
|
||||
----
|
||||
This is a *library* that provides grep-style line-by-line regex searching (with
|
||||
comparable performance to `grep` itself).
|
||||
@@ -19,6 +19,7 @@ pub use search::{Grep, GrepBuilder, Iter, Match};
|
||||
mod literals;
|
||||
mod nonl;
|
||||
mod search;
|
||||
mod word_boundary;
|
||||
|
||||
/// Result is a convenient type alias that fixes the type of the error to
|
||||
/// the `Error` type defined in this crate.
|
||||
@@ -62,7 +63,7 @@ impl fmt::Display for Error {
|
||||
match *self {
|
||||
Error::Regex(ref err) => err.fmt(f),
|
||||
Error::LiteralNotAllowed(chr) => {
|
||||
write!(f, "Literal '{}' not allowed.", chr)
|
||||
write!(f, "Literal {:?} not allowed.", chr)
|
||||
}
|
||||
Error::__Nonexhaustive => unreachable!(),
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ Note that this implementation is incredibly suspicious. We need something more
|
||||
principled.
|
||||
*/
|
||||
use std::cmp;
|
||||
use std::iter;
|
||||
|
||||
use regex::bytes::Regex;
|
||||
use syntax::{
|
||||
@@ -181,8 +180,6 @@ fn repeat_range_literals<F: FnMut(&Expr, &mut Literals)>(
|
||||
lits: &mut Literals,
|
||||
mut f: F,
|
||||
) {
|
||||
use syntax::Expr::*;
|
||||
|
||||
if min == 0 {
|
||||
// This is a bit conservative. If `max` is set, then we could
|
||||
// treat this as a finite set of alternations. For now, we
|
||||
@@ -190,8 +187,12 @@ fn repeat_range_literals<F: FnMut(&Expr, &mut Literals)>(
|
||||
lits.cut();
|
||||
} else {
|
||||
let n = cmp::min(lits.limit_size(), min as usize);
|
||||
let es = iter::repeat(e.clone()).take(n).collect();
|
||||
f(&Concat(es), lits);
|
||||
// We only extract literals from a single repetition, even though
|
||||
// we could do more. e.g., `a{3}` will have `a` extracted instead of
|
||||
// `aaa`. The reason is that inner literal extraction can't be unioned
|
||||
// across repetitions. e.g., extracting `foofoofoo` from `(\w+foo){3}`
|
||||
// is wrong.
|
||||
f(e, lits);
|
||||
if n < min as usize {
|
||||
lits.cut();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ use {Error, Result};
|
||||
/// If `byte` is not an ASCII character (i.e., greater than `0x7F`), then this
|
||||
/// function panics.
|
||||
pub fn remove(expr: Expr, byte: u8) -> Result<Expr> {
|
||||
// TODO(burntsushi): There is a bug in this routine where only `\n` is
|
||||
// handled correctly. Namely, `AnyChar` and `AnyByte` need to be translated
|
||||
// to proper character classes instead of the special `AnyCharNoNL` and
|
||||
// `AnyByteNoNL` classes.
|
||||
use syntax::Expr::*;
|
||||
assert!(byte <= 0x7F);
|
||||
let chr = byte as char;
|
||||
|
||||
@@ -4,6 +4,8 @@ use syntax;
|
||||
|
||||
use literals::LiteralSets;
|
||||
use nonl;
|
||||
use syntax::Expr;
|
||||
use word_boundary::strip_unicode_word_boundaries;
|
||||
use Result;
|
||||
|
||||
/// A matched line.
|
||||
@@ -50,6 +52,7 @@ pub struct GrepBuilder {
|
||||
#[derive(Clone, Debug)]
|
||||
struct Options {
|
||||
case_insensitive: bool,
|
||||
case_smart: bool,
|
||||
line_terminator: u8,
|
||||
size_limit: usize,
|
||||
dfa_size_limit: usize,
|
||||
@@ -59,6 +62,7 @@ impl Default for Options {
|
||||
fn default() -> Options {
|
||||
Options {
|
||||
case_insensitive: false,
|
||||
case_smart: false,
|
||||
line_terminator: b'\n',
|
||||
size_limit: 10 * (1 << 20),
|
||||
dfa_size_limit: 10 * (1 << 20),
|
||||
@@ -96,6 +100,18 @@ impl GrepBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to enable smart case search or not (disabled by default).
|
||||
///
|
||||
/// Smart case uses case insensitive search if the regex is contains all
|
||||
/// lowercase literal characters. Otherwise, a case sensitive search is
|
||||
/// used instead.
|
||||
///
|
||||
/// Enabling the case_insensitive flag overrides this.
|
||||
pub fn case_smart(mut self, yes: bool) -> GrepBuilder {
|
||||
self.opts.case_smart = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the approximate size limit of the compiled regular expression.
|
||||
///
|
||||
/// This roughly corresponds to the number of bytes occupied by a
|
||||
@@ -127,22 +143,38 @@ impl GrepBuilder {
|
||||
pub fn build(self) -> Result<Grep> {
|
||||
let expr = try!(self.parse());
|
||||
let literals = LiteralSets::create(&expr);
|
||||
let re = try!(
|
||||
RegexBuilder::new(&expr.to_string())
|
||||
.case_insensitive(self.opts.case_insensitive)
|
||||
.multi_line(true)
|
||||
.unicode(true)
|
||||
.size_limit(self.opts.size_limit)
|
||||
.dfa_size_limit(self.opts.dfa_size_limit)
|
||||
.compile()
|
||||
);
|
||||
let re = try!(self.regex(&expr));
|
||||
let required = literals.to_regex().or_else(|| {
|
||||
let expr = match strip_unicode_word_boundaries(&expr) {
|
||||
None => return None,
|
||||
Some(expr) => expr,
|
||||
};
|
||||
debug!("Stripped Unicode word boundaries. New AST:\n{:?}", expr);
|
||||
self.regex(&expr).ok()
|
||||
});
|
||||
Ok(Grep {
|
||||
re: re,
|
||||
required: literals.to_regex(),
|
||||
required: required,
|
||||
opts: self.opts,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new regex from the given expression with the current
|
||||
/// configuration.
|
||||
fn regex(&self, expr: &Expr) -> Result<Regex> {
|
||||
let casei =
|
||||
self.opts.case_insensitive
|
||||
|| (self.opts.case_smart && !has_uppercase_literal(expr));
|
||||
RegexBuilder::new(&expr.to_string())
|
||||
.case_insensitive(casei)
|
||||
.multi_line(true)
|
||||
.unicode(true)
|
||||
.size_limit(self.opts.size_limit)
|
||||
.dfa_size_limit(self.opts.dfa_size_limit)
|
||||
.compile()
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
/// Parses the underlying pattern and ensures the pattern can never match
|
||||
/// the line terminator.
|
||||
fn parse(&self) -> Result<syntax::Expr> {
|
||||
@@ -152,8 +184,9 @@ impl GrepBuilder {
|
||||
.unicode(true)
|
||||
.case_insensitive(self.opts.case_insensitive)
|
||||
.parse(&self.pattern));
|
||||
let expr = try!(nonl::remove(expr, self.opts.line_terminator));
|
||||
debug!("regex ast:\n{:#?}", expr);
|
||||
Ok(try!(nonl::remove(expr, self.opts.line_terminator)))
|
||||
Ok(expr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,6 +292,23 @@ impl<'b, 's> Iterator for Iter<'b, 's> {
|
||||
}
|
||||
}
|
||||
|
||||
fn has_uppercase_literal(expr: &Expr) -> bool {
|
||||
use syntax::Expr::*;
|
||||
match *expr {
|
||||
Literal { ref chars, casei } => {
|
||||
casei || chars.iter().any(|c| c.is_uppercase())
|
||||
}
|
||||
LiteralBytes { ref bytes, casei } => {
|
||||
casei || bytes.iter().any(|&b| b'A' <= b && b <= b'Z')
|
||||
}
|
||||
Group { ref e, .. } => has_uppercase_literal(e),
|
||||
Repeat { ref e, .. } => has_uppercase_literal(e),
|
||||
Concat(ref es) => es.iter().any(has_uppercase_literal),
|
||||
Alternate(ref es) => es.iter().any(has_uppercase_literal),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_imports)]
|
||||
|
||||
54
grep/src/word_boundary.rs
Normal file
54
grep/src/word_boundary.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use syntax::Expr;
|
||||
|
||||
/// Strips Unicode word boundaries from the given expression.
|
||||
///
|
||||
/// The key invariant this maintains is that the expression returned will match
|
||||
/// *at least* every where the expression given will match. Namely, a match of
|
||||
/// the returned expression can report false positives but it will never report
|
||||
/// false negatives.
|
||||
///
|
||||
/// If no word boundaries could be stripped, then None is returned.
|
||||
pub fn strip_unicode_word_boundaries(expr: &Expr) -> Option<Expr> {
|
||||
// The real reason we do this is because Unicode word boundaries are the
|
||||
// one thing that Rust's regex DFA engine can't handle. When it sees a
|
||||
// Unicode word boundary among non-ASCII text, it falls back to one of the
|
||||
// slower engines. We work around this limitation by attempting to use
|
||||
// a regex to find candidate matches without a Unicode word boundary. We'll
|
||||
// only then use the full (and slower) regex to confirm a candidate as a
|
||||
// match or not during search.
|
||||
use syntax::Expr::*;
|
||||
|
||||
match *expr {
|
||||
Concat(ref es) if !es.is_empty() => {
|
||||
let first = is_unicode_word_boundary(&es[0]);
|
||||
let last = is_unicode_word_boundary(es.last().unwrap());
|
||||
// Be careful not to strip word boundaries if there are no other
|
||||
// expressions to match.
|
||||
match (first, last) {
|
||||
(true, false) if es.len() > 1 => {
|
||||
Some(Concat(es[1..].to_vec()))
|
||||
}
|
||||
(false, true) if es.len() > 1 => {
|
||||
Some(Concat(es[..es.len() - 1].to_vec()))
|
||||
}
|
||||
(true, true) if es.len() > 2 => {
|
||||
Some(Concat(es[1..es.len() - 1].to_vec()))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the given expression is a Unicode word boundary.
|
||||
fn is_unicode_word_boundary(expr: &Expr) -> bool {
|
||||
use syntax::Expr::*;
|
||||
|
||||
match *expr {
|
||||
WordBoundary => true,
|
||||
NotWordBoundary => true,
|
||||
Group { ref e, .. } => is_unicode_word_boundary(e),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
36
ignore/Cargo.toml
Normal file
36
ignore/Cargo.toml
Normal file
@@ -0,0 +1,36 @@
|
||||
[package]
|
||||
name = "ignore"
|
||||
version = "0.1.1" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
A fast library for efficiently matching ignore files such as `.gitignore`
|
||||
against file paths.
|
||||
"""
|
||||
documentation = "https://docs.rs/ignore"
|
||||
homepage = "https://github.com/BurntSushi/ripgrep/tree/master/ignore"
|
||||
repository = "https://github.com/BurntSushi/ripgrep/tree/master/ignore"
|
||||
readme = "README.md"
|
||||
keywords = ["glob", "ignore", "gitignore", "pattern", "file"]
|
||||
license = "Unlicense/MIT"
|
||||
|
||||
[lib]
|
||||
name = "ignore"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
globset = { version = "0.1.1", path = "../globset" }
|
||||
lazy_static = "0.2"
|
||||
log = "0.3"
|
||||
memchr = "0.1"
|
||||
regex = "0.1.77"
|
||||
thread_local = "0.3.0"
|
||||
walkdir = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3.5"
|
||||
|
||||
[features]
|
||||
simd-accel = ["globset/simd-accel"]
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
66
ignore/README.md
Normal file
66
ignore/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
ignore
|
||||
======
|
||||
The ignore crate provides a fast recursive directory iterator that respects
|
||||
various filters such as globs, file types and `.gitignore` files. This crate
|
||||
also provides lower level direct access to gitignore and file type matchers.
|
||||
|
||||
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||
[](https://crates.io/crates/ignore)
|
||||
|
||||
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||
|
||||
### Documentation
|
||||
|
||||
[https://docs.rs/ignore](https://docs.rs/ignore)
|
||||
|
||||
### Usage
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
ignore = "0.1"
|
||||
```
|
||||
|
||||
and this to your crate root:
|
||||
|
||||
```rust
|
||||
extern crate ignore;
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
This example shows the most basic usage of this crate. This code will
|
||||
recursively traverse the current directory while automatically filtering out
|
||||
files and directories according to ignore globs found in files like
|
||||
`.ignore` and `.gitignore`:
|
||||
|
||||
|
||||
```rust,no_run
|
||||
use ignore::Walk;
|
||||
|
||||
for result in Walk::new("./") {
|
||||
// Each item yielded by the iterator is either a directory entry or an
|
||||
// error, so either print the path or the error.
|
||||
match result {
|
||||
Ok(entry) => println!("{}", entry.path().display()),
|
||||
Err(err) => println!("ERROR: {}", err),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example: advanced
|
||||
|
||||
By default, the recursive directory iterator will ignore hidden files and
|
||||
directories. This can be disabled by building the iterator with `WalkBuilder`:
|
||||
|
||||
```rust,no_run
|
||||
use ignore::WalkBuilder;
|
||||
|
||||
for result in WalkBuilder::new("./").hidden(false).build() {
|
||||
println!("{:?}", result);
|
||||
}
|
||||
```
|
||||
|
||||
See the documentation for `WalkBuilder` for many other options.
|
||||
28
ignore/examples/walk.rs
Normal file
28
ignore/examples/walk.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
extern crate ignore;
|
||||
extern crate walkdir;
|
||||
|
||||
use std::env;
|
||||
use std::io::{self, Write};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
use ignore::ignore::IgnoreBuilder;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
fn main() {
|
||||
let path = env::args().nth(1).unwrap();
|
||||
let ig = IgnoreBuilder::new().build();
|
||||
let wd = WalkDir::new(path);
|
||||
let walker = ignore::walk::Iter::new(ig, wd);
|
||||
|
||||
let mut stdout = io::BufWriter::new(io::stdout());
|
||||
// let mut count = 0;
|
||||
for dirent in walker {
|
||||
// count += 1;
|
||||
stdout.write(dirent.path().as_os_str().as_bytes()).unwrap();
|
||||
stdout.write(b"\n").unwrap();
|
||||
}
|
||||
// println!("{}", count);
|
||||
}
|
||||
*/
|
||||
fn main() {}
|
||||
803
ignore/src/dir.rs
Normal file
803
ignore/src/dir.rs
Normal file
@@ -0,0 +1,803 @@
|
||||
// This module provides a data structure, `Ignore`, that connects "directory
|
||||
// traversal" with "ignore matchers." Specifically, it knows about gitignore
|
||||
// semantics and precedence, and is organized based on directory hierarchy.
|
||||
// Namely, every matcher logically corresponds to ignore rules from a single
|
||||
// directory, and points to the matcher for its corresponding parent directory.
|
||||
// In this sense, `Ignore` is a *persistent* data structure.
|
||||
//
|
||||
// This design was specifically chosen to make it possible to use this data
|
||||
// structure in a parallel directory iterator.
|
||||
//
|
||||
// My initial intention was to expose this module as part of this crate's
|
||||
// public API, but I think the data structure's public API is too complicated
|
||||
// with non-obvious failure modes. Alas, such things haven't been documented
|
||||
// well.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use gitignore::{self, Gitignore, GitignoreBuilder};
|
||||
use pathutil::{is_hidden, strip_prefix};
|
||||
use overrides::{self, Override};
|
||||
use types::{self, Types};
|
||||
use {Error, Match, PartialErrorBuilder};
|
||||
|
||||
/// IgnoreMatch represents information about where a match came from when using
|
||||
/// the `Ignore` matcher.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IgnoreMatch<'a>(IgnoreMatchInner<'a>);
|
||||
|
||||
/// IgnoreMatchInner describes precisely where the match information came from.
|
||||
/// This is private to allow expansion to more matchers in the future.
|
||||
#[derive(Clone, Debug)]
|
||||
enum IgnoreMatchInner<'a> {
|
||||
Override(overrides::Glob<'a>),
|
||||
Gitignore(&'a gitignore::Glob),
|
||||
Types(types::Glob<'a>),
|
||||
Hidden,
|
||||
}
|
||||
|
||||
impl<'a> IgnoreMatch<'a> {
|
||||
fn overrides(x: overrides::Glob<'a>) -> IgnoreMatch<'a> {
|
||||
IgnoreMatch(IgnoreMatchInner::Override(x))
|
||||
}
|
||||
|
||||
fn gitignore(x: &'a gitignore::Glob) -> IgnoreMatch<'a> {
|
||||
IgnoreMatch(IgnoreMatchInner::Gitignore(x))
|
||||
}
|
||||
|
||||
fn types(x: types::Glob<'a>) -> IgnoreMatch<'a> {
|
||||
IgnoreMatch(IgnoreMatchInner::Types(x))
|
||||
}
|
||||
|
||||
fn hidden() -> IgnoreMatch<'static> {
|
||||
IgnoreMatch(IgnoreMatchInner::Hidden)
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for the ignore matcher, shared between the matcher itself and the
|
||||
/// builder.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct IgnoreOptions {
|
||||
/// Whether to ignore hidden file paths or not.
|
||||
hidden: bool,
|
||||
/// Whether to read .ignore files.
|
||||
ignore: bool,
|
||||
/// Whether to read git's global gitignore file.
|
||||
git_global: bool,
|
||||
/// Whether to read .gitignore files.
|
||||
git_ignore: bool,
|
||||
/// Whether to read .git/info/exclude files.
|
||||
git_exclude: bool,
|
||||
}
|
||||
|
||||
impl IgnoreOptions {
|
||||
/// Returns true if at least one type of ignore rules should be matched.
|
||||
fn should_ignores(&self) -> bool {
|
||||
self.ignore || self.git_global || self.git_ignore || self.git_exclude
|
||||
}
|
||||
}
|
||||
|
||||
/// Ignore is a matcher useful for recursively walking one or more directories.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Ignore(Arc<IgnoreInner>);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct IgnoreInner {
|
||||
/// A map of all existing directories that have already been
|
||||
/// compiled into matchers.
|
||||
///
|
||||
/// Note that this is never used during matching, only when adding new
|
||||
/// parent directory matchers. This avoids needing to rebuild glob sets for
|
||||
/// parent directories if many paths are being searched.
|
||||
compiled: Arc<RwLock<HashMap<OsString, Ignore>>>,
|
||||
/// The path to the directory that this matcher was built from.
|
||||
dir: PathBuf,
|
||||
/// An override matcher (default is empty).
|
||||
overrides: Arc<Override>,
|
||||
/// A file type matcher.
|
||||
types: Arc<Types>,
|
||||
/// The parent directory to match next.
|
||||
///
|
||||
/// If this is the root directory or there are otherwise no more
|
||||
/// directories to match, then `parent` is `None`.
|
||||
parent: Option<Ignore>,
|
||||
/// Whether this is an absolute parent matcher, as added by add_parent.
|
||||
is_absolute_parent: bool,
|
||||
/// The absolute base path of this matcher. Populated only if parent
|
||||
/// directories are added.
|
||||
absolute_base: Option<Arc<PathBuf>>,
|
||||
/// Explicit ignore matchers specified by the caller.
|
||||
explicit_ignores: Arc<Vec<Gitignore>>,
|
||||
/// The matcher for .ignore files.
|
||||
ignore_matcher: Gitignore,
|
||||
/// A global gitignore matcher, usually from $XDG_CONFIG_HOME/git/ignore.
|
||||
git_global_matcher: Arc<Gitignore>,
|
||||
/// The matcher for .gitignore files.
|
||||
git_ignore_matcher: Gitignore,
|
||||
/// Special matcher for `.git/info/exclude` files.
|
||||
git_exclude_matcher: Gitignore,
|
||||
/// Whether this directory contains a .git sub-directory.
|
||||
has_git: bool,
|
||||
/// Ignore config.
|
||||
opts: IgnoreOptions,
|
||||
}
|
||||
|
||||
impl Ignore {
|
||||
/// Return the directory path of this matcher.
|
||||
#[allow(dead_code)]
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.0.dir
|
||||
}
|
||||
|
||||
/// Return true if this matcher has no parent.
|
||||
pub fn is_root(&self) -> bool {
|
||||
self.0.parent.is_none()
|
||||
}
|
||||
|
||||
/// Return this matcher's parent, if one exists.
|
||||
pub fn parent(&self) -> Option<Ignore> {
|
||||
self.0.parent.clone()
|
||||
}
|
||||
|
||||
/// Create a new `Ignore` matcher with the parent directories of `dir`.
|
||||
///
|
||||
/// Note that this can only be called on an `Ignore` matcher with no
|
||||
/// parents (i.e., `is_root` returns `true`). This will panic otherwise.
|
||||
pub fn add_parents<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> (Ignore, Option<Error>) {
|
||||
if !self.is_root() {
|
||||
panic!("Ignore::add_parents called on non-root matcher");
|
||||
}
|
||||
let absolute_base = match path.as_ref().canonicalize() {
|
||||
Ok(path) => Arc::new(path),
|
||||
Err(_) => {
|
||||
// There's not much we can do here, so just return our
|
||||
// existing matcher. We drop the error to be consistent
|
||||
// with our general pattern of ignoring I/O errors when
|
||||
// processing ignore files.
|
||||
return (self.clone(), None);
|
||||
}
|
||||
};
|
||||
// List of parents, from child to root.
|
||||
let mut parents = vec![];
|
||||
let mut path = &**absolute_base;
|
||||
while let Some(parent) = path.parent() {
|
||||
parents.push(parent);
|
||||
path = parent;
|
||||
}
|
||||
let mut errs = PartialErrorBuilder::default();
|
||||
let mut ig = self.clone();
|
||||
for parent in parents.into_iter().rev() {
|
||||
let mut compiled = self.0.compiled.write().unwrap();
|
||||
if let Some(prebuilt) = compiled.get(parent.as_os_str()) {
|
||||
ig = prebuilt.clone();
|
||||
continue;
|
||||
}
|
||||
let (mut igtmp, err) = ig.add_child_path(parent);
|
||||
errs.maybe_push(err);
|
||||
igtmp.is_absolute_parent = true;
|
||||
igtmp.absolute_base = Some(absolute_base.clone());
|
||||
ig = Ignore(Arc::new(igtmp));
|
||||
compiled.insert(parent.as_os_str().to_os_string(), ig.clone());
|
||||
}
|
||||
(ig, errs.into_error_option())
|
||||
}
|
||||
|
||||
/// Create a new `Ignore` matcher for the given child directory.
|
||||
///
|
||||
/// Since building the matcher may require reading from multiple
|
||||
/// files, it's possible that this method partially succeeds. Therefore,
|
||||
/// a matcher is always returned (which may match nothing) and an error is
|
||||
/// returned if it exists.
|
||||
///
|
||||
/// Note that all I/O errors are completely ignored.
|
||||
pub fn add_child<P: AsRef<Path>>(
|
||||
&self,
|
||||
dir: P,
|
||||
) -> (Ignore, Option<Error>) {
|
||||
let (ig, err) = self.add_child_path(dir.as_ref());
|
||||
(Ignore(Arc::new(ig)), err)
|
||||
}
|
||||
|
||||
/// Like add_child, but takes a full path and returns an IgnoreInner.
|
||||
fn add_child_path(&self, dir: &Path) -> (IgnoreInner, Option<Error>) {
|
||||
static IG_NAMES: &'static [&'static str] = &[".rgignore", ".ignore"];
|
||||
|
||||
let mut errs = PartialErrorBuilder::default();
|
||||
let ig_matcher =
|
||||
if !self.0.opts.ignore {
|
||||
Gitignore::empty()
|
||||
} else {
|
||||
let (m, err) = create_gitignore(&dir, IG_NAMES);
|
||||
errs.maybe_push(err);
|
||||
m
|
||||
};
|
||||
let gi_matcher =
|
||||
if !self.0.opts.git_ignore {
|
||||
Gitignore::empty()
|
||||
} else {
|
||||
let (m, err) = create_gitignore(&dir, &[".gitignore"]);
|
||||
errs.maybe_push(err);
|
||||
m
|
||||
};
|
||||
let gi_exclude_matcher =
|
||||
if !self.0.opts.git_exclude {
|
||||
Gitignore::empty()
|
||||
} else {
|
||||
let (m, err) = create_gitignore(&dir, &[".git/info/exclude"]);
|
||||
errs.maybe_push(err);
|
||||
m
|
||||
};
|
||||
let ig = IgnoreInner {
|
||||
compiled: self.0.compiled.clone(),
|
||||
dir: dir.to_path_buf(),
|
||||
overrides: self.0.overrides.clone(),
|
||||
types: self.0.types.clone(),
|
||||
parent: Some(self.clone()),
|
||||
is_absolute_parent: false,
|
||||
absolute_base: self.0.absolute_base.clone(),
|
||||
explicit_ignores: self.0.explicit_ignores.clone(),
|
||||
ignore_matcher: ig_matcher,
|
||||
git_global_matcher: self.0.git_global_matcher.clone(),
|
||||
git_ignore_matcher: gi_matcher,
|
||||
git_exclude_matcher: gi_exclude_matcher,
|
||||
has_git: dir.join(".git").is_dir(),
|
||||
opts: self.0.opts,
|
||||
};
|
||||
(ig, errs.into_error_option())
|
||||
}
|
||||
|
||||
/// Returns a match indicating whether the given file path should be
|
||||
/// ignored or not.
|
||||
///
|
||||
/// The match contains information about its origin.
|
||||
pub fn matched<'a, P: AsRef<Path>>(
|
||||
&'a self,
|
||||
path: P,
|
||||
is_dir: bool,
|
||||
) -> Match<IgnoreMatch<'a>> {
|
||||
// We need to be careful with our path. If it has a leading ./, then
|
||||
// strip it because it causes nothing but trouble.
|
||||
let mut path = path.as_ref();
|
||||
if let Some(p) = strip_prefix("./", path) {
|
||||
path = p;
|
||||
}
|
||||
// Match against the override patterns. If an override matches
|
||||
// regardless of whether it's whitelist/ignore, then we quit and
|
||||
// return that result immediately. Overrides have the highest
|
||||
// precedence.
|
||||
if !self.0.overrides.is_empty() {
|
||||
let mat =
|
||||
self.0.overrides.matched(path, is_dir)
|
||||
.map(IgnoreMatch::overrides);
|
||||
if !mat.is_none() {
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
let mut whitelisted = Match::None;
|
||||
if self.0.opts.should_ignores() {
|
||||
let mat = self.matched_ignore(path, is_dir);
|
||||
if mat.is_ignore() {
|
||||
return mat;
|
||||
} else if mat.is_whitelist() {
|
||||
whitelisted = mat;
|
||||
}
|
||||
}
|
||||
if !self.0.types.is_empty() {
|
||||
let mat =
|
||||
self.0.types.matched(path, is_dir).map(IgnoreMatch::types);
|
||||
if mat.is_ignore() {
|
||||
return mat;
|
||||
} else if mat.is_whitelist() {
|
||||
whitelisted = mat;
|
||||
}
|
||||
}
|
||||
if whitelisted.is_none() && self.0.opts.hidden && is_hidden(path) {
|
||||
return Match::Ignore(IgnoreMatch::hidden());
|
||||
}
|
||||
whitelisted
|
||||
}
|
||||
|
||||
/// Performs matching only on the ignore files for this directory and
|
||||
/// all parent directories.
|
||||
fn matched_ignore<'a>(
|
||||
&'a self,
|
||||
path: &Path,
|
||||
is_dir: bool,
|
||||
) -> Match<IgnoreMatch<'a>> {
|
||||
let (mut m_ignore, mut m_gi, mut m_gi_exclude, mut m_explicit) =
|
||||
(Match::None, Match::None, Match::None, Match::None);
|
||||
let mut saw_git = false;
|
||||
for ig in self.parents().take_while(|ig| !ig.0.is_absolute_parent) {
|
||||
if m_ignore.is_none() {
|
||||
m_ignore =
|
||||
ig.0.ignore_matcher.matched(path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
if !saw_git && m_gi.is_none() {
|
||||
m_gi =
|
||||
ig.0.git_ignore_matcher.matched(path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
if !saw_git && m_gi_exclude.is_none() {
|
||||
m_gi_exclude =
|
||||
ig.0.git_exclude_matcher.matched(path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
saw_git = saw_git || ig.0.has_git;
|
||||
}
|
||||
if let Some(abs_parent_path) = self.absolute_base() {
|
||||
let path = abs_parent_path.join(path);
|
||||
for ig in self.parents().skip_while(|ig|!ig.0.is_absolute_parent) {
|
||||
if m_ignore.is_none() {
|
||||
m_ignore =
|
||||
ig.0.ignore_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
if !saw_git && m_gi.is_none() {
|
||||
m_gi =
|
||||
ig.0.git_ignore_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
if !saw_git && m_gi_exclude.is_none() {
|
||||
m_gi_exclude =
|
||||
ig.0.git_exclude_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
saw_git = saw_git || ig.0.has_git;
|
||||
}
|
||||
}
|
||||
for gi in self.0.explicit_ignores.iter().rev() {
|
||||
if !m_explicit.is_none() {
|
||||
break;
|
||||
}
|
||||
m_explicit = gi.matched(&path, is_dir).map(IgnoreMatch::gitignore);
|
||||
}
|
||||
let m_global = self.0.git_global_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
if !m_ignore.is_none() {
|
||||
m_ignore
|
||||
} else if !m_gi.is_none() {
|
||||
m_gi
|
||||
} else if !m_gi_exclude.is_none() {
|
||||
m_gi_exclude
|
||||
} else if !m_global.is_none() {
|
||||
m_global
|
||||
} else if !m_explicit.is_none() {
|
||||
m_explicit
|
||||
} else {
|
||||
Match::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over parent ignore matchers, including this one.
|
||||
fn parents(&self) -> Parents {
|
||||
Parents(Some(self))
|
||||
}
|
||||
|
||||
/// Returns the first absolute path of the first absolute parent, if
|
||||
/// one exists.
|
||||
fn absolute_base(&self) -> Option<&Path> {
|
||||
self.0.absolute_base.as_ref().map(|p| &***p)
|
||||
}
|
||||
}
|
||||
|
||||
struct Parents<'a>(Option<&'a Ignore>);
|
||||
|
||||
impl<'a> Iterator for Parents<'a> {
|
||||
type Item = &'a Ignore;
|
||||
|
||||
fn next(&mut self) -> Option<&'a Ignore> {
|
||||
match self.0.take() {
|
||||
None => None,
|
||||
Some(ig) => {
|
||||
self.0 = ig.0.parent.as_ref();
|
||||
Some(ig)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for creating an Ignore matcher.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IgnoreBuilder {
|
||||
/// The root directory path for this ignore matcher.
|
||||
dir: PathBuf,
|
||||
/// An override matcher (default is empty).
|
||||
overrides: Arc<Override>,
|
||||
/// A type matcher (default is empty).
|
||||
types: Arc<Types>,
|
||||
/// Explicit ignore matchers.
|
||||
explicit_ignores: Vec<Gitignore>,
|
||||
/// Ignore config.
|
||||
opts: IgnoreOptions,
|
||||
}
|
||||
|
||||
impl IgnoreBuilder {
|
||||
/// Create a new builder for an `Ignore` matcher.
|
||||
///
|
||||
/// All relative file paths are resolved with respect to the current
|
||||
/// working directory.
|
||||
pub fn new() -> IgnoreBuilder {
|
||||
IgnoreBuilder {
|
||||
dir: Path::new("").to_path_buf(),
|
||||
overrides: Arc::new(Override::empty()),
|
||||
types: Arc::new(Types::empty()),
|
||||
explicit_ignores: vec![],
|
||||
opts: IgnoreOptions {
|
||||
hidden: true,
|
||||
ignore: true,
|
||||
git_global: true,
|
||||
git_ignore: true,
|
||||
git_exclude: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new `Ignore` matcher.
|
||||
///
|
||||
/// The matcher returned won't match anything until ignore rules from
|
||||
/// directories are added to it.
|
||||
pub fn build(&self) -> Ignore {
|
||||
let git_global_matcher =
|
||||
if !self.opts.git_global {
|
||||
Gitignore::empty()
|
||||
} else {
|
||||
let (gi, err) = Gitignore::global();
|
||||
if let Some(err) = err {
|
||||
debug!("{}", err);
|
||||
}
|
||||
gi
|
||||
};
|
||||
Ignore(Arc::new(IgnoreInner {
|
||||
compiled: Arc::new(RwLock::new(HashMap::new())),
|
||||
dir: self.dir.clone(),
|
||||
overrides: self.overrides.clone(),
|
||||
types: self.types.clone(),
|
||||
parent: None,
|
||||
is_absolute_parent: true,
|
||||
absolute_base: None,
|
||||
explicit_ignores: Arc::new(self.explicit_ignores.clone()),
|
||||
ignore_matcher: Gitignore::empty(),
|
||||
git_global_matcher: Arc::new(git_global_matcher),
|
||||
git_ignore_matcher: Gitignore::empty(),
|
||||
git_exclude_matcher: Gitignore::empty(),
|
||||
has_git: false,
|
||||
opts: self.opts,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Add an override matcher.
|
||||
///
|
||||
/// By default, no override matcher is used.
|
||||
///
|
||||
/// This overrides any previous setting.
|
||||
pub fn overrides(&mut self, overrides: Override) -> &mut IgnoreBuilder {
|
||||
self.overrides = Arc::new(overrides);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a file type matcher.
|
||||
///
|
||||
/// By default, no file type matcher is used.
|
||||
///
|
||||
/// This overrides any previous setting.
|
||||
pub fn types(&mut self, types: Types) -> &mut IgnoreBuilder {
|
||||
self.types = Arc::new(types);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a new global ignore matcher from the ignore file path given.
|
||||
pub fn add_ignore(&mut self, ig: Gitignore) -> &mut IgnoreBuilder {
|
||||
self.explicit_ignores.push(ig);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables ignoring hidden files.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn hidden(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||
self.opts.hidden = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables reading `.ignore` files.
|
||||
///
|
||||
/// `.ignore` files have the same semantics as `gitignore` files and are
|
||||
/// supported by search tools such as ripgrep and The Silver Searcher.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn ignore(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||
self.opts.ignore = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a global gitignore matcher.
|
||||
///
|
||||
/// Its precedence is lower than both normal `.gitignore` files and
|
||||
/// `.git/info/exclude` files.
|
||||
///
|
||||
/// This overwrites any previous global gitignore setting.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn git_global(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||
self.opts.git_global = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables reading `.gitignore` files.
|
||||
///
|
||||
/// `.gitignore` files have match semantics as described in the `gitignore`
|
||||
/// man page.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn git_ignore(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||
self.opts.git_ignore = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables reading `.git/info/exclude` files.
|
||||
///
|
||||
/// `.git/info/exclude` files have match semantics as described in the
|
||||
/// `gitignore` man page.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn git_exclude(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||
self.opts.git_exclude = yes;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new gitignore matcher for the directory given.
|
||||
///
|
||||
/// Ignore globs are extracted from each of the file names in `dir` in the
|
||||
/// order given (earlier names have lower precedence than later names).
|
||||
///
|
||||
/// I/O errors are ignored.
|
||||
pub fn create_gitignore(
|
||||
dir: &Path,
|
||||
names: &[&str],
|
||||
) -> (Gitignore, Option<Error>) {
|
||||
let mut builder = GitignoreBuilder::new(dir);
|
||||
let mut errs = PartialErrorBuilder::default();
|
||||
for name in names {
|
||||
let gipath = dir.join(name);
|
||||
errs.maybe_push_ignore_io(builder.add(gipath));
|
||||
}
|
||||
let gi = match builder.build() {
|
||||
Ok(gi) => gi,
|
||||
Err(err) => {
|
||||
errs.push(err);
|
||||
GitignoreBuilder::new(dir).build().unwrap()
|
||||
}
|
||||
};
|
||||
(gi, errs.into_error_option())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use tempdir::TempDir;
|
||||
|
||||
use dir::IgnoreBuilder;
|
||||
use gitignore::Gitignore;
|
||||
use Error;
|
||||
|
||||
fn wfile<P: AsRef<Path>>(path: P, contents: &str) {
|
||||
let mut file = File::create(path).unwrap();
|
||||
file.write_all(contents.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
fn mkdirp<P: AsRef<Path>>(path: P) {
|
||||
fs::create_dir_all(path).unwrap();
|
||||
}
|
||||
|
||||
fn partial(err: Error) -> Vec<Error> {
|
||||
match err {
|
||||
Error::Partial(errs) => errs,
|
||||
_ => panic!("expected partial error but got {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_ignore() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
wfile(td.path().join("not-an-ignore"), "foo\n!bar");
|
||||
|
||||
let (gi, err) = Gitignore::new(td.path().join("not-an-ignore"));
|
||||
assert!(err.is_none());
|
||||
let (ig, err) = IgnoreBuilder::new()
|
||||
.add_ignore(gi).build().add_child(td.path());
|
||||
assert!(err.is_none());
|
||||
assert!(ig.matched("foo", false).is_ignore());
|
||||
assert!(ig.matched("bar", false).is_whitelist());
|
||||
assert!(ig.matched("baz", false).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_exclude() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
mkdirp(td.path().join(".git/info"));
|
||||
wfile(td.path().join(".git/info/exclude"), "foo\n!bar");
|
||||
|
||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert!(err.is_none());
|
||||
assert!(ig.matched("foo", false).is_ignore());
|
||||
assert!(ig.matched("bar", false).is_whitelist());
|
||||
assert!(ig.matched("baz", false).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gitignore() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
wfile(td.path().join(".gitignore"), "foo\n!bar");
|
||||
|
||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert!(err.is_none());
|
||||
assert!(ig.matched("foo", false).is_ignore());
|
||||
assert!(ig.matched("bar", false).is_whitelist());
|
||||
assert!(ig.matched("baz", false).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
wfile(td.path().join(".ignore"), "foo\n!bar");
|
||||
|
||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert!(err.is_none());
|
||||
assert!(ig.matched("foo", false).is_ignore());
|
||||
assert!(ig.matched("bar", false).is_whitelist());
|
||||
assert!(ig.matched("baz", false).is_none());
|
||||
}
|
||||
|
||||
// Tests that an .ignore will override a .gitignore.
|
||||
#[test]
|
||||
fn ignore_over_gitignore() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
wfile(td.path().join(".gitignore"), "foo");
|
||||
wfile(td.path().join(".ignore"), "!foo");
|
||||
|
||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert!(err.is_none());
|
||||
assert!(ig.matched("foo", false).is_whitelist());
|
||||
}
|
||||
|
||||
// Tests that exclude has lower precedent than both .ignore and .gitignore.
|
||||
#[test]
|
||||
fn exclude_lowest() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
wfile(td.path().join(".gitignore"), "!foo");
|
||||
wfile(td.path().join(".ignore"), "!bar");
|
||||
mkdirp(td.path().join(".git/info"));
|
||||
wfile(td.path().join(".git/info/exclude"), "foo\nbar\nbaz");
|
||||
|
||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert!(err.is_none());
|
||||
assert!(ig.matched("baz", false).is_ignore());
|
||||
assert!(ig.matched("foo", false).is_whitelist());
|
||||
assert!(ig.matched("bar", false).is_whitelist());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errored() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
wfile(td.path().join(".gitignore"), "f**oo");
|
||||
|
||||
let (_, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert!(err.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errored_both() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
wfile(td.path().join(".gitignore"), "f**oo");
|
||||
wfile(td.path().join(".ignore"), "fo**o");
|
||||
|
||||
let (_, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert_eq!(2, partial(err.expect("an error")).len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errored_partial() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
wfile(td.path().join(".gitignore"), "f**oo\nbar");
|
||||
|
||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert!(err.is_some());
|
||||
assert!(ig.matched("bar", false).is_ignore());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errored_partial_and_ignore() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
wfile(td.path().join(".gitignore"), "f**oo\nbar");
|
||||
wfile(td.path().join(".ignore"), "!bar");
|
||||
|
||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert!(err.is_some());
|
||||
assert!(ig.matched("bar", false).is_whitelist());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_present_empty() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
|
||||
let (_, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert!(err.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stops_at_git_dir() {
|
||||
// This tests that .gitignore files beyond a .git barrier aren't
|
||||
// matched, but .ignore files are.
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
mkdirp(td.path().join(".git"));
|
||||
mkdirp(td.path().join("foo/.git"));
|
||||
wfile(td.path().join(".gitignore"), "foo");
|
||||
wfile(td.path().join(".ignore"), "bar");
|
||||
|
||||
let ig0 = IgnoreBuilder::new().build();
|
||||
let (ig1, err) = ig0.add_child(td.path());
|
||||
assert!(err.is_none());
|
||||
let (ig2, err) = ig1.add_child(ig1.path().join("foo"));
|
||||
assert!(err.is_none());
|
||||
|
||||
assert!(ig1.matched("foo", false).is_ignore());
|
||||
assert!(ig2.matched("foo", false).is_none());
|
||||
|
||||
assert!(ig1.matched("bar", false).is_ignore());
|
||||
assert!(ig2.matched("bar", false).is_ignore());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absolute_parent() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
mkdirp(td.path().join(".git"));
|
||||
mkdirp(td.path().join("foo"));
|
||||
wfile(td.path().join(".gitignore"), "bar");
|
||||
|
||||
// First, check that the parent gitignore file isn't detected if the
|
||||
// parent isn't added. This establishes a baseline.
|
||||
let ig0 = IgnoreBuilder::new().build();
|
||||
let (ig1, err) = ig0.add_child(td.path().join("foo"));
|
||||
assert!(err.is_none());
|
||||
assert!(ig1.matched("bar", false).is_none());
|
||||
|
||||
// Second, check that adding a parent directory actually works.
|
||||
let ig0 = IgnoreBuilder::new().build();
|
||||
let (ig1, err) = ig0.add_parents(td.path().join("foo"));
|
||||
assert!(err.is_none());
|
||||
let (ig2, err) = ig1.add_child(td.path().join("foo"));
|
||||
assert!(err.is_none());
|
||||
assert!(ig2.matched("bar", false).is_ignore());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absolute_parent_anchored() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
mkdirp(td.path().join(".git"));
|
||||
mkdirp(td.path().join("src/llvm"));
|
||||
wfile(td.path().join(".gitignore"), "/llvm/\nfoo");
|
||||
|
||||
let ig0 = IgnoreBuilder::new().build();
|
||||
let (ig1, err) = ig0.add_parents(td.path().join("src"));
|
||||
assert!(err.is_none());
|
||||
let (ig2, err) = ig1.add_child("src");
|
||||
assert!(err.is_none());
|
||||
|
||||
assert!(ig1.matched("llvm", true).is_none());
|
||||
assert!(ig2.matched("llvm", true).is_none());
|
||||
assert!(ig2.matched("src/llvm", true).is_none());
|
||||
assert!(ig2.matched("foo", false).is_ignore());
|
||||
assert!(ig2.matched("src/foo", false).is_ignore());
|
||||
}
|
||||
}
|
||||
607
ignore/src/gitignore.rs
Normal file
607
ignore/src/gitignore.rs
Normal file
@@ -0,0 +1,607 @@
|
||||
/*!
|
||||
The gitignore module provides a way to match globs from a gitignore file
|
||||
against file paths.
|
||||
|
||||
Note that this module implements the specification as described in the
|
||||
`gitignore` man page from scratch. That is, this module does *not* shell out to
|
||||
the `git` command line tool.
|
||||
*/
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
|
||||
use globset::{Candidate, GlobBuilder, GlobSet, GlobSetBuilder};
|
||||
use regex::bytes::Regex;
|
||||
use thread_local::ThreadLocal;
|
||||
|
||||
use pathutil::{is_file_name, strip_prefix};
|
||||
use {Error, Match, PartialErrorBuilder};
|
||||
|
||||
/// Glob represents a single glob in a gitignore file.
|
||||
///
|
||||
/// This is used to report information about the highest precedent glob that
|
||||
/// matched in one or more gitignore files.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Glob {
|
||||
/// The file path that this glob was extracted from.
|
||||
from: Option<PathBuf>,
|
||||
/// The original glob string.
|
||||
original: String,
|
||||
/// The actual glob string used to convert to a regex.
|
||||
actual: String,
|
||||
/// Whether this is a whitelisted glob or not.
|
||||
is_whitelist: bool,
|
||||
/// Whether this glob should only match directories or not.
|
||||
is_only_dir: bool,
|
||||
}
|
||||
|
||||
impl Glob {
|
||||
/// Returns the file path that defined this glob.
|
||||
pub fn from(&self) -> Option<&Path> {
|
||||
self.from.as_ref().map(|p| &**p)
|
||||
}
|
||||
|
||||
/// The original glob as it was defined in a gitignore file.
|
||||
pub fn original(&self) -> &str {
|
||||
&self.original
|
||||
}
|
||||
|
||||
/// The actual glob that was compiled to respect gitignore
|
||||
/// semantics.
|
||||
pub fn actual(&self) -> &str {
|
||||
&self.actual
|
||||
}
|
||||
|
||||
/// Whether this was a whitelisted glob or not.
|
||||
pub fn is_whitelist(&self) -> bool {
|
||||
self.is_whitelist
|
||||
}
|
||||
|
||||
/// Whether this glob must match a directory or not.
|
||||
pub fn is_only_dir(&self) -> bool {
|
||||
self.is_only_dir
|
||||
}
|
||||
}
|
||||
|
||||
/// Gitignore is a matcher for the globs in one or more gitignore files
|
||||
/// in the same directory.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Gitignore {
|
||||
set: GlobSet,
|
||||
root: PathBuf,
|
||||
globs: Vec<Glob>,
|
||||
num_ignores: u64,
|
||||
num_whitelists: u64,
|
||||
matches: Arc<ThreadLocal<RefCell<Vec<usize>>>>,
|
||||
}
|
||||
|
||||
impl Gitignore {
|
||||
/// Creates a new gitignore matcher from the gitignore file path given.
|
||||
///
|
||||
/// If it's desirable to include multiple gitignore files in a single
|
||||
/// matcher, or read gitignore globs from a different source, then
|
||||
/// use `GitignoreBuilder`.
|
||||
///
|
||||
/// This always returns a valid matcher, even if it's empty. In particular,
|
||||
/// a Gitignore file can be partially valid, e.g., when one glob is invalid
|
||||
/// but the rest aren't.
|
||||
///
|
||||
/// Note that I/O errors are ignored. For more granular control over
|
||||
/// errors, use `GitignoreBuilder`.
|
||||
pub fn new<P: AsRef<Path>>(
|
||||
gitignore_path: P,
|
||||
) -> (Gitignore, Option<Error>) {
|
||||
let path = gitignore_path.as_ref();
|
||||
let parent = path.parent().unwrap_or(Path::new("/"));
|
||||
let mut builder = GitignoreBuilder::new(parent);
|
||||
let mut errs = PartialErrorBuilder::default();
|
||||
errs.maybe_push_ignore_io(builder.add(path));
|
||||
match builder.build() {
|
||||
Ok(gi) => (gi, errs.into_error_option()),
|
||||
Err(err) => {
|
||||
errs.push(err);
|
||||
(Gitignore::empty(), errs.into_error_option())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new gitignore matcher from the global ignore file, if one
|
||||
/// exists.
|
||||
///
|
||||
/// The global config file path is specified by git's `core.excludesFile`
|
||||
/// config option.
|
||||
///
|
||||
/// Git's config file location is `$HOME/.gitconfig`. If `$HOME/.gitconfig`
|
||||
/// does not exist or does not specify `core.excludesFile`, then
|
||||
/// `$XDG_CONFIG_HOME/git/ignore` is read. If `$XDG_CONFIG_HOME` is not
|
||||
/// set or is empty, then `$HOME/.config/git/ignore` is used instead.
|
||||
pub fn global() -> (Gitignore, Option<Error>) {
|
||||
match gitconfig_excludes_path() {
|
||||
None => (Gitignore::empty(), None),
|
||||
Some(path) => {
|
||||
if !path.is_file() {
|
||||
(Gitignore::empty(), None)
|
||||
} else {
|
||||
Gitignore::new(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new empty gitignore matcher that never matches anything.
|
||||
///
|
||||
/// Its path is empty.
|
||||
pub fn empty() -> Gitignore {
|
||||
GitignoreBuilder::new("").build().unwrap()
|
||||
}
|
||||
|
||||
/// Returns the directory containing this gitignore matcher.
|
||||
///
|
||||
/// All matches are done relative to this path.
|
||||
pub fn path(&self) -> &Path {
|
||||
&*self.root
|
||||
}
|
||||
|
||||
/// Returns true if and only if this gitignore has zero globs, and
|
||||
/// therefore never matches any file path.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.set.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the total number of globs, which should be equivalent to
|
||||
/// `num_ignores + num_whitelists`.
|
||||
pub fn len(&self) -> usize {
|
||||
self.set.len()
|
||||
}
|
||||
|
||||
/// Returns the total number of ignore globs.
|
||||
pub fn num_ignores(&self) -> u64 {
|
||||
self.num_ignores
|
||||
}
|
||||
|
||||
/// Returns the total number of whitelisted globs.
|
||||
pub fn num_whitelists(&self) -> u64 {
|
||||
self.num_whitelists
|
||||
}
|
||||
|
||||
/// Returns whether the given file path matched a pattern in this gitignore
|
||||
/// matcher.
|
||||
///
|
||||
/// `is_dir` should be true if the path refers to a directory and false
|
||||
/// otherwise.
|
||||
///
|
||||
/// The given path is matched relative to the path given when building
|
||||
/// the matcher. Specifically, before matching `path`, its prefix (as
|
||||
/// determined by a common suffix of the directory containing this
|
||||
/// gitignore) is stripped. If there is no common suffix/prefix overlap,
|
||||
/// then `path` is assumed to be relative to this matcher.
|
||||
pub fn matched<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
is_dir: bool,
|
||||
) -> Match<&Glob> {
|
||||
if self.is_empty() {
|
||||
return Match::None;
|
||||
}
|
||||
self.matched_stripped(self.strip(path.as_ref()), is_dir)
|
||||
}
|
||||
|
||||
/// Like matched, but takes a path that has already been stripped.
|
||||
fn matched_stripped<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
is_dir: bool,
|
||||
) -> Match<&Glob> {
|
||||
if self.is_empty() {
|
||||
return Match::None;
|
||||
}
|
||||
let path = path.as_ref();
|
||||
let _matches = self.matches.get_default();
|
||||
let mut matches = _matches.borrow_mut();
|
||||
let candidate = Candidate::new(path);
|
||||
self.set.matches_candidate_into(&candidate, &mut *matches);
|
||||
for &i in matches.iter().rev() {
|
||||
let glob = &self.globs[i];
|
||||
if !glob.is_only_dir() || is_dir {
|
||||
return if glob.is_whitelist() {
|
||||
Match::Whitelist(glob)
|
||||
} else {
|
||||
Match::Ignore(glob)
|
||||
};
|
||||
}
|
||||
}
|
||||
Match::None
|
||||
}
|
||||
|
||||
/// Strips the given path such that it's suitable for matching with this
|
||||
/// gitignore matcher.
|
||||
fn strip<'a, P: 'a + AsRef<Path> + ?Sized>(
|
||||
&'a self,
|
||||
path: &'a P,
|
||||
) -> &'a Path {
|
||||
let mut path = path.as_ref();
|
||||
// A leading ./ is completely superfluous. We also strip it from
|
||||
// our gitignore root path, so we need to strip it from our candidate
|
||||
// path too.
|
||||
if let Some(p) = strip_prefix("./", path) {
|
||||
path = p;
|
||||
}
|
||||
// Strip any common prefix between the candidate path and the root
|
||||
// of the gitignore, to make sure we get relative matching right.
|
||||
// BUT, a file name might not have any directory components to it,
|
||||
// in which case, we don't want to accidentally strip any part of the
|
||||
// file name.
|
||||
if !is_file_name(path) {
|
||||
if let Some(p) = strip_prefix(&self.root, path) {
|
||||
path = p;
|
||||
// If we're left with a leading slash, get rid of it.
|
||||
if let Some(p) = strip_prefix("/", path) {
|
||||
path = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a matcher for a single set of globs from a .gitignore file.
|
||||
pub struct GitignoreBuilder {
|
||||
builder: GlobSetBuilder,
|
||||
root: PathBuf,
|
||||
globs: Vec<Glob>,
|
||||
}
|
||||
|
||||
impl GitignoreBuilder {
|
||||
/// Create a new builder for a gitignore file.
|
||||
///
|
||||
/// The path given should be the path at which the globs for this gitignore
|
||||
/// file should be matched. Note that paths are always matched relative
|
||||
/// to the root path given here. Generally, the root path should correspond
|
||||
/// to the *directory* containing a `.gitignore` file.
|
||||
pub fn new<P: AsRef<Path>>(root: P) -> GitignoreBuilder {
|
||||
let root = root.as_ref();
|
||||
GitignoreBuilder {
|
||||
builder: GlobSetBuilder::new(),
|
||||
root: strip_prefix("./", root).unwrap_or(root).to_path_buf(),
|
||||
globs: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new matcher from the globs added so far.
|
||||
///
|
||||
/// Once a matcher is built, no new globs can be added to it.
|
||||
pub fn build(&self) -> Result<Gitignore, Error> {
|
||||
let nignore = self.globs.iter().filter(|g| !g.is_whitelist()).count();
|
||||
let nwhite = self.globs.iter().filter(|g| g.is_whitelist()).count();
|
||||
let set = try!(
|
||||
self.builder.build().map_err(|err| Error::Glob(err.to_string())));
|
||||
Ok(Gitignore {
|
||||
set: set,
|
||||
root: self.root.clone(),
|
||||
globs: self.globs.clone(),
|
||||
num_ignores: nignore as u64,
|
||||
num_whitelists: nwhite as u64,
|
||||
matches: Arc::new(ThreadLocal::default()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Add each glob from the file path given.
|
||||
///
|
||||
/// The file given should be formatted as a `gitignore` file.
|
||||
///
|
||||
/// Note that partial errors can be returned. For example, if there was
|
||||
/// a problem adding one glob, an error for that will be returned, but
|
||||
/// all other valid globs will still be added.
|
||||
pub fn add<P: AsRef<Path>>(&mut self, path: P) -> Option<Error> {
|
||||
let path = path.as_ref();
|
||||
let file = match File::open(path) {
|
||||
Err(err) => return Some(Error::Io(err).with_path(path)),
|
||||
Ok(file) => file,
|
||||
};
|
||||
let rdr = io::BufReader::new(file);
|
||||
let mut errs = PartialErrorBuilder::default();
|
||||
for (i, line) in rdr.lines().enumerate() {
|
||||
let lineno = (i + 1) as u64;
|
||||
let line = match line {
|
||||
Ok(line) => line,
|
||||
Err(err) => {
|
||||
errs.push(Error::Io(err).tagged(path, lineno));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Err(err) = self.add_line(Some(path.to_path_buf()), &line) {
|
||||
errs.push(err.tagged(path, lineno));
|
||||
}
|
||||
}
|
||||
errs.into_error_option()
|
||||
}
|
||||
|
||||
/// Add each glob line from the string given.
|
||||
///
|
||||
/// If this string came from a particular `gitignore` file, then its path
|
||||
/// should be provided here.
|
||||
///
|
||||
/// The string given should be formatted as a `gitignore` file.
|
||||
#[cfg(test)]
|
||||
fn add_str(
|
||||
&mut self,
|
||||
from: Option<PathBuf>,
|
||||
gitignore: &str,
|
||||
) -> Result<&mut GitignoreBuilder, Error> {
|
||||
for line in gitignore.lines() {
|
||||
try!(self.add_line(from.clone(), line));
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Add a line from a gitignore file to this builder.
|
||||
///
|
||||
/// If this line came from a particular `gitignore` file, then its path
|
||||
/// should be provided here.
|
||||
///
|
||||
/// If the line could not be parsed as a glob, then an error is returned.
|
||||
pub fn add_line(
|
||||
&mut self,
|
||||
from: Option<PathBuf>,
|
||||
mut line: &str,
|
||||
) -> Result<&mut GitignoreBuilder, Error> {
|
||||
if line.starts_with("#") {
|
||||
return Ok(self);
|
||||
}
|
||||
if !line.ends_with("\\ ") {
|
||||
line = line.trim_right();
|
||||
}
|
||||
if line.is_empty() {
|
||||
return Ok(self);
|
||||
}
|
||||
let mut glob = Glob {
|
||||
from: from,
|
||||
original: line.to_string(),
|
||||
actual: String::new(),
|
||||
is_whitelist: false,
|
||||
is_only_dir: false,
|
||||
};
|
||||
let mut literal_separator = false;
|
||||
let has_slash = line.chars().any(|c| c == '/');
|
||||
let is_absolute = line.chars().nth(0).unwrap() == '/';
|
||||
if line.starts_with("\\!") || line.starts_with("\\#") {
|
||||
line = &line[1..];
|
||||
} else {
|
||||
if line.starts_with("!") {
|
||||
glob.is_whitelist = true;
|
||||
line = &line[1..];
|
||||
}
|
||||
if line.starts_with("/") {
|
||||
// `man gitignore` says that if a glob starts with a slash,
|
||||
// then the glob can only match the beginning of a path
|
||||
// (relative to the location of gitignore). We achieve this by
|
||||
// simply banning wildcards from matching /.
|
||||
literal_separator = true;
|
||||
line = &line[1..];
|
||||
}
|
||||
}
|
||||
// If it ends with a slash, then this should only match directories,
|
||||
// but the slash should otherwise not be used while globbing.
|
||||
if let Some((i, c)) = line.char_indices().rev().nth(0) {
|
||||
if c == '/' {
|
||||
glob.is_only_dir = true;
|
||||
line = &line[..i];
|
||||
}
|
||||
}
|
||||
// If there is a literal slash, then we note that so that globbing
|
||||
// doesn't let wildcards match slashes.
|
||||
glob.actual = line.to_string();
|
||||
if has_slash {
|
||||
literal_separator = true;
|
||||
}
|
||||
// If there was a leading slash, then this is a glob that must
|
||||
// match the entire path name. Otherwise, we should let it match
|
||||
// anywhere, so use a **/ prefix.
|
||||
if !is_absolute {
|
||||
// ... but only if we don't already have a **/ prefix.
|
||||
if !glob.actual.starts_with("**/") {
|
||||
glob.actual = format!("**/{}", glob.actual);
|
||||
}
|
||||
}
|
||||
// If the glob ends with `/**`, then we should only match everything
|
||||
// inside a directory, but not the directory itself. Standard globs
|
||||
// will match the directory. So we add `/*` to force the issue.
|
||||
if glob.actual.ends_with("/**") {
|
||||
glob.actual = format!("{}/*", glob.actual);
|
||||
}
|
||||
let parsed = try!(
|
||||
GlobBuilder::new(&glob.actual)
|
||||
.literal_separator(literal_separator)
|
||||
.build()
|
||||
.map_err(|err| Error::Glob(err.to_string())));
|
||||
self.builder.add(parsed);
|
||||
self.globs.push(glob);
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the file path of the current environment's global gitignore file.
|
||||
///
|
||||
/// Note that the file path returned may not exist.
|
||||
fn gitconfig_excludes_path() -> Option<PathBuf> {
|
||||
gitconfig_contents()
|
||||
.and_then(|data| parse_excludes_file(&data))
|
||||
.or_else(excludes_file_default)
|
||||
}
|
||||
|
||||
/// Returns the file contents of git's global config file, if one exists.
|
||||
fn gitconfig_contents() -> Option<Vec<u8>> {
|
||||
let home = match env::var_os("HOME") {
|
||||
None => return None,
|
||||
Some(home) => PathBuf::from(home),
|
||||
};
|
||||
let mut file = match File::open(home.join(".gitconfig")) {
|
||||
Err(_) => return None,
|
||||
Ok(file) => io::BufReader::new(file),
|
||||
};
|
||||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents).ok().map(|_| contents)
|
||||
}
|
||||
|
||||
/// Returns the default file path for a global .gitignore file.
|
||||
///
|
||||
/// Specifically, this respects XDG_CONFIG_HOME.
|
||||
fn excludes_file_default() -> Option<PathBuf> {
|
||||
env::var_os("XDG_CONFIG_HOME")
|
||||
.and_then(|x| if x.is_empty() { None } else { Some(x) })
|
||||
.or_else(|| env::var_os("HOME"))
|
||||
.map(|x| PathBuf::from(x).join("git/ignore"))
|
||||
}
|
||||
|
||||
/// Extract git's `core.excludesfile` config setting from the raw file contents
|
||||
/// given.
|
||||
fn parse_excludes_file(data: &[u8]) -> Option<PathBuf> {
|
||||
// N.B. This is the lazy approach, and isn't technically correct, but
|
||||
// probably works in more circumstances. I guess we would ideally have
|
||||
// a full INI parser. Yuck.
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(
|
||||
r"(?ium)^\s*excludesfile\s*=\s*(.+)\s*$").unwrap();
|
||||
};
|
||||
let caps = match RE.captures(data) {
|
||||
None => return None,
|
||||
Some(caps) => caps,
|
||||
};
|
||||
str::from_utf8(&caps[1]).ok().map(|s| PathBuf::from(expand_tilde(s)))
|
||||
}
|
||||
|
||||
/// Expands ~ in file paths to the value of $HOME.
|
||||
fn expand_tilde(path: &str) -> String {
|
||||
let home = match env::var("HOME") {
|
||||
Err(_) => return path.to_string(),
|
||||
Ok(home) => home,
|
||||
};
|
||||
path.replace("~", &home)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
use super::{Gitignore, GitignoreBuilder};
|
||||
|
||||
fn gi_from_str<P: AsRef<Path>>(root: P, s: &str) -> Gitignore {
|
||||
let mut builder = GitignoreBuilder::new(root);
|
||||
builder.add_str(None, s).unwrap();
|
||||
builder.build().unwrap()
|
||||
}
|
||||
|
||||
macro_rules! ignored {
|
||||
($name:ident, $root:expr, $gi:expr, $path:expr) => {
|
||||
ignored!($name, $root, $gi, $path, false);
|
||||
};
|
||||
($name:ident, $root:expr, $gi:expr, $path:expr, $is_dir:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let gi = gi_from_str($root, $gi);
|
||||
assert!(gi.matched($path, $is_dir).is_ignore());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! not_ignored {
|
||||
($name:ident, $root:expr, $gi:expr, $path:expr) => {
|
||||
not_ignored!($name, $root, $gi, $path, false);
|
||||
};
|
||||
($name:ident, $root:expr, $gi:expr, $path:expr, $is_dir:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let gi = gi_from_str($root, $gi);
|
||||
assert!(!gi.matched($path, $is_dir).is_ignore());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const ROOT: &'static str = "/home/foobar/rust/rg";
|
||||
|
||||
ignored!(ig1, ROOT, "months", "months");
|
||||
ignored!(ig2, ROOT, "*.lock", "Cargo.lock");
|
||||
ignored!(ig3, ROOT, "*.rs", "src/main.rs");
|
||||
ignored!(ig4, ROOT, "src/*.rs", "src/main.rs");
|
||||
ignored!(ig5, ROOT, "/*.c", "cat-file.c");
|
||||
ignored!(ig6, ROOT, "/src/*.rs", "src/main.rs");
|
||||
ignored!(ig7, ROOT, "!src/main.rs\n*.rs", "src/main.rs");
|
||||
ignored!(ig8, ROOT, "foo/", "foo", true);
|
||||
ignored!(ig9, ROOT, "**/foo", "foo");
|
||||
ignored!(ig10, ROOT, "**/foo", "src/foo");
|
||||
ignored!(ig11, ROOT, "**/foo/**", "src/foo/bar");
|
||||
ignored!(ig12, ROOT, "**/foo/**", "wat/src/foo/bar/baz");
|
||||
ignored!(ig13, ROOT, "**/foo/bar", "foo/bar");
|
||||
ignored!(ig14, ROOT, "**/foo/bar", "src/foo/bar");
|
||||
ignored!(ig15, ROOT, "abc/**", "abc/x");
|
||||
ignored!(ig16, ROOT, "abc/**", "abc/x/y");
|
||||
ignored!(ig17, ROOT, "abc/**", "abc/x/y/z");
|
||||
ignored!(ig18, ROOT, "a/**/b", "a/b");
|
||||
ignored!(ig19, ROOT, "a/**/b", "a/x/b");
|
||||
ignored!(ig20, ROOT, "a/**/b", "a/x/y/b");
|
||||
ignored!(ig21, ROOT, r"\!xy", "!xy");
|
||||
ignored!(ig22, ROOT, r"\#foo", "#foo");
|
||||
ignored!(ig23, ROOT, "foo", "./foo");
|
||||
ignored!(ig24, ROOT, "target", "grep/target");
|
||||
ignored!(ig25, ROOT, "Cargo.lock", "./tabwriter-bin/Cargo.lock");
|
||||
ignored!(ig26, ROOT, "/foo/bar/baz", "./foo/bar/baz");
|
||||
ignored!(ig27, ROOT, "foo/", "xyz/foo", true);
|
||||
ignored!(ig28, ROOT, "src/*.rs", "src/grep/src/main.rs");
|
||||
ignored!(ig29, "./src", "/llvm/", "./src/llvm", true);
|
||||
ignored!(ig30, ROOT, "node_modules/ ", "node_modules", true);
|
||||
|
||||
not_ignored!(ignot1, ROOT, "amonths", "months");
|
||||
not_ignored!(ignot2, ROOT, "monthsa", "months");
|
||||
not_ignored!(ignot3, ROOT, "/src/*.rs", "src/grep/src/main.rs");
|
||||
not_ignored!(ignot4, ROOT, "/*.c", "mozilla-sha1/sha1.c");
|
||||
not_ignored!(ignot5, ROOT, "/src/*.rs", "src/grep/src/main.rs");
|
||||
not_ignored!(ignot6, ROOT, "*.rs\n!src/main.rs", "src/main.rs");
|
||||
not_ignored!(ignot7, ROOT, "foo/", "foo", false);
|
||||
not_ignored!(ignot8, ROOT, "**/foo/**", "wat/src/afoo/bar/baz");
|
||||
not_ignored!(ignot9, ROOT, "**/foo/**", "wat/src/fooa/bar/baz");
|
||||
not_ignored!(ignot10, ROOT, "**/foo/bar", "foo/src/bar");
|
||||
not_ignored!(ignot11, ROOT, "#foo", "#foo");
|
||||
not_ignored!(ignot12, ROOT, "\n\n\n", "foo");
|
||||
not_ignored!(ignot13, ROOT, "foo/**", "foo", true);
|
||||
not_ignored!(
|
||||
ignot14, "./third_party/protobuf", "m4/ltoptions.m4",
|
||||
"./third_party/protobuf/csharp/src/packages/repositories.config");
|
||||
|
||||
fn bytes(s: &str) -> Vec<u8> {
|
||||
s.to_string().into_bytes()
|
||||
}
|
||||
|
||||
fn path_string<P: AsRef<Path>>(path: P) -> String {
|
||||
path.as_ref().to_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_excludes_file1() {
|
||||
let data = bytes("[core]\nexcludesFile = /foo/bar");
|
||||
let got = super::parse_excludes_file(&data).unwrap();
|
||||
assert_eq!(path_string(got), "/foo/bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_excludes_file2() {
|
||||
let data = bytes("[core]\nexcludesFile = ~/foo/bar");
|
||||
let got = super::parse_excludes_file(&data).unwrap();
|
||||
assert_eq!(path_string(got), super::expand_tilde("~/foo/bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_excludes_file3() {
|
||||
let data = bytes("[core]\nexcludeFile = /foo/bar");
|
||||
assert!(super::parse_excludes_file(&data).is_none());
|
||||
}
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/106
|
||||
#[test]
|
||||
fn regression_106() {
|
||||
gi_from_str("/", " ");
|
||||
}
|
||||
}
|
||||
300
ignore/src/lib.rs
Normal file
300
ignore/src/lib.rs
Normal file
@@ -0,0 +1,300 @@
|
||||
/*!
|
||||
The ignore crate provides a fast recursive directory iterator that respects
|
||||
various filters such as globs, file types and `.gitignore` files. The precise
|
||||
matching rules and precedence is explained in the documentation for
|
||||
`WalkBuilder`.
|
||||
|
||||
Secondarily, this crate exposes gitignore and file type matchers for use cases
|
||||
that demand more fine-grained control.
|
||||
|
||||
# Example
|
||||
|
||||
This example shows the most basic usage of this crate. This code will
|
||||
recursively traverse the current directory while automatically filtering out
|
||||
files and directories according to ignore globs found in files like
|
||||
`.ignore` and `.gitignore`:
|
||||
|
||||
|
||||
```rust,no_run
|
||||
use ignore::Walk;
|
||||
|
||||
for result in Walk::new("./") {
|
||||
// Each item yielded by the iterator is either a directory entry or an
|
||||
// error, so either print the path or the error.
|
||||
match result {
|
||||
Ok(entry) => println!("{}", entry.path().display()),
|
||||
Err(err) => println!("ERROR: {}", err),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Example: advanced
|
||||
|
||||
By default, the recursive directory iterator will ignore hidden files and
|
||||
directories. This can be disabled by building the iterator with `WalkBuilder`:
|
||||
|
||||
```rust,no_run
|
||||
use ignore::WalkBuilder;
|
||||
|
||||
for result in WalkBuilder::new("./").hidden(false).build() {
|
||||
println!("{:?}", result);
|
||||
}
|
||||
```
|
||||
|
||||
See the documentation for `WalkBuilder` for many other options.
|
||||
*/
|
||||
|
||||
extern crate globset;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate memchr;
|
||||
extern crate regex;
|
||||
#[cfg(test)]
|
||||
extern crate tempdir;
|
||||
extern crate thread_local;
|
||||
extern crate walkdir;
|
||||
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub use walk::{DirEntry, Walk, WalkBuilder};
|
||||
|
||||
mod dir;
|
||||
pub mod gitignore;
|
||||
mod pathutil;
|
||||
pub mod overrides;
|
||||
pub mod types;
|
||||
mod walk;
|
||||
|
||||
/// Represents an error that can occur when parsing a gitignore file.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// A collection of "soft" errors. These occur when adding an ignore
|
||||
/// file partially succeeded.
|
||||
Partial(Vec<Error>),
|
||||
/// An error associated with a specific line number.
|
||||
WithLineNumber { line: u64, err: Box<Error> },
|
||||
/// An error associated with a particular file path.
|
||||
WithPath { path: PathBuf, err: Box<Error> },
|
||||
/// An error that occurs when doing I/O, such as reading an ignore file.
|
||||
Io(io::Error),
|
||||
/// An error that occurs when trying to parse a glob.
|
||||
Glob(String),
|
||||
/// A type selection for a file type that is not defined.
|
||||
UnrecognizedFileType(String),
|
||||
/// A user specified file type definition could not be parsed.
|
||||
InvalidDefinition,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Returns true if this is a partial error.
|
||||
///
|
||||
/// A partial error occurs when only some operations failed while others
|
||||
/// may have succeeded. For example, an ignore file may contain an invalid
|
||||
/// glob among otherwise valid globs.
|
||||
pub fn is_partial(&self) -> bool {
|
||||
match *self {
|
||||
Error::Partial(_) => true,
|
||||
Error::WithLineNumber { ref err, .. } => err.is_partial(),
|
||||
Error::WithPath { ref err, .. } => err.is_partial(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this error is exclusively an I/O error.
|
||||
pub fn is_io(&self) -> bool {
|
||||
match *self {
|
||||
Error::Partial(ref errs) => errs.len() == 1 && errs[0].is_io(),
|
||||
Error::WithLineNumber { ref err, .. } => err.is_io(),
|
||||
Error::WithPath { ref err, .. } => err.is_io(),
|
||||
Error::Io(_) => true,
|
||||
Error::Glob(_) => false,
|
||||
Error::UnrecognizedFileType(_) => false,
|
||||
Error::InvalidDefinition => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn an error into a tagged error with the given file path.
|
||||
fn with_path<P: AsRef<Path>>(self, path: P) -> Error {
|
||||
Error::WithPath {
|
||||
path: path.as_ref().to_path_buf(),
|
||||
err: Box::new(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn an error into a tagged error with the given file path and line
|
||||
/// number. If path is empty, then it is omitted from the error.
|
||||
fn tagged<P: AsRef<Path>>(self, path: P, lineno: u64) -> Error {
|
||||
let errline = Error::WithLineNumber {
|
||||
line: lineno,
|
||||
err: Box::new(self),
|
||||
};
|
||||
if path.as_ref().as_os_str().is_empty() {
|
||||
return errline;
|
||||
}
|
||||
errline.with_path(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::Partial(_) => "partial error",
|
||||
Error::WithLineNumber { ref err, .. } => err.description(),
|
||||
Error::WithPath { ref err, .. } => err.description(),
|
||||
Error::Io(ref err) => err.description(),
|
||||
Error::Glob(ref msg) => msg,
|
||||
Error::UnrecognizedFileType(_) => "unrecognized file type",
|
||||
Error::InvalidDefinition => "invalid definition",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::Partial(ref errs) => {
|
||||
let msgs: Vec<String> =
|
||||
errs.iter().map(|err| err.to_string()).collect();
|
||||
write!(f, "{}", msgs.join("\n"))
|
||||
}
|
||||
Error::WithLineNumber { line, ref err } => {
|
||||
write!(f, "line {}: {}", line, err)
|
||||
}
|
||||
Error::WithPath { ref path, ref err } => {
|
||||
write!(f, "{}: {}", path.display(), err)
|
||||
}
|
||||
Error::Io(ref err) => err.fmt(f),
|
||||
Error::Glob(ref msg) => write!(f, "{}", msg),
|
||||
Error::UnrecognizedFileType(ref ty) => {
|
||||
write!(f, "unrecognized file type: {}", ty)
|
||||
}
|
||||
Error::InvalidDefinition => {
|
||||
write!(f, "invalid definition (format is type:glob, e.g., \
|
||||
html:*.html)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct PartialErrorBuilder(Vec<Error>);
|
||||
|
||||
impl PartialErrorBuilder {
|
||||
fn push(&mut self, err: Error) {
|
||||
self.0.push(err);
|
||||
}
|
||||
|
||||
fn push_ignore_io(&mut self, err: Error) {
|
||||
if !err.is_io() {
|
||||
self.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_push(&mut self, err: Option<Error>) {
|
||||
if let Some(err) = err {
|
||||
self.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_push_ignore_io(&mut self, err: Option<Error>) {
|
||||
if let Some(err) = err {
|
||||
self.push_ignore_io(err);
|
||||
}
|
||||
}
|
||||
|
||||
fn into_error_option(mut self) -> Option<Error> {
|
||||
if self.0.is_empty() {
|
||||
None
|
||||
} else if self.0.len() == 1 {
|
||||
Some(self.0.pop().unwrap())
|
||||
} else {
|
||||
Some(Error::Partial(self.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of a glob match.
|
||||
///
|
||||
/// The type parameter `T` typically refers to a type that provides more
|
||||
/// information about a particular match. For example, it might identify
|
||||
/// the specific gitignore file and the specific glob pattern that caused
|
||||
/// the match.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Match<T> {
|
||||
/// The path didn't match any glob.
|
||||
None,
|
||||
/// The highest precedent glob matched indicates the path should be
|
||||
/// ignored.
|
||||
Ignore(T),
|
||||
/// The highest precedent glob matched indicates the path should be
|
||||
/// whitelisted.
|
||||
Whitelist(T),
|
||||
}
|
||||
|
||||
impl<T> Match<T> {
|
||||
/// Returns true if the match result didn't match any globs.
|
||||
pub fn is_none(&self) -> bool {
|
||||
match *self {
|
||||
Match::None => true,
|
||||
Match::Ignore(_) | Match::Whitelist(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the match result implies the path should be ignored.
|
||||
pub fn is_ignore(&self) -> bool {
|
||||
match *self {
|
||||
Match::Ignore(_) => true,
|
||||
Match::None | Match::Whitelist(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the match result implies the path should be
|
||||
/// whitelisted.
|
||||
pub fn is_whitelist(&self) -> bool {
|
||||
match *self {
|
||||
Match::Whitelist(_) => true,
|
||||
Match::None | Match::Ignore(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Inverts the match so that `Ignore` becomes `Whitelist` and
|
||||
/// `Whitelist` becomes `Ignore`. A non-match remains the same.
|
||||
pub fn invert(self) -> Match<T> {
|
||||
match self {
|
||||
Match::None => Match::None,
|
||||
Match::Ignore(t) => Match::Whitelist(t),
|
||||
Match::Whitelist(t) => Match::Ignore(t),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the value inside this match if it exists.
|
||||
pub fn inner(&self) -> Option<&T> {
|
||||
match *self {
|
||||
Match::None => None,
|
||||
Match::Ignore(ref t) => Some(t),
|
||||
Match::Whitelist(ref t) => Some(t),
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply the given function to the value inside this match.
|
||||
///
|
||||
/// If the match has no value, then return the match unchanged.
|
||||
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Match<U> {
|
||||
match self {
|
||||
Match::None => Match::None,
|
||||
Match::Ignore(t) => Match::Ignore(f(t)),
|
||||
Match::Whitelist(t) => Match::Whitelist(f(t)),
|
||||
}
|
||||
}
|
||||
}
|
||||
202
ignore/src/overrides.rs
Normal file
202
ignore/src/overrides.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
/*!
|
||||
The overrides module provides a way to specify a set of override globs.
|
||||
This provides functionality similar to `--include` or `--exclude` in command
|
||||
line tools.
|
||||
*/
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use gitignore::{self, Gitignore, GitignoreBuilder};
|
||||
use {Error, Match};
|
||||
|
||||
/// Glob represents a single glob in an override matcher.
|
||||
///
|
||||
/// This is used to report information about the highest precedent glob
|
||||
/// that matched.
|
||||
///
|
||||
/// Note that not all matches necessarily correspond to a specific glob. For
|
||||
/// example, if there are one or more whitelist globs and a file path doesn't
|
||||
/// match any glob in the set, then the file path is considered to be ignored.
|
||||
///
|
||||
/// The lifetime `'a` refers to the lifetime of the matcher that produced
|
||||
/// this glob.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Glob<'a>(GlobInner<'a>);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum GlobInner<'a> {
|
||||
/// No glob matched, but the file path should still be ignored.
|
||||
UnmatchedIgnore,
|
||||
/// A glob matched.
|
||||
Matched(&'a gitignore::Glob),
|
||||
}
|
||||
|
||||
impl<'a> Glob<'a> {
|
||||
fn unmatched() -> Glob<'a> {
|
||||
Glob(GlobInner::UnmatchedIgnore)
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages a set of overrides provided explicitly by the end user.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Override(Gitignore);
|
||||
|
||||
impl Override {
|
||||
/// Returns an empty matcher that never matches any file path.
|
||||
pub fn empty() -> Override {
|
||||
Override(Gitignore::empty())
|
||||
}
|
||||
|
||||
/// Returns the directory of this override set.
|
||||
///
|
||||
/// All matches are done relative to this path.
|
||||
pub fn path(&self) -> &Path {
|
||||
self.0.path()
|
||||
}
|
||||
|
||||
/// Returns true if and only if this matcher is empty.
|
||||
///
|
||||
/// When a matcher is empty, it will never match any file path.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the total number of ignore globs.
|
||||
pub fn num_ignores(&self) -> u64 {
|
||||
self.0.num_whitelists()
|
||||
}
|
||||
|
||||
/// Returns the total number of whitelisted globs.
|
||||
pub fn num_whitelists(&self) -> u64 {
|
||||
self.0.num_ignores()
|
||||
}
|
||||
|
||||
/// Returns whether the given file path matched a pattern in this override
|
||||
/// matcher.
|
||||
///
|
||||
/// `is_dir` should be true if the path refers to a directory and false
|
||||
/// otherwise.
|
||||
///
|
||||
/// If there are no overrides, then this always returns `Match::None`.
|
||||
///
|
||||
/// If there is at least one whitelist override, then this never returns
|
||||
/// `Match::None`, since non-matches are interpreted as ignored.
|
||||
///
|
||||
/// The given path is matched to the globs relative to the path given
|
||||
/// when building the override matcher. Specifically, before matching
|
||||
/// `path`, its prefix (as determined by a common suffix of the directory
|
||||
/// given) is stripped. If there is no common suffix/prefix overlap, then
|
||||
/// `path` is assumed to reside in the same directory as the root path for
|
||||
/// this set of overrides.
|
||||
pub fn matched<'a, P: AsRef<Path>>(
|
||||
&'a self,
|
||||
path: P,
|
||||
is_dir: bool,
|
||||
) -> Match<Glob<'a>> {
|
||||
if self.is_empty() {
|
||||
return Match::None;
|
||||
}
|
||||
let mat = self.0.matched(path, is_dir).invert();
|
||||
if mat.is_none() && self.num_whitelists() > 0 {
|
||||
return Match::Ignore(Glob::unmatched());
|
||||
}
|
||||
mat.map(move |giglob| Glob(GlobInner::Matched(giglob)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a matcher for a set of glob overrides.
|
||||
pub struct OverrideBuilder {
|
||||
builder: GitignoreBuilder,
|
||||
}
|
||||
|
||||
impl OverrideBuilder {
|
||||
/// Create a new override builder.
|
||||
///
|
||||
/// Matching is done relative to the directory path provided.
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> OverrideBuilder {
|
||||
OverrideBuilder {
|
||||
builder: GitignoreBuilder::new(path),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new override matcher from the globs added so far.
|
||||
///
|
||||
/// Once a matcher is built, no new globs can be added to it.
|
||||
pub fn build(&self) -> Result<Override, Error> {
|
||||
Ok(Override(try!(self.builder.build())))
|
||||
}
|
||||
|
||||
/// Add a glob to the set of overrides.
|
||||
///
|
||||
/// Globs provided here have precisely the same semantics as a single
|
||||
/// line in a `gitignore` file, where the meaning of `!` is inverted:
|
||||
/// namely, `!` at the beginning of a glob will ignore a file. Without `!`,
|
||||
/// all matches of the glob provided are treated as whitelist matches.
|
||||
pub fn add(&mut self, glob: &str) -> Result<&mut OverrideBuilder, Error> {
|
||||
try!(self.builder.add_line(None, glob));
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Override, OverrideBuilder};
|
||||
|
||||
const ROOT: &'static str = "/home/andrew/foo";
|
||||
|
||||
fn ov(globs: &[&str]) -> Override {
|
||||
let mut builder = OverrideBuilder::new(ROOT);
|
||||
for glob in globs {
|
||||
builder.add(glob).unwrap();
|
||||
}
|
||||
builder.build().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let ov = ov(&[]);
|
||||
assert!(ov.matched("a.foo", false).is_none());
|
||||
assert!(ov.matched("a", false).is_none());
|
||||
assert!(ov.matched("", false).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let ov = ov(&["*.foo", "!*.bar"]);
|
||||
assert!(ov.matched("a.foo", false).is_whitelist());
|
||||
assert!(ov.matched("a.foo", true).is_whitelist());
|
||||
assert!(ov.matched("a.rs", false).is_ignore());
|
||||
assert!(ov.matched("a.rs", true).is_ignore());
|
||||
assert!(ov.matched("a.bar", false).is_ignore());
|
||||
assert!(ov.matched("a.bar", true).is_ignore());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_ignores() {
|
||||
let ov = ov(&["!*.bar"]);
|
||||
assert!(ov.matched("a.rs", false).is_none());
|
||||
assert!(ov.matched("a.rs", true).is_none());
|
||||
assert!(ov.matched("a.bar", false).is_ignore());
|
||||
assert!(ov.matched("a.bar", true).is_ignore());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn precedence() {
|
||||
let ov = ov(&["*.foo", "!*.bar.foo"]);
|
||||
assert!(ov.matched("a.foo", false).is_whitelist());
|
||||
assert!(ov.matched("a.baz", false).is_ignore());
|
||||
assert!(ov.matched("a.bar.foo", false).is_ignore());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gitignore() {
|
||||
let ov = ov(&["/foo", "bar/*.rs", "baz/**"]);
|
||||
assert!(ov.matched("bar/wat/lib.rs", false).is_ignore());
|
||||
assert!(ov.matched("wat/bar/lib.rs", false).is_whitelist());
|
||||
assert!(ov.matched("foo", false).is_whitelist());
|
||||
assert!(ov.matched("wat/foo", false).is_ignore());
|
||||
assert!(ov.matched("baz", false).is_ignore());
|
||||
assert!(ov.matched("baz/a", false).is_whitelist());
|
||||
assert!(ov.matched("baz/a/b", false).is_whitelist());
|
||||
}
|
||||
}
|
||||
108
ignore/src/pathutil.rs
Normal file
108
ignore/src/pathutil.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
/// Returns true if and only if this file path is considered to be hidden.
|
||||
#[cfg(unix)]
|
||||
pub fn is_hidden<P: AsRef<Path>>(path: P) -> bool {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
if let Some(name) = file_name(path.as_ref()) {
|
||||
name.as_bytes().get(0) == Some(&b'.')
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if and only if this file path is considered to be hidden.
|
||||
#[cfg(not(unix))]
|
||||
pub fn is_hidden<P: AsRef<Path>>(path: P) -> bool {
|
||||
if let Some(name) = file_name(path.as_ref()) {
|
||||
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Strip `prefix` from the `path` and return the remainder.
|
||||
///
|
||||
/// If `path` doesn't have a prefix `prefix`, then return `None`.
|
||||
#[cfg(unix)]
|
||||
pub fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
||||
prefix: &'a P,
|
||||
path: &'a Path,
|
||||
) -> Option<&'a Path> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let prefix = prefix.as_ref().as_os_str().as_bytes();
|
||||
let path = path.as_os_str().as_bytes();
|
||||
if prefix.len() > path.len() || prefix != &path[0..prefix.len()] {
|
||||
None
|
||||
} else {
|
||||
Some(&Path::new(OsStr::from_bytes(&path[prefix.len()..])))
|
||||
}
|
||||
}
|
||||
|
||||
/// Strip `prefix` from the `path` and return the remainder.
|
||||
///
|
||||
/// If `path` doesn't have a prefix `prefix`, then return `None`.
|
||||
#[cfg(not(unix))]
|
||||
pub fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
||||
prefix: &'a P,
|
||||
path: &'a Path,
|
||||
) -> Option<&'a Path> {
|
||||
path.strip_prefix(prefix).ok()
|
||||
}
|
||||
|
||||
/// Returns true if this file path is just a file name. i.e., Its parent is
|
||||
/// the empty string.
|
||||
#[cfg(unix)]
|
||||
pub fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use memchr::memchr;
|
||||
|
||||
let path = path.as_ref().as_os_str().as_bytes();
|
||||
memchr(b'/', path).is_none()
|
||||
}
|
||||
|
||||
/// Returns true if this file path is just a file name. i.e., Its parent is
|
||||
/// the empty string.
|
||||
#[cfg(not(unix))]
|
||||
pub fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
|
||||
path.as_ref().parent().map(|p| p.as_os_str().is_empty()).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// The final component of the path, if it is a normal file.
|
||||
///
|
||||
/// If the path terminates in ., .., or consists solely of a root of prefix,
|
||||
/// file_name will return None.
|
||||
#[cfg(unix)]
|
||||
pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
|
||||
path: &'a P,
|
||||
) -> Option<&'a OsStr> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use memchr::memrchr;
|
||||
|
||||
let path = path.as_ref().as_os_str().as_bytes();
|
||||
if path.is_empty() {
|
||||
return None;
|
||||
} else if path.len() == 1 && path[0] == b'.' {
|
||||
return None;
|
||||
} else if path.last() == Some(&b'.') {
|
||||
return None;
|
||||
} else if path.len() >= 2 && &path[path.len() - 2..] == &b".."[..] {
|
||||
return None;
|
||||
}
|
||||
let last_slash = memrchr(b'/', path).map(|i| i + 1).unwrap_or(0);
|
||||
Some(OsStr::from_bytes(&path[last_slash..]))
|
||||
}
|
||||
|
||||
/// The final component of the path, if it is a normal file.
|
||||
///
|
||||
/// If the path terminates in ., .., or consists solely of a root of prefix,
|
||||
/// file_name will return None.
|
||||
#[cfg(not(unix))]
|
||||
pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
|
||||
path: &'a P,
|
||||
) -> Option<&'a OsStr> {
|
||||
path.as_ref().file_name()
|
||||
}
|
||||
568
ignore/src/types.rs
Normal file
568
ignore/src/types.rs
Normal file
@@ -0,0 +1,568 @@
|
||||
/*!
|
||||
The types module provides a way of associating globs on file names to file
|
||||
types.
|
||||
|
||||
This can be used to match specific types of files. For example, among
|
||||
the default file types provided, the Rust file type is defined to be `*.rs`
|
||||
with name `rust`. Similarly, the C file type is defined to be `*.{c,h}` with
|
||||
name `c`.
|
||||
|
||||
Note that the set of default types may change over time.
|
||||
|
||||
# Example
|
||||
|
||||
This shows how to create and use a simple file type matcher using the default
|
||||
file types defined in this crate.
|
||||
|
||||
```
|
||||
use ignore::types::TypesBuilder;
|
||||
|
||||
let mut builder = TypesBuilder::new();
|
||||
builder.add_defaults();
|
||||
builder.select("rust");
|
||||
let matcher = builder.build().unwrap();
|
||||
|
||||
assert!(matcher.matched("foo.rs", false).is_whitelist());
|
||||
assert!(matcher.matched("foo.c", false).is_ignore());
|
||||
```
|
||||
|
||||
# Example: negation
|
||||
|
||||
This is like the previous example, but shows how negating a file type works.
|
||||
That is, this will let us match file paths that *don't* correspond to a
|
||||
particular file type.
|
||||
|
||||
```
|
||||
use ignore::types::TypesBuilder;
|
||||
|
||||
let mut builder = TypesBuilder::new();
|
||||
builder.add_defaults();
|
||||
builder.negate("c");
|
||||
let matcher = builder.build().unwrap();
|
||||
|
||||
assert!(matcher.matched("foo.rs", false).is_none());
|
||||
assert!(matcher.matched("foo.c", false).is_ignore());
|
||||
```
|
||||
|
||||
# Example: custom file type definitions
|
||||
|
||||
This shows how to extend this library default file type definitions with
|
||||
your own.
|
||||
|
||||
```
|
||||
use ignore::types::TypesBuilder;
|
||||
|
||||
let mut builder = TypesBuilder::new();
|
||||
builder.add_defaults();
|
||||
builder.add("foo", "*.foo");
|
||||
// Another way of adding a file type definition.
|
||||
// This is useful when accepting input from an end user.
|
||||
builder.add_def("bar:*.bar");
|
||||
// Note: we only select `foo`, not `bar`.
|
||||
builder.select("foo");
|
||||
let matcher = builder.build().unwrap();
|
||||
|
||||
assert!(matcher.matched("x.foo", false).is_whitelist());
|
||||
// This is ignored because we only selected the `foo` file type.
|
||||
assert!(matcher.matched("x.bar", false).is_ignore());
|
||||
```
|
||||
*/
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use globset::{GlobBuilder, GlobSet, GlobSetBuilder};
|
||||
use thread_local::ThreadLocal;
|
||||
|
||||
use pathutil::file_name;
|
||||
use {Error, Match};
|
||||
|
||||
const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
||||
("asm", &["*.asm", "*.s", "*.S"]),
|
||||
("awk", &["*.awk"]),
|
||||
("c", &["*.c", "*.h", "*.H"]),
|
||||
("cbor", &["*.cbor"]),
|
||||
("clojure", &["*.clj", "*.cljc", "*.cljs", "*.cljx"]),
|
||||
("cmake", &["*.cmake", "CMakeLists.txt"]),
|
||||
("coffeescript", &["*.coffee"]),
|
||||
("config", &["*.config"]),
|
||||
("cpp", &[
|
||||
"*.C", "*.cc", "*.cpp", "*.cxx",
|
||||
"*.h", "*.H", "*.hh", "*.hpp",
|
||||
]),
|
||||
("csharp", &["*.cs"]),
|
||||
("css", &["*.css"]),
|
||||
("cython", &["*.pyx"]),
|
||||
("dart", &["*.dart"]),
|
||||
("d", &["*.d"]),
|
||||
("elisp", &["*.el"]),
|
||||
("erlang", &["*.erl", "*.hrl"]),
|
||||
("fortran", &[
|
||||
"*.f", "*.F", "*.f77", "*.F77", "*.pfo",
|
||||
"*.f90", "*.F90", "*.f95", "*.F95",
|
||||
]),
|
||||
("fsharp", &["*.fs", "*.fsx", "*.fsi"]),
|
||||
("go", &["*.go"]),
|
||||
("groovy", &["*.groovy", "*.gradle"]),
|
||||
("hbs", &["*.hbs"]),
|
||||
("haskell", &["*.hs", "*.lhs"]),
|
||||
("html", &["*.htm", "*.html"]),
|
||||
("java", &["*.java"]),
|
||||
("jinja", &["*.jinja", "*.jinja2"]),
|
||||
("js", &[
|
||||
"*.js", "*.jsx", "*.vue",
|
||||
]),
|
||||
("json", &["*.json"]),
|
||||
("jsonl", &["*.jsonl"]),
|
||||
("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
|
||||
("lua", &["*.lua"]),
|
||||
("m4", &["*.ac", "*.m4"]),
|
||||
("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk"]),
|
||||
("markdown", &["*.md"]),
|
||||
("md", &["*.md"]),
|
||||
("matlab", &["*.m"]),
|
||||
("mk", &["mkfile"]),
|
||||
("ml", &["*.ml"]),
|
||||
("nim", &["*.nim"]),
|
||||
("objc", &["*.h", "*.m"]),
|
||||
("objcpp", &["*.h", "*.mm"]),
|
||||
("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]),
|
||||
("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm"]),
|
||||
("php", &["*.php", "*.php3", "*.php4", "*.php5", "*.phtml"]),
|
||||
("py", &["*.py"]),
|
||||
("readme", &["README*", "*README"]),
|
||||
("r", &["*.R", "*.r", "*.Rmd", "*.Rnw"]),
|
||||
("rst", &["*.rst"]),
|
||||
("ruby", &["*.rb"]),
|
||||
("rust", &["*.rs"]),
|
||||
("scala", &["*.scala"]),
|
||||
("sh", &["*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh"]),
|
||||
("spark", &["*.spark"]),
|
||||
("sql", &["*.sql"]),
|
||||
("sv", &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
|
||||
("swift", &["*.swift"]),
|
||||
("tcl", &["*.tcl"]),
|
||||
("tex", &["*.tex", "*.cls", "*.sty"]),
|
||||
("ts", &["*.ts", "*.tsx"]),
|
||||
("txt", &["*.txt"]),
|
||||
("toml", &["*.toml", "Cargo.lock"]),
|
||||
("vala", &["*.vala"]),
|
||||
("vb", &["*.vb"]),
|
||||
("vimscript", &["*.vim"]),
|
||||
("xml", &["*.xml"]),
|
||||
("yacc", &["*.y"]),
|
||||
("yaml", &["*.yaml", "*.yml"]),
|
||||
];
|
||||
|
||||
/// Glob represents a single glob in a set of file type definitions.
|
||||
///
|
||||
/// There may be more than one glob for a particular file type.
|
||||
///
|
||||
/// This is used to report information about the highest precedent glob
|
||||
/// that matched.
|
||||
///
|
||||
/// Note that not all matches necessarily correspond to a specific glob.
|
||||
/// For example, if there are one or more selections and a file path doesn't
|
||||
/// match any of those selections, then the file path is considered to be
|
||||
/// ignored.
|
||||
///
|
||||
/// The lifetime `'a` refers to the lifetime of the underlying file type
|
||||
/// definition, which corresponds to the lifetime of the file type matcher.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Glob<'a>(GlobInner<'a>);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum GlobInner<'a> {
|
||||
/// No glob matched, but the file path should still be ignored.
|
||||
UnmatchedIgnore,
|
||||
/// A glob matched.
|
||||
Matched {
|
||||
/// The file type definition which provided the glob.
|
||||
def: &'a FileTypeDef,
|
||||
/// The index of the glob that matched inside the file type definition.
|
||||
which: usize,
|
||||
/// Whether the selection was negated or not.
|
||||
negated: bool,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Glob<'a> {
|
||||
fn unmatched() -> Glob<'a> {
|
||||
Glob(GlobInner::UnmatchedIgnore)
|
||||
}
|
||||
}
|
||||
|
||||
/// A single file type definition.
|
||||
///
|
||||
/// File type definitions can be retrieved in aggregate from a file type
|
||||
/// matcher. File type definitions are also reported when its responsible
|
||||
/// for a match.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileTypeDef {
|
||||
name: String,
|
||||
globs: Vec<String>,
|
||||
}
|
||||
|
||||
impl FileTypeDef {
|
||||
/// Return the name of this file type.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Return the globs used to recognize this file type.
|
||||
pub fn globs(&self) -> &[String] {
|
||||
&self.globs
|
||||
}
|
||||
}
|
||||
|
||||
/// Types is a file type matcher.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Types {
|
||||
/// All of the file type definitions, sorted lexicographically by name.
|
||||
defs: Vec<FileTypeDef>,
|
||||
/// All of the selections made by the user.
|
||||
selections: Vec<Selection<FileTypeDef>>,
|
||||
/// Whether there is at least one Selection::Select in our selections.
|
||||
/// When this is true, a Match::None is converted to Match::Ignore.
|
||||
has_selected: bool,
|
||||
/// A mapping from glob index in the set to two indices. The first is an
|
||||
/// index into `selections` and the second is an index into the
|
||||
/// corresponding file type definition's list of globs.
|
||||
glob_to_selection: Vec<(usize, usize)>,
|
||||
/// The set of all glob selections, used for actual matching.
|
||||
set: GlobSet,
|
||||
/// Temporary storage for globs that match.
|
||||
matches: Arc<ThreadLocal<RefCell<Vec<usize>>>>,
|
||||
}
|
||||
|
||||
/// Indicates the type of a selection for a particular file type.
|
||||
#[derive(Clone, Debug)]
|
||||
enum Selection<T> {
|
||||
Select(String, T),
|
||||
Negate(String, T),
|
||||
}
|
||||
|
||||
impl<T> Selection<T> {
|
||||
fn is_negated(&self) -> bool {
|
||||
match *self {
|
||||
Selection::Select(..) => false,
|
||||
Selection::Negate(..) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
match *self {
|
||||
Selection::Select(ref name, _) => name,
|
||||
Selection::Negate(ref name, _) => name,
|
||||
}
|
||||
}
|
||||
|
||||
fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Selection<U> {
|
||||
match self {
|
||||
Selection::Select(name, inner) => {
|
||||
Selection::Select(name, f(inner))
|
||||
}
|
||||
Selection::Negate(name, inner) => {
|
||||
Selection::Negate(name, f(inner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inner(&self) -> &T {
|
||||
match *self {
|
||||
Selection::Select(_, ref inner) => inner,
|
||||
Selection::Negate(_, ref inner) => inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Types {
|
||||
/// Creates a new file type matcher that never matches any path and
|
||||
/// contains no file type definitions.
|
||||
pub fn empty() -> Types {
|
||||
Types {
|
||||
defs: vec![],
|
||||
selections: vec![],
|
||||
has_selected: false,
|
||||
glob_to_selection: vec![],
|
||||
set: GlobSetBuilder::new().build().unwrap(),
|
||||
matches: Arc::new(ThreadLocal::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if and only if this matcher has zero selections.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.selections.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the number of selections used in this matcher.
|
||||
pub fn len(&self) -> usize {
|
||||
self.selections.len()
|
||||
}
|
||||
|
||||
/// Return the set of current file type definitions.
|
||||
///
|
||||
/// Definitions and globs are sorted.
|
||||
pub fn definitions(&self) -> &[FileTypeDef] {
|
||||
&self.defs
|
||||
}
|
||||
|
||||
/// Returns a match for the given path against this file type matcher.
|
||||
///
|
||||
/// The path is considered whitelisted if it matches a selected file type.
|
||||
/// The path is considered ignored if it matches a negated file type.
|
||||
/// If at least one file type is selected and `path` doesn't match, then
|
||||
/// the path is also considered ignored.
|
||||
pub fn matched<'a, P: AsRef<Path>>(
|
||||
&'a self,
|
||||
path: P,
|
||||
is_dir: bool,
|
||||
) -> Match<Glob<'a>> {
|
||||
// File types don't apply to directories, and we can't do anything
|
||||
// if our glob set is empty.
|
||||
if is_dir || self.set.is_empty() {
|
||||
return Match::None;
|
||||
}
|
||||
// We only want to match against the file name, so extract it.
|
||||
// If one doesn't exist, then we can't match it.
|
||||
let name = match file_name(path.as_ref()) {
|
||||
Some(name) => name,
|
||||
None if self.has_selected => {
|
||||
return Match::Ignore(Glob::unmatched());
|
||||
}
|
||||
None => {
|
||||
return Match::None;
|
||||
}
|
||||
};
|
||||
let mut matches = self.matches.get_default().borrow_mut();
|
||||
self.set.matches_into(name, &mut *matches);
|
||||
// The highest precedent match is the last one.
|
||||
if let Some(&i) = matches.last() {
|
||||
let (isel, iglob) = self.glob_to_selection[i];
|
||||
let sel = &self.selections[isel];
|
||||
let glob = Glob(GlobInner::Matched {
|
||||
def: sel.inner(),
|
||||
which: iglob,
|
||||
negated: sel.is_negated(),
|
||||
});
|
||||
return if sel.is_negated() {
|
||||
Match::Ignore(glob)
|
||||
} else {
|
||||
Match::Whitelist(glob)
|
||||
};
|
||||
}
|
||||
if self.has_selected {
|
||||
Match::Ignore(Glob::unmatched())
|
||||
} else {
|
||||
Match::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TypesBuilder builds a type matcher from a set of file type definitions and
|
||||
/// a set of file type selections.
|
||||
pub struct TypesBuilder {
|
||||
types: HashMap<String, FileTypeDef>,
|
||||
selections: Vec<Selection<()>>,
|
||||
}
|
||||
|
||||
impl TypesBuilder {
|
||||
/// Create a new builder for a file type matcher.
|
||||
///
|
||||
/// The builder contains *no* type definitions to start with. A set
|
||||
/// of default type definitions can be added with `add_defaults`, and
|
||||
/// additional type definitions can be added with `select` and `negate`.
|
||||
pub fn new() -> TypesBuilder {
|
||||
TypesBuilder {
|
||||
types: HashMap::new(),
|
||||
selections: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the current set of file type definitions *and* selections into
|
||||
/// a file type matcher.
|
||||
pub fn build(&self) -> Result<Types, Error> {
|
||||
let defs = self.definitions();
|
||||
let has_selected = self.selections.iter().any(|s| !s.is_negated());
|
||||
|
||||
let mut selections = vec![];
|
||||
let mut glob_to_selection = vec![];
|
||||
let mut build_set = GlobSetBuilder::new();
|
||||
for (isel, selection) in self.selections.iter().enumerate() {
|
||||
let def = match self.types.get(selection.name()) {
|
||||
Some(def) => def.clone(),
|
||||
None => {
|
||||
let name = selection.name().to_string();
|
||||
return Err(Error::UnrecognizedFileType(name));
|
||||
}
|
||||
};
|
||||
for (iglob, glob) in def.globs.iter().enumerate() {
|
||||
build_set.add(try!(
|
||||
GlobBuilder::new(glob)
|
||||
.literal_separator(true)
|
||||
.build()
|
||||
.map_err(|err| Error::Glob(err.to_string()))));
|
||||
glob_to_selection.push((isel, iglob));
|
||||
}
|
||||
selections.push(selection.clone().map(move |_| def));
|
||||
}
|
||||
let set = try!(build_set.build().map_err(|err| {
|
||||
Error::Glob(err.to_string())
|
||||
}));
|
||||
Ok(Types {
|
||||
defs: defs,
|
||||
selections: selections,
|
||||
has_selected: has_selected,
|
||||
glob_to_selection: glob_to_selection,
|
||||
set: set,
|
||||
matches: Arc::new(ThreadLocal::default()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the set of current file type definitions.
|
||||
///
|
||||
/// Definitions and globs are sorted.
|
||||
pub fn definitions(&self) -> Vec<FileTypeDef> {
|
||||
let mut defs = vec![];
|
||||
for def in self.types.values() {
|
||||
let mut def = def.clone();
|
||||
def.globs.sort();
|
||||
defs.push(def);
|
||||
}
|
||||
defs.sort_by(|def1, def2| def1.name().cmp(def2.name()));
|
||||
defs
|
||||
}
|
||||
|
||||
/// Select the file type given by `name`.
|
||||
///
|
||||
/// If `name` is `all`, then all file types currently defined are selected.
|
||||
pub fn select(&mut self, name: &str) -> &mut TypesBuilder {
|
||||
if name == "all" {
|
||||
for name in self.types.keys() {
|
||||
self.selections.push(Selection::Select(name.to_string(), ()));
|
||||
}
|
||||
} else {
|
||||
self.selections.push(Selection::Select(name.to_string(), ()));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Ignore the file type given by `name`.
|
||||
///
|
||||
/// If `name` is `all`, then all file types currently defined are negated.
|
||||
pub fn negate(&mut self, name: &str) -> &mut TypesBuilder {
|
||||
if name == "all" {
|
||||
for name in self.types.keys() {
|
||||
self.selections.push(Selection::Negate(name.to_string(), ()));
|
||||
}
|
||||
} else {
|
||||
self.selections.push(Selection::Negate(name.to_string(), ()));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Clear any file type definitions for the type name given.
|
||||
pub fn clear(&mut self, name: &str) -> &mut TypesBuilder {
|
||||
self.types.remove(name);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a new file type definition. `name` can be arbitrary and `pat`
|
||||
/// should be a glob recognizing file paths belonging to the `name` type.
|
||||
///
|
||||
/// If `name` is `all` or otherwise contains a `:`, then an error is
|
||||
/// returned.
|
||||
pub fn add(&mut self, name: &str, glob: &str) -> Result<(), Error> {
|
||||
if name == "all" || name.contains(':') {
|
||||
return Err(Error::InvalidDefinition);
|
||||
}
|
||||
let (key, glob) = (name.to_string(), glob.to_string());
|
||||
self.types.entry(key).or_insert_with(|| {
|
||||
FileTypeDef { name: name.to_string(), globs: vec![] }
|
||||
}).globs.push(glob);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a new file type definition specified in string form. The format
|
||||
/// is `name:glob`. Names may not include a colon.
|
||||
pub fn add_def(&mut self, def: &str) -> Result<(), Error> {
|
||||
let name: String = def.chars().take_while(|&c| c != ':').collect();
|
||||
let pat: String = def.chars().skip(name.chars().count() + 1).collect();
|
||||
if name.is_empty() || pat.is_empty() {
|
||||
return Err(Error::InvalidDefinition);
|
||||
}
|
||||
self.add(&name, &pat)
|
||||
}
|
||||
|
||||
/// Add a set of default file type definitions.
|
||||
pub fn add_defaults(&mut self) -> &mut TypesBuilder {
|
||||
static MSG: &'static str = "adding a default type should never fail";
|
||||
for &(name, exts) in DEFAULT_TYPES {
|
||||
for ext in exts {
|
||||
self.add(name, ext).expect(MSG);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::TypesBuilder;
|
||||
|
||||
macro_rules! matched {
|
||||
($name:ident, $types:expr, $sel:expr, $selnot:expr,
|
||||
$path:expr) => {
|
||||
matched!($name, $types, $sel, $selnot, $path, true);
|
||||
};
|
||||
(not, $name:ident, $types:expr, $sel:expr, $selnot:expr,
|
||||
$path:expr) => {
|
||||
matched!($name, $types, $sel, $selnot, $path, false);
|
||||
};
|
||||
($name:ident, $types:expr, $sel:expr, $selnot:expr,
|
||||
$path:expr, $matched:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let mut btypes = TypesBuilder::new();
|
||||
for tydef in $types {
|
||||
btypes.add_def(tydef).unwrap();
|
||||
}
|
||||
for sel in $sel {
|
||||
btypes.select(sel);
|
||||
}
|
||||
for selnot in $selnot {
|
||||
btypes.negate(selnot);
|
||||
}
|
||||
let types = btypes.build().unwrap();
|
||||
let mat = types.matched($path, false);
|
||||
assert_eq!($matched, !mat.is_ignore());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn types() -> Vec<&'static str> {
|
||||
vec![
|
||||
"html:*.html",
|
||||
"html:*.htm",
|
||||
"rust:*.rs",
|
||||
"js:*.js",
|
||||
"foo:*.{rs,foo}",
|
||||
]
|
||||
}
|
||||
|
||||
matched!(match1, types(), vec!["rust"], vec![], "lib.rs");
|
||||
matched!(match2, types(), vec!["html"], vec![], "index.html");
|
||||
matched!(match3, types(), vec!["html"], vec![], "index.htm");
|
||||
matched!(match4, types(), vec!["html", "rust"], vec![], "main.rs");
|
||||
matched!(match5, types(), vec![], vec![], "index.html");
|
||||
matched!(match6, types(), vec![], vec!["rust"], "index.html");
|
||||
matched!(match7, types(), vec!["foo"], vec!["rust"], "main.foo");
|
||||
|
||||
matched!(not, matchnot1, types(), vec!["rust"], vec![], "index.html");
|
||||
matched!(not, matchnot2, types(), vec![], vec!["rust"], "main.rs");
|
||||
matched!(not, matchnot3, types(), vec!["foo"], vec!["rust"], "main.rs");
|
||||
matched!(not, matchnot4, types(), vec!["rust"], vec!["foo"], "main.rs");
|
||||
matched!(not, matchnot5, types(), vec!["rust"], vec!["foo"], "main.foo");
|
||||
}
|
||||
592
ignore/src/walk.rs
Normal file
592
ignore/src/walk.rs
Normal file
@@ -0,0 +1,592 @@
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{FileType, Metadata};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::vec;
|
||||
|
||||
use walkdir::{self, WalkDir, WalkDirIterator};
|
||||
|
||||
use dir::{Ignore, IgnoreBuilder};
|
||||
use gitignore::GitignoreBuilder;
|
||||
use overrides::Override;
|
||||
use types::Types;
|
||||
use {Error, PartialErrorBuilder};
|
||||
|
||||
/// WalkBuilder builds a recursive directory iterator.
|
||||
///
|
||||
/// The builder supports a large number of configurable options. This includes
|
||||
/// specific glob overrides, file type matching, toggling whether hidden
|
||||
/// files are ignored or not, and of course, support for respecting gitignore
|
||||
/// files.
|
||||
///
|
||||
/// By default, all ignore files found are respected. This includes `.ignore`,
|
||||
/// `.gitignore`, `.git/info/exclude` and even your global gitignore
|
||||
/// globs, usually found in `$XDG_CONFIG_HOME/git/ignore`.
|
||||
///
|
||||
/// Some standard recursive directory options are also supported, such as
|
||||
/// limiting the recursive depth or whether to follow symbolic links (disabled
|
||||
/// by default).
|
||||
///
|
||||
/// # Ignore rules
|
||||
///
|
||||
/// There are many rules that influence whether a particular file or directory
|
||||
/// is skipped by this iterator. Those rules are documented here. Note that
|
||||
/// the rules assume a default configuration.
|
||||
///
|
||||
/// * First, glob overrides are checked. If a path matches a glob override,
|
||||
/// then matching stops. The path is then only skipped if the glob that matched
|
||||
/// the path is an ignore glob. (An override glob is a whitelist glob unless it
|
||||
/// starts with a `!`, in which case it is an ignore glob.)
|
||||
/// * Second, ignore files are checked. Ignore files currently only come from
|
||||
/// git ignore files (`.gitignore`, `.git/info/exclude` and the configured
|
||||
/// global gitignore file), plain `.ignore` files, which have the same format
|
||||
/// as gitignore files, or explicitly added ignore files. The precedence order
|
||||
/// is: `.ignore`, `.gitignore`, `.git/info/exclude`, global gitignore and
|
||||
/// finally explicitly added ignore files. Note that precedence between
|
||||
/// different types of ignore files is not impacted by the directory hierarchy;
|
||||
/// any `.ignore` file overrides all `.gitignore` files. Within each
|
||||
/// precedence level, more nested ignore files have a higher precedence over
|
||||
/// less nested ignore files.
|
||||
/// * Third, if the previous step yields an ignore match, than all matching
|
||||
/// is stopped and the path is skipped.. If it yields a whitelist match, then
|
||||
/// process continues. A whitelist match can be overridden by a later matcher.
|
||||
/// * Fourth, unless the path is a directory, the file type matcher is run on
|
||||
/// the path. As above, if it's an ignore match, then all matching is stopped
|
||||
/// and the path is skipped. If it's a whitelist match, then matching
|
||||
/// continues.
|
||||
/// * Fifth, if the path hasn't been whitelisted and it is hidden, then the
|
||||
/// path is skipped.
|
||||
/// * Sixth, if the path has made it this far then it is yielded in the
|
||||
/// iterator.
|
||||
pub struct WalkBuilder {
|
||||
paths: Vec<PathBuf>,
|
||||
ig_builder: IgnoreBuilder,
|
||||
parents: bool,
|
||||
max_depth: Option<usize>,
|
||||
follow_links: bool,
|
||||
}
|
||||
|
||||
impl WalkBuilder {
|
||||
/// Create a new builder for a recursive directory iterator for the
|
||||
/// directory given.
|
||||
///
|
||||
/// Note that if you want to traverse multiple different directories, it
|
||||
/// is better to call `add` on this builder than to create multiple
|
||||
/// `Walk` values.
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> WalkBuilder {
|
||||
WalkBuilder {
|
||||
paths: vec![path.as_ref().to_path_buf()],
|
||||
ig_builder: IgnoreBuilder::new(),
|
||||
parents: true,
|
||||
max_depth: None,
|
||||
follow_links: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a new `Walk` iterator.
|
||||
pub fn build(&self) -> Walk {
|
||||
let follow_links = self.follow_links;
|
||||
let max_depth = self.max_depth;
|
||||
let its = self.paths.iter().map(move |p| {
|
||||
if p == Path::new("-") {
|
||||
(p.to_path_buf(), None)
|
||||
} else {
|
||||
let mut wd = WalkDir::new(p);
|
||||
wd = wd.follow_links(follow_links || p.is_file());
|
||||
if let Some(max_depth) = max_depth {
|
||||
wd = wd.max_depth(max_depth);
|
||||
}
|
||||
(p.to_path_buf(), Some(WalkEventIter::from(wd)))
|
||||
}
|
||||
}).collect::<Vec<_>>().into_iter();
|
||||
let ig_root = self.ig_builder.build();
|
||||
Walk {
|
||||
its: its,
|
||||
it: None,
|
||||
ig_root: ig_root.clone(),
|
||||
ig: ig_root.clone(),
|
||||
parents: self.parents,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a file path to the iterator.
|
||||
///
|
||||
/// Each additional file path added is traversed recursively. This should
|
||||
/// be preferred over building multiple `Walk` iterators since this
|
||||
/// enables reusing resources across iteration.
|
||||
pub fn add<P: AsRef<Path>>(&mut self, path: P) -> &mut WalkBuilder {
|
||||
self.paths.push(path.as_ref().to_path_buf());
|
||||
self
|
||||
}
|
||||
|
||||
/// The maximum depth to recurse.
|
||||
///
|
||||
/// The default, `None`, imposes no depth restriction.
|
||||
pub fn max_depth(&mut self, depth: Option<usize>) -> &mut WalkBuilder {
|
||||
self.max_depth = depth;
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to follow symbolic links or not.
|
||||
pub fn follow_links(&mut self, yes: bool) -> &mut WalkBuilder {
|
||||
self.follow_links = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an ignore file to the matcher.
|
||||
///
|
||||
/// This has lower precedence than all other sources of ignore rules.
|
||||
///
|
||||
/// If there was a problem adding the ignore file, then an error is
|
||||
/// returned. Note that the error may indicate *partial* failure. For
|
||||
/// example, if an ignore file contains an invalid glob, all other globs
|
||||
/// are still applied.
|
||||
pub fn add_ignore<P: AsRef<Path>>(&mut self, path: P) -> Option<Error> {
|
||||
let mut builder = GitignoreBuilder::new("");
|
||||
let mut errs = PartialErrorBuilder::default();
|
||||
errs.maybe_push_ignore_io(builder.add(path));
|
||||
match builder.build() {
|
||||
Ok(gi) => { self.ig_builder.add_ignore(gi); }
|
||||
Err(err) => { errs.push(err); }
|
||||
}
|
||||
errs.into_error_option()
|
||||
}
|
||||
|
||||
/// Add an override matcher.
|
||||
///
|
||||
/// By default, no override matcher is used.
|
||||
///
|
||||
/// This overrides any previous setting.
|
||||
pub fn overrides(&mut self, overrides: Override) -> &mut WalkBuilder {
|
||||
self.ig_builder.overrides(overrides);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a file type matcher.
|
||||
///
|
||||
/// By default, no file type matcher is used.
|
||||
///
|
||||
/// This overrides any previous setting.
|
||||
pub fn types(&mut self, types: Types) -> &mut WalkBuilder {
|
||||
self.ig_builder.types(types);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables ignoring hidden files.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn hidden(&mut self, yes: bool) -> &mut WalkBuilder {
|
||||
self.ig_builder.hidden(yes);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables reading ignore files from parent directories.
|
||||
///
|
||||
/// If this is enabled, then the parent directories of each file path given
|
||||
/// are traversed for ignore files (subject to the ignore settings on
|
||||
/// this builder). Note that file paths are canonicalized with respect to
|
||||
/// the current working directory in order to determine parent directories.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn parents(&mut self, yes: bool) -> &mut WalkBuilder {
|
||||
self.parents = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables reading `.ignore` files.
|
||||
///
|
||||
/// `.ignore` files have the same semantics as `gitignore` files and are
|
||||
/// supported by search tools such as ripgrep and The Silver Searcher.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn ignore(&mut self, yes: bool) -> &mut WalkBuilder {
|
||||
self.ig_builder.ignore(yes);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables reading a global gitignore file, whose path is specified in
|
||||
/// git's `core.excludesFile` config option.
|
||||
///
|
||||
/// Git's config file location is `$HOME/.gitconfig`. If `$HOME/.gitconfig`
|
||||
/// does not exist or does not specify `core.excludesFile`, then
|
||||
/// `$XDG_CONFIG_HOME/git/ignore` is read. If `$XDG_CONFIG_HOME` is not
|
||||
/// set or is empty, then `$HOME/.config/git/ignore` is used instead.
|
||||
pub fn git_global(&mut self, yes: bool) -> &mut WalkBuilder {
|
||||
self.ig_builder.git_global(yes);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables reading `.gitignore` files.
|
||||
///
|
||||
/// `.gitignore` files have match semantics as described in the `gitignore`
|
||||
/// man page.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn git_ignore(&mut self, yes: bool) -> &mut WalkBuilder {
|
||||
self.ig_builder.git_ignore(yes);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables reading `.git/info/exclude` files.
|
||||
///
|
||||
/// `.git/info/exclude` files have match semantics as described in the
|
||||
/// `gitignore` man page.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn git_exclude(&mut self, yes: bool) -> &mut WalkBuilder {
|
||||
self.ig_builder.git_exclude(yes);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk is a recursive directory iterator over file paths in a directory.
|
||||
///
|
||||
/// Only file and directory paths matching the rules are returned. By default,
|
||||
/// ignore files like `.gitignore` are respected. The precise matching rules
|
||||
/// and precedence is explained in the documentation for `WalkBuilder`.
|
||||
pub struct Walk {
|
||||
its: vec::IntoIter<(PathBuf, Option<WalkEventIter>)>,
|
||||
it: Option<WalkEventIter>,
|
||||
ig_root: Ignore,
|
||||
ig: Ignore,
|
||||
parents: bool,
|
||||
}
|
||||
|
||||
impl Walk {
|
||||
/// Creates a new recursive directory iterator for the file path given.
|
||||
///
|
||||
/// Note that this uses default settings, which include respecting
|
||||
/// `.gitignore` files. To configure the iterator, use `WalkBuilder`
|
||||
/// instead.
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> Walk {
|
||||
WalkBuilder::new(path).build()
|
||||
}
|
||||
|
||||
fn skip_entry(&self, ent: &walkdir::DirEntry) -> bool {
|
||||
if ent.depth() == 0 {
|
||||
// Never skip the root directory.
|
||||
return false;
|
||||
}
|
||||
let m = self.ig.matched(ent.path(), ent.file_type().is_dir());
|
||||
if m.is_ignore() {
|
||||
debug!("ignoring {}: {:?}", ent.path().display(), m);
|
||||
return true;
|
||||
} else if m.is_whitelist() {
|
||||
debug!("whitelisting {}: {:?}", ent.path().display(), m);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Walk {
|
||||
type Item = Result<DirEntry, Error>;
|
||||
|
||||
#[inline(always)]
|
||||
fn next(&mut self) -> Option<Result<DirEntry, Error>> {
|
||||
loop {
|
||||
let ev = match self.it.as_mut().and_then(|it| it.next()) {
|
||||
Some(ev) => ev,
|
||||
None => {
|
||||
match self.its.next() {
|
||||
None => return None,
|
||||
Some((_, None)) => {
|
||||
return Some(Ok(DirEntry {
|
||||
dent: None,
|
||||
err: None,
|
||||
}));
|
||||
}
|
||||
Some((path, Some(it))) => {
|
||||
self.it = Some(it);
|
||||
if self.parents && path.is_dir() {
|
||||
let (ig, err) = self.ig_root.add_parents(path);
|
||||
self.ig = ig;
|
||||
if let Some(err) = err {
|
||||
return Some(Err(err));
|
||||
}
|
||||
} else {
|
||||
self.ig = self.ig_root.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match ev {
|
||||
Err(err) => {
|
||||
let path = err.path().map(|p| p.to_path_buf());
|
||||
let mut ig_err = Error::Io(io::Error::from(err));
|
||||
if let Some(path) = path {
|
||||
ig_err = Error::WithPath {
|
||||
path: path.to_path_buf(),
|
||||
err: Box::new(ig_err),
|
||||
};
|
||||
}
|
||||
return Some(Err(ig_err));
|
||||
}
|
||||
Ok(WalkEvent::Exit) => {
|
||||
self.ig = self.ig.parent().unwrap();
|
||||
}
|
||||
Ok(WalkEvent::Dir(ent)) => {
|
||||
if self.skip_entry(&ent) {
|
||||
self.it.as_mut().unwrap().it.skip_current_dir();
|
||||
// Still need to push this on the stack because
|
||||
// we'll get a WalkEvent::Exit event for this dir.
|
||||
// We don't care if it errors though.
|
||||
let (igtmp, _) = self.ig.add_child(ent.path());
|
||||
self.ig = igtmp;
|
||||
continue;
|
||||
}
|
||||
let (igtmp, err) = self.ig.add_child(ent.path());
|
||||
self.ig = igtmp;
|
||||
return Some(Ok(DirEntry { dent: Some(ent), err: err }));
|
||||
}
|
||||
Ok(WalkEvent::File(ent)) => {
|
||||
if self.skip_entry(&ent) {
|
||||
continue;
|
||||
}
|
||||
// If this isn't actually a file (e.g., a symlink),
|
||||
// then skip it.
|
||||
if !ent.file_type().is_file() {
|
||||
continue;
|
||||
}
|
||||
return Some(Ok(DirEntry { dent: Some(ent), err: None }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A directory entry with a possible error attached.
|
||||
///
|
||||
/// The error typically refers to a problem parsing ignore files in a
|
||||
/// particular directory.
|
||||
#[derive(Debug)]
|
||||
pub struct DirEntry {
|
||||
dent: Option<walkdir::DirEntry>,
|
||||
err: Option<Error>,
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
/// The full path that this entry represents.
|
||||
pub fn path(&self) -> &Path {
|
||||
self.dent.as_ref().map_or(Path::new("<stdin>"), |x| x.path())
|
||||
}
|
||||
|
||||
/// Whether this entry corresponds to a symbolic link or not.
|
||||
pub fn path_is_symbolic_link(&self) -> bool {
|
||||
self.dent.as_ref().map_or(false, |x| x.path_is_symbolic_link())
|
||||
}
|
||||
|
||||
/// Returns true if and only if this entry corresponds to stdin.
|
||||
///
|
||||
/// i.e., The entry has depth 0 and its file name is `-`.
|
||||
pub fn is_stdin(&self) -> bool {
|
||||
self.dent.is_none()
|
||||
}
|
||||
|
||||
/// Return the metadata for the file that this entry points to.
|
||||
pub fn metadata(&self) -> Result<Metadata, Error> {
|
||||
if let Some(dent) = self.dent.as_ref() {
|
||||
dent.metadata().map_err(|err| Error::WithPath {
|
||||
path: self.path().to_path_buf(),
|
||||
err: Box::new(Error::Io(io::Error::from(err))),
|
||||
})
|
||||
} else {
|
||||
let ioerr = io::Error::new(
|
||||
io::ErrorKind::Other, "stdin has no metadata");
|
||||
Err(Error::WithPath {
|
||||
path: Path::new("<stdin>").to_path_buf(),
|
||||
err: Box::new(Error::Io(ioerr)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the file type for the file that this entry points to.
|
||||
///
|
||||
/// This entry doesn't have a file type if it corresponds to stdin.
|
||||
pub fn file_type(&self) -> Option<FileType> {
|
||||
self.dent.as_ref().map(|x| x.file_type())
|
||||
}
|
||||
|
||||
/// Return the file name of this entry.
|
||||
///
|
||||
/// If this entry has no file name (e.g., `/`), then the full path is
|
||||
/// returned.
|
||||
pub fn file_name(&self) -> &OsStr {
|
||||
self.dent.as_ref().map_or(OsStr::new("<stdin>"), |x| x.file_name())
|
||||
}
|
||||
|
||||
/// Returns the depth at which this entry was created relative to the root.
|
||||
pub fn depth(&self) -> usize {
|
||||
self.dent.as_ref().map_or(0, |x| x.depth())
|
||||
}
|
||||
|
||||
/// Returns an error, if one exists, associated with processing this entry.
|
||||
///
|
||||
/// An example of an error is one that occurred while parsing an ignore
|
||||
/// file.
|
||||
pub fn error(&self) -> Option<&Error> {
|
||||
self.err.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// WalkEventIter transforms a WalkDir iterator into an iterator that more
|
||||
/// accurately describes the directory tree. Namely, it emits events that are
|
||||
/// one of three types: directory, file or "exit." An "exit" event means that
|
||||
/// the entire contents of a directory have been enumerated.
|
||||
struct WalkEventIter {
|
||||
depth: usize,
|
||||
it: walkdir::Iter,
|
||||
next: Option<Result<walkdir::DirEntry, walkdir::Error>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum WalkEvent {
|
||||
Dir(walkdir::DirEntry),
|
||||
File(walkdir::DirEntry),
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl From<WalkDir> for WalkEventIter {
|
||||
fn from(it: WalkDir) -> WalkEventIter {
|
||||
WalkEventIter { depth: 0, it: it.into_iter(), next: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for WalkEventIter {
|
||||
type Item = walkdir::Result<WalkEvent>;
|
||||
|
||||
#[inline(always)]
|
||||
fn next(&mut self) -> Option<walkdir::Result<WalkEvent>> {
|
||||
let dent = self.next.take().or_else(|| self.it.next());
|
||||
let depth = match dent {
|
||||
None => 0,
|
||||
Some(Ok(ref dent)) => dent.depth(),
|
||||
Some(Err(ref err)) => err.depth(),
|
||||
};
|
||||
if depth < self.depth {
|
||||
self.depth -= 1;
|
||||
self.next = dent;
|
||||
return Some(Ok(WalkEvent::Exit));
|
||||
}
|
||||
self.depth = depth;
|
||||
match dent {
|
||||
None => None,
|
||||
Some(Err(err)) => Some(Err(err)),
|
||||
Some(Ok(dent)) => {
|
||||
if dent.file_type().is_dir() {
|
||||
self.depth += 1;
|
||||
Some(Ok(WalkEvent::Dir(dent)))
|
||||
} else {
|
||||
Some(Ok(WalkEvent::File(dent)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use tempdir::TempDir;
|
||||
|
||||
use super::{Walk, WalkBuilder};
|
||||
|
||||
fn wfile<P: AsRef<Path>>(path: P, contents: &str) {
|
||||
let mut file = File::create(path).unwrap();
|
||||
file.write_all(contents.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
fn mkdirp<P: AsRef<Path>>(path: P) {
|
||||
fs::create_dir_all(path).unwrap();
|
||||
}
|
||||
|
||||
fn normal_path(unix: &str) -> String {
|
||||
if cfg!(windows) {
|
||||
unix.replace("\\", "/")
|
||||
} else {
|
||||
unix.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_collect(prefix: &Path, walk: Walk) -> Vec<String> {
|
||||
let mut paths = vec![];
|
||||
for dent in walk {
|
||||
let dent = dent.unwrap();
|
||||
let path = dent.path().strip_prefix(prefix).unwrap();
|
||||
if path.as_os_str().is_empty() {
|
||||
continue;
|
||||
}
|
||||
paths.push(normal_path(path.to_str().unwrap()));
|
||||
}
|
||||
paths.sort();
|
||||
paths
|
||||
}
|
||||
|
||||
fn mkpaths(paths: &[&str]) -> Vec<String> {
|
||||
let mut paths: Vec<_> = paths.iter().map(|s| s.to_string()).collect();
|
||||
paths.sort();
|
||||
paths
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_ignores() {
|
||||
let td = TempDir::new("walk-test-").unwrap();
|
||||
mkdirp(td.path().join("a/b/c"));
|
||||
mkdirp(td.path().join("x/y"));
|
||||
wfile(td.path().join("a/b/foo"), "");
|
||||
wfile(td.path().join("x/y/foo"), "");
|
||||
|
||||
let got = walk_collect(td.path(), Walk::new(td.path()));
|
||||
assert_eq!(got, mkpaths(&[
|
||||
"x", "x/y", "x/y/foo", "a", "a/b", "a/b/foo", "a/b/c",
|
||||
]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gitignore() {
|
||||
let td = TempDir::new("walk-test-").unwrap();
|
||||
mkdirp(td.path().join("a"));
|
||||
wfile(td.path().join(".gitignore"), "foo");
|
||||
wfile(td.path().join("foo"), "");
|
||||
wfile(td.path().join("a/foo"), "");
|
||||
wfile(td.path().join("bar"), "");
|
||||
wfile(td.path().join("a/bar"), "");
|
||||
|
||||
let got = walk_collect(td.path(), Walk::new(td.path()));
|
||||
assert_eq!(got, mkpaths(&["bar", "a", "a/bar"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_ignore() {
|
||||
let td = TempDir::new("walk-test-").unwrap();
|
||||
let igpath = td.path().join(".not-an-ignore");
|
||||
mkdirp(td.path().join("a"));
|
||||
wfile(&igpath, "foo");
|
||||
wfile(td.path().join("foo"), "");
|
||||
wfile(td.path().join("a/foo"), "");
|
||||
wfile(td.path().join("bar"), "");
|
||||
wfile(td.path().join("a/bar"), "");
|
||||
|
||||
let mut builder = WalkBuilder::new(td.path());
|
||||
assert!(builder.add_ignore(&igpath).is_none());
|
||||
let got = walk_collect(td.path(), builder.build());
|
||||
assert_eq!(got, mkpaths(&["bar", "a", "a/bar"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gitignore_parent() {
|
||||
let td = TempDir::new("walk-test-").unwrap();
|
||||
mkdirp(td.path().join("a"));
|
||||
wfile(td.path().join(".gitignore"), "foo");
|
||||
wfile(td.path().join("a/foo"), "");
|
||||
wfile(td.path().join("a/bar"), "");
|
||||
|
||||
let root = td.path().join("a");
|
||||
let got = walk_collect(&root, Walk::new(&root));
|
||||
assert_eq!(got, mkpaths(&["bar"]));
|
||||
}
|
||||
}
|
||||
4
pkg/archlinux/.gitignore
vendored
Normal file
4
pkg/archlinux/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.xz
|
||||
src
|
||||
pkg
|
||||
*.gz
|
||||
37
pkg/archlinux/PKGBUILD
Normal file
37
pkg/archlinux/PKGBUILD
Normal file
@@ -0,0 +1,37 @@
|
||||
# Contributor: Andrew Gallant <jamslam@gmail.com>
|
||||
# Maintainer: Andrew Gallant
|
||||
pkgname=ripgrep
|
||||
pkgver=0.2.3
|
||||
pkgrel=1
|
||||
pkgdesc="A search tool that combines the usability of The Silver Searcher with the raw speed of grep."
|
||||
arch=('i686' 'x86_64')
|
||||
url="https://github.com/BurntSushi/ripgrep"
|
||||
license=('UNLICENSE')
|
||||
makedepends=('cargo')
|
||||
source=("https://github.com/BurntSushi/$pkgname/archive/$pkgver.tar.gz")
|
||||
sha256sums=('a88531558d2023df76190ea2e52bee50d739eabece8a57df29abbad0c6bdb917')
|
||||
|
||||
build() {
|
||||
cd "$pkgname-$pkgver"
|
||||
if command -v rustup > /dev/null 2>&1; then
|
||||
RUSTFLAGS="-C target-cpu=native" rustup run nightly \
|
||||
cargo build --release --features simd-accel
|
||||
elif rustc --version | grep -q nightly; then
|
||||
RUSTFLAGS="-C target-cpu=native" \
|
||||
cargo build --release --features simd-accel
|
||||
else
|
||||
cargo build --release
|
||||
fi
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$pkgname-$pkgver"
|
||||
|
||||
install -Dm755 "target/release/rg" "$pkgdir/usr/bin/rg"
|
||||
install -Dm644 "doc/rg.1" "$pkgdir/usr/share/man/man1/rg.1"
|
||||
install -Dm644 "README.md" "$pkgdir/usr/share/doc/ripgrep/README.md"
|
||||
install -Dm644 "COPYING" "$pkgdir/usr/share/doc/ripgrep/COPYING"
|
||||
install -Dm644 "LICENSE-MIT" "$pkgdir/usr/share/doc/ripgrep/LICENSE-MIT"
|
||||
install -Dm644 "UNLICENSE" "$pkgdir/usr/share/doc/ripgrep/UNLICENSE"
|
||||
install -Dm644 "CHANGELOG.md" "$pkgdir/usr/share/doc/ripgrep/CHANGELOG.md"
|
||||
}
|
||||
14
pkg/brew/ripgrep-bin.rb
Normal file
14
pkg/brew/ripgrep-bin.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
class RipgrepBin < Formula
|
||||
version '0.2.3'
|
||||
desc "Search tool like grep and The Silver Searcher."
|
||||
homepage "https://github.com/BurntSushi/ripgrep"
|
||||
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-apple-darwin.tar.gz"
|
||||
sha256 "7407555dfe040a2631a7efdd1eea62cf1d1c50e5a6ecf8ee82e0bef9d5f37298"
|
||||
|
||||
conflicts_with "ripgrep"
|
||||
|
||||
def install
|
||||
bin.install "rg"
|
||||
man1.install "rg.1"
|
||||
end
|
||||
end
|
||||
@@ -1 +0,0 @@
|
||||
au BufWritePost *.rs silent!make ctags > /dev/null 2>&1
|
||||
433
src/args.rs
433
src/args.rs
@@ -10,17 +10,21 @@ use log;
|
||||
use num_cpus;
|
||||
use regex;
|
||||
use term::Terminal;
|
||||
use walkdir::WalkDir;
|
||||
#[cfg(not(windows))]
|
||||
use term;
|
||||
#[cfg(windows)]
|
||||
use term::WinConsole;
|
||||
|
||||
use atty;
|
||||
use gitignore::{Gitignore, GitignoreBuilder};
|
||||
use ignore::Ignore;
|
||||
use out::{Out, OutBuffer};
|
||||
use ignore::overrides::{Override, OverrideBuilder};
|
||||
use ignore::types::{FileTypeDef, Types, TypesBuilder};
|
||||
use ignore;
|
||||
use out::{Out, ColoredTerminal};
|
||||
use printer::Printer;
|
||||
use search_buffer::BufferSearcher;
|
||||
use search_stream::{InputBuffer, Searcher};
|
||||
use types::{FileTypeDef, Types, TypesBuilder};
|
||||
use walk;
|
||||
#[cfg(windows)]
|
||||
use terminal_win::WindowsBuffer;
|
||||
|
||||
use Result;
|
||||
|
||||
@@ -29,13 +33,14 @@ use Result;
|
||||
/// If you've never heard of Docopt before, see: http://docopt.org
|
||||
/// (TL;DR: The CLI parser is generated from the usage string below.)
|
||||
const USAGE: &'static str = "
|
||||
Usage: rg [options] <pattern> [<path> ...]
|
||||
Usage: rg [options] -e PATTERN ... [<path> ...]
|
||||
rg [options] <pattern> [<path> ...]
|
||||
rg [options] --files [<path> ...]
|
||||
rg [options] --type-list
|
||||
rg --help
|
||||
rg --version
|
||||
rg [options] --help
|
||||
rg [options] --version
|
||||
|
||||
rg combines the usability of the silver search with the raw speed of grep.
|
||||
rg recursively searches your current directory for a regex pattern.
|
||||
|
||||
Common options:
|
||||
-a, --text Search binary files as if they were text.
|
||||
@@ -43,6 +48,11 @@ Common options:
|
||||
--color WHEN Whether to use coloring in match.
|
||||
Valid values are never, always or auto.
|
||||
[default: auto]
|
||||
-e, --regexp PATTERN ... Use PATTERN to search. This option can be
|
||||
provided multiple times, where all patterns
|
||||
given are searched.
|
||||
-F, --fixed-strings Treat the pattern as a literal string instead of
|
||||
a regular expression.
|
||||
-g, --glob GLOB ... Include or exclude files for searching that
|
||||
match the given glob. This always overrides any
|
||||
other ignore logic. Multiple glob flags may be
|
||||
@@ -50,18 +60,23 @@ Common options:
|
||||
Precede a glob with a '!' to exclude it.
|
||||
-h, --help Show this usage message.
|
||||
-i, --ignore-case Case insensitive search.
|
||||
Overridden by --case-sensitive.
|
||||
-n, --line-number Show line numbers (1-based). This is enabled
|
||||
by default at a tty.
|
||||
-N, --no-line-number Suppress line numbers.
|
||||
-q, --quiet Do not print anything to stdout.
|
||||
-r, --replace ARG Replace every match with the string given.
|
||||
Capture group indices (e.g., $5) and names
|
||||
(e.g., $foo) are supported.
|
||||
-q, --quiet Do not print anything to stdout. If a match is
|
||||
found in a file, stop searching that file.
|
||||
-t, --type TYPE ... Only search files matching TYPE. Multiple type
|
||||
flags may be provided. Use the --type-list flag
|
||||
to list all available types.
|
||||
-T, --type-not TYPE ... Do not search files matching TYPE. Multiple
|
||||
not-type flags may be provided.
|
||||
-u, --unrestricted ... Reduce the level of 'smart' searching. A
|
||||
single -u doesn't respect .gitignore (etc.)
|
||||
files. Two -u flags will search hidden files
|
||||
and directories. Three -u flags will search
|
||||
binary files. -uu is equivalent to grep -r,
|
||||
and -uuu is equivalent to grep -a -r.
|
||||
-v, --invert-match Invert matching.
|
||||
-w, --word-regexp Only show matches surrounded by word boundaries.
|
||||
This is equivalent to putting \\b before and
|
||||
@@ -92,10 +107,17 @@ Less common options:
|
||||
--files
|
||||
Print each file that would be searched (but don't search).
|
||||
|
||||
-l, --files-with-matches
|
||||
Only show path of each file with matches.
|
||||
|
||||
-H, --with-filename
|
||||
Prefix each match with the file name that contains it. This is the
|
||||
default when more than one file is searched.
|
||||
|
||||
--no-filename
|
||||
Never show the filename for a match. This is the default when
|
||||
one file is searched.
|
||||
|
||||
--heading
|
||||
Show the file name above clusters of matches from each file.
|
||||
This is the default mode at a tty.
|
||||
@@ -107,46 +129,92 @@ Less common options:
|
||||
Search hidden directories and files. (Hidden directories and files are
|
||||
skipped by default.)
|
||||
|
||||
--ignore-file FILE ...
|
||||
Specify additional ignore files for filtering file paths. Ignore files
|
||||
should be in the gitignore format and are matched relative to the
|
||||
current working directory. These ignore files have lower precedence
|
||||
than all other ignore file types. When specifying multiple ignore
|
||||
files, earlier files have lower precedence than later files.
|
||||
|
||||
-L, --follow
|
||||
Follow symlinks.
|
||||
|
||||
--maxdepth NUM
|
||||
Descend at most NUM directories below the command line arguments.
|
||||
A value of zero only searches the starting-points themselves.
|
||||
|
||||
--mmap
|
||||
Search using memory maps when possible. This is enabled by default
|
||||
when ripgrep thinks it will be faster. (Note that mmap searching
|
||||
doesn't current support the various context related options.)
|
||||
doesn't currently support the various context related options.)
|
||||
|
||||
--no-mmap
|
||||
Never use memory maps, even when they might be faster.
|
||||
|
||||
--no-ignore
|
||||
Don't respect ignore files (.gitignore, .rgignore, etc.)
|
||||
Don't respect ignore files (.gitignore, .ignore, etc.)
|
||||
This implies --no-ignore-parent.
|
||||
|
||||
--no-ignore-parent
|
||||
Don't respect ignore files in parent directories.
|
||||
|
||||
--no-ignore-vcs
|
||||
Don't respect version control ignore files (e.g., .gitignore).
|
||||
Note that .ignore files will continue to be respected.
|
||||
|
||||
--null
|
||||
Whenever a file name is printed, follow it with a NUL byte.
|
||||
This includes printing filenames before matches, and when printing
|
||||
a list of matching files such as with --count, --files-with-matches
|
||||
and --files.
|
||||
|
||||
-p, --pretty
|
||||
Alias for --color=always --heading -n.
|
||||
|
||||
-Q, --literal
|
||||
Treat the pattern as a literal string instead of a regular expression.
|
||||
-r, --replace ARG
|
||||
Replace every match with the string given when printing search results.
|
||||
Neither this flag nor any other flag will modify your files.
|
||||
|
||||
Capture group indices (e.g., $5) and names (e.g., $foo) are supported
|
||||
in the replacement string.
|
||||
|
||||
-s, --case-sensitive
|
||||
Search case sensitively. This overrides --ignore-case and --smart-case.
|
||||
|
||||
-S, --smart-case
|
||||
Search case insensitively if the pattern is all lowercase.
|
||||
Search case sensitively otherwise. This is overridden by
|
||||
either --case-sensitive or --ignore-case.
|
||||
|
||||
-j, --threads ARG
|
||||
The number of threads to use. Defaults to the number of logical CPUs
|
||||
The number of threads to use. 0 means use the number of logical CPUs
|
||||
(capped at 6). [default: 0]
|
||||
|
||||
--version
|
||||
Show the version number of ripgrep and exit.
|
||||
|
||||
--vimgrep
|
||||
Show results with every match on its own line, including line
|
||||
numbers and column numbers. (With this option, a line with more
|
||||
than one match of the regex will be printed more than once.)
|
||||
|
||||
File type management options:
|
||||
--type-list
|
||||
Show all supported file types and their associated globs.
|
||||
|
||||
--type-add ARG ...
|
||||
Add a new glob for a particular file type.
|
||||
Example: --type-add html:*.html,*.htm
|
||||
Add a new glob for a particular file type. Only one glob can be
|
||||
added at a time. Multiple --type-add flags can be provided.
|
||||
Unless --type-clear is used, globs are added to any existing globs
|
||||
inside of ripgrep. Note that this must be passed to every invocation of
|
||||
rg. Type settings are NOT persisted.
|
||||
|
||||
Example: `rg --type-add 'foo:*.foo' -tfoo PATTERN`
|
||||
|
||||
--type-clear TYPE ...
|
||||
Clear the file type globs for TYPE.
|
||||
Clear the file type globs previously defined for TYPE. This only clears
|
||||
the default type definitions that are found inside of ripgrep. Note
|
||||
that this must be passed to every invocation of rg.
|
||||
";
|
||||
|
||||
/// RawArgs are the args as they are parsed from Docopt. They aren't used
|
||||
@@ -157,11 +225,13 @@ pub struct RawArgs {
|
||||
arg_path: Vec<String>,
|
||||
flag_after_context: usize,
|
||||
flag_before_context: usize,
|
||||
flag_case_sensitive: bool,
|
||||
flag_color: String,
|
||||
flag_column: bool,
|
||||
flag_context: usize,
|
||||
flag_context_separator: String,
|
||||
flag_count: bool,
|
||||
flag_files_with_matches: bool,
|
||||
flag_debug: bool,
|
||||
flag_files: bool,
|
||||
flag_follow: bool,
|
||||
@@ -169,18 +239,25 @@ pub struct RawArgs {
|
||||
flag_heading: bool,
|
||||
flag_hidden: bool,
|
||||
flag_ignore_case: bool,
|
||||
flag_ignore_file: Vec<String>,
|
||||
flag_invert_match: bool,
|
||||
flag_line_number: bool,
|
||||
flag_literal: bool,
|
||||
flag_fixed_strings: bool,
|
||||
flag_maxdepth: Option<usize>,
|
||||
flag_mmap: bool,
|
||||
flag_no_heading: bool,
|
||||
flag_no_ignore: bool,
|
||||
flag_no_ignore_parent: bool,
|
||||
flag_no_ignore_vcs: bool,
|
||||
flag_no_line_number: bool,
|
||||
flag_no_mmap: bool,
|
||||
flag_no_filename: bool,
|
||||
flag_null: bool,
|
||||
flag_pretty: bool,
|
||||
flag_quiet: bool,
|
||||
flag_regexp: Vec<String>,
|
||||
flag_replace: Option<String>,
|
||||
flag_smart_case: bool,
|
||||
flag_text: bool,
|
||||
flag_threads: usize,
|
||||
flag_type: Vec<String>,
|
||||
@@ -188,6 +265,8 @@ pub struct RawArgs {
|
||||
flag_type_list: bool,
|
||||
flag_type_add: Vec<String>,
|
||||
flag_type_clear: Vec<String>,
|
||||
flag_unrestricted: u32,
|
||||
flag_vimgrep: bool,
|
||||
flag_with_filename: bool,
|
||||
flag_word_regexp: bool,
|
||||
}
|
||||
@@ -195,7 +274,6 @@ pub struct RawArgs {
|
||||
/// Args are transformed/normalized from RawArgs.
|
||||
#[derive(Debug)]
|
||||
pub struct Args {
|
||||
pattern: String,
|
||||
paths: Vec<PathBuf>,
|
||||
after_context: usize,
|
||||
before_context: usize,
|
||||
@@ -203,24 +281,29 @@ pub struct Args {
|
||||
column: bool,
|
||||
context_separator: Vec<u8>,
|
||||
count: bool,
|
||||
files_with_matches: bool,
|
||||
eol: u8,
|
||||
files: bool,
|
||||
follow: bool,
|
||||
glob_overrides: Option<Gitignore>,
|
||||
glob_overrides: Override,
|
||||
grep: Grep,
|
||||
heading: bool,
|
||||
hidden: bool,
|
||||
ignore_case: bool,
|
||||
ignore_files: Vec<PathBuf>,
|
||||
invert_match: bool,
|
||||
line_number: bool,
|
||||
line_per_match: bool,
|
||||
maxdepth: Option<usize>,
|
||||
mmap: bool,
|
||||
no_ignore: bool,
|
||||
no_ignore_parent: bool,
|
||||
no_ignore_vcs: bool,
|
||||
null: bool,
|
||||
quiet: bool,
|
||||
replace: Option<Vec<u8>>,
|
||||
text: bool,
|
||||
threads: usize,
|
||||
type_defs: Vec<FileTypeDef>,
|
||||
type_list: bool,
|
||||
types: Types,
|
||||
with_filename: bool,
|
||||
@@ -229,24 +312,12 @@ pub struct Args {
|
||||
impl RawArgs {
|
||||
/// Convert arguments parsed into a configuration used by ripgrep.
|
||||
fn to_args(&self) -> Result<Args> {
|
||||
let pattern = {
|
||||
let pattern =
|
||||
if self.flag_literal {
|
||||
regex::quote(&self.arg_pattern)
|
||||
} else {
|
||||
self.arg_pattern.clone()
|
||||
};
|
||||
if self.flag_word_regexp {
|
||||
format!(r"\b{}\b", pattern)
|
||||
} else {
|
||||
pattern
|
||||
}
|
||||
};
|
||||
let paths =
|
||||
if self.arg_path.is_empty() {
|
||||
if atty::on_stdin()
|
||||
|| self.flag_files
|
||||
|| self.flag_type_list {
|
||||
|| self.flag_type_list
|
||||
|| !atty::stdin_is_readable() {
|
||||
vec![Path::new("./").to_path_buf()]
|
||||
} else {
|
||||
vec![Path::new("-").to_path_buf()]
|
||||
@@ -267,6 +338,12 @@ impl RawArgs {
|
||||
false
|
||||
} else if self.flag_mmap {
|
||||
true
|
||||
} else if cfg!(windows) {
|
||||
// On Windows, memory maps appear faster than read calls. Neat.
|
||||
true
|
||||
} else if cfg!(target_os = "macos") {
|
||||
// On Mac, memory maps appear to suck. Neat.
|
||||
false
|
||||
} else {
|
||||
// If we're only searching a few paths and all of them are
|
||||
// files, then memory maps are probably faster.
|
||||
@@ -277,14 +354,13 @@ impl RawArgs {
|
||||
}
|
||||
let glob_overrides =
|
||||
if self.flag_glob.is_empty() {
|
||||
None
|
||||
Override::empty()
|
||||
} else {
|
||||
let cwd = try!(env::current_dir());
|
||||
let mut bgi = GitignoreBuilder::new(cwd);
|
||||
let mut ovr = OverrideBuilder::new(try!(env::current_dir()));
|
||||
for pat in &self.flag_glob {
|
||||
try!(bgi.add("<argv>", pat));
|
||||
try!(ovr.add(pat));
|
||||
}
|
||||
Some(try!(bgi.build()))
|
||||
try!(ovr.build())
|
||||
};
|
||||
let threads =
|
||||
if self.flag_threads == 0 {
|
||||
@@ -293,28 +369,29 @@ impl RawArgs {
|
||||
self.flag_threads
|
||||
};
|
||||
let color =
|
||||
if self.flag_color == "auto" {
|
||||
if self.flag_color == "always" {
|
||||
true
|
||||
} else if self.flag_vimgrep {
|
||||
false
|
||||
} else if self.flag_color == "auto" {
|
||||
atty::on_stdout() || self.flag_pretty
|
||||
} else {
|
||||
self.flag_color == "always"
|
||||
false
|
||||
};
|
||||
let eol = b'\n';
|
||||
|
||||
let mut with_filename = self.flag_with_filename;
|
||||
if !with_filename {
|
||||
with_filename = paths.len() > 1 || paths[0].is_dir();
|
||||
}
|
||||
let mut btypes = TypesBuilder::new();
|
||||
btypes.add_defaults();
|
||||
try!(self.add_types(&mut btypes));
|
||||
let types = try!(btypes.build());
|
||||
let grep = try!(
|
||||
GrepBuilder::new(&pattern)
|
||||
.case_insensitive(self.flag_ignore_case)
|
||||
.line_terminator(eol)
|
||||
.build()
|
||||
);
|
||||
with_filename = with_filename && !self.flag_no_filename;
|
||||
|
||||
let no_ignore = self.flag_no_ignore || self.flag_unrestricted >= 1;
|
||||
let hidden = self.flag_hidden || self.flag_unrestricted >= 2;
|
||||
let text = self.flag_text || self.flag_unrestricted >= 3;
|
||||
let ignore_files: Vec<_> = self.flag_ignore_file.iter().map(|p| {
|
||||
Path::new(p).to_path_buf()
|
||||
}).collect();
|
||||
let mut args = Args {
|
||||
pattern: pattern,
|
||||
paths: paths,
|
||||
after_context: after_context,
|
||||
before_context: before_context,
|
||||
@@ -322,26 +399,35 @@ impl RawArgs {
|
||||
column: self.flag_column,
|
||||
context_separator: unescape(&self.flag_context_separator),
|
||||
count: self.flag_count,
|
||||
eol: eol,
|
||||
files_with_matches: self.flag_files_with_matches,
|
||||
eol: self.eol(),
|
||||
files: self.flag_files,
|
||||
follow: self.flag_follow,
|
||||
glob_overrides: glob_overrides,
|
||||
grep: grep,
|
||||
grep: try!(self.grep()),
|
||||
heading: !self.flag_no_heading && self.flag_heading,
|
||||
hidden: self.flag_hidden,
|
||||
hidden: hidden,
|
||||
ignore_case: self.flag_ignore_case,
|
||||
ignore_files: ignore_files,
|
||||
invert_match: self.flag_invert_match,
|
||||
line_number: !self.flag_no_line_number && self.flag_line_number,
|
||||
line_per_match: self.flag_vimgrep,
|
||||
maxdepth: self.flag_maxdepth,
|
||||
mmap: mmap,
|
||||
no_ignore: self.flag_no_ignore,
|
||||
no_ignore_parent: self.flag_no_ignore_parent,
|
||||
no_ignore: no_ignore,
|
||||
no_ignore_parent:
|
||||
// --no-ignore implies --no-ignore-parent
|
||||
self.flag_no_ignore_parent || no_ignore,
|
||||
no_ignore_vcs:
|
||||
// --no-ignore implies --no-ignore-vcs
|
||||
self.flag_no_ignore_vcs || no_ignore,
|
||||
null: self.flag_null,
|
||||
quiet: self.flag_quiet,
|
||||
replace: self.flag_replace.clone().map(|s| s.into_bytes()),
|
||||
text: self.flag_text,
|
||||
text: text,
|
||||
threads: threads,
|
||||
type_defs: btypes.definitions(),
|
||||
type_list: self.flag_type_list,
|
||||
types: types,
|
||||
types: try!(self.types()),
|
||||
with_filename: with_filename,
|
||||
};
|
||||
// If stdout is a tty, then apply some special default options.
|
||||
@@ -353,23 +439,78 @@ impl RawArgs {
|
||||
args.heading = true;
|
||||
}
|
||||
}
|
||||
if self.flag_vimgrep {
|
||||
args.column = true;
|
||||
args.line_number = true;
|
||||
}
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn add_types(&self, types: &mut TypesBuilder) -> Result<()> {
|
||||
fn types(&self) -> Result<Types> {
|
||||
let mut btypes = TypesBuilder::new();
|
||||
btypes.add_defaults();
|
||||
for ty in &self.flag_type_clear {
|
||||
types.clear(ty);
|
||||
btypes.clear(ty);
|
||||
}
|
||||
for def in &self.flag_type_add {
|
||||
try!(types.add_def(def));
|
||||
try!(btypes.add_def(def));
|
||||
}
|
||||
for ty in &self.flag_type {
|
||||
types.select(ty);
|
||||
btypes.select(ty);
|
||||
}
|
||||
for ty in &self.flag_type_not {
|
||||
types.select_not(ty);
|
||||
btypes.negate(ty);
|
||||
}
|
||||
Ok(())
|
||||
btypes.build().map_err(From::from)
|
||||
}
|
||||
|
||||
fn pattern(&self) -> String {
|
||||
if !self.flag_regexp.is_empty() {
|
||||
if self.flag_fixed_strings {
|
||||
self.flag_regexp.iter().cloned().map(|lit| {
|
||||
self.word_pattern(regex::quote(&lit))
|
||||
}).collect::<Vec<String>>().join("|")
|
||||
} else {
|
||||
self.flag_regexp.iter().cloned().map(|pat| {
|
||||
self.word_pattern(pat)
|
||||
}).collect::<Vec<String>>().join("|")
|
||||
}
|
||||
} else {
|
||||
if self.flag_fixed_strings {
|
||||
self.word_pattern(regex::quote(&self.arg_pattern))
|
||||
} else {
|
||||
self.word_pattern(self.arg_pattern.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn word_pattern(&self, s: String) -> String {
|
||||
if self.flag_word_regexp {
|
||||
format!(r"\b{}\b", s)
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
fn eol(&self) -> u8 {
|
||||
// We might want to make this configurable.
|
||||
b'\n'
|
||||
}
|
||||
|
||||
fn grep(&self) -> Result<Grep> {
|
||||
let smart =
|
||||
self.flag_smart_case
|
||||
&& !self.flag_ignore_case
|
||||
&& !self.flag_case_sensitive;
|
||||
let casei =
|
||||
self.flag_ignore_case
|
||||
&& !self.flag_case_sensitive;
|
||||
GrepBuilder::new(&self.pattern())
|
||||
.case_smart(smart)
|
||||
.case_insensitive(casei)
|
||||
.line_terminator(self.eol())
|
||||
.build()
|
||||
.map_err(From::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,9 +523,22 @@ impl Args {
|
||||
///
|
||||
/// Also, initialize a global logger.
|
||||
pub fn parse() -> Result<Args> {
|
||||
let raw: RawArgs =
|
||||
// Get all of the arguments, being careful to require valid UTF-8.
|
||||
let mut argv = vec![];
|
||||
for arg in env::args_os() {
|
||||
match arg.into_string() {
|
||||
Ok(s) => argv.push(s),
|
||||
Err(s) => {
|
||||
errored!("Argument '{}' is not valid UTF-8. \
|
||||
Use hex escape sequences to match arbitrary \
|
||||
bytes in a pattern (e.g., \\xFF).",
|
||||
s.to_string_lossy());
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut raw: RawArgs =
|
||||
Docopt::new(USAGE)
|
||||
.and_then(|d| d.version(Some(version())).decode())
|
||||
.and_then(|d| d.argv(argv).version(Some(version())).decode())
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
|
||||
let mut logb = env_logger::LogBuilder::new();
|
||||
@@ -397,6 +551,13 @@ impl Args {
|
||||
errored!("failed to initialize logger: {}", err);
|
||||
}
|
||||
|
||||
// *sigh*... If --files is given, then the first path ends up in
|
||||
// pattern.
|
||||
if raw.flag_files {
|
||||
if !raw.arg_pattern.is_empty() {
|
||||
raw.arg_path.insert(0, raw.arg_pattern.clone());
|
||||
}
|
||||
}
|
||||
raw.to_args().map_err(From::from)
|
||||
}
|
||||
|
||||
@@ -427,15 +588,21 @@ impl Args {
|
||||
self.mmap
|
||||
}
|
||||
|
||||
/// Whether ripgrep should be quiet or not.
|
||||
pub fn quiet(&self) -> bool {
|
||||
self.quiet
|
||||
}
|
||||
|
||||
/// Create a new printer of individual search results that writes to the
|
||||
/// writer given.
|
||||
pub fn printer<W: Send + Terminal>(&self, wtr: W) -> Printer<W> {
|
||||
pub fn printer<W: Terminal + Send>(&self, wtr: W) -> Printer<W> {
|
||||
let mut p = Printer::new(wtr)
|
||||
.column(self.column)
|
||||
.context_separator(self.context_separator.clone())
|
||||
.eol(self.eol)
|
||||
.heading(self.heading)
|
||||
.quiet(self.quiet)
|
||||
.line_per_match(self.line_per_match)
|
||||
.null(self.null)
|
||||
.with_filename(self.with_filename);
|
||||
if let Some(ref rep) = self.replace {
|
||||
p = p.replace(rep.clone());
|
||||
@@ -447,17 +614,47 @@ impl Args {
|
||||
/// to the writer given.
|
||||
pub fn out(&self) -> Out {
|
||||
let mut out = Out::new(self.color);
|
||||
if self.heading && !self.count {
|
||||
out = out.file_separator(b"".to_vec());
|
||||
} else if self.before_context > 0 || self.after_context > 0 {
|
||||
out = out.file_separator(self.context_separator.clone());
|
||||
if let Some(filesep) = self.file_separator() {
|
||||
out = out.file_separator(filesep);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Retrieve the configured file separator.
|
||||
pub fn file_separator(&self) -> Option<Vec<u8>> {
|
||||
if self.heading && !self.count && !self.files_with_matches {
|
||||
Some(b"".to_vec())
|
||||
} else if self.before_context > 0 || self.after_context > 0 {
|
||||
Some(self.context_separator.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new buffer for use with searching.
|
||||
pub fn outbuf(&self) -> OutBuffer {
|
||||
OutBuffer::new(self.color)
|
||||
#[cfg(not(windows))]
|
||||
pub fn outbuf(&self) -> ColoredTerminal<term::TerminfoTerminal<Vec<u8>>> {
|
||||
ColoredTerminal::new(vec![], self.color)
|
||||
}
|
||||
|
||||
/// Create a new buffer for use with searching.
|
||||
#[cfg(windows)]
|
||||
pub fn outbuf(&self) -> ColoredTerminal<WindowsBuffer> {
|
||||
ColoredTerminal::new_buffer(self.color)
|
||||
}
|
||||
|
||||
/// Create a new buffer for use with searching.
|
||||
#[cfg(not(windows))]
|
||||
pub fn stdout(
|
||||
&self,
|
||||
) -> ColoredTerminal<term::TerminfoTerminal<io::BufWriter<io::Stdout>>> {
|
||||
ColoredTerminal::new(io::BufWriter::new(io::stdout()), self.color)
|
||||
}
|
||||
|
||||
/// Create a new buffer for use with searching.
|
||||
#[cfg(windows)]
|
||||
pub fn stdout(&self) -> ColoredTerminal<WinConsole<io::Stdout>> {
|
||||
ColoredTerminal::new_stdout(self.color)
|
||||
}
|
||||
|
||||
/// Return the paths that should be searched.
|
||||
@@ -480,9 +677,11 @@ impl Args {
|
||||
.after_context(self.after_context)
|
||||
.before_context(self.before_context)
|
||||
.count(self.count)
|
||||
.files_with_matches(self.files_with_matches)
|
||||
.eol(self.eol)
|
||||
.line_number(self.line_number)
|
||||
.invert_match(self.invert_match)
|
||||
.quiet(self.quiet)
|
||||
.text(self.text)
|
||||
}
|
||||
|
||||
@@ -498,9 +697,11 @@ impl Args {
|
||||
) -> BufferSearcher<'a, W> {
|
||||
BufferSearcher::new(printer, grep, path, buf)
|
||||
.count(self.count)
|
||||
.files_with_matches(self.files_with_matches)
|
||||
.eol(self.eol)
|
||||
.line_number(self.line_number)
|
||||
.invert_match(self.invert_match)
|
||||
.quiet(self.quiet)
|
||||
.text(self.text)
|
||||
}
|
||||
|
||||
@@ -511,7 +712,7 @@ impl Args {
|
||||
|
||||
/// Returns a list of type definitions currently loaded.
|
||||
pub fn type_defs(&self) -> &[FileTypeDef] {
|
||||
&self.type_defs
|
||||
self.types.definitions()
|
||||
}
|
||||
|
||||
/// Returns true if ripgrep should print the type definitions currently
|
||||
@@ -520,20 +721,30 @@ impl Args {
|
||||
self.type_list
|
||||
}
|
||||
|
||||
/// Create a new recursive directory iterator at the path given.
|
||||
pub fn walker(&self, path: &Path) -> Result<walk::Iter> {
|
||||
let wd = WalkDir::new(path).follow_links(self.follow);
|
||||
let mut ig = Ignore::new();
|
||||
ig.ignore_hidden(!self.hidden);
|
||||
ig.no_ignore(self.no_ignore);
|
||||
ig.add_types(self.types.clone());
|
||||
if !self.no_ignore_parent {
|
||||
try!(ig.push_parents(path));
|
||||
/// Create a new recursive directory iterator over the paths in argv.
|
||||
pub fn walker(&self) -> Walk {
|
||||
let paths = self.paths();
|
||||
let mut wd = ignore::WalkBuilder::new(&paths[0]);
|
||||
for path in &paths[1..] {
|
||||
wd.add(path);
|
||||
}
|
||||
if let Some(ref overrides) = self.glob_overrides {
|
||||
ig.add_override(overrides.clone());
|
||||
for path in &self.ignore_files {
|
||||
if let Some(err) = wd.add_ignore(path) {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
}
|
||||
Ok(walk::Iter::new(ig, wd))
|
||||
|
||||
wd.follow_links(self.follow);
|
||||
wd.hidden(!self.hidden);
|
||||
wd.max_depth(self.maxdepth);
|
||||
wd.overrides(self.glob_overrides.clone());
|
||||
wd.types(self.types.clone());
|
||||
wd.git_global(!self.no_ignore && !self.no_ignore_vcs);
|
||||
wd.git_ignore(!self.no_ignore && !self.no_ignore_vcs);
|
||||
wd.git_exclude(!self.no_ignore && !self.no_ignore_vcs);
|
||||
wd.ignore(!self.no_ignore);
|
||||
wd.parents(!self.no_ignore_parent);
|
||||
Walk(wd.build())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,6 +761,34 @@ fn version() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple wrapper around the ignore::Walk iterator. This will
|
||||
/// automatically emit error messages to stderr and will skip directories.
|
||||
pub struct Walk(ignore::Walk);
|
||||
|
||||
impl Iterator for Walk {
|
||||
type Item = ignore::DirEntry;
|
||||
|
||||
fn next(&mut self) -> Option<ignore::DirEntry> {
|
||||
while let Some(result) = self.0.next() {
|
||||
match result {
|
||||
Ok(dent) => {
|
||||
if let Some(err) = dent.error() {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
if dent.file_type().map_or(false, |x| x.is_dir()) {
|
||||
continue;
|
||||
}
|
||||
return Some(dent);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A single state in the state machine used by `unescape`.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
enum State {
|
||||
@@ -559,7 +798,7 @@ enum State {
|
||||
Literal,
|
||||
}
|
||||
|
||||
/// Unescapes a string given on the command line. It supports a limit set of
|
||||
/// Unescapes a string given on the command line. It supports a limited set of
|
||||
/// escape sequences:
|
||||
///
|
||||
/// * \t, \r and \n are mapped to their corresponding ASCII bytes.
|
||||
|
||||
44
src/atty.rs
44
src/atty.rs
@@ -4,30 +4,58 @@ from (or to) a terminal. Windows and Unix do this differently, so implement
|
||||
both here.
|
||||
*/
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn stdin_is_readable() -> bool {
|
||||
use std::fs::File;
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||
use libc;
|
||||
|
||||
let file = unsafe { File::from_raw_fd(libc::STDIN_FILENO) };
|
||||
let md = file.metadata();
|
||||
let _ = file.into_raw_fd();
|
||||
let ft = match md {
|
||||
Err(_) => return false,
|
||||
Ok(md) => md.file_type(),
|
||||
};
|
||||
ft.is_file() || ft.is_fifo()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn stdin_is_readable() -> bool {
|
||||
// ???
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns true if there is a tty on stdin.
|
||||
#[cfg(unix)]
|
||||
pub fn on_stdin() -> bool {
|
||||
use libc;
|
||||
0 < unsafe { libc::isatty(libc::STDIN_FILENO) }
|
||||
}
|
||||
|
||||
/// Returns true if there is a tty on stdout.
|
||||
#[cfg(unix)]
|
||||
pub fn on_stdout() -> bool {
|
||||
use libc;
|
||||
0 < unsafe { libc::isatty(libc::STDOUT_FILENO) }
|
||||
}
|
||||
|
||||
/// Returns true if there is a tty on stdin.
|
||||
#[cfg(windows)]
|
||||
pub fn on_stdin() -> bool {
|
||||
use kernel32;
|
||||
use winapi;
|
||||
|
||||
unsafe {
|
||||
let fd = winapi::winbase::STD_INPUT_HANDLE;
|
||||
let mut out = 0;
|
||||
kernel32::GetConsoleMode(kernel32::GetStdHandle(fd), &mut out) != 0
|
||||
}
|
||||
// BUG: https://github.com/BurntSushi/ripgrep/issues/19
|
||||
// It's not clear to me how to determine whether there is a tty on stdin.
|
||||
// Checking GetConsoleMode(GetStdHandle(stdin)) != 0 appears to report
|
||||
// that stdin is a pipe, even if it's not in a cygwin terminal, for
|
||||
// example.
|
||||
//
|
||||
// To fix this, we just assume there is always a tty on stdin. If Windows
|
||||
// users need to search stdin, they'll have to pass -. Ug.
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns true if there is a tty on stdout.
|
||||
#[cfg(windows)]
|
||||
pub fn on_stdout() -> bool {
|
||||
use kernel32;
|
||||
|
||||
422
src/gitignore.rs
422
src/gitignore.rs
@@ -1,422 +0,0 @@
|
||||
/*!
|
||||
The gitignore module provides a way of reading a gitignore file and applying
|
||||
it to a particular file name to determine whether it should be ignore or not.
|
||||
The motivation for this submodule is performance and portability:
|
||||
|
||||
1. There is a gitignore crate on crates.io, but it uses the standard `glob`
|
||||
crate and checks patterns one-by-one. This is a reasonable implementation,
|
||||
but not suitable for the performance we need here.
|
||||
2. We could shell out to a `git` sub-command like ls-files or status, but it
|
||||
seems better to not rely on the existence of external programs for a search
|
||||
tool. Besides, we need to implement this logic anyway to support things like
|
||||
an .rgignore file.
|
||||
|
||||
The key implementation detail here is that a single gitignore file is compiled
|
||||
into a single RegexSet, which can be used to report which globs match a
|
||||
particular file name. We can then do a quick post-processing step to implement
|
||||
additional rules such as whitelists (prefix of `!`) or directory-only globs
|
||||
(suffix of `/`).
|
||||
*/
|
||||
|
||||
// TODO(burntsushi): Implement something similar, but for Mercurial. We can't
|
||||
// use this exact implementation because hgignore files are different.
|
||||
|
||||
use std::env;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use regex;
|
||||
|
||||
use glob;
|
||||
|
||||
/// Represents an error that can occur when parsing a gitignore file.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Glob(glob::Error),
|
||||
Regex(regex::Error),
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl StdError for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::Glob(ref err) => err.description(),
|
||||
Error::Regex(ref err) => err.description(),
|
||||
Error::Io(ref err) => err.description(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::Glob(ref err) => err.fmt(f),
|
||||
Error::Regex(ref err) => err.fmt(f),
|
||||
Error::Io(ref err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<glob::Error> for Error {
|
||||
fn from(err: glob::Error) -> Error {
|
||||
Error::Glob(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<regex::Error> for Error {
|
||||
fn from(err: regex::Error) -> Error {
|
||||
Error::Regex(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gitignore is a matcher for the glob patterns in a single gitignore file.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Gitignore {
|
||||
set: glob::Set,
|
||||
root: PathBuf,
|
||||
patterns: Vec<Pattern>,
|
||||
num_ignores: u64,
|
||||
num_whitelist: u64,
|
||||
}
|
||||
|
||||
impl Gitignore {
|
||||
/// Create a new gitignore glob matcher from the gitignore file at the
|
||||
/// given path. The root of the gitignore file is the basename of path.
|
||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Gitignore, Error> {
|
||||
let root = match path.as_ref().parent() {
|
||||
Some(parent) => parent.to_path_buf(),
|
||||
None => env::current_dir().unwrap_or(Path::new("/").to_path_buf()),
|
||||
};
|
||||
let mut builder = GitignoreBuilder::new(root);
|
||||
try!(builder.add_path(path));
|
||||
builder.build()
|
||||
}
|
||||
|
||||
/// Create a new gitignore glob matcher from the given root directory and
|
||||
/// string containing the contents of a gitignore file.
|
||||
pub fn from_str<P: AsRef<Path>>(
|
||||
root: P,
|
||||
gitignore: &str,
|
||||
) -> Result<Gitignore, Error> {
|
||||
let mut builder = GitignoreBuilder::new(root);
|
||||
try!(builder.add_str(gitignore));
|
||||
builder.build()
|
||||
}
|
||||
|
||||
/// Returns true if and only if the given file path should be ignored
|
||||
/// according to the globs in this gitignore. `is_dir` should be true if
|
||||
/// the path refers to a directory and false otherwise.
|
||||
///
|
||||
/// Before matching path, its prefix (as determined by a common suffix
|
||||
/// of the directory containing this gitignore) is stripped. If there is
|
||||
/// no common suffix/prefix overlap, then path is assumed to reside in the
|
||||
/// same directory as this gitignore file.
|
||||
pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
|
||||
let mut path = path.as_ref();
|
||||
if let Ok(p) = path.strip_prefix(&self.root) {
|
||||
path = p;
|
||||
}
|
||||
self.matched_utf8(&*path.to_string_lossy(), is_dir)
|
||||
}
|
||||
|
||||
/// Like matched, but takes a path that has already been stripped and
|
||||
/// converted to UTF-8.
|
||||
pub fn matched_utf8(&self, path: &str, is_dir: bool) -> Match {
|
||||
// A single regex with a bunch of alternations of glob patterns is
|
||||
// unfortunately typically faster than a regex, so we use it as a
|
||||
// first pass filter. We still need to run the RegexSet to get the most
|
||||
// recently defined glob that matched.
|
||||
if !self.set.is_match(path) {
|
||||
return Match::None;
|
||||
}
|
||||
// The regex set can't actually pick the right glob that matched all
|
||||
// on its own. In particular, some globs require that only directories
|
||||
// can match. Thus, only accept a match from the regex set if the given
|
||||
// path satisfies the corresponding glob's directory criteria.
|
||||
for i in self.set.matches(path).iter().rev() {
|
||||
let pat = &self.patterns[i];
|
||||
if !pat.only_dir || is_dir {
|
||||
return if pat.whitelist {
|
||||
Match::Whitelist(pat)
|
||||
} else {
|
||||
Match::Ignored(pat)
|
||||
};
|
||||
}
|
||||
}
|
||||
Match::None
|
||||
}
|
||||
|
||||
/// Returns the total number of ignore patterns.
|
||||
pub fn num_ignores(&self) -> u64 {
|
||||
self.num_ignores
|
||||
}
|
||||
|
||||
/// Returns the total number of whitelisted patterns.
|
||||
pub fn num_whitelist(&self) -> u64 {
|
||||
self.num_whitelist
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of a glob match.
|
||||
///
|
||||
/// The lifetime `'a` refers to the lifetime of the pattern that resulted in
|
||||
/// a match (whether ignored or whitelisted).
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Match<'a> {
|
||||
/// The path didn't match any glob in the gitignore file.
|
||||
None,
|
||||
/// The last glob matched indicates the path should be ignored.
|
||||
Ignored(&'a Pattern),
|
||||
/// The last glob matched indicates the path should be whitelisted.
|
||||
Whitelist(&'a Pattern),
|
||||
}
|
||||
|
||||
impl<'a> Match<'a> {
|
||||
/// Returns true if the match result implies the path should be ignored.
|
||||
pub fn is_ignored(&self) -> bool {
|
||||
match *self {
|
||||
Match::Ignored(_) => true,
|
||||
Match::None | Match::Whitelist(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the match result didn't match any globs.
|
||||
pub fn is_none(&self) -> bool {
|
||||
match *self {
|
||||
Match::None => true,
|
||||
Match::Ignored(_) | Match::Whitelist(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Inverts the match so that Ignored becomes Whitelisted and Whitelisted
|
||||
/// becomes Ignored. A non-match remains the same.
|
||||
pub fn invert(self) -> Match<'a> {
|
||||
match self {
|
||||
Match::None => Match::None,
|
||||
Match::Ignored(pat) => Match::Whitelist(pat),
|
||||
Match::Whitelist(pat) => Match::Ignored(pat),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GitignoreBuilder constructs a matcher for a single set of globs from a
|
||||
/// .gitignore file.
|
||||
pub struct GitignoreBuilder {
|
||||
builder: glob::SetBuilder,
|
||||
root: PathBuf,
|
||||
patterns: Vec<Pattern>,
|
||||
}
|
||||
|
||||
/// Pattern represents a single pattern in a gitignore file. It doesn't
|
||||
/// know how to do glob matching directly, but it does store additional
|
||||
/// options on a pattern, such as whether it's whitelisted.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Pattern {
|
||||
/// The file path that this pattern was extracted from (may be empty).
|
||||
pub from: PathBuf,
|
||||
/// The original glob pattern string.
|
||||
pub original: String,
|
||||
/// The actual glob pattern string used to convert to a regex.
|
||||
pub pat: String,
|
||||
/// Whether this is a whitelisted pattern or not.
|
||||
pub whitelist: bool,
|
||||
/// Whether this pattern should only match directories or not.
|
||||
pub only_dir: bool,
|
||||
}
|
||||
|
||||
impl GitignoreBuilder {
|
||||
/// Create a new builder for a gitignore file.
|
||||
///
|
||||
/// The path given should be the path at which the globs for this gitignore
|
||||
/// file should be matched.
|
||||
pub fn new<P: AsRef<Path>>(root: P) -> GitignoreBuilder {
|
||||
GitignoreBuilder {
|
||||
builder: glob::SetBuilder::new(),
|
||||
root: root.as_ref().to_path_buf(),
|
||||
patterns: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new matcher from the glob patterns added so far.
|
||||
///
|
||||
/// Once a matcher is built, no new glob patterns can be added to it.
|
||||
pub fn build(self) -> Result<Gitignore, Error> {
|
||||
let nignores = self.patterns.iter().filter(|p| !p.whitelist).count();
|
||||
let nwhitelist = self.patterns.iter().filter(|p| p.whitelist).count();
|
||||
Ok(Gitignore {
|
||||
set: try!(self.builder.build()),
|
||||
root: self.root,
|
||||
patterns: self.patterns,
|
||||
num_ignores: nignores as u64,
|
||||
num_whitelist: nwhitelist as u64,
|
||||
})
|
||||
}
|
||||
|
||||
/// Add each pattern line from the file path given.
|
||||
pub fn add_path<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
|
||||
let rdr = io::BufReader::new(try!(File::open(&path)));
|
||||
for line in rdr.lines() {
|
||||
try!(self.add(&path, &try!(line)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add each pattern line from the string given.
|
||||
pub fn add_str(&mut self, gitignore: &str) -> Result<(), Error> {
|
||||
for line in gitignore.lines() {
|
||||
try!(self.add("", line));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a line from a gitignore file to this builder.
|
||||
///
|
||||
/// If the line could not be parsed as a glob, then an error is returned.
|
||||
pub fn add<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
from: P,
|
||||
mut line: &str,
|
||||
) -> Result<(), Error> {
|
||||
if line.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut pat = Pattern {
|
||||
from: from.as_ref().to_path_buf(),
|
||||
original: line.to_string(),
|
||||
pat: String::new(),
|
||||
whitelist: false,
|
||||
only_dir: false,
|
||||
};
|
||||
let mut opts = glob::MatchOptions::default();
|
||||
let has_slash = line.chars().any(|c| c == '/');
|
||||
// If the line starts with an escaped '!', then remove the escape.
|
||||
// Otherwise, if it starts with an unescaped '!', then this is a
|
||||
// whitelist pattern.
|
||||
match line.chars().nth(0) {
|
||||
Some('#') => return Ok(()),
|
||||
Some('\\') => {
|
||||
match line.chars().nth(1) {
|
||||
Some('!') | Some('#') => {
|
||||
line = &line[1..];
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Some('!') => {
|
||||
pat.whitelist = true;
|
||||
line = &line[1..];
|
||||
}
|
||||
Some('/') => {
|
||||
// `man gitignore` says that if a glob starts with a slash,
|
||||
// then the glob can only match the beginning of a path
|
||||
// (relative to the location of gitignore). We achieve this by
|
||||
// simply banning wildcards from matching /.
|
||||
opts.require_literal_separator = true;
|
||||
line = &line[1..];
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// If it ends with a slash, then this should only match directories,
|
||||
// but the slash should otherwise not be used while globbing.
|
||||
if let Some((i, c)) = line.char_indices().rev().nth(0) {
|
||||
if c == '/' {
|
||||
pat.only_dir = true;
|
||||
line = &line[..i];
|
||||
}
|
||||
}
|
||||
// If there is a literal slash, then we note that so that globbing
|
||||
// doesn't let wildcards match slashes. Otherwise, we need to let
|
||||
// the pattern match anywhere, so we add a `**/` prefix to achieve
|
||||
// that behavior.
|
||||
pat.pat = line.to_string();
|
||||
if has_slash {
|
||||
opts.require_literal_separator = true;
|
||||
} else {
|
||||
pat.pat = format!("**/{}", pat.pat);
|
||||
}
|
||||
try!(self.builder.add_with(&pat.pat, &opts));
|
||||
self.patterns.push(pat);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Gitignore;
|
||||
|
||||
macro_rules! ignored {
|
||||
($name:ident, $root:expr, $gi:expr, $path:expr) => {
|
||||
ignored!($name, $root, $gi, $path, false);
|
||||
};
|
||||
($name:ident, $root:expr, $gi:expr, $path:expr, $is_dir:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let gi = Gitignore::from_str($root, $gi).unwrap();
|
||||
assert!(gi.matched($path, $is_dir).is_ignored());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! not_ignored {
|
||||
($name:ident, $root:expr, $gi:expr, $path:expr) => {
|
||||
not_ignored!($name, $root, $gi, $path, false);
|
||||
};
|
||||
($name:ident, $root:expr, $gi:expr, $path:expr, $is_dir:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let gi = Gitignore::from_str($root, $gi).unwrap();
|
||||
assert!(!gi.matched($path, $is_dir).is_ignored());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const ROOT: &'static str = "/home/foobar/rust/rg";
|
||||
|
||||
ignored!(ig1, ROOT, "months", "months");
|
||||
ignored!(ig2, ROOT, "*.lock", "Cargo.lock");
|
||||
ignored!(ig3, ROOT, "*.rs", "src/main.rs");
|
||||
ignored!(ig4, ROOT, "src/*.rs", "src/main.rs");
|
||||
ignored!(ig5, ROOT, "/*.c", "cat-file.c");
|
||||
ignored!(ig6, ROOT, "/src/*.rs", "src/main.rs");
|
||||
ignored!(ig7, ROOT, "!src/main.rs\n*.rs", "src/main.rs");
|
||||
ignored!(ig8, ROOT, "foo/", "foo", true);
|
||||
ignored!(ig9, ROOT, "**/foo", "foo");
|
||||
ignored!(ig10, ROOT, "**/foo", "src/foo");
|
||||
ignored!(ig11, ROOT, "**/foo/**", "src/foo/bar");
|
||||
ignored!(ig12, ROOT, "**/foo/**", "wat/src/foo/bar/baz");
|
||||
ignored!(ig13, ROOT, "**/foo/bar", "foo/bar");
|
||||
ignored!(ig14, ROOT, "**/foo/bar", "src/foo/bar");
|
||||
ignored!(ig15, ROOT, "abc/**", "abc/x");
|
||||
ignored!(ig16, ROOT, "abc/**", "abc/x/y");
|
||||
ignored!(ig17, ROOT, "abc/**", "abc/x/y/z");
|
||||
ignored!(ig18, ROOT, "a/**/b", "a/b");
|
||||
ignored!(ig19, ROOT, "a/**/b", "a/x/b");
|
||||
ignored!(ig20, ROOT, "a/**/b", "a/x/y/b");
|
||||
ignored!(ig21, ROOT, r"\!xy", "!xy");
|
||||
ignored!(ig22, ROOT, r"\#foo", "#foo");
|
||||
ignored!(ig23, ROOT, "foo", "./foo");
|
||||
ignored!(ig24, ROOT, "target", "grep/target");
|
||||
ignored!(ig25, ROOT, "Cargo.lock", "./tabwriter-bin/Cargo.lock");
|
||||
|
||||
not_ignored!(ignot1, ROOT, "amonths", "months");
|
||||
not_ignored!(ignot2, ROOT, "monthsa", "months");
|
||||
not_ignored!(ignot3, ROOT, "src/*.rs", "src/grep/src/main.rs");
|
||||
not_ignored!(ignot4, ROOT, "/*.c", "mozilla-sha1/sha1.c");
|
||||
not_ignored!(ignot5, ROOT, "/src/*.rs", "src/grep/src/main.rs");
|
||||
not_ignored!(ignot6, ROOT, "*.rs\n!src/main.rs", "src/main.rs");
|
||||
not_ignored!(ignot7, ROOT, "foo/", "foo", false);
|
||||
not_ignored!(ignot8, ROOT, "**/foo/**", "wat/src/afoo/bar/baz");
|
||||
not_ignored!(ignot9, ROOT, "**/foo/**", "wat/src/fooa/bar/baz");
|
||||
not_ignored!(ignot10, ROOT, "**/foo/bar", "foo/src/bar");
|
||||
not_ignored!(ignot11, ROOT, "#foo", "#foo");
|
||||
not_ignored!(ignot12, ROOT, "\n\n\n", "foo");
|
||||
}
|
||||
693
src/glob.rs
693
src/glob.rs
@@ -1,693 +0,0 @@
|
||||
/*!
|
||||
The glob module provides standard shell globbing, but is specifically
|
||||
implemented by converting glob syntax to regular expressions. The reasoning is
|
||||
two fold:
|
||||
|
||||
1. The regex library is *really* fast. Regaining performance in a distinct
|
||||
implementation of globbing is non-trivial.
|
||||
2. Most crucially, a `RegexSet` can be used to match many globs simultaneously.
|
||||
|
||||
This module is written with some amount of intention of eventually splitting it
|
||||
out into its own separate crate, but I didn't quite have the energy for all
|
||||
that rigamorole when I wrote this. In particular, it could be fast/good enough
|
||||
to make its way into `glob` proper.
|
||||
*/
|
||||
|
||||
// TODO(burntsushi): I'm pretty dismayed by the performance of regex sets
|
||||
// here. For example, we do a first pass single-regex-of-all-globs filter
|
||||
// before actually running the regex set. This turns out to be faster,
|
||||
// especially in fresh checkouts of repos that don't have a lot of ignored
|
||||
// files. It's not clear how hard it is to make the regex set faster.
|
||||
//
|
||||
// An alternative avenue is to stop doing "regex all the things." (Which, to
|
||||
// be fair, is pretty fast---I just expected it to be faster.) We could do
|
||||
// something clever using assumptions along the lines of "oh, most ignore
|
||||
// patterns are either literals or are for ignoring file extensions." (Look
|
||||
// at the .gitignore for the chromium repo---just about every pattern satisfies
|
||||
// that assumption.)
|
||||
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::iter;
|
||||
use std::str;
|
||||
|
||||
use regex;
|
||||
use regex::bytes::{Regex, RegexSet, SetMatches};
|
||||
|
||||
/// Represents an error that can occur when parsing a glob pattern.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Error {
|
||||
InvalidRecursive,
|
||||
UnclosedClass,
|
||||
InvalidRange(char, char),
|
||||
}
|
||||
|
||||
impl StdError for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::InvalidRecursive => {
|
||||
"invalid use of **; must be one path component"
|
||||
}
|
||||
Error::UnclosedClass => {
|
||||
"unclosed character class; missing ']'"
|
||||
}
|
||||
Error::InvalidRange(_, _) => {
|
||||
"invalid character range"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::InvalidRecursive | Error::UnclosedClass => {
|
||||
write!(f, "{}", self.description())
|
||||
}
|
||||
Error::InvalidRange(s, e) => {
|
||||
write!(f, "invalid range; '{}' > '{}'", s, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set represents a group of globs that can be matched together in a single
|
||||
/// pass.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Set {
|
||||
re: Regex,
|
||||
set: RegexSet,
|
||||
}
|
||||
|
||||
impl Set {
|
||||
/// Returns true if and only if the given path matches at least one glob
|
||||
/// in this set.
|
||||
pub fn is_match<T: AsRef<[u8]>>(&self, path: T) -> bool {
|
||||
self.re.is_match(path.as_ref())
|
||||
}
|
||||
|
||||
/// Returns every glob pattern (by sequence number) that matches the given
|
||||
/// path.
|
||||
pub fn matches<T: AsRef<[u8]>>(&self, path: T) -> SetMatches {
|
||||
// TODO(burntsushi): If we split this out into a separate crate, don't
|
||||
// expose the regex::SetMatches type in the public API.
|
||||
self.set.matches(path.as_ref())
|
||||
}
|
||||
|
||||
/// Returns the number of glob patterns in this set.
|
||||
pub fn len(&self) -> usize {
|
||||
self.set.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// SetBuilder builds a group of patterns that can be used to simultaneously
|
||||
/// match a file path.
|
||||
pub struct SetBuilder {
|
||||
pats: Vec<(Pattern, MatchOptions)>,
|
||||
}
|
||||
|
||||
impl SetBuilder {
|
||||
/// Create a new SetBuilder. A SetBuilder can be used to add new patterns.
|
||||
/// Once all patterns have been added, `build` should be called to produce
|
||||
/// a `Set`, which can then be used for matching.
|
||||
pub fn new() -> SetBuilder {
|
||||
SetBuilder { pats: vec![] }
|
||||
}
|
||||
|
||||
/// Builds a new matcher from all of the glob patterns added so far.
|
||||
///
|
||||
/// Once a matcher is built, no new patterns can be added to it.
|
||||
pub fn build(&self) -> Result<Set, regex::Error> {
|
||||
let it = self.pats.iter().map(|&(ref p, ref o)| p.to_regex_with(o));
|
||||
let set = try!(RegexSet::new(it));
|
||||
|
||||
let mut joined = String::new();
|
||||
for &(ref p, ref o) in &self.pats {
|
||||
let part = format!("(?:{})", p.to_regex_with(o));
|
||||
if !joined.is_empty() {
|
||||
joined.push('|');
|
||||
}
|
||||
joined.push_str(&part);
|
||||
}
|
||||
let re = try!(Regex::new(&joined));
|
||||
Ok(Set { re: re, set: set })
|
||||
}
|
||||
|
||||
/// Add a new pattern to this set.
|
||||
///
|
||||
/// If the pattern could not be parsed as a glob, then an error is
|
||||
/// returned.
|
||||
pub fn add(&mut self, pat: &str) -> Result<(), Error> {
|
||||
self.add_with(pat, &MatchOptions::default())
|
||||
}
|
||||
|
||||
/// Like add, but sets the match options for this particular pattern.
|
||||
pub fn add_with(
|
||||
&mut self,
|
||||
pat: &str,
|
||||
opts: &MatchOptions,
|
||||
) -> Result<(), Error> {
|
||||
let pat = try!(Pattern::new(pat));
|
||||
self.pats.push((pat, opts.clone()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern represents a successfully parsed shell glob pattern.
|
||||
///
|
||||
/// It cannot be used directly to match file paths, but it can be converted
|
||||
/// to a regular expression string.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Pattern {
|
||||
tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
/// Options to control the matching semantics of a glob. The default value
|
||||
/// has all options disabled.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MatchOptions {
|
||||
/// When true, matching is done case insensitively.
|
||||
pub case_insensitive: bool,
|
||||
/// When true, neither `*` nor `?` match the current system's path
|
||||
/// separator.
|
||||
pub require_literal_separator: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
enum Token {
|
||||
Literal(char),
|
||||
Any,
|
||||
ZeroOrMore,
|
||||
RecursivePrefix,
|
||||
RecursiveSuffix,
|
||||
RecursiveZeroOrMore,
|
||||
Class {
|
||||
negated: bool,
|
||||
ranges: Vec<(char, char)>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
/// Parse a shell glob pattern.
|
||||
///
|
||||
/// If the pattern is not a valid glob, then an error is returned.
|
||||
pub fn new(pat: &str) -> Result<Pattern, Error> {
|
||||
let mut p = Parser {
|
||||
p: Pattern::default(),
|
||||
chars: pat.chars().peekable(),
|
||||
prev: None,
|
||||
cur: None,
|
||||
};
|
||||
try!(p.parse());
|
||||
Ok(p.p)
|
||||
}
|
||||
|
||||
/// Convert this pattern to a string that is guaranteed to be a valid
|
||||
/// regular expression and will represent the matching semantics of this
|
||||
/// glob pattern. This uses a default set of options.
|
||||
pub fn to_regex(&self) -> String {
|
||||
self.to_regex_with(&MatchOptions::default())
|
||||
}
|
||||
|
||||
/// Convert this pattern to a string that is guaranteed to be a valid
|
||||
/// regular expression and will represent the matching semantics of this
|
||||
/// glob pattern and the options given.
|
||||
pub fn to_regex_with(&self, options: &MatchOptions) -> String {
|
||||
let seps = regex::quote(r"/\");
|
||||
let mut re = String::new();
|
||||
re.push_str("(?-u)");
|
||||
if options.case_insensitive {
|
||||
re.push_str("(?i)");
|
||||
}
|
||||
re.push('^');
|
||||
// Special case. If the entire glob is just `**`, then it should match
|
||||
// everything.
|
||||
if self.tokens.len() == 1 && self.tokens[0] == Token::RecursivePrefix {
|
||||
re.push_str(".*");
|
||||
re.push('$');
|
||||
return re;
|
||||
}
|
||||
for tok in &self.tokens {
|
||||
match *tok {
|
||||
Token::Literal(c) => {
|
||||
re.push_str(®ex::quote(&c.to_string()));
|
||||
}
|
||||
Token::Any => {
|
||||
if options.require_literal_separator {
|
||||
re.push_str(&format!("[^{}]", seps));
|
||||
} else {
|
||||
re.push_str(".");
|
||||
}
|
||||
}
|
||||
Token::ZeroOrMore => {
|
||||
if options.require_literal_separator {
|
||||
re.push_str(&format!("[^{}]*", seps));
|
||||
} else {
|
||||
re.push_str(".*");
|
||||
}
|
||||
}
|
||||
Token::RecursivePrefix => {
|
||||
re.push_str(&format!("(?:[{sep}]?|.*[{sep}])", sep=seps));
|
||||
}
|
||||
Token::RecursiveSuffix => {
|
||||
re.push_str(&format!("(?:[{sep}]?|[{sep}].*)", sep=seps));
|
||||
}
|
||||
Token::RecursiveZeroOrMore => {
|
||||
re.push_str(&format!("(?:[{sep}]|[{sep}].*[{sep}])",
|
||||
sep=seps));
|
||||
}
|
||||
Token::Class { negated, ref ranges } => {
|
||||
re.push('[');
|
||||
if negated {
|
||||
re.push('^');
|
||||
}
|
||||
for r in ranges {
|
||||
if r.0 == r.1 {
|
||||
// Not strictly necessary, but nicer to look at.
|
||||
re.push_str(®ex::quote(&r.0.to_string()));
|
||||
} else {
|
||||
re.push_str(®ex::quote(&r.0.to_string()));
|
||||
re.push('-');
|
||||
re.push_str(®ex::quote(&r.1.to_string()));
|
||||
}
|
||||
}
|
||||
re.push(']');
|
||||
}
|
||||
}
|
||||
}
|
||||
re.push('$');
|
||||
re
|
||||
}
|
||||
}
|
||||
|
||||
struct Parser<'a> {
|
||||
p: Pattern,
|
||||
chars: iter::Peekable<str::Chars<'a>>,
|
||||
prev: Option<char>,
|
||||
cur: Option<char>,
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
fn parse(&mut self) -> Result<(), Error> {
|
||||
while let Some(c) = self.bump() {
|
||||
match c {
|
||||
'?' => self.p.tokens.push(Token::Any),
|
||||
'*' => try!(self.parse_star()),
|
||||
'[' => try!(self.parse_class()),
|
||||
c => self.p.tokens.push(Token::Literal(c)),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_star(&mut self) -> Result<(), Error> {
|
||||
let prev = self.prev;
|
||||
if self.chars.peek() != Some(&'*') {
|
||||
self.p.tokens.push(Token::ZeroOrMore);
|
||||
return Ok(());
|
||||
}
|
||||
assert!(self.bump() == Some('*'));
|
||||
if self.p.tokens.is_empty() {
|
||||
self.p.tokens.push(Token::RecursivePrefix);
|
||||
let next = self.bump();
|
||||
if !next.is_none() && next != Some('/') {
|
||||
return Err(Error::InvalidRecursive);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let last = self.p.tokens.pop().unwrap();
|
||||
if prev != Some('/') {
|
||||
return Err(Error::InvalidRecursive);
|
||||
}
|
||||
let next = self.bump();
|
||||
if next.is_none() {
|
||||
self.p.tokens.push(Token::RecursiveSuffix);
|
||||
return Ok(());
|
||||
}
|
||||
if next != Some('/') {
|
||||
return Err(Error::InvalidRecursive);
|
||||
}
|
||||
self.p.tokens.push(Token::RecursiveZeroOrMore);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_class(&mut self) -> Result<(), Error> {
|
||||
fn add_to_last_range(
|
||||
r: &mut (char, char),
|
||||
add: char,
|
||||
) -> Result<(), Error> {
|
||||
r.1 = add;
|
||||
if r.1 < r.0 {
|
||||
Err(Error::InvalidRange(r.0, r.1))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
let mut negated = false;
|
||||
let mut ranges = vec![];
|
||||
if self.chars.peek() == Some(&'!') {
|
||||
assert!(self.bump() == Some('!'));
|
||||
negated = true;
|
||||
}
|
||||
let mut first = true;
|
||||
let mut in_range = false;
|
||||
loop {
|
||||
let c = match self.bump() {
|
||||
Some(c) => c,
|
||||
// The only way to successfully break this loop is to observe
|
||||
// a ']'.
|
||||
None => return Err(Error::UnclosedClass),
|
||||
};
|
||||
match c {
|
||||
']' => {
|
||||
if first {
|
||||
ranges.push((']', ']'));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
'-' => {
|
||||
if first {
|
||||
ranges.push(('-', '-'));
|
||||
} else if in_range {
|
||||
// invariant: in_range is only set when there is
|
||||
// already at least one character seen.
|
||||
let r = ranges.last_mut().unwrap();
|
||||
try!(add_to_last_range(r, '-'));
|
||||
in_range = false;
|
||||
} else {
|
||||
assert!(!ranges.is_empty());
|
||||
in_range = true;
|
||||
}
|
||||
}
|
||||
c => {
|
||||
if in_range {
|
||||
// invariant: in_range is only set when there is
|
||||
// already at least one character seen.
|
||||
try!(add_to_last_range(ranges.last_mut().unwrap(), c));
|
||||
} else {
|
||||
ranges.push((c, c));
|
||||
}
|
||||
in_range = false;
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if in_range {
|
||||
// Means that the last character in the class was a '-', so add
|
||||
// it as a literal.
|
||||
ranges.push(('-', '-'));
|
||||
}
|
||||
self.p.tokens.push(Token::Class {
|
||||
negated: negated,
|
||||
ranges: ranges,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bump(&mut self) -> Option<char> {
|
||||
self.prev = self.cur;
|
||||
self.cur = self.chars.next();
|
||||
self.cur
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use regex::bytes::Regex;
|
||||
|
||||
use super::{Error, Pattern, MatchOptions, SetBuilder, Token};
|
||||
use super::Token::*;
|
||||
|
||||
macro_rules! syntax {
|
||||
($name:ident, $pat:expr, $tokens:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let pat = Pattern::new($pat).unwrap();
|
||||
assert_eq!($tokens, pat.tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! syntaxerr {
|
||||
($name:ident, $pat:expr, $err:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let err = Pattern::new($pat).unwrap_err();
|
||||
assert_eq!($err, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! toregex {
|
||||
($name:ident, $pat:expr, $re:expr) => {
|
||||
toregex!($name, $pat, $re, MatchOptions::default());
|
||||
};
|
||||
($name:ident, $pat:expr, $re:expr, $options:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let pat = Pattern::new($pat).unwrap();
|
||||
assert_eq!(
|
||||
format!("(?-u){}", $re), pat.to_regex_with(&$options));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! matches {
|
||||
($name:ident, $pat:expr, $path:expr) => {
|
||||
matches!($name, $pat, $path, MatchOptions::default());
|
||||
};
|
||||
($name:ident, $pat:expr, $path:expr, $options:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let pat = Pattern::new($pat).unwrap();
|
||||
let path = &Path::new($path).to_str().unwrap();
|
||||
let re = Regex::new(&pat.to_regex_with(&$options)).unwrap();
|
||||
assert!(re.is_match(path.as_bytes()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! nmatches {
|
||||
($name:ident, $pat:expr, $path:expr) => {
|
||||
nmatches!($name, $pat, $path, MatchOptions::default());
|
||||
};
|
||||
($name:ident, $pat:expr, $path:expr, $options:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let pat = Pattern::new($pat).unwrap();
|
||||
let path = &Path::new($path).to_str().unwrap();
|
||||
let re = Regex::new(&pat.to_regex_with(&$options)).unwrap();
|
||||
// println!("PATTERN: {}", $pat);
|
||||
// println!("REGEX: {:?}", re);
|
||||
// println!("PATH: {}", path);
|
||||
assert!(!re.is_match(path.as_bytes()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn class(s: char, e: char) -> Token {
|
||||
Class { negated: false, ranges: vec![(s, e)] }
|
||||
}
|
||||
|
||||
fn classn(s: char, e: char) -> Token {
|
||||
Class { negated: true, ranges: vec![(s, e)] }
|
||||
}
|
||||
|
||||
fn rclass(ranges: &[(char, char)]) -> Token {
|
||||
Class { negated: false, ranges: ranges.to_vec() }
|
||||
}
|
||||
|
||||
fn rclassn(ranges: &[(char, char)]) -> Token {
|
||||
Class { negated: true, ranges: ranges.to_vec() }
|
||||
}
|
||||
|
||||
syntax!(literal1, "a", vec![Literal('a')]);
|
||||
syntax!(literal2, "ab", vec![Literal('a'), Literal('b')]);
|
||||
syntax!(any1, "?", vec![Any]);
|
||||
syntax!(any2, "a?b", vec![Literal('a'), Any, Literal('b')]);
|
||||
syntax!(seq1, "*", vec![ZeroOrMore]);
|
||||
syntax!(seq2, "a*b", vec![Literal('a'), ZeroOrMore, Literal('b')]);
|
||||
syntax!(seq3, "*a*b*", vec![
|
||||
ZeroOrMore, Literal('a'), ZeroOrMore, Literal('b'), ZeroOrMore,
|
||||
]);
|
||||
syntax!(rseq1, "**", vec![RecursivePrefix]);
|
||||
syntax!(rseq2, "**/", vec![RecursivePrefix]);
|
||||
syntax!(rseq3, "/**", vec![RecursiveSuffix]);
|
||||
syntax!(rseq4, "/**/", vec![RecursiveZeroOrMore]);
|
||||
syntax!(rseq5, "a/**/b", vec![
|
||||
Literal('a'), RecursiveZeroOrMore, Literal('b'),
|
||||
]);
|
||||
syntax!(cls1, "[a]", vec![class('a', 'a')]);
|
||||
syntax!(cls2, "[!a]", vec![classn('a', 'a')]);
|
||||
syntax!(cls3, "[a-z]", vec![class('a', 'z')]);
|
||||
syntax!(cls4, "[!a-z]", vec![classn('a', 'z')]);
|
||||
syntax!(cls5, "[-]", vec![class('-', '-')]);
|
||||
syntax!(cls6, "[]]", vec![class(']', ']')]);
|
||||
syntax!(cls7, "[*]", vec![class('*', '*')]);
|
||||
syntax!(cls8, "[!!]", vec![classn('!', '!')]);
|
||||
syntax!(cls9, "[a-]", vec![rclass(&[('a', 'a'), ('-', '-')])]);
|
||||
syntax!(cls10, "[-a-z]", vec![rclass(&[('-', '-'), ('a', 'z')])]);
|
||||
syntax!(cls11, "[a-z-]", vec![rclass(&[('a', 'z'), ('-', '-')])]);
|
||||
syntax!(cls12, "[-a-z-]", vec![
|
||||
rclass(&[('-', '-'), ('a', 'z'), ('-', '-')]),
|
||||
]);
|
||||
syntax!(cls13, "[]-z]", vec![class(']', 'z')]);
|
||||
syntax!(cls14, "[--z]", vec![class('-', 'z')]);
|
||||
syntax!(cls15, "[ --]", vec![class(' ', '-')]);
|
||||
syntax!(cls16, "[0-9a-z]", vec![rclass(&[('0', '9'), ('a', 'z')])]);
|
||||
syntax!(cls17, "[a-z0-9]", vec![rclass(&[('a', 'z'), ('0', '9')])]);
|
||||
syntax!(cls18, "[!0-9a-z]", vec![rclassn(&[('0', '9'), ('a', 'z')])]);
|
||||
syntax!(cls19, "[!a-z0-9]", vec![rclassn(&[('a', 'z'), ('0', '9')])]);
|
||||
|
||||
syntaxerr!(err_rseq1, "a**", Error::InvalidRecursive);
|
||||
syntaxerr!(err_rseq2, "**a", Error::InvalidRecursive);
|
||||
syntaxerr!(err_rseq3, "a**b", Error::InvalidRecursive);
|
||||
syntaxerr!(err_rseq4, "***", Error::InvalidRecursive);
|
||||
syntaxerr!(err_rseq5, "/a**", Error::InvalidRecursive);
|
||||
syntaxerr!(err_rseq6, "/**a", Error::InvalidRecursive);
|
||||
syntaxerr!(err_rseq7, "/a**b", Error::InvalidRecursive);
|
||||
syntaxerr!(err_unclosed1, "[", Error::UnclosedClass);
|
||||
syntaxerr!(err_unclosed2, "[]", Error::UnclosedClass);
|
||||
syntaxerr!(err_unclosed3, "[!", Error::UnclosedClass);
|
||||
syntaxerr!(err_unclosed4, "[!]", Error::UnclosedClass);
|
||||
syntaxerr!(err_range1, "[z-a]", Error::InvalidRange('z', 'a'));
|
||||
syntaxerr!(err_range2, "[z--]", Error::InvalidRange('z', '-'));
|
||||
|
||||
const SLASHLIT: MatchOptions = MatchOptions {
|
||||
case_insensitive: false,
|
||||
require_literal_separator: true,
|
||||
};
|
||||
const CASEI: MatchOptions = MatchOptions {
|
||||
case_insensitive: true,
|
||||
require_literal_separator: false,
|
||||
};
|
||||
|
||||
toregex!(re_casei, "a", "(?i)^a$", &CASEI);
|
||||
|
||||
toregex!(re_slash1, "?", r"^[^/\\]$", SLASHLIT);
|
||||
toregex!(re_slash2, "*", r"^[^/\\]*$", SLASHLIT);
|
||||
|
||||
toregex!(re1, "a", "^a$");
|
||||
toregex!(re2, "?", "^.$");
|
||||
toregex!(re3, "*", "^.*$");
|
||||
toregex!(re4, "a?", "^a.$");
|
||||
toregex!(re5, "?a", "^.a$");
|
||||
toregex!(re6, "a*", "^a.*$");
|
||||
toregex!(re7, "*a", "^.*a$");
|
||||
toregex!(re8, "[*]", r"^[\*]$");
|
||||
toregex!(re9, "[+]", r"^[\+]$");
|
||||
toregex!(re10, "+", r"^\+$");
|
||||
toregex!(re11, "**", r"^.*$");
|
||||
|
||||
matches!(match1, "a", "a");
|
||||
matches!(match2, "a*b", "a_b");
|
||||
matches!(match3, "a*b*c", "abc");
|
||||
matches!(match4, "a*b*c", "a_b_c");
|
||||
matches!(match5, "a*b*c", "a___b___c");
|
||||
matches!(match6, "abc*abc*abc", "abcabcabcabcabcabcabc");
|
||||
matches!(match7, "a*a*a*a*a*a*a*a*a", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
matches!(match8, "a*b[xyz]c*d", "abxcdbxcddd");
|
||||
|
||||
matches!(matchrec1, "some/**/needle.txt", "some/needle.txt");
|
||||
matches!(matchrec2, "some/**/needle.txt", "some/one/needle.txt");
|
||||
matches!(matchrec3, "some/**/needle.txt", "some/one/two/needle.txt");
|
||||
matches!(matchrec4, "some/**/needle.txt", "some/other/needle.txt");
|
||||
matches!(matchrec5, "**", "abcde");
|
||||
matches!(matchrec6, "**", "");
|
||||
matches!(matchrec7, "**", ".asdf");
|
||||
matches!(matchrec8, "**", "/x/.asdf");
|
||||
matches!(matchrec9, "some/**/**/needle.txt", "some/needle.txt");
|
||||
matches!(matchrec10, "some/**/**/needle.txt", "some/one/needle.txt");
|
||||
matches!(matchrec11, "some/**/**/needle.txt", "some/one/two/needle.txt");
|
||||
matches!(matchrec12, "some/**/**/needle.txt", "some/other/needle.txt");
|
||||
matches!(matchrec13, "**/test", "one/two/test");
|
||||
matches!(matchrec14, "**/test", "one/test");
|
||||
matches!(matchrec15, "**/test", "test");
|
||||
matches!(matchrec16, "/**/test", "/one/two/test");
|
||||
matches!(matchrec17, "/**/test", "/one/test");
|
||||
matches!(matchrec18, "/**/test", "/test");
|
||||
matches!(matchrec19, "**/.*", ".abc");
|
||||
matches!(matchrec20, "**/.*", "abc/.abc");
|
||||
matches!(matchrec21, ".*/**", ".abc");
|
||||
matches!(matchrec22, ".*/**", ".abc/abc");
|
||||
|
||||
matches!(matchrange1, "a[0-9]b", "a0b");
|
||||
matches!(matchrange2, "a[0-9]b", "a9b");
|
||||
matches!(matchrange3, "a[!0-9]b", "a_b");
|
||||
matches!(matchrange4, "[a-z123]", "1");
|
||||
matches!(matchrange5, "[1a-z23]", "1");
|
||||
matches!(matchrange6, "[123a-z]", "1");
|
||||
matches!(matchrange7, "[abc-]", "-");
|
||||
matches!(matchrange8, "[-abc]", "-");
|
||||
matches!(matchrange9, "[-a-c]", "b");
|
||||
matches!(matchrange10, "[a-c-]", "b");
|
||||
matches!(matchrange11, "[-]", "-");
|
||||
|
||||
matches!(matchpat1, "*hello.txt", "hello.txt");
|
||||
matches!(matchpat2, "*hello.txt", "gareth_says_hello.txt");
|
||||
matches!(matchpat3, "*hello.txt", "some/path/to/hello.txt");
|
||||
matches!(matchpat4, "*hello.txt", "some\\path\\to\\hello.txt");
|
||||
matches!(matchpat5, "*hello.txt", "/an/absolute/path/to/hello.txt");
|
||||
matches!(matchpat6, "*some/path/to/hello.txt", "some/path/to/hello.txt");
|
||||
matches!(matchpat7, "*some/path/to/hello.txt",
|
||||
"a/bigger/some/path/to/hello.txt");
|
||||
|
||||
matches!(matchescape, "_[[]_[]]_[?]_[*]_!_", "_[_]_?_*_!_");
|
||||
|
||||
matches!(matchcasei1, "aBcDeFg", "aBcDeFg", CASEI);
|
||||
matches!(matchcasei2, "aBcDeFg", "abcdefg", CASEI);
|
||||
matches!(matchcasei3, "aBcDeFg", "ABCDEFG", CASEI);
|
||||
matches!(matchcasei4, "aBcDeFg", "AbCdEfG", CASEI);
|
||||
|
||||
matches!(matchslash1, "abc/def", "abc/def", SLASHLIT);
|
||||
nmatches!(matchslash2, "abc?def", "abc/def", SLASHLIT);
|
||||
nmatches!(matchslash2_win, "abc?def", "abc\\def", SLASHLIT);
|
||||
nmatches!(matchslash3, "abc*def", "abc/def", SLASHLIT);
|
||||
matches!(matchslash4, "abc[/]def", "abc/def", SLASHLIT); // differs
|
||||
|
||||
nmatches!(matchnot1, "a*b*c", "abcd");
|
||||
nmatches!(matchnot2, "abc*abc*abc", "abcabcabcabcabcabcabca");
|
||||
nmatches!(matchnot3, "some/**/needle.txt", "some/other/notthis.txt");
|
||||
nmatches!(matchnot4, "some/**/**/needle.txt", "some/other/notthis.txt");
|
||||
nmatches!(matchnot5, "/**/test", "test");
|
||||
nmatches!(matchnot6, "/**/test", "/one/notthis");
|
||||
nmatches!(matchnot7, "/**/test", "/notthis");
|
||||
nmatches!(matchnot8, "**/.*", "ab.c");
|
||||
nmatches!(matchnot9, "**/.*", "abc/ab.c");
|
||||
nmatches!(matchnot10, ".*/**", "a.bc");
|
||||
nmatches!(matchnot11, ".*/**", "abc/a.bc");
|
||||
nmatches!(matchnot12, "a[0-9]b", "a_b");
|
||||
nmatches!(matchnot13, "a[!0-9]b", "a0b");
|
||||
nmatches!(matchnot14, "a[!0-9]b", "a9b");
|
||||
nmatches!(matchnot15, "[!-]", "-");
|
||||
nmatches!(matchnot16, "*hello.txt", "hello.txt-and-then-some");
|
||||
nmatches!(matchnot17, "*hello.txt", "goodbye.txt");
|
||||
nmatches!(matchnot18, "*some/path/to/hello.txt",
|
||||
"some/path/to/hello.txt-and-then-some");
|
||||
nmatches!(matchnot19, "*some/path/to/hello.txt",
|
||||
"some/other/path/to/hello.txt");
|
||||
|
||||
#[test]
|
||||
fn set_works() {
|
||||
let mut builder = SetBuilder::new();
|
||||
builder.add("src/**/*.rs").unwrap();
|
||||
builder.add("*.c").unwrap();
|
||||
builder.add("src/lib.rs").unwrap();
|
||||
let set = builder.build().unwrap();
|
||||
|
||||
assert!(set.is_match("foo.c"));
|
||||
assert!(set.is_match("src/foo.c"));
|
||||
assert!(!set.is_match("foo.rs"));
|
||||
assert!(!set.is_match("tests/foo.rs"));
|
||||
assert!(set.is_match("src/foo.rs"));
|
||||
assert!(set.is_match("src/grep/src/main.rs"));
|
||||
|
||||
assert_eq!(2, set.matches("src/lib.rs").iter().count());
|
||||
assert!(set.matches("src/lib.rs").matched(0));
|
||||
assert!(!set.matches("src/lib.rs").matched(1));
|
||||
assert!(set.matches("src/lib.rs").matched(2));
|
||||
}
|
||||
}
|
||||
432
src/ignore.rs
432
src/ignore.rs
@@ -1,432 +0,0 @@
|
||||
/*!
|
||||
The ignore module is responsible for managing the state required to determine
|
||||
whether a *single* file path should be searched or not.
|
||||
|
||||
In general, there are two ways to ignore a particular file:
|
||||
|
||||
1. Specify an ignore rule in some "global" configuration, such as a
|
||||
$HOME/.rgignore or on the command line.
|
||||
2. A specific ignore file (like .gitignore) found during directory traversal.
|
||||
|
||||
The `IgnoreDir` type handles ignore patterns for any one particular directory
|
||||
(including "global" ignore patterns), while the `Ignore` type handles a stack
|
||||
of `IgnoreDir`s for use during directory traversal.
|
||||
*/
|
||||
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use gitignore::{self, Gitignore, GitignoreBuilder, Match, Pattern};
|
||||
use types::Types;
|
||||
|
||||
const IGNORE_NAMES: &'static [&'static str] = &[
|
||||
".gitignore",
|
||||
".agignore",
|
||||
".rgignore",
|
||||
];
|
||||
|
||||
/// Represents an error that can occur when parsing a gitignore file.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Gitignore(gitignore::Error),
|
||||
Io {
|
||||
path: PathBuf,
|
||||
err: io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
fn from_io<P: AsRef<Path>>(path: P, err: io::Error) -> Error {
|
||||
Error::Io { path: path.as_ref().to_path_buf(), err: err }
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::Gitignore(ref err) => err.description(),
|
||||
Error::Io { ref err, .. } => err.description(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::Gitignore(ref err) => err.fmt(f),
|
||||
Error::Io { ref path, ref err } => {
|
||||
write!(f, "{}: {}", path.display(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<gitignore::Error> for Error {
|
||||
fn from(err: gitignore::Error) -> Error {
|
||||
Error::Gitignore(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ignore represents a collection of ignore patterns organized by directory.
|
||||
/// In particular, a stack is maintained, where the top of the stack
|
||||
/// corresponds to the current directory being searched and the bottom of the
|
||||
/// stack represents the root of a search. Ignore patterns at the top of the
|
||||
/// stack take precedence over ignore patterns at the bottom of the stack.
|
||||
pub struct Ignore {
|
||||
/// A stack of ignore patterns at each directory level of traversal.
|
||||
/// A directory that contributes no ignore patterns is `None`.
|
||||
stack: Vec<Option<IgnoreDir>>,
|
||||
/// A set of override globs that are always checked first. A match (whether
|
||||
/// it's whitelist or blacklist) trumps anything in stack.
|
||||
overrides: Overrides,
|
||||
/// A file type matcher.
|
||||
types: Types,
|
||||
ignore_hidden: bool,
|
||||
no_ignore: bool,
|
||||
}
|
||||
|
||||
impl Ignore {
|
||||
/// Create an empty set of ignore patterns.
|
||||
pub fn new() -> Ignore {
|
||||
Ignore {
|
||||
stack: vec![],
|
||||
overrides: Overrides::new(None),
|
||||
types: Types::empty(),
|
||||
ignore_hidden: true,
|
||||
no_ignore: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether hidden files/folders should be ignored (defaults to true).
|
||||
pub fn ignore_hidden(&mut self, yes: bool) -> &mut Ignore {
|
||||
self.ignore_hidden = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// When set, ignore files are ignored.
|
||||
pub fn no_ignore(&mut self, yes: bool) -> &mut Ignore {
|
||||
self.no_ignore = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a set of globs that overrides all other match logic.
|
||||
pub fn add_override(&mut self, gi: Gitignore) -> &mut Ignore {
|
||||
self.overrides = Overrides::new(Some(gi));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a file type matcher. The file type matcher has the lowest
|
||||
/// precedence.
|
||||
pub fn add_types(&mut self, types: Types) -> &mut Ignore {
|
||||
self.types = types;
|
||||
self
|
||||
}
|
||||
|
||||
/// Push parent directories of `path` on to the stack.
|
||||
pub fn push_parents<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
) -> Result<(), Error> {
|
||||
let path = try!(path.as_ref().canonicalize().map_err(|err| {
|
||||
Error::from_io(path.as_ref(), err)
|
||||
}));
|
||||
let mut path = &*path;
|
||||
let mut saw_git = path.join(".git").is_dir();
|
||||
let mut ignore_names = IGNORE_NAMES.to_vec();
|
||||
let mut ignore_dir_results = vec![];
|
||||
while let Some(parent) = path.parent() {
|
||||
if self.no_ignore {
|
||||
ignore_dir_results.push(Ok(None));
|
||||
} else {
|
||||
if saw_git {
|
||||
ignore_names.retain(|&name| name != ".gitignore");
|
||||
} else {
|
||||
saw_git = parent.join(".git").is_dir();
|
||||
}
|
||||
let ignore_dir_result =
|
||||
IgnoreDir::with_ignore_names(parent, ignore_names.iter());
|
||||
ignore_dir_results.push(ignore_dir_result);
|
||||
}
|
||||
path = parent;
|
||||
}
|
||||
|
||||
for ignore_dir_result in ignore_dir_results.into_iter().rev() {
|
||||
try!(self.push_ignore_dir(ignore_dir_result));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a directory to the stack.
|
||||
///
|
||||
/// Note that even if this returns an error, the directory is added to the
|
||||
/// stack (and therefore should be popped).
|
||||
pub fn push<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
|
||||
if self.no_ignore {
|
||||
self.stack.push(None);
|
||||
return Ok(());
|
||||
}
|
||||
self.push_ignore_dir(IgnoreDir::new(path))
|
||||
}
|
||||
|
||||
/// Pushes the result of building a directory matcher on to the stack.
|
||||
///
|
||||
/// If the result given contains an error, then it is returned.
|
||||
pub fn push_ignore_dir(
|
||||
&mut self,
|
||||
result: Result<Option<IgnoreDir>, Error>,
|
||||
) -> Result<(), Error> {
|
||||
match result {
|
||||
Ok(id) => {
|
||||
self.stack.push(id);
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
// Don't leave the stack in an inconsistent state.
|
||||
self.stack.push(None);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pop a directory from the stack.
|
||||
///
|
||||
/// This panics if the stack is empty.
|
||||
pub fn pop(&mut self) {
|
||||
self.stack.pop().expect("non-empty stack");
|
||||
}
|
||||
|
||||
/// Returns true if and only if the given file path should be ignored.
|
||||
pub fn ignored<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> bool {
|
||||
let path = path.as_ref();
|
||||
let mat = self.overrides.matched(path, is_dir);
|
||||
if let Some(is_ignored) = self.ignore_match(path, mat) {
|
||||
return is_ignored;
|
||||
}
|
||||
if self.ignore_hidden && is_hidden(&path) {
|
||||
debug!("{} ignored because it is hidden", path.display());
|
||||
return true;
|
||||
}
|
||||
for id in self.stack.iter().rev().filter_map(|id| id.as_ref()) {
|
||||
let mat = id.matched(path, is_dir);
|
||||
if let Some(is_ignored) = self.ignore_match(path, mat) {
|
||||
if is_ignored {
|
||||
return true;
|
||||
}
|
||||
// If this path is whitelisted by an ignore, then fallthrough
|
||||
// and let the file type matcher have a say.
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mat = self.types.matched(path, is_dir);
|
||||
if let Some(is_ignored) = self.ignore_match(path, mat) {
|
||||
return is_ignored;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the given match says the given pattern should be
|
||||
/// ignored or false if the given pattern should be explicitly whitelisted.
|
||||
/// Returns None otherwise.
|
||||
pub fn ignore_match<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
mat: Match,
|
||||
) -> Option<bool> {
|
||||
let path = path.as_ref();
|
||||
match mat {
|
||||
Match::Whitelist(ref pat) => {
|
||||
debug!("{} whitelisted by {:?}", path.display(), pat);
|
||||
Some(false)
|
||||
}
|
||||
Match::Ignored(ref pat) => {
|
||||
debug!("{} ignored by {:?}", path.display(), pat);
|
||||
Some(true)
|
||||
}
|
||||
Match::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// IgnoreDir represents a set of ignore patterns retrieved from a single
|
||||
/// directory.
|
||||
pub struct IgnoreDir {
|
||||
/// The path to this directory as given.
|
||||
path: PathBuf,
|
||||
/// A single accumulation of glob patterns for this directory, matched
|
||||
/// using gitignore semantics.
|
||||
///
|
||||
/// This will include patterns from rgignore as well. The patterns are
|
||||
/// ordered so that precedence applies automatically (e.g., rgignore
|
||||
/// patterns procede gitignore patterns).
|
||||
gi: Option<Gitignore>,
|
||||
// TODO(burntsushi): Matching other types of glob patterns that don't
|
||||
// conform to gitignore will probably require refactoring this approach.
|
||||
}
|
||||
|
||||
impl IgnoreDir {
|
||||
/// Create a new matcher for the given directory.
|
||||
///
|
||||
/// If no ignore glob patterns could be found in the directory then `None`
|
||||
/// is returned.
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> Result<Option<IgnoreDir>, Error> {
|
||||
IgnoreDir::with_ignore_names(path, IGNORE_NAMES.iter())
|
||||
}
|
||||
|
||||
/// Create a new matcher for the given directory using only the ignore
|
||||
/// patterns found in the file names given.
|
||||
///
|
||||
/// If no ignore glob patterns could be found in the directory then `None`
|
||||
/// is returned.
|
||||
///
|
||||
/// Note that the order of the names given is meaningful. Names appearing
|
||||
/// later in the list have precedence over names appearing earlier in the
|
||||
/// list.
|
||||
pub fn with_ignore_names<P: AsRef<Path>, S, I>(
|
||||
path: P,
|
||||
names: I,
|
||||
) -> Result<Option<IgnoreDir>, Error>
|
||||
where P: AsRef<Path>, S: AsRef<str>, I: Iterator<Item=S> {
|
||||
let mut id = IgnoreDir {
|
||||
path: path.as_ref().to_path_buf(),
|
||||
gi: None,
|
||||
};
|
||||
let mut ok = false;
|
||||
let mut builder = GitignoreBuilder::new(&id.path);
|
||||
// The ordering here is important. Later globs have higher precedence.
|
||||
for name in names {
|
||||
ok = builder.add_path(id.path.join(name.as_ref())).is_ok() || ok;
|
||||
}
|
||||
if !ok {
|
||||
Ok(None)
|
||||
} else {
|
||||
id.gi = Some(try!(builder.build()));
|
||||
Ok(Some(id))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if and only if the given file path should be ignored
|
||||
/// according to the globs in this directory. `is_dir` should be true if
|
||||
/// the path refers to a directory and false otherwise.
|
||||
///
|
||||
/// Before matching path, its prefix (as determined by a common suffix
|
||||
/// of this directory) is stripped. If there is
|
||||
/// no common suffix/prefix overlap, then path is assumed to reside
|
||||
/// directly in this directory.
|
||||
///
|
||||
/// If the given path has a `./` prefix then it is stripped before
|
||||
/// matching.
|
||||
pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
|
||||
self.gi.as_ref()
|
||||
.map(|gi| gi.matched(path, is_dir))
|
||||
.unwrap_or(Match::None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages a set of overrides provided explicitly by the end user.
|
||||
struct Overrides {
|
||||
gi: Option<Gitignore>,
|
||||
unmatched_pat: Pattern,
|
||||
}
|
||||
|
||||
impl Overrides {
|
||||
/// Creates a new set of overrides from the gitignore matcher provided.
|
||||
/// If no matcher is provided, then the resulting overrides have no effect.
|
||||
fn new(gi: Option<Gitignore>) -> Overrides {
|
||||
Overrides {
|
||||
gi: gi,
|
||||
unmatched_pat: Pattern {
|
||||
from: Path::new("<argv>").to_path_buf(),
|
||||
original: "<none>".to_string(),
|
||||
pat: "<none>".to_string(),
|
||||
whitelist: false,
|
||||
only_dir: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a match for the given path against this set of overrides.
|
||||
///
|
||||
/// If there are no overrides, then this always returns Match::None.
|
||||
///
|
||||
/// If there is at least one positive override, then this never returns
|
||||
/// Match::None (and interpreting non-matches as ignored) unless is_dir
|
||||
/// is true.
|
||||
pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
|
||||
// File types don't apply to directories.
|
||||
if is_dir {
|
||||
return Match::None;
|
||||
}
|
||||
let path = path.as_ref();
|
||||
self.gi.as_ref()
|
||||
.map(|gi| {
|
||||
let path = &*path.to_string_lossy();
|
||||
let mat = gi.matched_utf8(path, is_dir).invert();
|
||||
if mat.is_none() && !is_dir {
|
||||
if gi.num_ignores() > 0 {
|
||||
return Match::Ignored(&self.unmatched_pat);
|
||||
}
|
||||
}
|
||||
mat
|
||||
})
|
||||
.unwrap_or(Match::None)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_hidden<P: AsRef<Path>>(path: P) -> bool {
|
||||
if let Some(name) = path.as_ref().file_name() {
|
||||
name.to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
use gitignore::GitignoreBuilder;
|
||||
use super::IgnoreDir;
|
||||
|
||||
macro_rules! ignored_dir {
|
||||
($name:ident, $root:expr, $gi:expr, $xi:expr, $path:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let mut builder = GitignoreBuilder::new(&$root);
|
||||
builder.add_str($gi).unwrap();
|
||||
builder.add_str($xi).unwrap();
|
||||
let gi = builder.build().unwrap();
|
||||
let id = IgnoreDir {
|
||||
path: Path::new($root).to_path_buf(),
|
||||
gi: Some(gi),
|
||||
};
|
||||
assert!(id.matched($path, false).is_ignored());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! not_ignored_dir {
|
||||
($name:ident, $root:expr, $gi:expr, $xi:expr, $path:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let mut builder = GitignoreBuilder::new(&$root);
|
||||
builder.add_str($gi).unwrap();
|
||||
builder.add_str($xi).unwrap();
|
||||
let gi = builder.build().unwrap();
|
||||
let id = IgnoreDir {
|
||||
path: Path::new($root).to_path_buf(),
|
||||
gi: Some(gi),
|
||||
};
|
||||
assert!(!id.matched($path, false).is_ignored());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const ROOT: &'static str = "/home/foobar/rust/rg";
|
||||
|
||||
ignored_dir!(id1, ROOT, "src/main.rs", "", "src/main.rs");
|
||||
ignored_dir!(id2, ROOT, "", "src/main.rs", "src/main.rs");
|
||||
ignored_dir!(id3, ROOT, "!src/main.rs", "*.rs", "src/main.rs");
|
||||
|
||||
not_ignored_dir!(idnot1, ROOT, "*.rs", "!src/main.rs", "src/main.rs");
|
||||
}
|
||||
250
src/main.rs
250
src/main.rs
@@ -1,9 +1,9 @@
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
extern crate crossbeam;
|
||||
extern crate ctrlc;
|
||||
extern crate deque;
|
||||
extern crate docopt;
|
||||
extern crate env_logger;
|
||||
extern crate grep;
|
||||
extern crate ignore;
|
||||
#[cfg(windows)]
|
||||
extern crate kernel32;
|
||||
#[macro_use]
|
||||
@@ -15,33 +15,36 @@ extern crate memchr;
|
||||
extern crate memmap;
|
||||
extern crate num_cpus;
|
||||
extern crate regex;
|
||||
extern crate regex_syntax as syntax;
|
||||
extern crate rustc_serialize;
|
||||
extern crate term;
|
||||
extern crate thread_local;
|
||||
extern crate walkdir;
|
||||
#[cfg(windows)]
|
||||
extern crate winapi;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::thread;
|
||||
use std::cmp;
|
||||
|
||||
use crossbeam::sync::chase_lev::{self, Steal, Stealer};
|
||||
use deque::{Stealer, Stolen};
|
||||
use grep::Grep;
|
||||
use memmap::{Mmap, Protection};
|
||||
use term::Terminal;
|
||||
use walkdir::DirEntry;
|
||||
use ignore::DirEntry;
|
||||
|
||||
use args::Args;
|
||||
use out::{NoColorTerminal, Out, OutBuffer};
|
||||
use out::{ColoredTerminal, Out};
|
||||
use pathutil::strip_prefix;
|
||||
use printer::Printer;
|
||||
use search_stream::InputBuffer;
|
||||
#[cfg(windows)]
|
||||
use terminal_win::WindowsBuffer;
|
||||
|
||||
macro_rules! errored {
|
||||
($($tt:tt)*) => {
|
||||
@@ -58,23 +61,20 @@ macro_rules! eprintln {
|
||||
|
||||
mod args;
|
||||
mod atty;
|
||||
mod gitignore;
|
||||
mod glob;
|
||||
mod ignore;
|
||||
mod out;
|
||||
mod pathutil;
|
||||
mod printer;
|
||||
mod search_buffer;
|
||||
mod search_stream;
|
||||
mod terminal;
|
||||
mod types;
|
||||
mod walk;
|
||||
#[cfg(windows)]
|
||||
mod terminal_win;
|
||||
|
||||
pub type Result<T> = result::Result<T, Box<Error + Send + Sync>>;
|
||||
|
||||
fn main() {
|
||||
match Args::parse().and_then(run) {
|
||||
Ok(count) if count == 0 => process::exit(1),
|
||||
Ok(count) => process::exit(0),
|
||||
Ok(_) => process::exit(0),
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
process::exit(1);
|
||||
@@ -83,41 +83,71 @@ fn main() {
|
||||
}
|
||||
|
||||
fn run(args: Args) -> Result<u64> {
|
||||
let args = Arc::new(args);
|
||||
|
||||
let handler_args = args.clone();
|
||||
ctrlc::set_handler(move || {
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
let _ = handler_args.stdout().reset();
|
||||
let _ = stdout.flush();
|
||||
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
let paths = args.paths();
|
||||
let threads = cmp::max(1, args.threads() - 1);
|
||||
let isone =
|
||||
paths.len() == 1 && (paths[0] == Path::new("-") || paths[0].is_file());
|
||||
if args.files() {
|
||||
return run_files(args);
|
||||
return run_files(args.clone());
|
||||
}
|
||||
if args.type_list() {
|
||||
return run_types(args);
|
||||
return run_types(args.clone());
|
||||
}
|
||||
if threads == 1 || isone {
|
||||
return run_one_thread(args.clone());
|
||||
}
|
||||
let args = Arc::new(args);
|
||||
let out = Arc::new(Mutex::new(args.out()));
|
||||
let outbuf = args.outbuf();
|
||||
let quiet_matched = QuietMatched::new(args.quiet());
|
||||
let mut workers = vec![];
|
||||
|
||||
let mut workq = {
|
||||
let (workq, stealer) = chase_lev::deque();
|
||||
for _ in 0..args.threads() {
|
||||
let worker = Worker {
|
||||
args: args.clone(),
|
||||
out: out.clone(),
|
||||
let workq = {
|
||||
let (workq, stealer) = deque::new();
|
||||
for _ in 0..threads {
|
||||
let worker = MultiWorker {
|
||||
chan_work: stealer.clone(),
|
||||
inpbuf: args.input_buffer(),
|
||||
outbuf: Some(outbuf.clone()),
|
||||
grep: args.grep(),
|
||||
match_count: 0,
|
||||
quiet_matched: quiet_matched.clone(),
|
||||
out: out.clone(),
|
||||
outbuf: Some(args.outbuf()),
|
||||
worker: Worker {
|
||||
args: args.clone(),
|
||||
inpbuf: args.input_buffer(),
|
||||
grep: args.grep(),
|
||||
match_count: 0,
|
||||
},
|
||||
};
|
||||
workers.push(thread::spawn(move || worker.run()));
|
||||
}
|
||||
workq
|
||||
};
|
||||
for p in args.paths() {
|
||||
if p == Path::new("-") {
|
||||
workq.push(Work::Stdin)
|
||||
} else {
|
||||
for ent in try!(args.walker(p)) {
|
||||
workq.push(Work::File(ent));
|
||||
}
|
||||
let mut paths_searched: u64 = 0;
|
||||
for dent in args.walker() {
|
||||
if quiet_matched.has_match() {
|
||||
break;
|
||||
}
|
||||
paths_searched += 1;
|
||||
if dent.is_stdin() {
|
||||
workq.push(Work::Stdin);
|
||||
} else {
|
||||
workq.push(Work::File(dent));
|
||||
}
|
||||
}
|
||||
if !paths.is_empty() && paths_searched == 0 {
|
||||
eprintln!("No files were searched, which means ripgrep probably \
|
||||
applied a filter you didn't expect. \
|
||||
Try running again with --debug.");
|
||||
}
|
||||
for _ in 0..workers.len() {
|
||||
workq.push(Work::Quit);
|
||||
@@ -129,26 +159,60 @@ fn run(args: Args) -> Result<u64> {
|
||||
Ok(match_count)
|
||||
}
|
||||
|
||||
fn run_files(args: Args) -> Result<u64> {
|
||||
let term = NoColorTerminal::new(io::BufWriter::new(io::stdout()));
|
||||
let mut printer = args.printer(term);
|
||||
let mut file_count = 0;
|
||||
for p in args.paths() {
|
||||
if p == Path::new("-") {
|
||||
printer.path(&Path::new("<stdin>"));
|
||||
file_count += 1;
|
||||
} else {
|
||||
for ent in try!(args.walker(p)) {
|
||||
printer.path(ent.path());
|
||||
file_count += 1;
|
||||
fn run_one_thread(args: Arc<Args>) -> Result<u64> {
|
||||
let mut worker = Worker {
|
||||
args: args.clone(),
|
||||
inpbuf: args.input_buffer(),
|
||||
grep: args.grep(),
|
||||
match_count: 0,
|
||||
};
|
||||
let mut term = args.stdout();
|
||||
let mut paths_searched: u64 = 0;
|
||||
for dent in args.walker() {
|
||||
let mut printer = args.printer(&mut term);
|
||||
if worker.match_count > 0 {
|
||||
if args.quiet() {
|
||||
break;
|
||||
}
|
||||
if let Some(sep) = args.file_separator() {
|
||||
printer = printer.file_separator(sep);
|
||||
}
|
||||
}
|
||||
paths_searched += 1;
|
||||
if dent.is_stdin() {
|
||||
worker.do_work(&mut printer, WorkReady::Stdin);
|
||||
} else {
|
||||
let file = match File::open(dent.path()) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
eprintln!("{}: {}", dent.path().display(), err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
worker.do_work(&mut printer, WorkReady::DirFile(dent, file));
|
||||
}
|
||||
}
|
||||
if !args.paths().is_empty() && paths_searched == 0 {
|
||||
eprintln!("No files were searched, which means ripgrep probably \
|
||||
applied a filter you didn't expect. \
|
||||
Try running again with --debug.");
|
||||
}
|
||||
Ok(worker.match_count)
|
||||
}
|
||||
|
||||
fn run_files(args: Arc<Args>) -> Result<u64> {
|
||||
let term = args.stdout();
|
||||
let mut printer = args.printer(term);
|
||||
let mut file_count = 0;
|
||||
for dent in args.walker() {
|
||||
printer.path(dent.path());
|
||||
file_count += 1;
|
||||
}
|
||||
Ok(file_count)
|
||||
}
|
||||
|
||||
fn run_types(args: Args) -> Result<u64> {
|
||||
let term = NoColorTerminal::new(io::BufWriter::new(io::stdout()));
|
||||
fn run_types(args: Arc<Args>) -> Result<u64> {
|
||||
let term = args.stdout();
|
||||
let mut printer = args.printer(term);
|
||||
let mut ty_count = 0;
|
||||
for def in args.type_defs() {
|
||||
@@ -166,30 +230,40 @@ enum Work {
|
||||
|
||||
enum WorkReady {
|
||||
Stdin,
|
||||
File(DirEntry, File),
|
||||
DirFile(DirEntry, File),
|
||||
}
|
||||
|
||||
struct MultiWorker {
|
||||
chan_work: Stealer<Work>,
|
||||
quiet_matched: QuietMatched,
|
||||
out: Arc<Mutex<Out>>,
|
||||
#[cfg(not(windows))]
|
||||
outbuf: Option<ColoredTerminal<term::TerminfoTerminal<Vec<u8>>>>,
|
||||
#[cfg(windows)]
|
||||
outbuf: Option<ColoredTerminal<WindowsBuffer>>,
|
||||
worker: Worker,
|
||||
}
|
||||
|
||||
struct Worker {
|
||||
args: Arc<Args>,
|
||||
out: Arc<Mutex<Out>>,
|
||||
chan_work: Stealer<Work>,
|
||||
inpbuf: InputBuffer,
|
||||
outbuf: Option<OutBuffer>,
|
||||
grep: Grep,
|
||||
match_count: u64,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
impl MultiWorker {
|
||||
fn run(mut self) -> u64 {
|
||||
self.match_count = 0;
|
||||
loop {
|
||||
if self.quiet_matched.has_match() {
|
||||
break;
|
||||
}
|
||||
let work = match self.chan_work.steal() {
|
||||
Steal::Empty | Steal::Abort => continue,
|
||||
Steal::Data(Work::Quit) => break,
|
||||
Steal::Data(Work::Stdin) => WorkReady::Stdin,
|
||||
Steal::Data(Work::File(ent)) => {
|
||||
Stolen::Empty | Stolen::Abort => continue,
|
||||
Stolen::Data(Work::Quit) => break,
|
||||
Stolen::Data(Work::Stdin) => WorkReady::Stdin,
|
||||
Stolen::Data(Work::File(ent)) => {
|
||||
match File::open(ent.path()) {
|
||||
Ok(file) => WorkReady::File(ent, file),
|
||||
Ok(file) => WorkReady::DirFile(ent, file),
|
||||
Err(err) => {
|
||||
eprintln!("{}: {}", ent.path().display(), err);
|
||||
continue;
|
||||
@@ -199,8 +273,11 @@ impl Worker {
|
||||
};
|
||||
let mut outbuf = self.outbuf.take().unwrap();
|
||||
outbuf.clear();
|
||||
let mut printer = self.args.printer(outbuf);
|
||||
self.do_work(&mut printer, work);
|
||||
let mut printer = self.worker.args.printer(outbuf);
|
||||
self.worker.do_work(&mut printer, work);
|
||||
if self.quiet_matched.set_match(self.worker.match_count > 0) {
|
||||
break;
|
||||
}
|
||||
let outbuf = printer.into_inner();
|
||||
if !outbuf.get_ref().is_empty() {
|
||||
let mut out = self.out.lock().unwrap();
|
||||
@@ -208,10 +285,12 @@ impl Worker {
|
||||
}
|
||||
self.outbuf = Some(outbuf);
|
||||
}
|
||||
self.match_count
|
||||
self.worker.match_count
|
||||
}
|
||||
}
|
||||
|
||||
fn do_work<W: Send + Terminal>(
|
||||
impl Worker {
|
||||
fn do_work<W: Terminal + Send>(
|
||||
&mut self,
|
||||
printer: &mut Printer<W>,
|
||||
work: WorkReady,
|
||||
@@ -222,9 +301,9 @@ impl Worker {
|
||||
let stdin = stdin.lock();
|
||||
self.search(printer, &Path::new("<stdin>"), stdin)
|
||||
}
|
||||
WorkReady::File(ent, file) => {
|
||||
WorkReady::DirFile(ent, file) => {
|
||||
let mut path = ent.path();
|
||||
if let Ok(p) = path.strip_prefix("./") {
|
||||
if let Some(p) = strip_prefix("./", path) {
|
||||
path = p;
|
||||
}
|
||||
if self.args.mmap() {
|
||||
@@ -244,7 +323,7 @@ impl Worker {
|
||||
}
|
||||
}
|
||||
|
||||
fn search<R: io::Read, W: Send + Terminal>(
|
||||
fn search<R: io::Read, W: Terminal + Send>(
|
||||
&mut self,
|
||||
printer: &mut Printer<W>,
|
||||
path: &Path,
|
||||
@@ -259,7 +338,7 @@ impl Worker {
|
||||
).run().map_err(From::from)
|
||||
}
|
||||
|
||||
fn search_mmap<W: Send + Terminal>(
|
||||
fn search_mmap<W: Terminal + Send>(
|
||||
&mut self,
|
||||
printer: &mut Printer<W>,
|
||||
path: &Path,
|
||||
@@ -267,7 +346,11 @@ impl Worker {
|
||||
) -> Result<u64> {
|
||||
if try!(file.metadata()).len() == 0 {
|
||||
// Opening a memory map with an empty file results in an error.
|
||||
return Ok(0);
|
||||
// However, this may not actually be an empty file! For example,
|
||||
// /proc/cpuinfo reports itself as an empty file, but it can
|
||||
// produce data when it's read from. Therefore, we fall back to
|
||||
// regular read calls.
|
||||
return self.search(printer, path, file);
|
||||
}
|
||||
let mmap = try!(Mmap::open(file, Protection::Read));
|
||||
Ok(self.args.searcher_buffer(
|
||||
@@ -278,3 +361,28 @@ impl Worker {
|
||||
).run())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct QuietMatched(Arc<Option<AtomicBool>>);
|
||||
|
||||
impl QuietMatched {
|
||||
fn new(quiet: bool) -> QuietMatched {
|
||||
let atomic = if quiet { Some(AtomicBool::new(false)) } else { None };
|
||||
QuietMatched(Arc::new(atomic))
|
||||
}
|
||||
|
||||
fn has_match(&self) -> bool {
|
||||
match *self.0 {
|
||||
None => false,
|
||||
Some(ref matched) => matched.load(Ordering::SeqCst),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_match(&self, yes: bool) -> bool {
|
||||
match *self.0 {
|
||||
None => false,
|
||||
Some(_) if !yes => false,
|
||||
Some(ref m) => { m.store(true, Ordering::SeqCst); true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
507
src/out.rs
507
src/out.rs
@@ -1,40 +1,13 @@
|
||||
use std::io::{self, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use term::{self, Terminal};
|
||||
use term::color::Color;
|
||||
#[cfg(not(windows))]
|
||||
use term::terminfo::TermInfo;
|
||||
#[cfg(windows)]
|
||||
use term::WinConsole;
|
||||
|
||||
use terminal::TerminfoTerminal;
|
||||
|
||||
pub type StdoutTerminal = Box<Terminal<Output=io::Stdout> + Send>;
|
||||
|
||||
/// Gets a terminal that supports color if available.
|
||||
#[cfg(windows)]
|
||||
fn term_stdout(color: bool) -> StdoutTerminal {
|
||||
let stdout = io::stdout();
|
||||
WinConsole::new(stdout)
|
||||
.ok()
|
||||
.map(|t| Box::new(t) as StdoutTerminal)
|
||||
.unwrap_or_else(|| {
|
||||
let stdout = io::stdout();
|
||||
Box::new(NoColorTerminal::new(stdout)) as StdoutTerminal
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets a terminal that supports color if available.
|
||||
#[cfg(not(windows))]
|
||||
fn term_stdout(color: bool) -> StdoutTerminal {
|
||||
let stdout = io::stdout();
|
||||
if !color || TERMINFO.is_none() {
|
||||
Box::new(NoColorTerminal::new(stdout))
|
||||
} else {
|
||||
let info = TERMINFO.clone().unwrap();
|
||||
Box::new(TerminfoTerminal::new_with_terminfo(stdout, info))
|
||||
}
|
||||
}
|
||||
use terminal_win::WindowsBuffer;
|
||||
|
||||
/// Out controls the actual output of all search results for a particular file
|
||||
/// to the end user.
|
||||
@@ -43,16 +16,31 @@ fn term_stdout(color: bool) -> StdoutTerminal {
|
||||
/// individual search results where as Out works with search results for each
|
||||
/// file as a whole. For example, it knows when to print a file separator.)
|
||||
pub struct Out {
|
||||
term: StdoutTerminal,
|
||||
#[cfg(not(windows))]
|
||||
term: ColoredTerminal<term::TerminfoTerminal<io::BufWriter<io::Stdout>>>,
|
||||
#[cfg(windows)]
|
||||
term: ColoredTerminal<WinConsole<io::Stdout>>,
|
||||
printed: bool,
|
||||
file_separator: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Out {
|
||||
/// Create a new Out that writes to the wtr given.
|
||||
#[cfg(not(windows))]
|
||||
pub fn new(color: bool) -> Out {
|
||||
let wtr = io::BufWriter::new(io::stdout());
|
||||
Out {
|
||||
term: ColoredTerminal::new(wtr, color),
|
||||
printed: false,
|
||||
file_separator: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new Out that writes to the wtr given.
|
||||
#[cfg(windows)]
|
||||
pub fn new(color: bool) -> Out {
|
||||
Out {
|
||||
term: term_stdout(color),
|
||||
term: ColoredTerminal::new_stdout(color),
|
||||
printed: false,
|
||||
file_separator: None,
|
||||
}
|
||||
@@ -60,8 +48,6 @@ impl Out {
|
||||
|
||||
/// If set, the separator is printed between matches from different files.
|
||||
/// By default, no separator is printed.
|
||||
///
|
||||
/// If sep is empty, then no file separator is printed.
|
||||
pub fn file_separator(mut self, sep: Vec<u8>) -> Out {
|
||||
self.file_separator = Some(sep);
|
||||
self
|
||||
@@ -69,154 +55,194 @@ impl Out {
|
||||
|
||||
/// Write the search results of a single file to the underlying wtr and
|
||||
/// flush wtr.
|
||||
pub fn write(&mut self, buf: &OutBuffer) {
|
||||
#[cfg(not(windows))]
|
||||
pub fn write(
|
||||
&mut self,
|
||||
buf: &ColoredTerminal<term::TerminfoTerminal<Vec<u8>>>,
|
||||
) {
|
||||
self.write_sep();
|
||||
match *buf {
|
||||
ColoredTerminal::Colored(ref tt) => {
|
||||
let _ = self.term.write_all(tt.get_ref());
|
||||
}
|
||||
ColoredTerminal::NoColor(ref buf) => {
|
||||
let _ = self.term.write_all(buf);
|
||||
}
|
||||
}
|
||||
self.write_done();
|
||||
}
|
||||
/// Write the search results of a single file to the underlying wtr and
|
||||
/// flush wtr.
|
||||
#[cfg(windows)]
|
||||
pub fn write(
|
||||
&mut self,
|
||||
buf: &ColoredTerminal<WindowsBuffer>,
|
||||
) {
|
||||
self.write_sep();
|
||||
match *buf {
|
||||
ColoredTerminal::Colored(ref tt) => {
|
||||
tt.print_stdout(&mut self.term);
|
||||
}
|
||||
ColoredTerminal::NoColor(ref buf) => {
|
||||
let _ = self.term.write_all(buf);
|
||||
}
|
||||
}
|
||||
self.write_done();
|
||||
}
|
||||
|
||||
fn write_sep(&mut self) {
|
||||
if let Some(ref sep) = self.file_separator {
|
||||
if self.printed {
|
||||
let _ = self.term.write_all(sep);
|
||||
let _ = self.term.write_all(b"\n");
|
||||
}
|
||||
}
|
||||
match *buf {
|
||||
OutBuffer::Colored(ref tt) => {
|
||||
let _ = self.term.write_all(tt.get_ref());
|
||||
}
|
||||
OutBuffer::Windows(ref w) => {
|
||||
w.print_stdout(&mut self.term);
|
||||
}
|
||||
OutBuffer::NoColor(ref buf) => {
|
||||
let _ = self.term.write_all(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_done(&mut self) {
|
||||
let _ = self.term.flush();
|
||||
self.printed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// OutBuffer corresponds to the final output buffer for search results. All
|
||||
/// search results are written to a buffer and then a buffer is flushed to
|
||||
/// stdout only after the full search has completed.
|
||||
/// ColoredTerminal provides optional colored output through the term::Terminal
|
||||
/// trait. In particular, it will dynamically configure itself to use coloring
|
||||
/// if it's available in the environment.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum OutBuffer {
|
||||
Colored(TerminfoTerminal<Vec<u8>>),
|
||||
Windows(WindowsBuffer),
|
||||
NoColor(Vec<u8>),
|
||||
pub enum ColoredTerminal<T: Terminal + Send> {
|
||||
Colored(T),
|
||||
NoColor(T::Output),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WindowsBuffer {
|
||||
buf: Vec<u8>,
|
||||
pos: usize,
|
||||
colors: Vec<WindowsColor>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WindowsColor {
|
||||
pos: usize,
|
||||
opt: WindowsOption,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum WindowsOption {
|
||||
Foreground(Color),
|
||||
Background(Color),
|
||||
Reset,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref TERMINFO: Option<Arc<TermInfo>> = {
|
||||
match TermInfo::from_env() {
|
||||
Ok(info) => Some(Arc::new(info)),
|
||||
Err(err) => {
|
||||
debug!("error loading terminfo for coloring: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl OutBuffer {
|
||||
#[cfg(not(windows))]
|
||||
impl<W: io::Write + Send> ColoredTerminal<term::TerminfoTerminal<W>> {
|
||||
/// Create a new output buffer.
|
||||
///
|
||||
/// When color is true, the buffer will attempt to support coloring.
|
||||
pub fn new(color: bool) -> OutBuffer {
|
||||
// If we want color, build a TerminfoTerminal and see if the current
|
||||
// environment supports coloring. If not, bail with NoColor. To avoid
|
||||
// losing our writer (ownership), do this the long way.
|
||||
pub fn new(wtr: W, color: bool) -> Self {
|
||||
lazy_static! {
|
||||
// Only pay for parsing the terminfo once.
|
||||
static ref TERMINFO: Option<TermInfo> = {
|
||||
match TermInfo::from_env() {
|
||||
Ok(info) => Some(info),
|
||||
Err(err) => {
|
||||
debug!("error loading terminfo for coloring: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// If we want color, build a term::TerminfoTerminal and see if the
|
||||
// current environment supports coloring. If not, bail with NoColor. To
|
||||
// avoid losing our writer (ownership), do this the long way.
|
||||
if !color {
|
||||
return OutBuffer::NoColor(vec![]);
|
||||
return ColoredTerminal::NoColor(wtr);
|
||||
}
|
||||
if cfg!(windows) {
|
||||
return OutBuffer::Windows(WindowsBuffer {
|
||||
buf: vec![],
|
||||
pos: 0,
|
||||
colors: vec![]
|
||||
});
|
||||
}
|
||||
if TERMINFO.is_none() {
|
||||
return OutBuffer::NoColor(vec![]);
|
||||
}
|
||||
let info = TERMINFO.clone().unwrap();
|
||||
let tt = TerminfoTerminal::new_with_terminfo(vec![], info);
|
||||
let terminfo = match *TERMINFO {
|
||||
None => return ColoredTerminal::NoColor(wtr),
|
||||
Some(ref ti) => {
|
||||
// Ug, this should go away with the next release of `term`.
|
||||
TermInfo {
|
||||
names: ti.names.clone(),
|
||||
bools: ti.bools.clone(),
|
||||
numbers: ti.numbers.clone(),
|
||||
strings: ti.strings.clone(),
|
||||
}
|
||||
}
|
||||
};
|
||||
let tt = term::TerminfoTerminal::new_with_terminfo(wtr, terminfo);
|
||||
if !tt.supports_color() {
|
||||
debug!("environment doesn't support coloring");
|
||||
return OutBuffer::NoColor(tt.into_inner());
|
||||
return ColoredTerminal::NoColor(tt.into_inner());
|
||||
}
|
||||
ColoredTerminal::Colored(tt)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
impl ColoredTerminal<term::TerminfoTerminal<Vec<u8>>> {
|
||||
/// Clear the give buffer of all search results such that it is reusable
|
||||
/// in another search.
|
||||
pub fn clear(&mut self) {
|
||||
match *self {
|
||||
ColoredTerminal::Colored(ref mut tt) => {
|
||||
tt.get_mut().clear();
|
||||
}
|
||||
ColoredTerminal::NoColor(ref mut buf) => {
|
||||
buf.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl ColoredTerminal<WindowsBuffer> {
|
||||
/// Create a new output buffer.
|
||||
///
|
||||
/// When color is true, the buffer will attempt to support coloring.
|
||||
pub fn new_buffer(color: bool) -> Self {
|
||||
if !color {
|
||||
ColoredTerminal::NoColor(vec![])
|
||||
} else {
|
||||
ColoredTerminal::Colored(WindowsBuffer::new())
|
||||
}
|
||||
OutBuffer::Colored(tt)
|
||||
}
|
||||
|
||||
/// Clear the give buffer of all search results such that it is reusable
|
||||
/// in another search.
|
||||
pub fn clear(&mut self) {
|
||||
match *self {
|
||||
OutBuffer::Colored(ref mut tt) => {
|
||||
tt.get_mut().clear();
|
||||
}
|
||||
OutBuffer::Windows(ref mut win) => {
|
||||
win.buf.clear();
|
||||
win.colors.clear();
|
||||
win.pos = 0;
|
||||
}
|
||||
OutBuffer::NoColor(ref mut buf) => {
|
||||
buf.clear();
|
||||
}
|
||||
ColoredTerminal::Colored(ref mut win) => win.clear(),
|
||||
ColoredTerminal::NoColor(ref mut buf) => buf.clear(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_result<F, G>(
|
||||
#[cfg(windows)]
|
||||
impl ColoredTerminal<WinConsole<io::Stdout>> {
|
||||
/// Create a new output buffer.
|
||||
///
|
||||
/// When color is true, the buffer will attempt to support coloring.
|
||||
pub fn new_stdout(color: bool) -> Self {
|
||||
if !color {
|
||||
return ColoredTerminal::NoColor(io::stdout());
|
||||
}
|
||||
match WinConsole::new(io::stdout()) {
|
||||
Ok(win) => ColoredTerminal::Colored(win),
|
||||
Err(_) => ColoredTerminal::NoColor(io::stdout()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Terminal + Send> ColoredTerminal<T> {
|
||||
fn map_result<F>(
|
||||
&mut self,
|
||||
mut f: F,
|
||||
mut g: G,
|
||||
) -> term::Result<()>
|
||||
where F: FnMut(&mut TerminfoTerminal<Vec<u8>>) -> term::Result<()>,
|
||||
G: FnMut(&mut WindowsBuffer) -> term::Result<()> {
|
||||
where F: FnMut(&mut T) -> term::Result<()> {
|
||||
match *self {
|
||||
OutBuffer::Colored(ref mut w) => f(w),
|
||||
OutBuffer::Windows(ref mut w) => g(w),
|
||||
OutBuffer::NoColor(_) => Err(term::Error::NotSupported),
|
||||
ColoredTerminal::Colored(ref mut w) => f(w),
|
||||
ColoredTerminal::NoColor(_) => Err(term::Error::NotSupported),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_bool<F, G>(
|
||||
fn map_bool<F>(
|
||||
&self,
|
||||
mut f: F,
|
||||
mut g: G,
|
||||
) -> bool
|
||||
where F: FnMut(&TerminfoTerminal<Vec<u8>>) -> bool,
|
||||
G: FnMut(&WindowsBuffer) -> bool {
|
||||
where F: FnMut(&T) -> bool {
|
||||
match *self {
|
||||
OutBuffer::Colored(ref w) => f(w),
|
||||
OutBuffer::Windows(ref w) => g(w),
|
||||
OutBuffer::NoColor(_) => false,
|
||||
ColoredTerminal::Colored(ref w) => f(w),
|
||||
ColoredTerminal::NoColor(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for OutBuffer {
|
||||
impl<T: Terminal + Send> io::Write for ColoredTerminal<T> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
match *self {
|
||||
OutBuffer::Colored(ref mut w) => w.write(buf),
|
||||
OutBuffer::Windows(ref mut w) => w.write(buf),
|
||||
OutBuffer::NoColor(ref mut w) => w.write(buf),
|
||||
ColoredTerminal::Colored(ref mut w) => w.write(buf),
|
||||
ColoredTerminal::NoColor(ref mut w) => w.write(buf),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,259 +251,124 @@ impl io::Write for OutBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
impl term::Terminal for OutBuffer {
|
||||
type Output = Vec<u8>;
|
||||
impl<T: Terminal + Send> term::Terminal for ColoredTerminal<T> {
|
||||
type Output = T::Output;
|
||||
|
||||
fn fg(&mut self, fg: term::color::Color) -> term::Result<()> {
|
||||
self.map_result(|w| w.fg(fg), |w| w.fg(fg))
|
||||
self.map_result(|w| w.fg(fg))
|
||||
}
|
||||
|
||||
fn bg(&mut self, bg: term::color::Color) -> term::Result<()> {
|
||||
self.map_result(|w| w.bg(bg), |w| w.bg(bg))
|
||||
self.map_result(|w| w.bg(bg))
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: term::Attr) -> term::Result<()> {
|
||||
self.map_result(|w| w.attr(attr), |w| w.attr(attr))
|
||||
self.map_result(|w| w.attr(attr))
|
||||
}
|
||||
|
||||
fn supports_attr(&self, attr: term::Attr) -> bool {
|
||||
self.map_bool(|w| w.supports_attr(attr), |w| w.supports_attr(attr))
|
||||
self.map_bool(|w| w.supports_attr(attr))
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> term::Result<()> {
|
||||
self.map_result(|w| w.reset(), |w| w.reset())
|
||||
self.map_result(|w| w.reset())
|
||||
}
|
||||
|
||||
fn supports_reset(&self) -> bool {
|
||||
self.map_bool(|w| w.supports_reset(), |w| w.supports_reset())
|
||||
self.map_bool(|w| w.supports_reset())
|
||||
}
|
||||
|
||||
fn supports_color(&self) -> bool {
|
||||
self.map_bool(|w| w.supports_color(), |w| w.supports_color())
|
||||
self.map_bool(|w| w.supports_color())
|
||||
}
|
||||
|
||||
fn cursor_up(&mut self) -> term::Result<()> {
|
||||
self.map_result(|w| w.cursor_up(), |w| w.cursor_up())
|
||||
self.map_result(|w| w.cursor_up())
|
||||
}
|
||||
|
||||
fn delete_line(&mut self) -> term::Result<()> {
|
||||
self.map_result(|w| w.delete_line(), |w| w.delete_line())
|
||||
self.map_result(|w| w.delete_line())
|
||||
}
|
||||
|
||||
fn carriage_return(&mut self) -> term::Result<()> {
|
||||
self.map_result(|w| w.carriage_return(), |w| w.carriage_return())
|
||||
self.map_result(|w| w.carriage_return())
|
||||
}
|
||||
|
||||
fn get_ref(&self) -> &Vec<u8> {
|
||||
fn get_ref(&self) -> &Self::Output {
|
||||
match *self {
|
||||
OutBuffer::Colored(ref w) => w.get_ref(),
|
||||
OutBuffer::Windows(ref w) => w.get_ref(),
|
||||
OutBuffer::NoColor(ref w) => w,
|
||||
ColoredTerminal::Colored(ref w) => w.get_ref(),
|
||||
ColoredTerminal::NoColor(ref w) => w,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> &mut Vec<u8> {
|
||||
fn get_mut(&mut self) -> &mut Self::Output {
|
||||
match *self {
|
||||
OutBuffer::Colored(ref mut w) => w.get_mut(),
|
||||
OutBuffer::Windows(ref mut w) => w.get_mut(),
|
||||
OutBuffer::NoColor(ref mut w) => w,
|
||||
ColoredTerminal::Colored(ref mut w) => w.get_mut(),
|
||||
ColoredTerminal::NoColor(ref mut w) => w,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_inner(self) -> Vec<u8> {
|
||||
fn into_inner(self) -> Self::Output {
|
||||
match self {
|
||||
OutBuffer::Colored(w) => w.into_inner(),
|
||||
OutBuffer::Windows(w) => w.into_inner(),
|
||||
OutBuffer::NoColor(w) => w,
|
||||
ColoredTerminal::Colored(w) => w.into_inner(),
|
||||
ColoredTerminal::NoColor(w) => w,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsBuffer {
|
||||
fn push(&mut self, opt: WindowsOption) {
|
||||
let pos = self.pos;
|
||||
self.colors.push(WindowsColor { pos: pos, opt: opt });
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsBuffer {
|
||||
/// Print the contents to the given terminal.
|
||||
pub fn print_stdout(&self, tt: &mut StdoutTerminal) {
|
||||
if !tt.supports_color() {
|
||||
let _ = tt.write_all(&self.buf);
|
||||
let _ = tt.flush();
|
||||
return;
|
||||
}
|
||||
let mut last = 0;
|
||||
for col in &self.colors {
|
||||
let _ = tt.write_all(&self.buf[last..col.pos]);
|
||||
match col.opt {
|
||||
WindowsOption::Foreground(c) => {
|
||||
let _ = tt.fg(c);
|
||||
}
|
||||
WindowsOption::Background(c) => {
|
||||
let _ = tt.bg(c);
|
||||
}
|
||||
WindowsOption::Reset => {
|
||||
let _ = tt.reset();
|
||||
}
|
||||
}
|
||||
last = col.pos;
|
||||
}
|
||||
let _ = tt.write_all(&self.buf[last..]);
|
||||
let _ = tt.flush();
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for WindowsBuffer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let n = try!(self.buf.write(buf));
|
||||
self.pos += n;
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl term::Terminal for WindowsBuffer {
|
||||
type Output = Vec<u8>;
|
||||
impl<'a, T: Terminal + Send> term::Terminal for &'a mut ColoredTerminal<T> {
|
||||
type Output = T::Output;
|
||||
|
||||
fn fg(&mut self, fg: term::color::Color) -> term::Result<()> {
|
||||
self.push(WindowsOption::Foreground(fg));
|
||||
Ok(())
|
||||
(**self).fg(fg)
|
||||
}
|
||||
|
||||
fn bg(&mut self, bg: term::color::Color) -> term::Result<()> {
|
||||
self.push(WindowsOption::Background(bg));
|
||||
Ok(())
|
||||
(**self).bg(bg)
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: term::Attr) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
(**self).attr(attr)
|
||||
}
|
||||
|
||||
fn supports_attr(&self, attr: term::Attr) -> bool {
|
||||
false
|
||||
(**self).supports_attr(attr)
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> term::Result<()> {
|
||||
self.push(WindowsOption::Reset);
|
||||
Ok(())
|
||||
(**self).reset()
|
||||
}
|
||||
|
||||
fn supports_reset(&self) -> bool {
|
||||
true
|
||||
(**self).supports_reset()
|
||||
}
|
||||
|
||||
fn supports_color(&self) -> bool {
|
||||
true
|
||||
(**self).supports_color()
|
||||
}
|
||||
|
||||
fn cursor_up(&mut self) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
(**self).cursor_up()
|
||||
}
|
||||
|
||||
fn delete_line(&mut self) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
(**self).delete_line()
|
||||
}
|
||||
|
||||
fn carriage_return(&mut self) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
(**self).carriage_return()
|
||||
}
|
||||
|
||||
fn get_ref(&self) -> &Vec<u8> {
|
||||
&self.buf
|
||||
fn get_ref(&self) -> &Self::Output {
|
||||
(**self).get_ref()
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> &mut Vec<u8> {
|
||||
&mut self.buf
|
||||
fn get_mut(&mut self) -> &mut Self::Output {
|
||||
(**self).get_mut()
|
||||
}
|
||||
|
||||
fn into_inner(self) -> Vec<u8> {
|
||||
self.buf
|
||||
}
|
||||
}
|
||||
|
||||
/// NoColorTerminal implements Terminal, but supports no coloring.
|
||||
///
|
||||
/// Its useful when an API requires a Terminal, but coloring isn't needed.
|
||||
pub struct NoColorTerminal<W> {
|
||||
wtr: W,
|
||||
}
|
||||
|
||||
impl<W: Send + io::Write> NoColorTerminal<W> {
|
||||
/// Wrap the given writer in a Terminal interface.
|
||||
pub fn new(wtr: W) -> NoColorTerminal<W> {
|
||||
NoColorTerminal {
|
||||
wtr: wtr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Send + io::Write> io::Write for NoColorTerminal<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.wtr.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.wtr.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Send + io::Write> term::Terminal for NoColorTerminal<W> {
|
||||
type Output = W;
|
||||
|
||||
fn fg(&mut self, fg: term::color::Color) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
}
|
||||
|
||||
fn bg(&mut self, bg: term::color::Color) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: term::Attr) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
}
|
||||
|
||||
fn supports_attr(&self, attr: term::Attr) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
}
|
||||
|
||||
fn supports_reset(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn supports_color(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn cursor_up(&mut self) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
}
|
||||
|
||||
fn delete_line(&mut self) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
}
|
||||
|
||||
fn carriage_return(&mut self) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
}
|
||||
|
||||
fn get_ref(&self) -> &W {
|
||||
&self.wtr
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> &mut W {
|
||||
&mut self.wtr
|
||||
}
|
||||
|
||||
fn into_inner(self) -> W {
|
||||
self.wtr
|
||||
fn into_inner(self) -> Self::Output {
|
||||
// Good golly miss molly...
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
42
src/pathutil.rs
Normal file
42
src/pathutil.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
/*!
|
||||
The pathutil module provides platform specific operations on paths that are
|
||||
typically faster than the same operations as provided in std::path. In
|
||||
particular, we really want to avoid the costly operation of parsing the path
|
||||
into its constituent components. We give up on Windows, but on Unix, we deal
|
||||
with the raw bytes directly.
|
||||
|
||||
On large repositories (like chromium), this can have a ~25% performance
|
||||
improvement on just listing the files to search (!).
|
||||
*/
|
||||
use std::path::Path;
|
||||
|
||||
/// Strip `prefix` from the `path` and return the remainder.
|
||||
///
|
||||
/// If `path` doesn't have a prefix `prefix`, then return `None`.
|
||||
#[cfg(unix)]
|
||||
pub fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
||||
prefix: &'a P,
|
||||
path: &'a Path,
|
||||
) -> Option<&'a Path> {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let prefix = prefix.as_ref().as_os_str().as_bytes();
|
||||
let path = path.as_os_str().as_bytes();
|
||||
if prefix.len() > path.len() || prefix != &path[0..prefix.len()] {
|
||||
None
|
||||
} else {
|
||||
Some(&Path::new(OsStr::from_bytes(&path[prefix.len()..])))
|
||||
}
|
||||
}
|
||||
|
||||
/// Strip `prefix` from the `path` and return the remainder.
|
||||
///
|
||||
/// If `path` doesn't have a prefix `prefix`, then return `None`.
|
||||
#[cfg(not(unix))]
|
||||
pub fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
|
||||
prefix: &'a P,
|
||||
path: &'a Path,
|
||||
) -> Option<&'a Path> {
|
||||
path.strip_prefix(prefix).ok()
|
||||
}
|
||||
195
src/printer.rs
195
src/printer.rs
@@ -4,7 +4,8 @@ use regex::bytes::Regex;
|
||||
use term::{Attr, Terminal};
|
||||
use term::color;
|
||||
|
||||
use types::FileTypeDef;
|
||||
use pathutil::strip_prefix;
|
||||
use ignore::types::FileTypeDef;
|
||||
|
||||
/// Printer encapsulates all output logic for searching.
|
||||
///
|
||||
@@ -24,19 +25,52 @@ pub struct Printer<W> {
|
||||
/// printed via the match directly, but occasionally we need to insert them
|
||||
/// ourselves (for example, to print a context separator).
|
||||
eol: u8,
|
||||
/// A file separator to show before any matches are printed.
|
||||
file_separator: Option<Vec<u8>>,
|
||||
/// Whether to show file name as a heading or not.
|
||||
///
|
||||
/// N.B. If with_filename is false, then this setting has no effect.
|
||||
heading: bool,
|
||||
/// Whether to suppress all output.
|
||||
quiet: bool,
|
||||
/// Whether to show every match on its own line.
|
||||
line_per_match: bool,
|
||||
/// Whether to print NUL bytes after a file path instead of new lines
|
||||
/// or `:`.
|
||||
null: bool,
|
||||
/// A string to use as a replacement of each match in a matching line.
|
||||
replace: Option<Vec<u8>>,
|
||||
/// Whether to prefix each match with the corresponding file name.
|
||||
with_filename: bool,
|
||||
/// The choice of colors.
|
||||
color_choice: ColorChoice
|
||||
}
|
||||
|
||||
impl<W: Send + Terminal> Printer<W> {
|
||||
struct ColorChoice {
|
||||
matched_line: color::Color,
|
||||
heading: color::Color,
|
||||
line_number: color::Color
|
||||
}
|
||||
|
||||
impl ColorChoice {
|
||||
#[cfg(unix)]
|
||||
pub fn new() -> ColorChoice {
|
||||
ColorChoice {
|
||||
matched_line: color::RED,
|
||||
heading: color::GREEN,
|
||||
line_number: color::BLUE
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub fn new() -> ColorChoice {
|
||||
ColorChoice {
|
||||
matched_line: color::BRIGHT_RED,
|
||||
heading: color::BRIGHT_GREEN,
|
||||
line_number: color::BRIGHT_BLUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Terminal + Send> Printer<W> {
|
||||
/// Create a new printer that writes to wtr.
|
||||
pub fn new(wtr: W) -> Printer<W> {
|
||||
Printer {
|
||||
@@ -45,10 +79,13 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
column: false,
|
||||
context_separator: "--".to_string().into_bytes(),
|
||||
eol: b'\n',
|
||||
file_separator: None,
|
||||
heading: false,
|
||||
quiet: false,
|
||||
line_per_match: false,
|
||||
null: false,
|
||||
replace: None,
|
||||
with_filename: false,
|
||||
color_choice: ColorChoice::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +108,13 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
self
|
||||
}
|
||||
|
||||
/// If set, the separator is printed before any matches. By default, no
|
||||
/// separator is printed.
|
||||
pub fn file_separator(mut self, sep: Vec<u8>) -> Printer<W> {
|
||||
self.file_separator = Some(sep);
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to show file name as a heading or not.
|
||||
///
|
||||
/// N.B. If with_filename is false, then this setting has no effect.
|
||||
@@ -79,9 +123,16 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
self
|
||||
}
|
||||
|
||||
/// When set, all output is suppressed.
|
||||
pub fn quiet(mut self, yes: bool) -> Printer<W> {
|
||||
self.quiet = yes;
|
||||
/// Whether to show every match on its own line.
|
||||
pub fn line_per_match(mut self, yes: bool) -> Printer<W> {
|
||||
self.line_per_match = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to cause NUL bytes to follow file paths instead of other
|
||||
/// visual separators (like `:`, `-` and `\n`).
|
||||
pub fn null(mut self, yes: bool) -> Printer<W> {
|
||||
self.null = yes;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -117,11 +168,11 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
self.write(def.name().as_bytes());
|
||||
self.write(b": ");
|
||||
let mut first = true;
|
||||
for pat in def.patterns() {
|
||||
for glob in def.globs() {
|
||||
if !first {
|
||||
self.write(b", ");
|
||||
}
|
||||
self.write(pat.as_bytes());
|
||||
self.write(glob.as_bytes());
|
||||
first = false;
|
||||
}
|
||||
self.write_eol();
|
||||
@@ -129,15 +180,24 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
|
||||
/// Prints the given path.
|
||||
pub fn path<P: AsRef<Path>>(&mut self, path: P) {
|
||||
self.write(path.as_ref().to_string_lossy().as_bytes());
|
||||
self.write_eol();
|
||||
let path = strip_prefix("./", path.as_ref()).unwrap_or(path.as_ref());
|
||||
self.write_path(path);
|
||||
if self.null {
|
||||
self.write(b"\x00");
|
||||
} else {
|
||||
self.write_eol();
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the given path and a count of the number of matches found.
|
||||
pub fn path_count<P: AsRef<Path>>(&mut self, path: P, count: u64) {
|
||||
if self.with_filename {
|
||||
self.write(path.as_ref().to_string_lossy().as_bytes());
|
||||
self.write(b":");
|
||||
self.write_path(path);
|
||||
if self.null {
|
||||
self.write(b"\x00");
|
||||
} else {
|
||||
self.write(b":");
|
||||
}
|
||||
}
|
||||
self.write(count.to_string().as_bytes());
|
||||
self.write_eol();
|
||||
@@ -146,9 +206,6 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
/// Prints the context separator.
|
||||
pub fn context_separate(&mut self) {
|
||||
// N.B. We can't use `write` here because of borrowing restrictions.
|
||||
if self.quiet {
|
||||
return;
|
||||
}
|
||||
if self.context_separator.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -165,19 +222,46 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
start: usize,
|
||||
end: usize,
|
||||
line_number: Option<u64>,
|
||||
) {
|
||||
if !self.line_per_match {
|
||||
let column =
|
||||
if self.column {
|
||||
Some(re.find(&buf[start..end])
|
||||
.map(|(s, _)| s).unwrap_or(0) as u64)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
return self.write_match(
|
||||
re, path, buf, start, end, line_number, column);
|
||||
}
|
||||
for (s, _) in re.find_iter(&buf[start..end]) {
|
||||
let column = if self.column { Some(s as u64) } else { None };
|
||||
self.write_match(
|
||||
re, path.as_ref(), buf, start, end, line_number, column);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_match<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
re: &Regex,
|
||||
path: P,
|
||||
buf: &[u8],
|
||||
start: usize,
|
||||
end: usize,
|
||||
line_number: Option<u64>,
|
||||
column: Option<u64>,
|
||||
) {
|
||||
if self.heading && self.with_filename && !self.has_printed {
|
||||
self.write_file_sep();
|
||||
self.write_heading(path.as_ref());
|
||||
} else if !self.heading && self.with_filename {
|
||||
self.write(path.as_ref().to_string_lossy().as_bytes());
|
||||
self.write(b":");
|
||||
self.write_non_heading_path(path.as_ref());
|
||||
}
|
||||
if let Some(line_number) = line_number {
|
||||
self.line_number(line_number, b':');
|
||||
}
|
||||
if self.column {
|
||||
let c = re.find(&buf[start..end]).map(|(s, _)| s + 1).unwrap_or(0);
|
||||
self.write(c.to_string().as_bytes());
|
||||
if let Some(c) = column {
|
||||
self.write((c + 1).to_string().as_bytes());
|
||||
self.write(b":");
|
||||
}
|
||||
if self.replace.is_some() {
|
||||
@@ -185,14 +269,14 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
&buf[start..end], &**self.replace.as_ref().unwrap());
|
||||
self.write(&line);
|
||||
} else {
|
||||
self.write_match(re, &buf[start..end]);
|
||||
self.write_matched_line(re, &buf[start..end]);
|
||||
}
|
||||
if buf[start..end].last() != Some(&self.eol) {
|
||||
self.write_eol();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_match(&mut self, re: &Regex, buf: &[u8]) {
|
||||
fn write_matched_line(&mut self, re: &Regex, buf: &[u8]) {
|
||||
if !self.wtr.supports_color() {
|
||||
self.write(buf);
|
||||
return;
|
||||
@@ -200,7 +284,7 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
let mut last_written = 0;
|
||||
for (s, e) in re.find_iter(buf) {
|
||||
self.write(&buf[last_written..s]);
|
||||
let _ = self.wtr.fg(color::BRIGHT_RED);
|
||||
let _ = self.wtr.fg(self.color_choice.matched_line);
|
||||
let _ = self.wtr.attr(Attr::Bold);
|
||||
self.write(&buf[s..e]);
|
||||
let _ = self.wtr.reset();
|
||||
@@ -218,10 +302,15 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
line_number: Option<u64>,
|
||||
) {
|
||||
if self.heading && self.with_filename && !self.has_printed {
|
||||
self.write_file_sep();
|
||||
self.write_heading(path.as_ref());
|
||||
} else if !self.heading && self.with_filename {
|
||||
self.write(path.as_ref().to_string_lossy().as_bytes());
|
||||
self.write(b"-");
|
||||
self.write_path(path.as_ref());
|
||||
if self.null {
|
||||
self.write(b"\x00");
|
||||
} else {
|
||||
self.write(b"-");
|
||||
}
|
||||
}
|
||||
if let Some(line_number) = line_number {
|
||||
self.line_number(line_number, b'-');
|
||||
@@ -234,19 +323,39 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
|
||||
fn write_heading<P: AsRef<Path>>(&mut self, path: P) {
|
||||
if self.wtr.supports_color() {
|
||||
let _ = self.wtr.fg(color::BRIGHT_GREEN);
|
||||
let _ = self.wtr.fg(self.color_choice.heading);
|
||||
let _ = self.wtr.attr(Attr::Bold);
|
||||
}
|
||||
self.write(path.as_ref().to_string_lossy().as_bytes());
|
||||
self.write_eol();
|
||||
self.write_path(path.as_ref());
|
||||
if self.null {
|
||||
self.write(b"\x00");
|
||||
} else {
|
||||
self.write_eol();
|
||||
}
|
||||
if self.wtr.supports_color() {
|
||||
let _ = self.wtr.reset();
|
||||
}
|
||||
}
|
||||
|
||||
fn write_non_heading_path<P: AsRef<Path>>(&mut self, path: P) {
|
||||
if self.wtr.supports_color() {
|
||||
let _ = self.wtr.fg(self.color_choice.heading);
|
||||
let _ = self.wtr.attr(Attr::Bold);
|
||||
}
|
||||
self.write_path(path.as_ref());
|
||||
if self.wtr.supports_color() {
|
||||
let _ = self.wtr.reset();
|
||||
}
|
||||
if self.null {
|
||||
self.write(b"\x00");
|
||||
} else {
|
||||
self.write(b":");
|
||||
}
|
||||
}
|
||||
|
||||
fn line_number(&mut self, n: u64, sep: u8) {
|
||||
if self.wtr.supports_color() {
|
||||
let _ = self.wtr.fg(color::BRIGHT_BLUE);
|
||||
let _ = self.wtr.fg(self.color_choice.line_number);
|
||||
let _ = self.wtr.attr(Attr::Bold);
|
||||
}
|
||||
self.write(n.to_string().as_bytes());
|
||||
@@ -256,10 +365,20 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
self.write(&[sep]);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn write_path<P: AsRef<Path>>(&mut self, path: P) {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let path = path.as_ref().as_os_str().as_bytes();
|
||||
self.write(path);
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn write_path<P: AsRef<Path>>(&mut self, path: P) {
|
||||
self.write(path.as_ref().to_string_lossy().as_bytes());
|
||||
}
|
||||
|
||||
fn write(&mut self, buf: &[u8]) {
|
||||
if self.quiet {
|
||||
return;
|
||||
}
|
||||
self.has_printed = true;
|
||||
let _ = self.wtr.write_all(buf);
|
||||
}
|
||||
@@ -268,4 +387,12 @@ impl<W: Send + Terminal> Printer<W> {
|
||||
let eol = self.eol;
|
||||
self.write(&[eol]);
|
||||
}
|
||||
|
||||
fn write_file_sep(&mut self) {
|
||||
if let Some(ref sep) = self.file_separator {
|
||||
self.has_printed = true;
|
||||
let _ = self.wtr.write_all(sep);
|
||||
let _ = self.wtr.write_all(b"\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,14 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
|
||||
self
|
||||
}
|
||||
|
||||
/// If enabled, searching will print the path instead of each match.
|
||||
///
|
||||
/// Disabled by default.
|
||||
pub fn files_with_matches(mut self, yes: bool) -> Self {
|
||||
self.opts.files_with_matches = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the end-of-line byte used by this searcher.
|
||||
pub fn eol(mut self, eol: u8) -> Self {
|
||||
self.opts.eol = eol;
|
||||
@@ -73,6 +81,13 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
|
||||
self
|
||||
}
|
||||
|
||||
/// If enabled, don't show any output and quit searching after the first
|
||||
/// match is found.
|
||||
pub fn quiet(mut self, yes: bool) -> Self {
|
||||
self.opts.quiet = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// If enabled, search binary files as if they were text.
|
||||
pub fn text(mut self, yes: bool) -> Self {
|
||||
self.opts.text = yes;
|
||||
@@ -96,6 +111,9 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
|
||||
self.print_match(m.start(), m.end());
|
||||
}
|
||||
last_end = m.end();
|
||||
if self.opts.stop_after_first_match() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if self.opts.invert_match {
|
||||
let upto = self.buf.len();
|
||||
@@ -104,13 +122,16 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
|
||||
if self.opts.count && self.match_count > 0 {
|
||||
self.printer.path_count(self.path, self.match_count);
|
||||
}
|
||||
if self.opts.files_with_matches && self.match_count > 0 {
|
||||
self.printer.path(self.path);
|
||||
}
|
||||
self.match_count
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn print_match(&mut self, start: usize, end: usize) {
|
||||
self.match_count += 1;
|
||||
if self.opts.count {
|
||||
if self.opts.skip_matches() {
|
||||
return;
|
||||
}
|
||||
self.count_lines(start);
|
||||
@@ -151,10 +172,10 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use grep::{Grep, GrepBuilder};
|
||||
use term::Terminal;
|
||||
use grep::GrepBuilder;
|
||||
use term::{Terminal, TerminfoTerminal};
|
||||
|
||||
use out::OutBuffer;
|
||||
use out::ColoredTerminal;
|
||||
use printer::Printer;
|
||||
|
||||
use super::BufferSearcher;
|
||||
@@ -168,38 +189,19 @@ but Doctor Watson has to have it taken out for him and dusted,
|
||||
and exhibited clearly, with a label attached.\
|
||||
";
|
||||
|
||||
const CODE: &'static str = "\
|
||||
extern crate snap;
|
||||
|
||||
use std::io;
|
||||
|
||||
fn main() {
|
||||
let stdin = io::stdin();
|
||||
let stdout = io::stdout();
|
||||
|
||||
// Wrap the stdin reader in a Snappy reader.
|
||||
let mut rdr = snap::Reader::new(stdin.lock());
|
||||
let mut wtr = stdout.lock();
|
||||
io::copy(&mut rdr, &mut wtr).expect(\"I/O operation failed\");
|
||||
}
|
||||
";
|
||||
|
||||
fn matcher(pat: &str) -> Grep {
|
||||
GrepBuilder::new(pat).build().unwrap()
|
||||
}
|
||||
|
||||
fn test_path() -> &'static Path {
|
||||
&Path::new("/baz.rs")
|
||||
}
|
||||
|
||||
type TestSearcher<'a> = BufferSearcher<'a, OutBuffer>;
|
||||
type TestSearcher<'a> =
|
||||
BufferSearcher<'a, ColoredTerminal<TerminfoTerminal<Vec<u8>>>>;
|
||||
|
||||
fn search<F: FnMut(TestSearcher) -> TestSearcher>(
|
||||
pat: &str,
|
||||
haystack: &str,
|
||||
mut map: F,
|
||||
) -> (u64, String) {
|
||||
let outbuf = OutBuffer::NoColor(vec![]);
|
||||
let outbuf = ColoredTerminal::NoColor(vec![]);
|
||||
let mut pp = Printer::new(outbuf).with_filename(true);
|
||||
let grep = GrepBuilder::new(pat).build().unwrap();
|
||||
let count = {
|
||||
@@ -256,6 +258,14 @@ fn main() {
|
||||
assert_eq!(out, "/baz.rs:2\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn files_with_matches() {
|
||||
let (count, out) = search(
|
||||
"Sherlock", SHERLOCK, |s| s.files_with_matches(true));
|
||||
assert_eq!(1, count);
|
||||
assert_eq!(out, "/baz.rs\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_match() {
|
||||
let (count, out) = search(
|
||||
|
||||
@@ -80,9 +80,11 @@ pub struct Options {
|
||||
pub after_context: usize,
|
||||
pub before_context: usize,
|
||||
pub count: bool,
|
||||
pub files_with_matches: bool,
|
||||
pub eol: u8,
|
||||
pub invert_match: bool,
|
||||
pub line_number: bool,
|
||||
pub quiet: bool,
|
||||
pub text: bool,
|
||||
}
|
||||
|
||||
@@ -92,15 +94,32 @@ impl Default for Options {
|
||||
after_context: 0,
|
||||
before_context: 0,
|
||||
count: false,
|
||||
files_with_matches: false,
|
||||
eol: b'\n',
|
||||
invert_match: false,
|
||||
line_number: false,
|
||||
quiet: false,
|
||||
text: false,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a, R: io::Read, W: Send + Terminal> Searcher<'a, R, W> {
|
||||
impl Options {
|
||||
/// Several options (--quiet, --count, --files-with-matches) imply that
|
||||
/// we shouldn't ever display matches.
|
||||
pub fn skip_matches(&self) -> bool {
|
||||
self.count || self.files_with_matches || self.quiet
|
||||
}
|
||||
|
||||
/// Some options (--quiet, --files-with-matches) imply that we can stop
|
||||
/// searching after the first match.
|
||||
pub fn stop_after_first_match(&self) -> bool {
|
||||
self.files_with_matches || self.quiet
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
|
||||
/// Create a new searcher.
|
||||
///
|
||||
/// `inp` is a reusable input buffer that is used as scratch space by this
|
||||
@@ -158,6 +177,14 @@ impl<'a, R: io::Read, W: Send + Terminal> Searcher<'a, R, W> {
|
||||
self
|
||||
}
|
||||
|
||||
/// If enabled, searching will print the path instead of each match.
|
||||
///
|
||||
/// Disabled by default.
|
||||
pub fn files_with_matches(mut self, yes: bool) -> Self {
|
||||
self.opts.files_with_matches = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the end-of-line byte used by this searcher.
|
||||
pub fn eol(mut self, eol: u8) -> Self {
|
||||
self.opts.eol = eol;
|
||||
@@ -178,6 +205,13 @@ impl<'a, R: io::Read, W: Send + Terminal> Searcher<'a, R, W> {
|
||||
self
|
||||
}
|
||||
|
||||
/// If enabled, don't show any output and quit searching after the first
|
||||
/// match is found.
|
||||
pub fn quiet(mut self, yes: bool) -> Self {
|
||||
self.opts.quiet = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// If enabled, search binary files as if they were text.
|
||||
pub fn text(mut self, yes: bool) -> Self {
|
||||
self.opts.text = yes;
|
||||
@@ -193,7 +227,7 @@ impl<'a, R: io::Read, W: Send + Terminal> Searcher<'a, R, W> {
|
||||
self.line_count = if self.opts.line_number { Some(0) } else { None };
|
||||
self.last_match = Match::default();
|
||||
self.after_context_remaining = 0;
|
||||
loop {
|
||||
while !self.terminate() {
|
||||
let upto = self.inp.lastnl;
|
||||
self.print_after_context(upto);
|
||||
if !try!(self.fill()) {
|
||||
@@ -202,7 +236,7 @@ impl<'a, R: io::Read, W: Send + Terminal> Searcher<'a, R, W> {
|
||||
if !self.opts.text && self.inp.is_binary {
|
||||
break;
|
||||
}
|
||||
while self.inp.pos < self.inp.lastnl {
|
||||
while !self.terminate() && self.inp.pos < self.inp.lastnl {
|
||||
let matched = self.grep.read_match(
|
||||
&mut self.last_match,
|
||||
&mut self.inp.buf[..self.inp.lastnl],
|
||||
@@ -234,12 +268,21 @@ impl<'a, R: io::Read, W: Send + Terminal> Searcher<'a, R, W> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.opts.count && self.match_count > 0 {
|
||||
self.printer.path_count(self.path, self.match_count);
|
||||
if self.match_count > 0 {
|
||||
if self.opts.count {
|
||||
self.printer.path_count(self.path, self.match_count);
|
||||
} else if self.opts.files_with_matches {
|
||||
self.printer.path(self.path);
|
||||
}
|
||||
}
|
||||
Ok(self.match_count)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn terminate(&self) -> bool {
|
||||
self.match_count > 0 && self.opts.stop_after_first_match()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn fill(&mut self) -> Result<bool, Error> {
|
||||
let mut keep = self.inp.lastnl;
|
||||
@@ -281,7 +324,7 @@ impl<'a, R: io::Read, W: Send + Terminal> Searcher<'a, R, W> {
|
||||
|
||||
#[inline(always)]
|
||||
fn print_before_context(&mut self, upto: usize) {
|
||||
if self.opts.count || self.opts.before_context == 0 {
|
||||
if self.opts.skip_matches() || self.opts.before_context == 0 {
|
||||
return;
|
||||
}
|
||||
let start = self.last_printed;
|
||||
@@ -304,7 +347,7 @@ impl<'a, R: io::Read, W: Send + Terminal> Searcher<'a, R, W> {
|
||||
|
||||
#[inline(always)]
|
||||
fn print_after_context(&mut self, upto: usize) {
|
||||
if self.opts.count || self.after_context_remaining == 0 {
|
||||
if self.opts.skip_matches() || self.after_context_remaining == 0 {
|
||||
return;
|
||||
}
|
||||
let start = self.last_printed;
|
||||
@@ -322,7 +365,7 @@ impl<'a, R: io::Read, W: Send + Terminal> Searcher<'a, R, W> {
|
||||
#[inline(always)]
|
||||
fn print_match(&mut self, start: usize, end: usize) {
|
||||
self.match_count += 1;
|
||||
if self.opts.count {
|
||||
if self.opts.skip_matches() {
|
||||
return;
|
||||
}
|
||||
self.print_separator(start);
|
||||
@@ -503,10 +546,6 @@ impl InputBuffer {
|
||||
if self.first && is_binary(&self.buf[self.end..self.end + n]) {
|
||||
self.is_binary = true;
|
||||
}
|
||||
if self.is_binary {
|
||||
replace_buf(
|
||||
&mut self.buf[self.end..self.end + n], b'\x00', self.eol);
|
||||
}
|
||||
self.first = false;
|
||||
// We assume that reading 0 bytes means we've hit EOF.
|
||||
if n == 0 {
|
||||
@@ -629,6 +668,7 @@ pub fn count_lines(buf: &[u8], eol: u8) -> u64 {
|
||||
}
|
||||
|
||||
/// Replaces a with b in buf.
|
||||
#[allow(dead_code)]
|
||||
fn replace_buf(buf: &mut [u8], a: u8, b: u8) {
|
||||
if a == b {
|
||||
return;
|
||||
@@ -763,10 +803,10 @@ mod tests {
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use grep::{Grep, GrepBuilder};
|
||||
use term::Terminal;
|
||||
use grep::GrepBuilder;
|
||||
use term::{Terminal, TerminfoTerminal};
|
||||
|
||||
use out::OutBuffer;
|
||||
use out::ColoredTerminal;
|
||||
use printer::Printer;
|
||||
|
||||
use super::{InputBuffer, Searcher, start_of_previous_lines};
|
||||
@@ -800,15 +840,15 @@ fn main() {
|
||||
io::Cursor::new(s.to_string().into_bytes())
|
||||
}
|
||||
|
||||
fn matcher(pat: &str) -> Grep {
|
||||
GrepBuilder::new(pat).build().unwrap()
|
||||
}
|
||||
|
||||
fn test_path() -> &'static Path {
|
||||
&Path::new("/baz.rs")
|
||||
}
|
||||
|
||||
type TestSearcher<'a> = Searcher<'a, io::Cursor<Vec<u8>>, OutBuffer>;
|
||||
type TestSearcher<'a> = Searcher<
|
||||
'a,
|
||||
io::Cursor<Vec<u8>>,
|
||||
ColoredTerminal<TerminfoTerminal<Vec<u8>>>,
|
||||
>;
|
||||
|
||||
fn search_smallcap<F: FnMut(TestSearcher) -> TestSearcher>(
|
||||
pat: &str,
|
||||
@@ -816,7 +856,7 @@ fn main() {
|
||||
mut map: F,
|
||||
) -> (u64, String) {
|
||||
let mut inp = InputBuffer::with_capacity(1);
|
||||
let outbuf = OutBuffer::NoColor(vec![]);
|
||||
let outbuf = ColoredTerminal::NoColor(vec![]);
|
||||
let mut pp = Printer::new(outbuf).with_filename(true);
|
||||
let grep = GrepBuilder::new(pat).build().unwrap();
|
||||
let count = {
|
||||
@@ -833,7 +873,7 @@ fn main() {
|
||||
mut map: F,
|
||||
) -> (u64, String) {
|
||||
let mut inp = InputBuffer::with_capacity(4096);
|
||||
let outbuf = OutBuffer::NoColor(vec![]);
|
||||
let outbuf = ColoredTerminal::NoColor(vec![]);
|
||||
let mut pp = Printer::new(outbuf).with_filename(true);
|
||||
let grep = GrepBuilder::new(pat).build().unwrap();
|
||||
let count = {
|
||||
@@ -970,7 +1010,7 @@ fn main() {
|
||||
let text = "Sherlock\n\x00Holmes\n";
|
||||
let (count, out) = search("Sherlock|Holmes", text, |s| s.text(true));
|
||||
assert_eq!(2, count);
|
||||
assert_eq!(out, "/baz.rs:Sherlock\n/baz.rs:Holmes\n");
|
||||
assert_eq!(out, "/baz.rs:Sherlock\n/baz.rs:\x00Holmes\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -992,6 +1032,14 @@ fn main() {
|
||||
assert_eq!(out, "/baz.rs:2\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn files_with_matches() {
|
||||
let (count, out) = search_smallcap(
|
||||
"Sherlock", SHERLOCK, |s| s.files_with_matches(true));
|
||||
assert_eq!(1, count);
|
||||
assert_eq!(out, "/baz.rs\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_match() {
|
||||
let (count, out) = search_smallcap(
|
||||
|
||||
202
src/terminal.rs
202
src/terminal.rs
@@ -1,202 +0,0 @@
|
||||
/*!
|
||||
This module contains an implementation of the `term::Terminal` trait.
|
||||
|
||||
The actual implementation is copied almost verbatim from the `term` crate, so
|
||||
this code is under the same license (MIT/Apache).
|
||||
|
||||
The specific reason why this is copied here is to wrap an Arc<TermInfo> instead
|
||||
of a TermInfo. This makes multithreaded sharing much more performant.
|
||||
|
||||
N.B. This is temporary until this issue is fixed:
|
||||
https://github.com/Stebalien/term/issues/64
|
||||
*/
|
||||
|
||||
use std::io::{self, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use term::{Attr, Error, Result, Terminal, color};
|
||||
use term::terminfo::TermInfo;
|
||||
use term::terminfo::parm::{Param, Variables, expand};
|
||||
|
||||
/// A Terminal that knows how many colors it supports, with a reference to its
|
||||
/// parsed Terminfo database record.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TerminfoTerminal<T> {
|
||||
num_colors: u16,
|
||||
out: T,
|
||||
ti: Arc<TermInfo>,
|
||||
}
|
||||
|
||||
impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
|
||||
type Output = T;
|
||||
fn fg(&mut self, color: color::Color) -> Result<()> {
|
||||
let color = self.dim_if_necessary(color);
|
||||
if self.num_colors > color {
|
||||
return apply_cap(&self.ti, "setaf", &[Param::Number(color as i32)], &mut self.out);
|
||||
}
|
||||
Err(Error::ColorOutOfRange)
|
||||
}
|
||||
|
||||
fn bg(&mut self, color: color::Color) -> Result<()> {
|
||||
let color = self.dim_if_necessary(color);
|
||||
if self.num_colors > color {
|
||||
return apply_cap(&self.ti, "setab", &[Param::Number(color as i32)], &mut self.out);
|
||||
}
|
||||
Err(Error::ColorOutOfRange)
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: Attr) -> Result<()> {
|
||||
match attr {
|
||||
Attr::ForegroundColor(c) => self.fg(c),
|
||||
Attr::BackgroundColor(c) => self.bg(c),
|
||||
_ => apply_cap(&self.ti, cap_for_attr(attr), &[], &mut self.out),
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_attr(&self, attr: Attr) -> bool {
|
||||
match attr {
|
||||
Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0,
|
||||
_ => {
|
||||
let cap = cap_for_attr(attr);
|
||||
self.ti.strings.get(cap).is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<()> {
|
||||
reset(&self.ti, &mut self.out)
|
||||
}
|
||||
|
||||
fn supports_reset(&self) -> bool {
|
||||
["sgr0", "sgr", "op"].iter().any(|&cap| self.ti.strings.get(cap).is_some())
|
||||
}
|
||||
|
||||
fn supports_color(&self) -> bool {
|
||||
self.num_colors > 0 && self.supports_reset()
|
||||
}
|
||||
|
||||
fn cursor_up(&mut self) -> Result<()> {
|
||||
apply_cap(&self.ti, "cuu1", &[], &mut self.out)
|
||||
}
|
||||
|
||||
fn delete_line(&mut self) -> Result<()> {
|
||||
apply_cap(&self.ti, "dl", &[], &mut self.out)
|
||||
}
|
||||
|
||||
fn carriage_return(&mut self) -> Result<()> {
|
||||
apply_cap(&self.ti, "cr", &[], &mut self.out)
|
||||
}
|
||||
|
||||
fn get_ref(&self) -> &T {
|
||||
&self.out
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.out
|
||||
}
|
||||
|
||||
fn into_inner(self) -> T
|
||||
where Self: Sized
|
||||
{
|
||||
self.out
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write + Send> TerminfoTerminal<T> {
|
||||
/// Create a new TerminfoTerminal with the given TermInfo and Write.
|
||||
pub fn new_with_terminfo(out: T, terminfo: Arc<TermInfo>) -> TerminfoTerminal<T> {
|
||||
let nc = if terminfo.strings.contains_key("setaf") &&
|
||||
terminfo.strings.contains_key("setab") {
|
||||
terminfo.numbers.get("colors").map_or(0, |&n| n)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
TerminfoTerminal {
|
||||
out: out,
|
||||
ti: terminfo,
|
||||
num_colors: nc,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new TerminfoTerminal for the current environment with the given Write.
|
||||
///
|
||||
/// Returns `None` when the terminfo cannot be found or parsed.
|
||||
pub fn new(out: T) -> Option<TerminfoTerminal<T>> {
|
||||
TermInfo::from_env().map(move |ti| {
|
||||
TerminfoTerminal::new_with_terminfo(out, Arc::new(ti))
|
||||
}).ok()
|
||||
}
|
||||
|
||||
fn dim_if_necessary(&self, color: color::Color) -> color::Color {
|
||||
if color >= self.num_colors && color >= 8 && color < 16 {
|
||||
color - 8
|
||||
} else {
|
||||
color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Write> Write for TerminfoTerminal<T> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.out.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.out.flush()
|
||||
}
|
||||
}
|
||||
|
||||
fn cap_for_attr(attr: Attr) -> &'static str {
|
||||
match attr {
|
||||
Attr::Bold => "bold",
|
||||
Attr::Dim => "dim",
|
||||
Attr::Italic(true) => "sitm",
|
||||
Attr::Italic(false) => "ritm",
|
||||
Attr::Underline(true) => "smul",
|
||||
Attr::Underline(false) => "rmul",
|
||||
Attr::Blink => "blink",
|
||||
Attr::Standout(true) => "smso",
|
||||
Attr::Standout(false) => "rmso",
|
||||
Attr::Reverse => "rev",
|
||||
Attr::Secure => "invis",
|
||||
Attr::ForegroundColor(_) => "setaf",
|
||||
Attr::BackgroundColor(_) => "setab",
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_cap(ti: &TermInfo, cmd: &str, params: &[Param], out: &mut io::Write) -> Result<()> {
|
||||
match ti.strings.get(cmd) {
|
||||
Some(cmd) => {
|
||||
match expand(cmd, params, &mut Variables::new()) {
|
||||
Ok(s) => {
|
||||
try!(out.write_all(&s));
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
None => Err(Error::NotSupported),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(ti: &TermInfo, out: &mut io::Write) -> Result<()> {
|
||||
// are there any terminals that have color/attrs and not sgr0?
|
||||
// Try falling back to sgr, then op
|
||||
let cmd = match [("sgr0", &[] as &[Param]), ("sgr", &[Param::Number(0)]), ("op", &[])]
|
||||
.iter()
|
||||
.filter_map(|&(cap, params)| {
|
||||
ti.strings.get(cap).map(|c| (c, params))
|
||||
})
|
||||
.next() {
|
||||
Some((op, params)) => {
|
||||
match expand(op, params, &mut Variables::new()) {
|
||||
Ok(cmd) => cmd,
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
None => return Err(Error::NotSupported),
|
||||
};
|
||||
try!(out.write_all(&cmd));
|
||||
Ok(())
|
||||
}
|
||||
176
src/terminal_win.rs
Normal file
176
src/terminal_win.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
/*!
|
||||
This module contains a Windows-only *in-memory* implementation of the
|
||||
`term::Terminal` trait.
|
||||
|
||||
This particular implementation is a bit idiosyncratic, and the "in-memory"
|
||||
specification is to blame. In particular, on Windows, coloring requires
|
||||
communicating with the console synchronously as data is written to stdout.
|
||||
This is anathema to how ripgrep fundamentally works: by writing search results
|
||||
to intermediate thread local buffers in order to maximize parallelism.
|
||||
|
||||
Eliminating parallelism on Windows isn't an option, because that would negate
|
||||
a tremendous performance benefit just for coloring.
|
||||
|
||||
We've worked around this by providing an implementation of `term::Terminal`
|
||||
that records precisely where a color or a reset should be invoked, according
|
||||
to a byte offset in the in memory buffer. When the buffer is actually printed,
|
||||
we copy the bytes from the buffer to stdout incrementally while invoking the
|
||||
corresponding console APIs for coloring at the right location.
|
||||
|
||||
(Another approach would be to do ANSI coloring unconditionally, then parse that
|
||||
and translate it to console commands. The advantage of that approach is that
|
||||
it doesn't require any additional memory for storing offsets. In practice
|
||||
though, coloring is only used in the terminal, which tends to correspond to
|
||||
searches that produce very few results with respect to the corpus searched.
|
||||
Therefore, this is an acceptable trade off. Namely, we do not pay for it when
|
||||
coloring is disabled.
|
||||
*/
|
||||
use std::io;
|
||||
|
||||
use term::{self, Terminal};
|
||||
use term::color::Color;
|
||||
|
||||
/// An in-memory buffer that provides Windows console coloring.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WindowsBuffer {
|
||||
buf: Vec<u8>,
|
||||
pos: usize,
|
||||
colors: Vec<WindowsColor>,
|
||||
}
|
||||
|
||||
/// A color associated with a particular location in a buffer.
|
||||
#[derive(Clone, Debug)]
|
||||
struct WindowsColor {
|
||||
pos: usize,
|
||||
opt: WindowsOption,
|
||||
}
|
||||
|
||||
/// A color or reset directive that can be translated into an instruction to
|
||||
/// the Windows console.
|
||||
#[derive(Clone, Debug)]
|
||||
enum WindowsOption {
|
||||
Foreground(Color),
|
||||
Background(Color),
|
||||
Reset,
|
||||
}
|
||||
|
||||
impl WindowsBuffer {
|
||||
/// Create a new empty buffer for Windows console coloring.
|
||||
pub fn new() -> WindowsBuffer {
|
||||
WindowsBuffer {
|
||||
buf: vec![],
|
||||
pos: 0,
|
||||
colors: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, opt: WindowsOption) {
|
||||
let pos = self.pos;
|
||||
self.colors.push(WindowsColor { pos: pos, opt: opt });
|
||||
}
|
||||
|
||||
/// Print the contents to the given terminal.
|
||||
pub fn print_stdout<T: Terminal + Send>(&self, tt: &mut T) {
|
||||
if !tt.supports_color() {
|
||||
let _ = tt.write_all(&self.buf);
|
||||
let _ = tt.flush();
|
||||
return;
|
||||
}
|
||||
let mut last = 0;
|
||||
for col in &self.colors {
|
||||
let _ = tt.write_all(&self.buf[last..col.pos]);
|
||||
match col.opt {
|
||||
WindowsOption::Foreground(c) => {
|
||||
let _ = tt.fg(c);
|
||||
}
|
||||
WindowsOption::Background(c) => {
|
||||
let _ = tt.bg(c);
|
||||
}
|
||||
WindowsOption::Reset => {
|
||||
let _ = tt.reset();
|
||||
}
|
||||
}
|
||||
last = col.pos;
|
||||
}
|
||||
let _ = tt.write_all(&self.buf[last..]);
|
||||
let _ = tt.flush();
|
||||
}
|
||||
|
||||
/// Clear the buffer.
|
||||
pub fn clear(&mut self) {
|
||||
self.buf.clear();
|
||||
self.colors.clear();
|
||||
self.pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for WindowsBuffer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let n = try!(self.buf.write(buf));
|
||||
self.pos += n;
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terminal for WindowsBuffer {
|
||||
type Output = Vec<u8>;
|
||||
|
||||
fn fg(&mut self, fg: Color) -> term::Result<()> {
|
||||
self.push(WindowsOption::Foreground(fg));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bg(&mut self, bg: Color) -> term::Result<()> {
|
||||
self.push(WindowsOption::Background(bg));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn attr(&mut self, _attr: term::Attr) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
}
|
||||
|
||||
fn supports_attr(&self, _attr: term::Attr) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> term::Result<()> {
|
||||
self.push(WindowsOption::Reset);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn supports_reset(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_color(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn cursor_up(&mut self) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
}
|
||||
|
||||
fn delete_line(&mut self) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
}
|
||||
|
||||
fn carriage_return(&mut self) -> term::Result<()> {
|
||||
Err(term::Error::NotSupported)
|
||||
}
|
||||
|
||||
fn get_ref(&self) -> &Vec<u8> {
|
||||
&self.buf
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> &mut Vec<u8> {
|
||||
&mut self.buf
|
||||
}
|
||||
|
||||
fn into_inner(self) -> Vec<u8> {
|
||||
self.buf
|
||||
}
|
||||
}
|
||||
363
src/types.rs
363
src/types.rs
@@ -1,363 +0,0 @@
|
||||
/*!
|
||||
The types module provides a way of associating glob patterns on file names to
|
||||
file types.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
use gitignore::{self, Gitignore, GitignoreBuilder, Match, Pattern};
|
||||
|
||||
const TYPE_EXTENSIONS: &'static [(&'static str, &'static [&'static str])] = &[
|
||||
("asm", &["*.asm", "*.s", "*.S"]),
|
||||
("awk", &["*.awk"]),
|
||||
("c", &["*.c", "*.h", "*.H"]),
|
||||
("cbor", &["*.cbor"]),
|
||||
("clojure", &["*.clj", "*.cljs"]),
|
||||
("cmake", &["CMakeLists.txt"]),
|
||||
("coffeescript", &["*.coffee"]),
|
||||
("cpp", &[
|
||||
"*.C", "*.cc", "*.cpp", "*.cxx",
|
||||
"*.h", "*.H", "*.hh", "*.hpp",
|
||||
]),
|
||||
("csharp", &["*.cs"]),
|
||||
("css", &["*.css"]),
|
||||
("cython", &["*.pyx"]),
|
||||
("dart", &["*.dart"]),
|
||||
("d", &["*.d"]),
|
||||
("elisp", &["*.el"]),
|
||||
("erlang", &["*.erl", "*.hrl"]),
|
||||
("fortran", &[
|
||||
"*.f", "*.F", "*.f77", "*.F77", "*.pfo",
|
||||
"*.f90", "*.F90", "*.f95", "*.F95",
|
||||
]),
|
||||
("go", &["*.go"]),
|
||||
("groovy", &["*.groovy"]),
|
||||
("haskell", &["*.hs", "*.lhs"]),
|
||||
("html", &["*.htm", "*.html"]),
|
||||
("java", &["*.java"]),
|
||||
("js", &["*.js"]),
|
||||
("json", &["*.json"]),
|
||||
("jsonl", &["*.jsonl"]),
|
||||
("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
|
||||
("lua", &["*.lua"]),
|
||||
("m4", &["*.ac", "*.m4"]),
|
||||
("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk"]),
|
||||
("markdown", &["*.md"]),
|
||||
("matlab", &["*.m"]),
|
||||
("mk", &["mkfile"]),
|
||||
("ml", &["*.ml"]),
|
||||
("objc", &["*.h", "*.m"]),
|
||||
("objcpp", &["*.h", "*.mm"]),
|
||||
("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]),
|
||||
("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm"]),
|
||||
("php", &["*.php", "*.php3", "*.php4", "*.php5", "*.phtml"]),
|
||||
("py", &["*.py"]),
|
||||
("rr", &["*.R"]),
|
||||
("rst", &["*.rst"]),
|
||||
("ruby", &["*.rb"]),
|
||||
("rust", &["*.rs"]),
|
||||
("scala", &["*.scala"]),
|
||||
("sh", &["*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh"]),
|
||||
("sql", &["*.sql"]),
|
||||
("tex", &["*.tex", "*.cls", "*.sty"]),
|
||||
("txt", &["*.txt"]),
|
||||
("toml", &["*.toml", "Cargo.lock"]),
|
||||
("vala", &["*.vala"]),
|
||||
("vimscript", &["*.vim"]),
|
||||
("xml", &["*.xml"]),
|
||||
("yacc", &["*.y"]),
|
||||
("yaml", &["*.yaml", "*.yml"]),
|
||||
];
|
||||
|
||||
/// Describes all the possible failure conditions for building a file type
|
||||
/// matcher.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// We tried to select (or negate) a file type that is not defined.
|
||||
UnrecognizedFileType(String),
|
||||
/// A user specified file type definition could not be parsed.
|
||||
InvalidDefinition,
|
||||
/// There was an error building the matcher (probably a bad glob).
|
||||
Gitignore(gitignore::Error),
|
||||
}
|
||||
|
||||
impl StdError for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::UnrecognizedFileType(_) => "unrecognized file type",
|
||||
Error::InvalidDefinition => "invalid definition",
|
||||
Error::Gitignore(ref err) => err.description(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::UnrecognizedFileType(ref ty) => {
|
||||
write!(f, "unrecognized file type: {}", ty)
|
||||
}
|
||||
Error::InvalidDefinition => {
|
||||
write!(f, "invalid definition (format is type:glob, e.g., \
|
||||
html:*.html)")
|
||||
}
|
||||
Error::Gitignore(ref err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<gitignore::Error> for Error {
|
||||
fn from(err: gitignore::Error) -> Error {
|
||||
Error::Gitignore(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// A single file type definition.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileTypeDef {
|
||||
name: String,
|
||||
pats: Vec<String>,
|
||||
}
|
||||
|
||||
impl FileTypeDef {
|
||||
/// Return the name of this file type.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Return the glob patterns used to recognize this file type.
|
||||
pub fn patterns(&self) -> &[String] {
|
||||
&self.pats
|
||||
}
|
||||
}
|
||||
|
||||
/// Types is a file type matcher.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Types {
|
||||
gi: Option<Gitignore>,
|
||||
has_selected: bool,
|
||||
unmatched_pat: Pattern,
|
||||
}
|
||||
|
||||
impl Types {
|
||||
/// Creates a new file type matcher from the given Gitignore matcher. If
|
||||
/// not Gitignore matcher is provided, then the file type matcher has no
|
||||
/// effect.
|
||||
///
|
||||
/// If has_selected is true, then at least one file type was selected.
|
||||
/// Therefore, any non-matches should be ignored.
|
||||
fn new(gi: Option<Gitignore>, has_selected: bool) -> Types {
|
||||
Types {
|
||||
gi: gi,
|
||||
has_selected: has_selected,
|
||||
unmatched_pat: Pattern {
|
||||
from: Path::new("<filetype>").to_path_buf(),
|
||||
original: "<none>".to_string(),
|
||||
pat: "<none>".to_string(),
|
||||
whitelist: false,
|
||||
only_dir: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new file type matcher that never matches.
|
||||
pub fn empty() -> Types {
|
||||
Types::new(None, false)
|
||||
}
|
||||
|
||||
/// Returns a match for the given path against this file type matcher.
|
||||
///
|
||||
/// The path is considered whitelisted if it matches a selected file type.
|
||||
/// The path is considered ignored if it matched a negated file type.
|
||||
/// If at least one file type is selected and path doesn't match, then
|
||||
/// the path is also considered ignored.
|
||||
pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
|
||||
// File types don't apply to directories.
|
||||
if is_dir {
|
||||
return Match::None;
|
||||
}
|
||||
let path = path.as_ref();
|
||||
self.gi.as_ref()
|
||||
.map(|gi| {
|
||||
let path = &*path.to_string_lossy();
|
||||
let mat = gi.matched_utf8(path, is_dir).invert();
|
||||
if self.has_selected && mat.is_none() {
|
||||
Match::Ignored(&self.unmatched_pat)
|
||||
} else {
|
||||
mat
|
||||
}
|
||||
})
|
||||
.unwrap_or(Match::None)
|
||||
}
|
||||
}
|
||||
|
||||
/// TypesBuilder builds a type matcher from a set of file type definitions and
|
||||
/// a set of file type selections.
|
||||
pub struct TypesBuilder {
|
||||
types: HashMap<String, Vec<String>>,
|
||||
select: Vec<String>,
|
||||
select_not: Vec<String>,
|
||||
}
|
||||
|
||||
impl TypesBuilder {
|
||||
/// Create a new builder for a file type matcher.
|
||||
pub fn new() -> TypesBuilder {
|
||||
TypesBuilder {
|
||||
types: HashMap::new(),
|
||||
select: vec![],
|
||||
select_not: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the current set of file type definitions *and* selections into
|
||||
/// a file type matcher.
|
||||
pub fn build(&self) -> Result<Types, Error> {
|
||||
if self.select.is_empty() && self.select_not.is_empty() {
|
||||
return Ok(Types::new(None, false));
|
||||
}
|
||||
let mut bgi = GitignoreBuilder::new("/");
|
||||
for name in &self.select {
|
||||
let globs = match self.types.get(name) {
|
||||
Some(globs) => globs,
|
||||
None => {
|
||||
return Err(Error::UnrecognizedFileType(name.to_string()));
|
||||
}
|
||||
};
|
||||
for glob in globs {
|
||||
try!(bgi.add("<filetype>", glob));
|
||||
}
|
||||
}
|
||||
for name in &self.select_not {
|
||||
let globs = match self.types.get(name) {
|
||||
Some(globs) => globs,
|
||||
None => {
|
||||
return Err(Error::UnrecognizedFileType(name.to_string()));
|
||||
}
|
||||
};
|
||||
for glob in globs {
|
||||
try!(bgi.add("<filetype>", &format!("!{}", glob)));
|
||||
}
|
||||
}
|
||||
Ok(Types::new(Some(try!(bgi.build())), !self.select.is_empty()))
|
||||
}
|
||||
|
||||
/// Return the set of current file type definitions.
|
||||
pub fn definitions(&self) -> Vec<FileTypeDef> {
|
||||
let mut defs = vec![];
|
||||
for (ref name, ref pats) in &self.types {
|
||||
let mut pats = pats.to_vec();
|
||||
pats.sort();
|
||||
defs.push(FileTypeDef {
|
||||
name: name.to_string(),
|
||||
pats: pats,
|
||||
});
|
||||
}
|
||||
defs.sort_by(|def1, def2| def1.name().cmp(def2.name()));
|
||||
defs
|
||||
}
|
||||
|
||||
/// Select the file type given by `name`.
|
||||
pub fn select(&mut self, name: &str) -> &mut TypesBuilder {
|
||||
self.select.push(name.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Ignore the file type given by `name`.
|
||||
pub fn select_not(&mut self, name: &str) -> &mut TypesBuilder {
|
||||
self.select_not.push(name.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Clear any file type definitions for the type given.
|
||||
pub fn clear(&mut self, name: &str) -> &mut TypesBuilder {
|
||||
self.types.remove(name);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a new file type definition. `name` can be arbitrary and `pat`
|
||||
/// should be a glob recognizing file paths belonging to the `name` type.
|
||||
pub fn add(&mut self, name: &str, pat: &str) -> &mut TypesBuilder {
|
||||
self.types.entry(name.to_string())
|
||||
.or_insert(vec![]).push(pat.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a new file type definition specified in string form. The format
|
||||
/// is `name:glob`. Names may not include a colon.
|
||||
pub fn add_def(&mut self, def: &str) -> Result<(), Error> {
|
||||
let name: String = def.chars().take_while(|&c| c != ':').collect();
|
||||
let pat: String = def.chars().skip(name.chars().count() + 1).collect();
|
||||
if name.is_empty() || pat.is_empty() {
|
||||
return Err(Error::InvalidDefinition);
|
||||
}
|
||||
self.add(&name, &pat);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a set of default file type definitions.
|
||||
pub fn add_defaults(&mut self) -> &mut TypesBuilder {
|
||||
for &(name, exts) in TYPE_EXTENSIONS {
|
||||
for ext in exts {
|
||||
self.add(name, ext);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::TypesBuilder;
|
||||
|
||||
macro_rules! matched {
|
||||
($name:ident, $types:expr, $sel:expr, $selnot:expr,
|
||||
$path:expr) => {
|
||||
matched!($name, $types, $sel, $selnot, $path, true);
|
||||
};
|
||||
(not, $name:ident, $types:expr, $sel:expr, $selnot:expr,
|
||||
$path:expr) => {
|
||||
matched!($name, $types, $sel, $selnot, $path, false);
|
||||
};
|
||||
($name:ident, $types:expr, $sel:expr, $selnot:expr,
|
||||
$path:expr, $matched:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let mut btypes = TypesBuilder::new();
|
||||
for tydef in $types {
|
||||
btypes.add_def(tydef).unwrap();
|
||||
}
|
||||
for sel in $sel {
|
||||
btypes.select(sel);
|
||||
}
|
||||
for selnot in $selnot {
|
||||
btypes.select_not(selnot);
|
||||
}
|
||||
let types = btypes.build().unwrap();
|
||||
let mat = types.matched($path, false);
|
||||
assert_eq!($matched, !mat.is_ignored());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn types() -> Vec<&'static str> {
|
||||
vec![
|
||||
"html:*.html",
|
||||
"html:*.htm",
|
||||
"rust:*.rs",
|
||||
"js:*.js",
|
||||
]
|
||||
}
|
||||
|
||||
matched!(match1, types(), vec!["rust"], vec![], "lib.rs");
|
||||
matched!(match2, types(), vec!["html"], vec![], "index.html");
|
||||
matched!(match3, types(), vec!["html"], vec![], "index.htm");
|
||||
matched!(match4, types(), vec!["html", "rust"], vec![], "main.rs");
|
||||
matched!(match5, types(), vec![], vec![], "index.html");
|
||||
matched!(match6, types(), vec![], vec!["rust"], "index.html");
|
||||
|
||||
matched!(not, matchnot1, types(), vec!["rust"], vec![], "index.html");
|
||||
matched!(not, matchnot2, types(), vec![], vec!["rust"], "main.rs");
|
||||
}
|
||||
142
src/walk.rs
142
src/walk.rs
@@ -1,142 +0,0 @@
|
||||
/*!
|
||||
The walk module implements a recursive directory iterator (using the `walkdir`)
|
||||
crate that can efficiently skip and ignore files and directories specified in
|
||||
a user's ignore patterns.
|
||||
*/
|
||||
|
||||
use walkdir::{self, DirEntry, WalkDir, WalkDirIterator};
|
||||
|
||||
use ignore::Ignore;
|
||||
|
||||
/// Iter is a recursive directory iterator over file paths in a directory.
|
||||
/// Only file paths should be searched are yielded.
|
||||
pub struct Iter {
|
||||
ig: Ignore,
|
||||
it: WalkEventIter,
|
||||
}
|
||||
|
||||
impl Iter {
|
||||
/// Create a new recursive directory iterator using the ignore patterns
|
||||
/// and walkdir iterator given.
|
||||
pub fn new(ig: Ignore, wd: WalkDir) -> Iter {
|
||||
Iter {
|
||||
ig: ig,
|
||||
it: WalkEventIter::from(wd),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this entry should be skipped.
|
||||
fn skip_entry(&self, ent: &DirEntry) -> bool {
|
||||
if ent.depth() == 0 {
|
||||
// Never skip the root directory.
|
||||
return false;
|
||||
}
|
||||
if self.ig.ignored(ent.path(), ent.file_type().is_dir()) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Iter {
|
||||
type Item = DirEntry;
|
||||
|
||||
fn next(&mut self) -> Option<DirEntry> {
|
||||
while let Some(ev) = self.it.next() {
|
||||
match ev {
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
Ok(WalkEvent::Exit) => {
|
||||
self.ig.pop();
|
||||
}
|
||||
Ok(WalkEvent::Dir(ent)) => {
|
||||
if self.skip_entry(&ent) {
|
||||
self.it.it.skip_current_dir();
|
||||
// Still need to push this on the stack because we'll
|
||||
// get a WalkEvent::Exit event for this dir. We don't
|
||||
// care if it errors though.
|
||||
let _ = self.ig.push(ent.path());
|
||||
continue;
|
||||
}
|
||||
if let Err(err) = self.ig.push(ent.path()) {
|
||||
eprintln!("{}", err);
|
||||
self.it.it.skip_current_dir();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Ok(WalkEvent::File(ent)) => {
|
||||
if self.skip_entry(&ent) {
|
||||
continue;
|
||||
}
|
||||
// If this isn't actually a file (e.g., a symlink), then
|
||||
// skip it.
|
||||
if !ent.file_type().is_file() {
|
||||
continue;
|
||||
}
|
||||
return Some(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// WalkEventIter transforms a WalkDir iterator into an iterator that more
|
||||
/// accurately describes the directory tree. Namely, it emits events that are
|
||||
/// one of three types: directory, file or "exit." An "exit" event means that
|
||||
/// the entire contents of a directory have been enumerated.
|
||||
struct WalkEventIter {
|
||||
depth: usize,
|
||||
it: walkdir::Iter,
|
||||
next: Option<Result<DirEntry, walkdir::Error>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum WalkEvent {
|
||||
Dir(DirEntry),
|
||||
File(DirEntry),
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl From<WalkDir> for WalkEventIter {
|
||||
fn from(it: WalkDir) -> WalkEventIter {
|
||||
WalkEventIter { depth: 0, it: it.into_iter(), next: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for WalkEventIter {
|
||||
type Item = walkdir::Result<WalkEvent>;
|
||||
|
||||
fn next(&mut self) -> Option<walkdir::Result<WalkEvent>> {
|
||||
let dent = self.next.take().or_else(|| self.it.next());
|
||||
let depth = match dent {
|
||||
None => 0,
|
||||
Some(Ok(ref dent)) => dent.depth(),
|
||||
Some(Err(ref err)) => err.depth(),
|
||||
};
|
||||
if depth < self.depth {
|
||||
self.depth -= 1;
|
||||
self.next = dent;
|
||||
return Some(Ok(WalkEvent::Exit));
|
||||
}
|
||||
self.depth = depth;
|
||||
match dent {
|
||||
None => None,
|
||||
Some(Err(err)) => Some(Err(err)),
|
||||
Some(Ok(dent)) => {
|
||||
if dent.file_type().is_dir() {
|
||||
self.depth += 1;
|
||||
Some(Ok(WalkEvent::Dir(dent)))
|
||||
} else {
|
||||
Some(Ok(WalkEvent::File(dent)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_hidden(ent: &DirEntry) -> bool {
|
||||
ent.depth() > 0 &&
|
||||
ent.file_name().to_str().map(|s| s.starts_with(".")).unwrap_or(false)
|
||||
}
|
||||
589
tests/tests.rs
589
tests/tests.rs
@@ -34,6 +34,47 @@ macro_rules! sherlock {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! clean {
|
||||
($name:ident, $query:expr, $path:expr, $fun:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let wd = WorkDir::new(stringify!($name));
|
||||
let mut cmd = wd.command();
|
||||
cmd.arg($query).arg($path);
|
||||
$fun(wd, cmd);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn path(unix: &str) -> String {
|
||||
if cfg!(windows) {
|
||||
unix.replace("/", "\\")
|
||||
} else {
|
||||
unix.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn paths(unix: &[&str]) -> Vec<String> {
|
||||
let mut xs: Vec<_> = unix.iter().map(|s| path(s)).collect();
|
||||
xs.sort();
|
||||
xs
|
||||
}
|
||||
|
||||
fn paths_from_stdout(stdout: String) -> Vec<String> {
|
||||
let mut paths: Vec<_> = stdout.lines().map(|s| {
|
||||
s.split(":").next().unwrap().to_string()
|
||||
}).collect();
|
||||
paths.sort();
|
||||
paths
|
||||
}
|
||||
|
||||
fn sort_lines(lines: &str) -> String {
|
||||
let mut lines: Vec<String> =
|
||||
lines.trim().lines().map(|s| s.to_owned()).collect();
|
||||
lines.sort();
|
||||
format!("{}\n", lines.join("\n"))
|
||||
}
|
||||
|
||||
sherlock!(single_file, |wd: WorkDir, mut cmd| {
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
@@ -118,7 +159,11 @@ be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
foo
|
||||
Sherlock Holmes lives on Baker Street.
|
||||
";
|
||||
assert!(lines == expected1 || lines == expected2);
|
||||
if lines != expected1 {
|
||||
assert_eq!(lines, expected2);
|
||||
} else {
|
||||
assert_eq!(lines, expected1);
|
||||
}
|
||||
});
|
||||
|
||||
sherlock!(inverted, |wd: WorkDir, mut cmd: Command| {
|
||||
@@ -166,7 +211,7 @@ For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
|
||||
sherlock!(literal, "()", "file", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file", "blib\n()\nblab\n");
|
||||
cmd.arg("-Q");
|
||||
cmd.arg("-F");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "()\n");
|
||||
});
|
||||
@@ -219,6 +264,13 @@ sherlock!(file_types, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
assert_eq!(lines, "file.rs:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(file_types_all, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file.py", "Sherlock");
|
||||
cmd.arg("-t").arg("all");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file.py:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(file_types_negate, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.remove("sherlock");
|
||||
wd.create("file.py", "Sherlock");
|
||||
@@ -228,6 +280,18 @@ sherlock!(file_types_negate, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
assert_eq!(lines, "file.py:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(file_types_negate_all, "Sherlock", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file.py", "Sherlock");
|
||||
cmd.arg("-T").arg("all");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "\
|
||||
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!(file_type_clear, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file.py", "Sherlock");
|
||||
wd.create("file.rs", "Sherlock");
|
||||
@@ -261,6 +325,20 @@ sherlock!(glob_negate, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
assert_eq!(lines, "file.py:Sherlock\n");
|
||||
});
|
||||
|
||||
sherlock!(count, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--count");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "sherlock:2\n";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(files_with_matches, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--files-with-matches");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "sherlock\n";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(after_context, |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-A").arg("1");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
@@ -358,6 +436,11 @@ sherlock!(ignore_git, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
|
||||
sherlock!(ignore_generic, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".ignore", "sherlock\n");
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
|
||||
sherlock!(ignore_ripgrep, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".rgignore", "sherlock\n");
|
||||
wd.assert_err(&mut cmd);
|
||||
@@ -473,7 +556,7 @@ sherlock!(symlink_nofollow, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.remove("sherlock");
|
||||
wd.create_dir("foo");
|
||||
wd.create_dir("foo/bar");
|
||||
wd.link("foo/baz", "foo/bar/baz");
|
||||
wd.link_dir("foo/baz", "foo/bar/baz");
|
||||
wd.create_dir("foo/baz");
|
||||
wd.create("foo/baz/sherlock", hay::SHERLOCK);
|
||||
cmd.current_dir(wd.path().join("foo/bar"));
|
||||
@@ -486,24 +569,478 @@ sherlock!(symlink_follow, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create_dir("foo/bar");
|
||||
wd.create_dir("foo/baz");
|
||||
wd.create("foo/baz/sherlock", hay::SHERLOCK);
|
||||
wd.link("foo/baz", "foo/bar/baz");
|
||||
wd.link_dir("foo/baz", "foo/bar/baz");
|
||||
cmd.arg("-L");
|
||||
cmd.current_dir(wd.path().join("foo/bar"));
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
if cfg!(windows) {
|
||||
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
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
} else {
|
||||
let expected = "\
|
||||
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
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
assert_eq!(lines, path(expected));
|
||||
});
|
||||
|
||||
sherlock!(unrestricted1, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", "sherlock\n");
|
||||
cmd.arg("-u");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
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
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(unrestricted2, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.remove("sherlock");
|
||||
wd.create(".sherlock", hay::SHERLOCK);
|
||||
cmd.arg("-uu");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
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
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(unrestricted3, "foo", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("file", "foo\x00bar\nfoo\x00baz\n");
|
||||
cmd.arg("-uuu");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "file:foo\x00bar\nfile:foo\x00baz\n");
|
||||
});
|
||||
|
||||
sherlock!(vimgrep, "Sherlock|Watson", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--vimgrep");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
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,
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/16
|
||||
clean!(regression_16, "xyz", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", "ghi/");
|
||||
wd.create_dir("ghi");
|
||||
wd.create_dir("def/ghi");
|
||||
wd.create("ghi/toplevel.txt", "xyz");
|
||||
wd.create("def/ghi/subdir.txt", "xyz");
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/25
|
||||
clean!(regression_25, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", "/llvm/");
|
||||
wd.create_dir("src/llvm");
|
||||
wd.create("src/llvm/foo", "test");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = path("src/llvm/foo:test\n");
|
||||
assert_eq!(lines, expected);
|
||||
|
||||
cmd.current_dir(wd.path().join("src"));
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = path("llvm/foo:test\n");
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/30
|
||||
clean!(regression_30, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
if cfg!(windows) {
|
||||
wd.create(".gitignore", "vendor/**\n!vendor\\manifest");
|
||||
} else {
|
||||
wd.create(".gitignore", "vendor/**\n!vendor/manifest");
|
||||
}
|
||||
wd.create_dir("vendor");
|
||||
wd.create("vendor/manifest", "test");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = path("vendor/manifest:test\n");
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/49
|
||||
clean!(regression_49, "xyz", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", "foo/bar");
|
||||
wd.create_dir("test/foo/bar");
|
||||
wd.create("test/foo/bar/baz", "test");
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/50
|
||||
clean!(regression_50, "xyz", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", "XXX/YYY/");
|
||||
wd.create_dir("abc/def/XXX/YYY");
|
||||
wd.create_dir("ghi/XXX/YYY");
|
||||
wd.create("abc/def/XXX/YYY/bar", "test");
|
||||
wd.create("ghi/XXX/YYY/bar", "test");
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/65
|
||||
clean!(regression_65, "xyz", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", "a/");
|
||||
wd.create_dir("a");
|
||||
wd.create("a/foo", "xyz");
|
||||
wd.create("a/bar", "xyz");
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/67
|
||||
clean!(regression_67, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", "/*\n!/dir");
|
||||
wd.create_dir("dir");
|
||||
wd.create_dir("foo");
|
||||
wd.create("foo/bar", "test");
|
||||
wd.create("dir/bar", "test");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, path("dir/bar:test\n"));
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/87
|
||||
clean!(regression_87, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", "foo\n**no-vcs**");
|
||||
wd.create("foo", "test");
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/90
|
||||
clean!(regression_90, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", "!.foo");
|
||||
wd.create(".foo", "test");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, ".foo:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/93
|
||||
clean!(regression_93, r"(\d{1,3}\.){3}\d{1,3}", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("foo", "192.168.1.1");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:192.168.1.1\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/99
|
||||
clean!(regression_99, "test", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("foo1", "test");
|
||||
wd.create("foo2", "zzz");
|
||||
wd.create("bar", "test");
|
||||
cmd.arg("-j1").arg("--heading");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(sort_lines(&lines), sort_lines("bar\ntest\n\nfoo1\ntest\n"));
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/105
|
||||
clean!(regression_105_part1, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("foo", "zztest");
|
||||
cmd.arg("--vimgrep");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:1:3:zztest\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/105
|
||||
clean!(regression_105_part2, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("foo", "zztest");
|
||||
cmd.arg("--column");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:3:zztest\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/127
|
||||
clean!(regression_127, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
// 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.
|
||||
wd.create(".gitignore", "foo/sherlock\n");
|
||||
wd.create_dir("foo");
|
||||
wd.create("foo/sherlock", hay::SHERLOCK);
|
||||
wd.create("foo/watson", hay::SHERLOCK);
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = format!("\
|
||||
{path}:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
{path}:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
", path=path("foo/watson"));
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/131
|
||||
//
|
||||
// TODO(burntsushi): Darwin doesn't like this test for some reason.
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
clean!(regression_131, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", "TopÑapa");
|
||||
wd.create("TopÑapa", "test");
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/137
|
||||
//
|
||||
// TODO(burntsushi): Figure out why Windows gives "access denied" errors
|
||||
// when trying to create a file symlink. For now, disable test on Windows.
|
||||
#[cfg(not(windows))]
|
||||
sherlock!(regression_137, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.link_file("sherlock", "sym1");
|
||||
wd.link_file("sherlock", "sym2");
|
||||
cmd.arg("sym1");
|
||||
cmd.arg("sym2");
|
||||
cmd.arg("-j1");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
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
|
||||
";
|
||||
assert_eq!(lines, path(expected));
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/156
|
||||
clean!(
|
||||
regression_156,
|
||||
r#"#(?:parse|include)\s*\(\s*(?:"|')[./A-Za-z_-]+(?:"|')"#,
|
||||
"testcase.txt",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
const TESTCASE: &'static str = 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")
|
||||
"#;
|
||||
wd.create("testcase.txt", TESTCASE);
|
||||
cmd.arg("-N");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, TESTCASE);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/184
|
||||
clean!(regression_184, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", ".*");
|
||||
wd.create_dir("foo/bar");
|
||||
wd.create("foo/bar/baz", "test");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, format!("{}:test\n", path("foo/bar/baz")));
|
||||
|
||||
cmd.current_dir(wd.path().join("./foo/bar"));
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "baz:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/20
|
||||
sherlock!(feature_20_no_filename, "Sherlock", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--no-filename");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
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
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/45
|
||||
sherlock!(feature_45_relative_cwd, "test", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".not-an-ignore", "foo\n/bar");
|
||||
wd.create_dir("bar");
|
||||
wd.create_dir("baz/bar");
|
||||
wd.create_dir("baz/baz/bar");
|
||||
wd.create("bar/test", "test");
|
||||
wd.create("baz/bar/test", "test");
|
||||
wd.create("baz/baz/bar/test", "test");
|
||||
wd.create("baz/foo", "test");
|
||||
wd.create("baz/test", "test");
|
||||
wd.create("foo", "test");
|
||||
wd.create("test", "test");
|
||||
|
||||
// First, get a baseline without applying ignore rules.
|
||||
let lines = paths_from_stdout(wd.stdout(&mut cmd));
|
||||
assert_eq!(lines, paths(&[
|
||||
"bar/test", "baz/bar/test", "baz/baz/bar/test", "baz/foo",
|
||||
"baz/test", "foo", "test",
|
||||
]));
|
||||
|
||||
// Now try again with the ignore file activated.
|
||||
cmd.arg("--ignore-file").arg(".not-an-ignore");
|
||||
let lines = paths_from_stdout(wd.stdout(&mut cmd));
|
||||
assert_eq!(lines, paths(&[
|
||||
"baz/bar/test", "baz/baz/bar/test", "baz/test", "test",
|
||||
]));
|
||||
|
||||
// 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 = wd.command();
|
||||
cmd.arg("test").arg(".").arg("--ignore-file").arg("../.not-an-ignore");
|
||||
cmd.current_dir(wd.path().join("baz"));
|
||||
let lines = paths_from_stdout(wd.stdout(&mut cmd));
|
||||
assert_eq!(lines, paths(&["baz/bar/test", "test"]));
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/45
|
||||
sherlock!(feature_45_precedence_with_others, "test", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".not-an-ignore", "*.log");
|
||||
wd.create(".ignore", "!imp.log");
|
||||
wd.create("imp.log", "test");
|
||||
wd.create("wat.log", "test");
|
||||
|
||||
cmd.arg("--ignore-file").arg(".not-an-ignore");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "imp.log:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/45
|
||||
sherlock!(feature_45_precedence_internal, "test", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".not-an-ignore1", "*.log");
|
||||
wd.create(".not-an-ignore2", "!imp.log");
|
||||
wd.create("imp.log", "test");
|
||||
wd.create("wat.log", "test");
|
||||
|
||||
cmd.arg("--ignore-file").arg(".not-an-ignore1");
|
||||
cmd.arg("--ignore-file").arg(".not-an-ignore2");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "imp.log:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/68
|
||||
clean!(feature_68_no_ignore_vcs, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create(".gitignore", "foo");
|
||||
wd.create(".ignore", "bar");
|
||||
wd.create("foo", "test");
|
||||
wd.create("bar", "test");
|
||||
cmd.arg("--no-ignore-vcs");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo:test\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/70
|
||||
sherlock!(feature_70_smart_case, "sherlock", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--smart-case");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
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
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||
sherlock!(feature_89_files_with_matches, "Sherlock", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--null").arg("--files-with-matches");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "sherlock\x00");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||
sherlock!(feature_89_count, "Sherlock", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--null").arg("--count");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "sherlock\x002\n");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||
sherlock!(feature_89_files, "NADA", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--null").arg("--files");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "sherlock\x00");
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/89
|
||||
sherlock!(feature_89_match, "Sherlock", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--null").arg("-C1");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
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;
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/109
|
||||
clean!(feature_109_max_depth, "far", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create_dir("one");
|
||||
wd.create("one/pass", "far");
|
||||
wd.create_dir("one/too");
|
||||
wd.create("one/too/many", "far");
|
||||
|
||||
cmd.arg("--maxdepth").arg("2");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = path("one/pass:far\n");
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/124
|
||||
clean!(feature_109_case_sensitive_part1, "test", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("foo", "tEsT");
|
||||
cmd.arg("--smart-case").arg("--case-sensitive");
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/124
|
||||
clean!(feature_109_case_sensitive_part2, "test", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("foo", "tEsT");
|
||||
cmd.arg("--ignore-case").arg("--case-sensitive");
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
|
||||
#[test]
|
||||
@@ -543,7 +1080,7 @@ fn binary_search_no_mmap() {
|
||||
let mut cmd = wd.command();
|
||||
cmd.arg("-a").arg("--no-mmap").arg("foo").arg("file");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, "foo\nfoo\n");
|
||||
assert_eq!(lines, "foo\x00bar\nfoo\x00baz\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -556,13 +1093,23 @@ fn files() {
|
||||
let mut cmd = wd.command();
|
||||
cmd.arg("--files");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
if cfg!(windows) {
|
||||
assert!(lines == "./dir\\file\n./file\n"
|
||||
|| lines == "./file\n./dir\\file\n");
|
||||
} else {
|
||||
assert!(lines == "./file\n./dir/file\n"
|
||||
|| lines == "./dir/file\n./file\n");
|
||||
}
|
||||
assert!(lines == path("file\ndir/file\n")
|
||||
|| lines == path("dir/file\nfile\n"));
|
||||
}
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/64
|
||||
#[test]
|
||||
fn regression_64() {
|
||||
let wd = WorkDir::new("regression_64");
|
||||
wd.create_dir("dir");
|
||||
wd.create_dir("foo");
|
||||
wd.create("dir/abc", "");
|
||||
wd.create("foo/abc", "");
|
||||
|
||||
let mut cmd = wd.command();
|
||||
cmd.arg("--files").arg("foo");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, path("foo/abc\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -83,7 +83,7 @@ impl WorkDir {
|
||||
/// Creates a directory symlink to the src with the given target name
|
||||
/// in this directory.
|
||||
#[cfg(not(windows))]
|
||||
pub fn link<S: AsRef<Path>, T: AsRef<Path>>(&self, src: S, target: T) {
|
||||
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);
|
||||
@@ -91,8 +91,10 @@ impl WorkDir {
|
||||
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<S: AsRef<Path>, T: AsRef<Path>>(&self, src: S, target: T) {
|
||||
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);
|
||||
@@ -100,6 +102,32 @@ impl WorkDir {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user