Compare commits

...

57 Commits

Author SHA1 Message Date
Andrew Gallant
8baa0e56b7 grep-0.1.4 2016-11-06 15:35:17 -05:00
Andrew Gallant
301ee6d3f5 globset-0.1.2 2016-11-06 15:35:05 -05:00
Andrew Gallant
77ad7588ae Add --no-messages flag.
This flag is similar to what's found in grep: it will suppress all error
messages, such as those shown when a particular file couldn't be read.

Closes #149
2016-11-06 14:36:08 -05:00
Andrew Gallant
58aca2efb2 Add -m/--max-count flag.
This flag limits the number of matches printed *per file*.

Closes #159
2016-11-06 13:09:53 -05:00
Andrew Gallant
351eddc17e Add new 'h' file type.
This is intended to correspond to C/C++ header files.

Fixes #186
2016-11-06 12:23:42 -05:00
Andrew Gallant
277dda544c Include the name "ripgrep" in more places.
Fixes #203
2016-11-06 12:21:36 -05:00
Andrew Gallant
8c869cbd87 update man page 2016-11-06 12:10:55 -05:00
Andrew Gallant
598b162fea Note -e/--regexp's additional usefulness.
Specifically, it can be used when searching for patterns that start
with a dash.

Fixes #215
2016-11-06 12:10:27 -05:00
Andrew Gallant
0222e024fe Fixes a bug with --smart-case.
This was a subtle bug, but the big picture was that the smart case
information wasn't being carried through to the literal extraction in
some cases. When this happened, it was possible to get back an incomplete
set of literals, which would therefore miss some valid matches.

The fix to this is to actually parse the regex and determine whether
smart case applies before doing anything else. It's a little extra work,
but parsing is pretty fast.

Fixes #199
2016-11-06 12:07:47 -05:00
Andrew Gallant
5bd0edbbe1 Actually use simd/avx optimizations in bytecount crate.
Also update compile script.
2016-11-05 22:44:33 -04:00
Andrew Gallant
4368913d8f Merge branch 'fast_linecount' 2016-11-05 22:29:42 -04:00
Andre Bogus
02de97b8ce Use the bytecount crate for fast line counting.
Fixes #128
2016-11-05 22:29:26 -04:00
Andrew Gallant
32db773d51 Merge pull request #223 from BurntSushi/ignore-parallel
Add parallel recursive directory iterator.
2016-11-05 22:19:47 -04:00
Andrew Gallant
b272be25fa Add parallel recursive directory iterator.
This adds a new walk type in the `ignore` crate, `WalkParallel`, which
provides a way for recursively iterating over a set of paths in parallel
while respecting various ignore rules.

The API is a bit strange, as a closure producing a closure isn't
something one often sees, but it does seem to work well.

This also allowed us to simplify much of the worker logic in ripgrep
proper, where MultiWorker is now gone.
2016-11-05 21:45:55 -04:00
Andrew Gallant
1aeae3e22d update ripgrep 2016-11-04 21:12:08 -04:00
Andrew Gallant
60d537c43d Merge pull request #220 from theamazingfedex/adding-mak-type
adding .mak extension for makefile filetype.
2016-11-03 20:46:29 -04:00
Andrew Gallant
ef5c07476b Merge pull request #219 from theamazingfedex/adding-pdf-filetype
adding .pdf filetype to available types.
2016-11-03 20:46:07 -04:00
Andrew Gallant
4f6f34307c Merge pull request #218 from theamazingfedex/adding-cs-filetype
adding cs filetype for ease of use.
2016-11-03 20:46:02 -04:00
Daniel Wood
7cf560d27c adding .mak extension for makefile filetype.
Fixes #217
2016-11-03 15:52:10 -06:00
Daniel Wood
15b263ff55 adding cs filetype for ease of use. 2016-11-03 15:33:00 -06:00
Daniel Wood
53121e0733 adding .pdf filetype to available types. 2016-11-03 15:30:58 -06:00
Andrew Gallant
404785f950 Merge pull request #214 from lyuha/master
Add textile, org, creole, rdoc, wiki filetype
2016-11-02 11:53:22 -04:00
Lyuha
103c4c953c Add pod filetype 2016-11-03 00:06:14 +09:00
Lyuha
82abf883c5 Add wiki filetype 2016-11-03 00:06:14 +09:00
Lyuha
a2315d5ee5 Add creole filetype 2016-11-03 00:06:14 +09:00
Lyuha
201d0cb8c1 Add org filetype 2016-11-03 00:06:14 +09:00
Lyuha
6f45478a7d Add rdoc filetype 2016-11-03 00:06:14 +09:00
Lyuha
9c2c569624 Add textile filetype 2016-11-03 00:06:14 +09:00
Andrew Gallant
a1e4e0f85c Merge pull request #213 from tjdgus3537/master
add asciidoc filetype and update markdown filetype
2016-11-02 10:44:31 -04:00
tjdgus3537
caf31a769b Add .markdown, .mdown, .mkdn extension to md and markdown 2016-11-02 22:50:59 +09:00
tjdgus3537
920112e640 Add .adoc, .asc, asciidoc extension to asciidoc 2016-11-02 22:49:31 +09:00
Andrew Gallant
a84ffe603b Merge pull request #212 from radhermit/gentoo
Add Gentoo info to the README
2016-11-02 07:05:47 -04:00
Tim Harder
e4f83f3161 Add Gentoo info to the README 2016-11-01 22:03:00 -04:00
Andrew Gallant
fbca4a0332 Merge pull request #209 from dueyfinster/patch-1
Added taskpaper as a file type
2016-11-01 10:29:15 -04:00
Neil Grogan
65c7df1c25 Added taskpaper as a file type 2016-11-01 13:59:59 +00:00
Alexander Altman
18237da9b2 Add Agda and improve TeX ignore support (#207)
Add Agda and improve TeX ignore support
2016-11-01 06:56:53 -04:00
Andrew Gallant
f147f3aa39 0.2.6 2016-10-31 20:01:37 -04:00
Andrew Gallant
599c4fc3f3 changelog 0.2.6 2016-10-31 20:01:31 -04:00
Andrew Gallant
d85a6dd5c8 update ignore dependency 2016-10-31 20:01:31 -04:00
Andrew Gallant
40abade8ee ignore-0.1.3 2016-10-31 19:54:47 -04:00
Andrew Gallant
fca4fdf6ea ignore-0.1.2 2016-10-31 19:54:38 -04:00
Andrew Gallant
16975797fe Fixes a matching bug in the glob override matcher.
This was probably a transcription error when moving the ignore matcher
code out of ripgrep core. Specifically, the override glob matcher should
not ignore directories if they don't match.

Fixes #206
2016-10-31 19:54:38 -04:00
Andrew Gallant
6507a48f97 Ignore ignore/Cargo.lock 2016-10-31 19:54:38 -04:00
Andrew Gallant
c8e2fa1869 update Cargo.lock 2016-10-31 19:54:38 -04:00
Andrew Gallant
f728708ce9 Merge pull request #204 from lyuha/master
Add .fish extension to fish shell
2016-10-30 17:31:51 -04:00
Lyuha
c302995d05 Add .fish extension to fish shell 2016-10-31 00:52:45 +09:00
Andrew Gallant
4a77cc8100 update brew tap to 0.2.5 2016-10-29 23:23:45 -04:00
Andrew Gallant
dc86666044 changelog 0.2.5 2016-10-29 22:44:07 -04:00
Andrew Gallant
6b038511c7 0.2.5 2016-10-29 22:42:28 -04:00
Andrew Gallant
c767bccade fix appveyor, sigh 2016-10-29 22:42:22 -04:00
Andrew Gallant
a075a462fa 0.2.4 2016-10-29 22:40:02 -04:00
Andrew Gallant
24f753c306 changelog 0.2.4 2016-10-29 22:27:39 -04:00
Andrew Gallant
1aae2759ad update deps 2016-10-29 22:27:29 -04:00
Andrew Gallant
91646f6cca bump ignore to 0.1.1 2016-10-29 22:19:00 -04:00
Andrew Gallant
031ace209d ignore-0.1.1 2016-10-29 22:17:39 -04:00
Andrew Gallant
942e9c4743 don't commit ignore/Cargo.lock 2016-10-29 22:17:27 -04:00
Andrew Gallant
12c9656b18 bump thread_local 2016-10-29 22:15:24 -04:00
31 changed files with 1929 additions and 795 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ tags
target target
/grep/Cargo.lock /grep/Cargo.lock
/globset/Cargo.lock /globset/Cargo.lock
/ignore/Cargo.lock

View File

@@ -1,3 +1,47 @@
0.2.6
=====
Feature enhancements:
* Added or improved file type filtering for Fish.
Bug fixes:
* [BUG #206](https://github.com/BurntSushi/ripgrep/issues/206):
Fixed a regression with `-g/--glob` flag in `0.2.5`.
0.2.5
=====
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.4
=====
SKIPPED.
0.2.3 0.2.3
===== =====
Bug fixes: Bug fixes:

83
Cargo.lock generated
View File

@@ -1,13 +1,13 @@
[root] [root]
name = "ripgrep" name = "ripgrep"
version = "0.2.3" version = "0.2.6"
dependencies = [ dependencies = [
"bytecount 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"grep 0.1.3", "grep 0.1.3",
"ignore 0.1.0", "ignore 0.1.3",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -29,6 +29,19 @@ dependencies = [
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "bytecount"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "ctrlc" name = "ctrlc"
version = "2.0.1" version = "2.0.1"
@@ -39,14 +52,6 @@ dependencies = [
"winapi 0.2.8 (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]] [[package]]
name = "docopt" name = "docopt"
version = "0.6.86" version = "0.6.86"
@@ -84,7 +89,7 @@ dependencies = [
[[package]] [[package]]
name = "globset" name = "globset"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -107,14 +112,15 @@ dependencies = [
[[package]] [[package]]
name = "ignore" name = "ignore"
version = "0.1.0" version = "0.1.3"
dependencies = [ dependencies = [
"globset 0.1.0", "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.1.1",
"lazy_static 0.2.1 (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)", "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)", "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)", "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.2.7 (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)", "walkdir 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@@ -169,14 +175,6 @@ dependencies = [
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "regex" name = "regex"
version = "0.1.80" version = "0.1.80"
@@ -228,6 +226,15 @@ dependencies = [
"libc 0.2.17 (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]] [[package]]
name = "thread_local" name = "thread_local"
version = "0.2.7" version = "0.2.7"
@@ -236,11 +243,33 @@ dependencies = [
"thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "utf8-ranges" name = "utf8-ranges"
version = "0.1.3" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "1.0.1" version = "1.0.1"
@@ -262,8 +291,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata] [metadata]
"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
"checksum bytecount 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49e3c21915578e2300b08d3c174a8ac887e0c6421dff86fdc4d741dc29e5d413"
"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97"
"checksum ctrlc 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "77f98bb69e3fefadcc5ca80a1368a55251f70295168203e01165bcaecb270891" "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 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 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 fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
@@ -275,7 +305,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" "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 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 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 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 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 rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b"
@@ -283,8 +312,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "50c069df92e4b01425a8bf3576d5d417943a6a7272fbabaf5bd80b1aaa76442e" "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 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 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.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 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 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 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" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ripgrep" name = "ripgrep"
version = "0.2.3" #:version version = "0.2.6" #:version
authors = ["Andrew Gallant <jamslam@gmail.com>"] authors = ["Andrew Gallant <jamslam@gmail.com>"]
description = """ description = """
Line oriented search tool using Rust's regex library. Combines the raw Line oriented search tool using Rust's regex library. Combines the raw
@@ -24,12 +24,12 @@ name = "integration"
path = "tests/tests.rs" path = "tests/tests.rs"
[dependencies] [dependencies]
bytecount = "0.1.4"
ctrlc = "2.0" ctrlc = "2.0"
deque = "0.3"
docopt = "0.6" docopt = "0.6"
env_logger = "0.3" env_logger = "0.3"
grep = { version = "0.1.3", path = "grep" } grep = { version = "0.1.3", path = "grep" }
ignore = { version = "0.1.0", path = "ignore" } ignore = { version = "0.1.3", path = "ignore" }
lazy_static = "0.2" lazy_static = "0.2"
libc = "0.2" libc = "0.2"
log = "0.3" log = "0.3"
@@ -45,7 +45,8 @@ kernel32-sys = "0.2"
winapi = "0.2" winapi = "0.2"
[features] [features]
simd-accel = ["regex/simd-accel"] avx-accel = ["bytecount/avx-accel"]
simd-accel = ["bytecount/avx-accel", "regex/simd-accel"]
[profile.release] [profile.release]
debug = true debug = true

View File

@@ -118,6 +118,12 @@ If you're an **Arch Linux** user, then you can install `ripgrep` from the offici
$ pacman -S ripgrep $ pacman -S ripgrep
``` ```
If you're a **Gentoo** user, you can install `ripgrep` from the [official repo](https://packages.gentoo.org/packages/sys-apps/ripgrep):
```
$ emerge ripgrep
```
If you're a **Fedora 24+** user, you can install `ripgrep` from [copr](https://copr.fedorainfracloud.org/coprs/carlgeorge/ripgrep/): If you're a **Fedora 24+** user, you can install `ripgrep` from [copr](https://copr.fedorainfracloud.org/coprs/carlgeorge/ripgrep/):
``` ```

View File

@@ -60,6 +60,7 @@ deploy:
branches: branches:
only: only:
- /\d+\.\d+\.\d+/
- master - master
# - appveyor # - appveyor
# - /\d+\.\d+\.\d+/ # - /\d+\.\d+\.\d+/

View File

@@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
export RUSTFLAGS="-C target-feature=+ssse3" # export RUSTFLAGS="-C target-feature=+ssse3"
# export RUSTFLAGS="-C target-cpu=native" export RUSTFLAGS="-C target-cpu=native"
cargo build --release --features simd-accel cargo build --release --features 'simd-accel avx-accel'

View File

@@ -1,4 +1,4 @@
.\" Automatically generated by Pandoc 1.17.2 .\" Automatically generated by Pandoc 1.18
.\" .\"
.TH "rg" "1" .TH "rg" "1"
.hy .hy
@@ -21,8 +21,10 @@ rg [\f[I]options\f[]] \-\-help
rg [\f[I]options\f[]] \-\-version rg [\f[I]options\f[]] \-\-version
.SH DESCRIPTION .SH DESCRIPTION
.PP .PP
rg (ripgrep) combines the usability of The Silver Searcher (an ack ripgrep (rg) combines the usability of The Silver Searcher (an ack
clone) with the raw speed of grep. clone) with the raw speed of grep.
.PP
Project home page: https://github.com/BurntSushi/ripgrep
.SH COMMON OPTIONS .SH COMMON OPTIONS
.TP .TP
.B \-a, \-\-text .B \-a, \-\-text
@@ -46,6 +48,7 @@ Valid values are never, always or auto.
Use PATTERN to search. Use PATTERN to search.
This option can be provided multiple times, where all patterns given are This option can be provided multiple times, where all patterns given are
searched. searched.
This is also useful when searching for patterns that start with a dash.
.RS .RS
.RE .RE
.TP .TP
@@ -204,6 +207,11 @@ Follow symlinks.
.RS .RS
.RE .RE
.TP .TP
.B \-m, \-\-max\-count NUM
Limit the number of matching lines per file searched to NUM.
.RS
.RE
.TP
.B \-\-maxdepth \f[I]NUM\f[] .B \-\-maxdepth \f[I]NUM\f[]
Descend at most NUM directories below the command line arguments. Descend at most NUM directories below the command line arguments.
A value of zero searches only the starting\-points themselves. A value of zero searches only the starting\-points themselves.
@@ -218,6 +226,11 @@ context related options.)
.RS .RS
.RE .RE
.TP .TP
.B \-\-no\-messages
Suppress all error messages.
.RS
.RE
.TP
.B \-\-no\-mmap .B \-\-no\-mmap
Never use memory maps, even when they might be faster. Never use memory maps, even when they might be faster.
.RS .RS

View File

@@ -18,9 +18,11 @@ rg [*options*] --version
# DESCRIPTION # DESCRIPTION
rg (ripgrep) combines the usability of The Silver Searcher (an ack clone) with ripgrep (rg) combines the usability of The Silver Searcher (an ack clone) with
the raw speed of grep. the raw speed of grep.
Project home page: https://github.com/BurntSushi/ripgrep
# COMMON OPTIONS # COMMON OPTIONS
-a, --text -a, --text
@@ -35,7 +37,8 @@ the raw speed of grep.
-e, --regexp *PATTERN* ... -e, --regexp *PATTERN* ...
: Use PATTERN to search. This option can be provided multiple times, where all : Use PATTERN to search. This option can be provided multiple times, where all
patterns given are searched. patterns given are searched. This is also useful when searching for patterns
that start with a dash.
-F, --fixed-strings -F, --fixed-strings
: Treat the pattern as a literal string instead of a regular expression. : Treat the pattern as a literal string instead of a regular expression.
@@ -132,6 +135,9 @@ the raw speed of grep.
-L, --follow -L, --follow
: Follow symlinks. : Follow symlinks.
-m, --max-count NUM
: Limit the number of matching lines per file searched to NUM.
--maxdepth *NUM* --maxdepth *NUM*
: Descend at most NUM directories below the command line arguments. : Descend at most NUM directories below the command line arguments.
A value of zero searches only the starting-points themselves. A value of zero searches only the starting-points themselves.
@@ -141,6 +147,9 @@ the raw speed of grep.
when ripgrep thinks it will be faster. (Note that mmap searching when ripgrep thinks it will be faster. (Note that mmap searching
doesn't currently support the various context related options.) doesn't currently support the various context related options.)
--no-messages
: Suppress all error messages.
--no-mmap --no-mmap
: Never use memory maps, even when they might be faster. : Never use memory maps, even when they might be faster.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "globset" name = "globset"
version = "0.1.1" #:version version = "0.1.2" #:version
authors = ["Andrew Gallant <jamslam@gmail.com>"] authors = ["Andrew Gallant <jamslam@gmail.com>"]
description = """ description = """
Cross platform single glob and glob set matching. Glob set matching is the Cross platform single glob and glob set matching. Glob set matching is the

View File

@@ -130,13 +130,6 @@ pub use glob::{Glob, GlobBuilder, GlobMatcher};
mod glob; mod glob;
mod pathutil; 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. /// Represents an error that can occur when parsing a glob pattern.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum Error { pub enum Error {

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "grep" name = "grep"
version = "0.1.3" #:version version = "0.1.4" #:version
authors = ["Andrew Gallant <jamslam@gmail.com>"] authors = ["Andrew Gallant <jamslam@gmail.com>"]
description = """ description = """
Fast line oriented regex searching as a library. Fast line oriented regex searching as a library.

View File

@@ -9,7 +9,7 @@ principled.
*/ */
use std::cmp; use std::cmp;
use regex::bytes::Regex; use regex::bytes::RegexBuilder;
use syntax::{ use syntax::{
Expr, Literals, Lit, Expr, Literals, Lit,
ByteClass, ByteRange, CharClass, ClassRange, Repeater, ByteClass, ByteRange, CharClass, ClassRange, Repeater,
@@ -33,7 +33,7 @@ impl LiteralSets {
} }
} }
pub fn to_regex(&self) -> Option<Regex> { pub fn to_regex_builder(&self) -> Option<RegexBuilder> {
if self.prefixes.all_complete() && !self.prefixes.is_empty() { if self.prefixes.all_complete() && !self.prefixes.is_empty() {
debug!("literal prefixes detected: {:?}", self.prefixes); debug!("literal prefixes detected: {:?}", self.prefixes);
// When this is true, the regex engine will do a literal scan. // When this is true, the regex engine will do a literal scan.
@@ -79,14 +79,12 @@ impl LiteralSets {
debug!("required literals found: {:?}", req_lits); debug!("required literals found: {:?}", req_lits);
let alts: Vec<String> = let alts: Vec<String> =
req_lits.into_iter().map(|x| bytes_to_regex(x)).collect(); req_lits.into_iter().map(|x| bytes_to_regex(x)).collect();
// Literals always compile. Some(RegexBuilder::new(&alts.join("|")))
Some(Regex::new(&alts.join("|")).unwrap())
} else if lit.is_empty() { } else if lit.is_empty() {
None None
} else { } else {
// Literals always compile.
debug!("required literal found: {:?}", show(lit)); debug!("required literal found: {:?}", show(lit));
Some(Regex::new(&bytes_to_regex(lit)).unwrap()) Some(RegexBuilder::new(&bytes_to_regex(lit)))
} }
} }
} }

View File

@@ -144,14 +144,19 @@ impl GrepBuilder {
let expr = try!(self.parse()); let expr = try!(self.parse());
let literals = LiteralSets::create(&expr); let literals = LiteralSets::create(&expr);
let re = try!(self.regex(&expr)); let re = try!(self.regex(&expr));
let required = literals.to_regex().or_else(|| { let required = match literals.to_regex_builder() {
let expr = match strip_unicode_word_boundaries(&expr) { Some(builder) => Some(try!(self.regex_build(builder))),
None => return None, None => {
Some(expr) => expr, match strip_unicode_word_boundaries(&expr) {
}; None => None,
debug!("Stripped Unicode word boundaries. New AST:\n{:?}", expr); Some(expr) => {
self.regex(&expr).ok() debug!("Stripped Unicode word boundaries. \
}); New AST:\n{:?}", expr);
self.regex(&expr).ok()
}
}
}
};
Ok(Grep { Ok(Grep {
re: re, re: re,
required: required, required: required,
@@ -162,11 +167,12 @@ impl GrepBuilder {
/// Creates a new regex from the given expression with the current /// Creates a new regex from the given expression with the current
/// configuration. /// configuration.
fn regex(&self, expr: &Expr) -> Result<Regex> { fn regex(&self, expr: &Expr) -> Result<Regex> {
let casei = self.regex_build(RegexBuilder::new(&expr.to_string()))
self.opts.case_insensitive }
|| (self.opts.case_smart && !has_uppercase_literal(expr));
RegexBuilder::new(&expr.to_string()) /// Builds a new regex from the given builder using the caller's settings.
.case_insensitive(casei) fn regex_build(&self, builder: RegexBuilder) -> Result<Regex> {
builder
.multi_line(true) .multi_line(true)
.unicode(true) .unicode(true)
.size_limit(self.opts.size_limit) .size_limit(self.opts.size_limit)
@@ -182,12 +188,30 @@ impl GrepBuilder {
try!(syntax::ExprBuilder::new() try!(syntax::ExprBuilder::new()
.allow_bytes(true) .allow_bytes(true)
.unicode(true) .unicode(true)
.case_insensitive(self.opts.case_insensitive) .case_insensitive(try!(self.is_case_insensitive()))
.parse(&self.pattern)); .parse(&self.pattern));
let expr = try!(nonl::remove(expr, self.opts.line_terminator)); let expr = try!(nonl::remove(expr, self.opts.line_terminator));
debug!("regex ast:\n{:#?}", expr); debug!("regex ast:\n{:#?}", expr);
Ok(expr) Ok(expr)
} }
/// Determines whether the case insensitive flag should be enabled or not.
///
/// An error is returned if the regex could not be parsed.
fn is_case_insensitive(&self) -> Result<bool> {
if self.opts.case_insensitive {
return Ok(true);
}
if !self.opts.case_smart {
return Ok(false);
}
let expr =
try!(syntax::ExprBuilder::new()
.allow_bytes(true)
.unicode(true)
.parse(&self.pattern));
Ok(!has_uppercase_literal(&expr))
}
} }
impl Grep { impl Grep {

170
ignore/Cargo.lock generated
View File

@@ -1,170 +0,0 @@
[root]
name = "ignore"
version = "0.1.0"
dependencies = [
"globset 0.1.0",
"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.77 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 1.0.1 (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 = "fnv"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "globset"
version = "0.1.0"
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.77 (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.16"
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.16 (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.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "0.1.77"
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.7 (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.7"
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 = "tempdir"
version = "0.3.5"
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 = "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.16 (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 = "utf8-ranges"
version = "0.1.3"
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 fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
"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.16 (registry+https://github.com/rust-lang/crates.io-index)" = "408014cace30ee0f767b1c4517980646a573ec61a57957aeeabcac8ac0a02e8d"
"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 rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5"
"checksum regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)" = "64b03446c466d35b42f2a8b203c8e03ed8b91c0f17b56e1f84f7210a257aa665"
"checksum regex-syntax 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "48f0573bcee95a48da786f8823465b5f2a1fae288a55407aca991e5b3e0eae11"
"checksum simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63b5847c2d766ca7ce7227672850955802fabd779ba616aeabead4c2c3877023"
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
"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"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ignore" name = "ignore"
version = "0.1.0" #:version version = "0.1.3" #:version
authors = ["Andrew Gallant <jamslam@gmail.com>"] authors = ["Andrew Gallant <jamslam@gmail.com>"]
description = """ description = """
A fast library for efficiently matching ignore files such as `.gitignore` A fast library for efficiently matching ignore files such as `.gitignore`
@@ -18,12 +18,13 @@ name = "ignore"
bench = false bench = false
[dependencies] [dependencies]
crossbeam = "0.2"
globset = { version = "0.1.1", path = "../globset" } globset = { version = "0.1.1", path = "../globset" }
lazy_static = "0.2" lazy_static = "0.2"
log = "0.3" log = "0.3"
memchr = "0.1" memchr = "0.1"
regex = "0.1.77" regex = "0.1.77"
thread_local = "0.2.7" thread_local = "0.3.0"
walkdir = "1" walkdir = "1"
[dev-dependencies] [dev-dependencies]

View File

@@ -1,28 +1,92 @@
/* #![allow(dead_code, unused_imports, unused_mut, unused_variables)]
extern crate crossbeam;
extern crate ignore; extern crate ignore;
extern crate walkdir; extern crate walkdir;
use std::env; use std::env;
use std::io::{self, Write}; use std::io::{self, Write};
use std::os::unix::ffi::OsStrExt; use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use ignore::ignore::IgnoreBuilder; use crossbeam::sync::MsQueue;
use ignore::WalkBuilder;
use walkdir::WalkDir; use walkdir::WalkDir;
fn main() { fn main() {
let path = env::args().nth(1).unwrap(); let mut path = env::args().nth(1).unwrap();
let ig = IgnoreBuilder::new().build(); let mut parallel = false;
let wd = WalkDir::new(path); let mut simple = false;
let walker = ignore::walk::Iter::new(ig, wd); let queue: Arc<MsQueue<Option<DirEntry>>> = Arc::new(MsQueue::new());
if path == "parallel" {
let mut stdout = io::BufWriter::new(io::stdout()); path = env::args().nth(2).unwrap();
// let mut count = 0; parallel = true;
for dirent in walker { } else if path == "walkdir" {
// count += 1; path = env::args().nth(2).unwrap();
stdout.write(dirent.path().as_os_str().as_bytes()).unwrap(); simple = true;
stdout.write(b"\n").unwrap();
} }
// println!("{}", count);
let stdout_queue = queue.clone();
let stdout_thread = thread::spawn(move || {
let mut stdout = io::BufWriter::new(io::stdout());
while let Some(dent) = stdout_queue.pop() {
write_path(&mut stdout, dent.path());
}
});
if parallel {
let walker = WalkBuilder::new(path).threads(6).build_parallel();
walker.run(|| {
let queue = queue.clone();
Box::new(move |result| {
use ignore::WalkState::*;
queue.push(Some(DirEntry::Y(result.unwrap())));
Continue
})
});
} else if simple {
let mut stdout = io::BufWriter::new(io::stdout());
let walker = WalkDir::new(path);
for result in walker {
queue.push(Some(DirEntry::X(result.unwrap())));
}
} else {
let mut stdout = io::BufWriter::new(io::stdout());
let walker = WalkBuilder::new(path).build();
for result in walker {
queue.push(Some(DirEntry::Y(result.unwrap())));
}
}
queue.push(None);
stdout_thread.join().unwrap();
}
enum DirEntry {
X(walkdir::DirEntry),
Y(ignore::DirEntry),
}
impl DirEntry {
fn path(&self) -> &Path {
match *self {
DirEntry::X(ref x) => x.path(),
DirEntry::Y(ref y) => y.path(),
}
}
}
#[cfg(unix)]
fn write_path<W: Write>(mut wtr: W, path: &Path) {
use std::os::unix::ffi::OsStrExt;
wtr.write(path.as_os_str().as_bytes()).unwrap();
wtr.write(b"\n").unwrap();
}
#[cfg(not(unix))]
fn write_path<W: Write>(mut wtr: W, path: &Path) {
wtr.write(path.to_string_lossy().as_bytes()).unwrap();
wtr.write(b"\n").unwrap();
} }
*/
fn main() {}

View File

@@ -137,6 +137,11 @@ impl Ignore {
self.0.parent.is_none() self.0.parent.is_none()
} }
/// Returns true if this matcher was added via the `add_parents` method.
pub fn is_absolute_parent(&self) -> bool {
self.0.is_absolute_parent
}
/// Return this matcher's parent, if one exists. /// Return this matcher's parent, if one exists.
pub fn parent(&self) -> Option<Ignore> { pub fn parent(&self) -> Option<Ignore> {
self.0.parent.clone() self.0.parent.clone()
@@ -376,7 +381,7 @@ impl Ignore {
} }
/// Returns an iterator over parent ignore matchers, including this one. /// Returns an iterator over parent ignore matchers, including this one.
fn parents(&self) -> Parents { pub fn parents(&self) -> Parents {
Parents(Some(self)) Parents(Some(self))
} }
@@ -387,7 +392,10 @@ impl Ignore {
} }
} }
struct Parents<'a>(Option<&'a Ignore>); /// An iterator over all parents of an ignore matcher, including itself.
///
/// The lifetime `'a` refers to the lifetime of the initial `Ignore` matcher.
pub struct Parents<'a>(Option<&'a Ignore>);
impl<'a> Iterator for Parents<'a> { impl<'a> Iterator for Parents<'a> {
type Item = &'a Ignore; type Item = &'a Ignore;

View File

@@ -44,6 +44,7 @@ for result in WalkBuilder::new("./").hidden(false).build() {
See the documentation for `WalkBuilder` for many other options. See the documentation for `WalkBuilder` for many other options.
*/ */
extern crate crossbeam;
extern crate globset; extern crate globset;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
@@ -61,7 +62,7 @@ use std::fmt;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub use walk::{DirEntry, Walk, WalkBuilder}; pub use walk::{DirEntry, Walk, WalkBuilder, WalkParallel, WalkState};
mod dir; mod dir;
pub mod gitignore; pub mod gitignore;
@@ -80,6 +81,12 @@ pub enum Error {
WithLineNumber { line: u64, err: Box<Error> }, WithLineNumber { line: u64, err: Box<Error> },
/// An error associated with a particular file path. /// An error associated with a particular file path.
WithPath { path: PathBuf, err: Box<Error> }, WithPath { path: PathBuf, err: Box<Error> },
/// An error associated with a particular directory depth when recursively
/// walking a directory.
WithDepth { depth: usize, err: Box<Error> },
/// An error that occurs when a file loop is detected when traversing
/// symbolic links.
Loop { ancestor: PathBuf, child: PathBuf },
/// An error that occurs when doing I/O, such as reading an ignore file. /// An error that occurs when doing I/O, such as reading an ignore file.
Io(io::Error), Io(io::Error),
/// An error that occurs when trying to parse a glob. /// An error that occurs when trying to parse a glob.
@@ -101,6 +108,7 @@ impl Error {
Error::Partial(_) => true, Error::Partial(_) => true,
Error::WithLineNumber { ref err, .. } => err.is_partial(), Error::WithLineNumber { ref err, .. } => err.is_partial(),
Error::WithPath { ref err, .. } => err.is_partial(), Error::WithPath { ref err, .. } => err.is_partial(),
Error::WithDepth { ref err, .. } => err.is_partial(),
_ => false, _ => false,
} }
} }
@@ -111,6 +119,8 @@ impl Error {
Error::Partial(ref errs) => errs.len() == 1 && errs[0].is_io(), Error::Partial(ref errs) => errs.len() == 1 && errs[0].is_io(),
Error::WithLineNumber { ref err, .. } => err.is_io(), Error::WithLineNumber { ref err, .. } => err.is_io(),
Error::WithPath { ref err, .. } => err.is_io(), Error::WithPath { ref err, .. } => err.is_io(),
Error::WithDepth { ref err, .. } => err.is_io(),
Error::Loop { .. } => false,
Error::Io(_) => true, Error::Io(_) => true,
Error::Glob(_) => false, Error::Glob(_) => false,
Error::UnrecognizedFileType(_) => false, Error::UnrecognizedFileType(_) => false,
@@ -118,6 +128,16 @@ impl Error {
} }
} }
/// Returns a depth associated with recursively walking a directory (if
/// this error was generated from a recursive directory iterator).
pub fn depth(&self) -> Option<usize> {
match *self {
Error::WithPath { ref err, .. } => err.depth(),
Error::WithDepth { depth, .. } => Some(depth),
_ => None,
}
}
/// Turn an error into a tagged error with the given file path. /// Turn an error into a tagged error with the given file path.
fn with_path<P: AsRef<Path>>(self, path: P) -> Error { fn with_path<P: AsRef<Path>>(self, path: P) -> Error {
Error::WithPath { Error::WithPath {
@@ -126,6 +146,14 @@ impl Error {
} }
} }
/// Turn an error into a tagged error with the given depth.
fn with_depth(self, depth: usize) -> Error {
Error::WithDepth {
depth: depth,
err: Box::new(self),
}
}
/// Turn an error into a tagged error with the given file path and line /// 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. /// number. If path is empty, then it is omitted from the error.
fn tagged<P: AsRef<Path>>(self, path: P, lineno: u64) -> Error { fn tagged<P: AsRef<Path>>(self, path: P, lineno: u64) -> Error {
@@ -146,6 +174,8 @@ impl error::Error for Error {
Error::Partial(_) => "partial error", Error::Partial(_) => "partial error",
Error::WithLineNumber { ref err, .. } => err.description(), Error::WithLineNumber { ref err, .. } => err.description(),
Error::WithPath { ref err, .. } => err.description(), Error::WithPath { ref err, .. } => err.description(),
Error::WithDepth { ref err, .. } => err.description(),
Error::Loop { .. } => "file system loop found",
Error::Io(ref err) => err.description(), Error::Io(ref err) => err.description(),
Error::Glob(ref msg) => msg, Error::Glob(ref msg) => msg,
Error::UnrecognizedFileType(_) => "unrecognized file type", Error::UnrecognizedFileType(_) => "unrecognized file type",
@@ -168,6 +198,12 @@ impl fmt::Display for Error {
Error::WithPath { ref path, ref err } => { Error::WithPath { ref path, ref err } => {
write!(f, "{}: {}", path.display(), err) write!(f, "{}: {}", path.display(), err)
} }
Error::WithDepth { ref err, .. } => err.fmt(f),
Error::Loop { ref ancestor, ref child } => {
write!(f, "File system loop found: \
{} points to an ancestor {}",
child.display(), ancestor.display())
}
Error::Io(ref err) => err.fmt(f), Error::Io(ref err) => err.fmt(f),
Error::Glob(ref msg) => write!(f, "{}", msg), Error::Glob(ref msg) => write!(f, "{}", msg),
Error::UnrecognizedFileType(ref ty) => { Error::UnrecognizedFileType(ref ty) => {
@@ -187,6 +223,30 @@ impl From<io::Error> for Error {
} }
} }
impl From<walkdir::Error> for Error {
fn from(err: walkdir::Error) -> Error {
let depth = err.depth();
if let (Some(anc), Some(child)) = (err.loop_ancestor(), err.path()) {
return Error::WithDepth {
depth: depth,
err: Box::new(Error::Loop {
ancestor: anc.to_path_buf(),
child: child.to_path_buf(),
}),
};
}
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,
err: Box::new(ig_err),
};
}
ig_err
}
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct PartialErrorBuilder(Vec<Error>); struct PartialErrorBuilder(Vec<Error>);

View File

@@ -79,8 +79,9 @@ impl Override {
/// ///
/// If there are no overrides, then this always returns `Match::None`. /// If there are no overrides, then this always returns `Match::None`.
/// ///
/// If there is at least one whitelist override, then this never returns /// If there is at least one whitelist override and `is_dir` is false, then
/// `Match::None`, since non-matches are interpreted as ignored. /// 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 /// The given path is matched to the globs relative to the path given
/// when building the override matcher. Specifically, before matching /// when building the override matcher. Specifically, before matching
@@ -97,7 +98,7 @@ impl Override {
return Match::None; return Match::None;
} }
let mat = self.0.matched(path, is_dir).invert(); let mat = self.0.matched(path, is_dir).invert();
if mat.is_none() && self.num_whitelists() > 0 { if mat.is_none() && self.num_whitelists() > 0 && !is_dir {
return Match::Ignore(Glob::unmatched()); return Match::Ignore(Glob::unmatched());
} }
mat.map(move |giglob| Glob(GlobInner::Matched(giglob))) mat.map(move |giglob| Glob(GlobInner::Matched(giglob)))
@@ -166,7 +167,7 @@ mod tests {
assert!(ov.matched("a.foo", false).is_whitelist()); assert!(ov.matched("a.foo", false).is_whitelist());
assert!(ov.matched("a.foo", true).is_whitelist()); assert!(ov.matched("a.foo", true).is_whitelist());
assert!(ov.matched("a.rs", false).is_ignore()); assert!(ov.matched("a.rs", false).is_ignore());
assert!(ov.matched("a.rs", true).is_ignore()); assert!(ov.matched("a.rs", true).is_none());
assert!(ov.matched("a.bar", false).is_ignore()); assert!(ov.matched("a.bar", false).is_ignore());
assert!(ov.matched("a.bar", true).is_ignore()); assert!(ov.matched("a.bar", true).is_ignore());
} }
@@ -199,4 +200,18 @@ mod tests {
assert!(ov.matched("baz/a", false).is_whitelist()); assert!(ov.matched("baz/a", false).is_whitelist());
assert!(ov.matched("baz/a/b", false).is_whitelist()); assert!(ov.matched("baz/a/b", false).is_whitelist());
} }
#[test]
fn allow_directories() {
// This tests that directories are NOT ignored when they are unmatched.
let ov = ov(&["*.rs"]);
assert!(ov.matched("foo.rs", false).is_whitelist());
assert!(ov.matched("foo.c", false).is_ignore());
assert!(ov.matched("foo", false).is_ignore());
assert!(ov.matched("foo", true).is_none());
assert!(ov.matched("src/foo.rs", false).is_whitelist());
assert!(ov.matched("src/foo.c", false).is_ignore());
assert!(ov.matched("src/foo", false).is_ignore());
assert!(ov.matched("src/foo", true).is_none());
}
} }

View File

@@ -80,6 +80,8 @@ use pathutil::file_name;
use {Error, Match}; use {Error, Match};
const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
("agda", &["*.agda", "*.lagda"]),
("asciidoc", &["*.adoc", "*.asc", "*.asciidoc"]),
("asm", &["*.asm", "*.s", "*.S"]), ("asm", &["*.asm", "*.s", "*.S"]),
("awk", &["*.awk"]), ("awk", &["*.awk"]),
("c", &["*.c", "*.h", "*.H"]), ("c", &["*.c", "*.h", "*.H"]),
@@ -87,11 +89,13 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
("clojure", &["*.clj", "*.cljc", "*.cljs", "*.cljx"]), ("clojure", &["*.clj", "*.cljc", "*.cljs", "*.cljx"]),
("cmake", &["*.cmake", "CMakeLists.txt"]), ("cmake", &["*.cmake", "CMakeLists.txt"]),
("coffeescript", &["*.coffee"]), ("coffeescript", &["*.coffee"]),
("creole", &["*.creole"]),
("config", &["*.config"]), ("config", &["*.config"]),
("cpp", &[ ("cpp", &[
"*.C", "*.cc", "*.cpp", "*.cxx", "*.C", "*.cc", "*.cpp", "*.cxx",
"*.h", "*.H", "*.hh", "*.hpp", "*.h", "*.H", "*.hh", "*.hpp",
]), ]),
("cs", &["*.cs"]),
("csharp", &["*.cs"]), ("csharp", &["*.cs"]),
("css", &["*.css"]), ("css", &["*.css"]),
("cython", &["*.pyx"]), ("cython", &["*.pyx"]),
@@ -99,6 +103,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
("d", &["*.d"]), ("d", &["*.d"]),
("elisp", &["*.el"]), ("elisp", &["*.el"]),
("erlang", &["*.erl", "*.hrl"]), ("erlang", &["*.erl", "*.hrl"]),
("fish", &["*.fish"]),
("fortran", &[ ("fortran", &[
"*.f", "*.F", "*.f77", "*.F77", "*.pfo", "*.f", "*.F", "*.f77", "*.F77", "*.pfo",
"*.f90", "*.F90", "*.f95", "*.F95", "*.f90", "*.F90", "*.f95", "*.F95",
@@ -106,6 +111,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
("fsharp", &["*.fs", "*.fsx", "*.fsi"]), ("fsharp", &["*.fs", "*.fsx", "*.fsi"]),
("go", &["*.go"]), ("go", &["*.go"]),
("groovy", &["*.groovy", "*.gradle"]), ("groovy", &["*.groovy", "*.gradle"]),
("h", &["*.h", "*.hpp"]),
("hbs", &["*.hbs"]), ("hbs", &["*.hbs"]),
("haskell", &["*.hs", "*.lhs"]), ("haskell", &["*.hs", "*.lhs"]),
("html", &["*.htm", "*.html"]), ("html", &["*.htm", "*.html"]),
@@ -119,9 +125,9 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]), ("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
("lua", &["*.lua"]), ("lua", &["*.lua"]),
("m4", &["*.ac", "*.m4"]), ("m4", &["*.ac", "*.m4"]),
("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk"]), ("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk", "*.mak"]),
("markdown", &["*.md"]), ("markdown", &["*.markdown", "*.md", "*.mdown", "*.mkdn"]),
("md", &["*.md"]), ("md", &["*.markdown", "*.md", "*.mdown", "*.mkdn"]),
("matlab", &["*.m"]), ("matlab", &["*.m"]),
("mk", &["mkfile"]), ("mk", &["mkfile"]),
("ml", &["*.ml"]), ("ml", &["*.ml"]),
@@ -129,11 +135,15 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
("objc", &["*.h", "*.m"]), ("objc", &["*.h", "*.m"]),
("objcpp", &["*.h", "*.mm"]), ("objcpp", &["*.h", "*.mm"]),
("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]), ("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]),
("org", &["*.org"]),
("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm"]), ("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm"]),
("pdf", &["*.pdf"]),
("php", &["*.php", "*.php3", "*.php4", "*.php5", "*.phtml"]), ("php", &["*.php", "*.php3", "*.php4", "*.php5", "*.phtml"]),
("pod", &["*.pod"]),
("py", &["*.py"]), ("py", &["*.py"]),
("readme", &["README*", "*README"]), ("readme", &["README*", "*README"]),
("r", &["*.R", "*.r", "*.Rmd", "*.Rnw"]), ("r", &["*.R", "*.r", "*.Rmd", "*.Rnw"]),
("rdoc", &["*.rdoc"]),
("rst", &["*.rst"]), ("rst", &["*.rst"]),
("ruby", &["*.rb"]), ("ruby", &["*.rb"]),
("rust", &["*.rs"]), ("rust", &["*.rs"]),
@@ -143,14 +153,17 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
("sql", &["*.sql"]), ("sql", &["*.sql"]),
("sv", &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]), ("sv", &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
("swift", &["*.swift"]), ("swift", &["*.swift"]),
("taskpaper", &["*.taskpaper"]),
("tcl", &["*.tcl"]), ("tcl", &["*.tcl"]),
("tex", &["*.tex", "*.cls", "*.sty"]), ("tex", &["*.tex", "*.ltx", "*.cls", "*.sty", "*.bib"]),
("textile", &["*.textile"]),
("ts", &["*.ts", "*.tsx"]), ("ts", &["*.ts", "*.tsx"]),
("txt", &["*.txt"]), ("txt", &["*.txt"]),
("toml", &["*.toml", "Cargo.lock"]), ("toml", &["*.toml", "Cargo.lock"]),
("vala", &["*.vala"]), ("vala", &["*.vala"]),
("vb", &["*.vb"]), ("vb", &["*.vb"]),
("vimscript", &["*.vim"]), ("vimscript", &["*.vim"]),
("wiki", &["*.mediawiki", "*.wiki"]),
("xml", &["*.xml"]), ("xml", &["*.xml"]),
("yacc", &["*.y"]), ("yacc", &["*.y"]),
("yaml", &["*.yaml", "*.yml"]), ("yaml", &["*.yaml", "*.yml"]),

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
class RipgrepBin < Formula class RipgrepBin < Formula
version '0.2.3' version '0.2.5'
desc "Search tool like grep and The Silver Searcher." desc "Search tool like grep and The Silver Searcher."
homepage "https://github.com/BurntSushi/ripgrep" homepage "https://github.com/BurntSushi/ripgrep"
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-apple-darwin.tar.gz" url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-apple-darwin.tar.gz"
sha256 "7407555dfe040a2631a7efdd1eea62cf1d1c50e5a6ecf8ee82e0bef9d5f37298" sha256 "c6775a50c6f769de2ee66892a700961ec60a85219aa414ef6880dfcc17bf2467"
conflicts_with "ripgrep" conflicts_with "ripgrep"

View File

@@ -1,9 +1,9 @@
use std::cmp;
use std::env; use std::env;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process;
use docopt::Docopt; use docopt::{self, Docopt};
use env_logger; use env_logger;
use grep::{Grep, GrepBuilder}; use grep::{Grep, GrepBuilder};
use log; use log;
@@ -21,10 +21,9 @@ use ignore::types::{FileTypeDef, Types, TypesBuilder};
use ignore; use ignore;
use out::{Out, ColoredTerminal}; use out::{Out, ColoredTerminal};
use printer::Printer; use printer::Printer;
use search_buffer::BufferSearcher;
use search_stream::{InputBuffer, Searcher};
#[cfg(windows)] #[cfg(windows)]
use terminal_win::WindowsBuffer; use terminal_win::WindowsBuffer;
use worker::{Worker, WorkerBuilder};
use Result; use Result;
@@ -40,7 +39,9 @@ Usage: rg [options] -e PATTERN ... [<path> ...]
rg [options] --help rg [options] --help
rg [options] --version rg [options] --version
rg recursively searches your current directory for a regex pattern. ripgrep (rg) recursively searches your current directory for a regex pattern.
Project home page: https://github.com/BurntSushi/ripgrep
Common options: Common options:
-a, --text Search binary files as if they were text. -a, --text Search binary files as if they were text.
@@ -50,7 +51,8 @@ Common options:
[default: auto] [default: auto]
-e, --regexp PATTERN ... Use PATTERN to search. This option can be -e, --regexp PATTERN ... Use PATTERN to search. This option can be
provided multiple times, where all patterns provided multiple times, where all patterns
given are searched. given are searched. This is also useful when
searching for a pattern that starts with a dash.
-F, --fixed-strings Treat the pattern as a literal string instead of -F, --fixed-strings Treat the pattern as a literal string instead of
a regular expression. a regular expression.
-g, --glob GLOB ... Include or exclude files for searching that -g, --glob GLOB ... Include or exclude files for searching that
@@ -139,6 +141,9 @@ Less common options:
-L, --follow -L, --follow
Follow symlinks. Follow symlinks.
-m, --max-count NUM
Limit the number of matching lines per file searched to NUM.
--maxdepth NUM --maxdepth NUM
Descend at most NUM directories below the command line arguments. Descend at most NUM directories below the command line arguments.
A value of zero only searches the starting-points themselves. A value of zero only searches the starting-points themselves.
@@ -148,6 +153,9 @@ Less common options:
when ripgrep thinks it will be faster. (Note that mmap searching when ripgrep thinks it will be faster. (Note that mmap searching
doesn't currently support the various context related options.) doesn't currently support the various context related options.)
--no-messages
Suppress all error messages.
--no-mmap --no-mmap
Never use memory maps, even when they might be faster. Never use memory maps, even when they might be faster.
@@ -243,6 +251,7 @@ pub struct RawArgs {
flag_invert_match: bool, flag_invert_match: bool,
flag_line_number: bool, flag_line_number: bool,
flag_fixed_strings: bool, flag_fixed_strings: bool,
flag_max_count: Option<usize>,
flag_maxdepth: Option<usize>, flag_maxdepth: Option<usize>,
flag_mmap: bool, flag_mmap: bool,
flag_no_heading: bool, flag_no_heading: bool,
@@ -250,6 +259,7 @@ pub struct RawArgs {
flag_no_ignore_parent: bool, flag_no_ignore_parent: bool,
flag_no_ignore_vcs: bool, flag_no_ignore_vcs: bool,
flag_no_line_number: bool, flag_no_line_number: bool,
flag_no_messages: bool,
flag_no_mmap: bool, flag_no_mmap: bool,
flag_no_filename: bool, flag_no_filename: bool,
flag_null: bool, flag_null: bool,
@@ -294,11 +304,13 @@ pub struct Args {
invert_match: bool, invert_match: bool,
line_number: bool, line_number: bool,
line_per_match: bool, line_per_match: bool,
max_count: Option<u64>,
maxdepth: Option<usize>, maxdepth: Option<usize>,
mmap: bool, mmap: bool,
no_ignore: bool, no_ignore: bool,
no_ignore_parent: bool, no_ignore_parent: bool,
no_ignore_vcs: bool, no_ignore_vcs: bool,
no_messages: bool,
null: bool, null: bool,
quiet: bool, quiet: bool,
replace: Option<Vec<u8>>, replace: Option<Vec<u8>>,
@@ -364,7 +376,7 @@ impl RawArgs {
}; };
let threads = let threads =
if self.flag_threads == 0 { if self.flag_threads == 0 {
cmp::min(8, num_cpus::get()) num_cpus::get()
} else { } else {
self.flag_threads self.flag_threads
}; };
@@ -412,6 +424,7 @@ impl RawArgs {
invert_match: self.flag_invert_match, invert_match: self.flag_invert_match,
line_number: !self.flag_no_line_number && self.flag_line_number, line_number: !self.flag_no_line_number && self.flag_line_number,
line_per_match: self.flag_vimgrep, line_per_match: self.flag_vimgrep,
max_count: self.flag_max_count.map(|max| max as u64),
maxdepth: self.flag_maxdepth, maxdepth: self.flag_maxdepth,
mmap: mmap, mmap: mmap,
no_ignore: no_ignore, no_ignore: no_ignore,
@@ -421,6 +434,7 @@ impl RawArgs {
no_ignore_vcs: no_ignore_vcs:
// --no-ignore implies --no-ignore-vcs // --no-ignore implies --no-ignore-vcs
self.flag_no_ignore_vcs || no_ignore, self.flag_no_ignore_vcs || no_ignore,
no_messages: self.flag_no_messages,
null: self.flag_null, null: self.flag_null,
quiet: self.flag_quiet, quiet: self.flag_quiet,
replace: self.flag_replace.clone().map(|s| s.into_bytes()), replace: self.flag_replace.clone().map(|s| s.into_bytes()),
@@ -539,7 +553,15 @@ impl Args {
let mut raw: RawArgs = let mut raw: RawArgs =
Docopt::new(USAGE) Docopt::new(USAGE)
.and_then(|d| d.argv(argv).version(Some(version())).decode()) .and_then(|d| d.argv(argv).version(Some(version())).decode())
.unwrap_or_else(|e| e.exit()); .unwrap_or_else(|e| {
match e {
docopt::Error::Version(ref v) => {
println!("ripgrep {}", v);
process::exit(0);
}
e => e.exit(),
}
});
let mut logb = env_logger::LogBuilder::new(); let mut logb = env_logger::LogBuilder::new();
if raw.flag_debug { if raw.flag_debug {
@@ -576,18 +598,6 @@ impl Args {
self.grep.clone() self.grep.clone()
} }
/// Creates a new input buffer that is used in searching.
pub fn input_buffer(&self) -> InputBuffer {
let mut inp = InputBuffer::new();
inp.eol(self.eol);
inp
}
/// Whether we should prefer memory maps for searching or not.
pub fn mmap(&self) -> bool {
self.mmap
}
/// Whether ripgrep should be quiet or not. /// Whether ripgrep should be quiet or not.
pub fn quiet(&self) -> bool { pub fn quiet(&self) -> bool {
self.quiet self.quiet
@@ -631,6 +641,11 @@ impl Args {
} }
} }
/// Returns true if the given arguments are known to never produce a match.
pub fn never_match(&self) -> bool {
self.max_count == Some(0)
}
/// Create a new buffer for use with searching. /// Create a new buffer for use with searching.
#[cfg(not(windows))] #[cfg(not(windows))]
pub fn outbuf(&self) -> ColoredTerminal<term::TerminfoTerminal<Vec<u8>>> { pub fn outbuf(&self) -> ColoredTerminal<term::TerminfoTerminal<Vec<u8>>> {
@@ -662,18 +677,16 @@ impl Args {
&self.paths &self.paths
} }
/// Create a new line based searcher whose configuration is taken from the /// Returns true if there is exactly one file path given to search.
/// command line. This searcher supports a dizzying array of features: pub fn is_one_path(&self) -> bool {
/// inverted matching, line counting, context control and more. self.paths.len() == 1
pub fn searcher<'a, R: io::Read, W: Send + Terminal>( && (self.paths[0] == Path::new("-") || self.paths[0].is_file())
&self, }
inp: &'a mut InputBuffer,
printer: &'a mut Printer<W>, /// Create a worker whose configuration is taken from the
grep: &'a Grep, /// command line.
path: &'a Path, pub fn worker(&self) -> Worker {
rdr: R, WorkerBuilder::new(self.grep())
) -> Searcher<'a, R, W> {
Searcher::new(inp, printer, grep, path, rdr)
.after_context(self.after_context) .after_context(self.after_context)
.before_context(self.before_context) .before_context(self.before_context)
.count(self.count) .count(self.count)
@@ -681,28 +694,11 @@ impl Args {
.eol(self.eol) .eol(self.eol)
.line_number(self.line_number) .line_number(self.line_number)
.invert_match(self.invert_match) .invert_match(self.invert_match)
.max_count(self.max_count)
.mmap(self.mmap)
.quiet(self.quiet) .quiet(self.quiet)
.text(self.text) .text(self.text)
} .build()
/// Create a new line based searcher whose configuration is taken from the
/// command line. This search operates on an entire file all once (which
/// may have been memory mapped).
pub fn searcher_buffer<'a, W: Send + Terminal>(
&self,
printer: &'a mut Printer<W>,
grep: &'a Grep,
path: &'a Path,
buf: &'a [u8],
) -> 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)
} }
/// Returns the number of worker search threads that should be used. /// Returns the number of worker search threads that should be used.
@@ -721,8 +717,23 @@ impl Args {
self.type_list self.type_list
} }
/// Returns true if error messages should be suppressed.
pub fn no_messages(&self) -> bool {
self.no_messages
}
/// Create a new recursive directory iterator over the paths in argv. /// Create a new recursive directory iterator over the paths in argv.
pub fn walker(&self) -> Walk { pub fn walker(&self) -> ignore::Walk {
self.walker_builder().build()
}
/// Create a new parallel recursive directory iterator over the paths
/// in argv.
pub fn walker_parallel(&self) -> ignore::WalkParallel {
self.walker_builder().build_parallel()
}
fn walker_builder(&self) -> ignore::WalkBuilder {
let paths = self.paths(); let paths = self.paths();
let mut wd = ignore::WalkBuilder::new(&paths[0]); let mut wd = ignore::WalkBuilder::new(&paths[0]);
for path in &paths[1..] { for path in &paths[1..] {
@@ -730,7 +741,9 @@ impl Args {
} }
for path in &self.ignore_files { for path in &self.ignore_files {
if let Some(err) = wd.add_ignore(path) { if let Some(err) = wd.add_ignore(path) {
eprintln!("{}", err); if !self.no_messages {
eprintln!("{}", err);
}
} }
} }
@@ -744,7 +757,8 @@ impl Args {
wd.git_exclude(!self.no_ignore && !self.no_ignore_vcs); wd.git_exclude(!self.no_ignore && !self.no_ignore_vcs);
wd.ignore(!self.no_ignore); wd.ignore(!self.no_ignore);
wd.parents(!self.no_ignore_parent); wd.parents(!self.no_ignore_parent);
Walk(wd.build()) wd.threads(self.threads());
wd
} }
} }
@@ -761,34 +775,6 @@ 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`. /// A single state in the state machine used by `unescape`.
#[derive(Clone, Copy, Eq, PartialEq)] #[derive(Clone, Copy, Eq, PartialEq)]
enum State { enum State {

View File

@@ -1,5 +1,4 @@
extern crate ctrlc; extern crate ctrlc;
extern crate deque;
extern crate docopt; extern crate docopt;
extern crate env_logger; extern crate env_logger;
extern crate grep; extern crate grep;
@@ -21,30 +20,20 @@ extern crate term;
extern crate winapi; extern crate winapi;
use std::error::Error; use std::error::Error;
use std::fs::File;
use std::io; use std::io;
use std::io::Write; use std::io::Write;
use std::path::Path;
use std::process; use std::process;
use std::result; use std::result;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc;
use std::thread; use std::thread;
use std::cmp; use std::cmp;
use deque::{Stealer, Stolen};
use grep::Grep;
use memmap::{Mmap, Protection};
use term::Terminal; use term::Terminal;
use ignore::DirEntry;
use args::Args; use args::Args;
use out::{ColoredTerminal, Out}; use worker::Work;
use pathutil::strip_prefix;
use printer::Printer;
use search_stream::InputBuffer;
#[cfg(windows)]
use terminal_win::WindowsBuffer;
macro_rules! errored { macro_rules! errored {
($($tt:tt)*) => { ($($tt:tt)*) => {
@@ -68,11 +57,12 @@ mod search_buffer;
mod search_stream; mod search_stream;
#[cfg(windows)] #[cfg(windows)]
mod terminal_win; mod terminal_win;
mod worker;
pub type Result<T> = result::Result<T, Box<Error + Send + Sync>>; pub type Result<T> = result::Result<T, Box<Error + Send + Sync>>;
fn main() { fn main() {
match Args::parse().and_then(run) { match Args::parse().map(Arc::new).and_then(run) {
Ok(count) if count == 0 => process::exit(1), Ok(count) if count == 0 => process::exit(1),
Ok(_) => process::exit(0), Ok(_) => process::exit(0),
Err(err) => { Err(err) => {
@@ -82,95 +72,113 @@ fn main() {
} }
} }
fn run(args: Args) -> Result<u64> { fn run(args: Arc<Args>) -> Result<u64> {
let args = Arc::new(args); if args.never_match() {
return Ok(0);
}
{
let args = args.clone();
ctrlc::set_handler(move || {
let stdout = io::stdout();
let mut stdout = stdout.lock();
let handler_args = args.clone(); let _ = args.stdout().reset();
ctrlc::set_handler(move || { let _ = stdout.flush();
let stdout = io::stdout();
let mut stdout = stdout.lock();
let _ = handler_args.stdout().reset(); process::exit(1);
let _ = stdout.flush(); });
}
process::exit(1);
});
let paths = args.paths();
let threads = cmp::max(1, args.threads() - 1); let threads = cmp::max(1, args.threads() - 1);
let isone =
paths.len() == 1 && (paths[0] == Path::new("-") || paths[0].is_file());
if args.files() { if args.files() {
return run_files(args.clone()); if threads == 1 || args.is_one_path() {
} run_files_one_thread(args)
if args.type_list() { } else {
return run_types(args.clone()); run_files_parallel(args)
} }
if threads == 1 || isone { } else if args.type_list() {
return run_one_thread(args.clone()); run_types(args)
} else if threads == 1 || args.is_one_path() {
run_one_thread(args)
} else {
run_parallel(args)
} }
}
fn run_parallel(args: Arc<Args>) -> Result<u64> {
let out = Arc::new(Mutex::new(args.out())); let out = Arc::new(Mutex::new(args.out()));
let quiet_matched = QuietMatched::new(args.quiet()); let quiet_matched = QuietMatched::new(args.quiet());
let mut workers = vec![]; let paths_searched = Arc::new(AtomicUsize::new(0));
let match_count = Arc::new(AtomicUsize::new(0));
let workq = { args.walker_parallel().run(|| {
let (workq, stealer) = deque::new(); let args = args.clone();
for _ in 0..threads { let quiet_matched = quiet_matched.clone();
let worker = MultiWorker { let paths_searched = paths_searched.clone();
chan_work: stealer.clone(), let match_count = match_count.clone();
quiet_matched: quiet_matched.clone(), let out = out.clone();
out: out.clone(), let mut outbuf = args.outbuf();
outbuf: Some(args.outbuf()), let mut worker = args.worker();
worker: Worker { Box::new(move |result| {
args: args.clone(), use ignore::WalkState::*;
inpbuf: args.input_buffer(),
grep: args.grep(), if quiet_matched.has_match() {
match_count: 0, return Quit;
}, }
let dent = match get_or_log_dir_entry(result, args.no_messages()) {
None => return Continue,
Some(dent) => dent,
}; };
workers.push(thread::spawn(move || worker.run())); paths_searched.fetch_add(1, Ordering::SeqCst);
} outbuf.clear();
workq {
}; // This block actually executes the search and prints the
let mut paths_searched: u64 = 0; // results into outbuf.
for dent in args.walker() { let mut printer = args.printer(&mut outbuf);
if quiet_matched.has_match() { let count =
break; if dent.is_stdin() {
} worker.run(&mut printer, Work::Stdin)
paths_searched += 1; } else {
if dent.is_stdin() { worker.run(&mut printer, Work::DirEntry(dent))
workq.push(Work::Stdin); };
} else { match_count.fetch_add(count as usize, Ordering::SeqCst);
workq.push(Work::File(dent)); if quiet_matched.set_match(count > 0) {
return Quit;
}
}
if !outbuf.get_ref().is_empty() {
// This should be the only mutex in all of ripgrep. Since the
// common case is to report a small number of matches relative
// to the corpus, this really shouldn't matter much.
//
// Still, it'd be nice to send this on a channel, but then we'd
// need to manage a pool of outbufs, which would complicate the
// code.
let mut out = out.lock().unwrap();
out.write(&outbuf);
}
Continue
})
});
if !args.paths().is_empty() && paths_searched.load(Ordering::SeqCst) == 0 {
if !args.no_messages() {
eprint_nothing_searched();
} }
} }
if !paths.is_empty() && paths_searched == 0 { Ok(match_count.load(Ordering::SeqCst) as u64)
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);
}
let mut match_count = 0;
for worker in workers {
match_count += worker.join().unwrap();
}
Ok(match_count)
} }
fn run_one_thread(args: Arc<Args>) -> Result<u64> { fn run_one_thread(args: Arc<Args>) -> Result<u64> {
let mut worker = Worker { let mut worker = args.worker();
args: args.clone(),
inpbuf: args.input_buffer(),
grep: args.grep(),
match_count: 0,
};
let mut term = args.stdout(); let mut term = args.stdout();
let mut paths_searched: u64 = 0; let mut paths_searched: u64 = 0;
for dent in args.walker() { let mut match_count = 0;
for result in args.walker() {
let dent = match get_or_log_dir_entry(result, args.no_messages()) {
None => continue,
Some(dent) => dent,
};
let mut printer = args.printer(&mut term); let mut printer = args.printer(&mut term);
if worker.match_count > 0 { if match_count > 0 {
if args.quiet() { if args.quiet() {
break; break;
} }
@@ -179,32 +187,56 @@ fn run_one_thread(args: Arc<Args>) -> Result<u64> {
} }
} }
paths_searched += 1; paths_searched += 1;
if dent.is_stdin() { match_count +=
worker.do_work(&mut printer, WorkReady::Stdin); if dent.is_stdin() {
} else { worker.run(&mut printer, Work::Stdin)
let file = match File::open(dent.path()) { } else {
Ok(file) => file, worker.run(&mut printer, Work::DirEntry(dent))
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 { if !args.paths().is_empty() && paths_searched == 0 {
eprintln!("No files were searched, which means ripgrep probably \ if !args.no_messages() {
applied a filter you didn't expect. \ eprint_nothing_searched();
Try running again with --debug."); }
} }
Ok(worker.match_count) Ok(match_count)
} }
fn run_files(args: Arc<Args>) -> Result<u64> { fn run_files_parallel(args: Arc<Args>) -> Result<u64> {
let print_args = args.clone();
let (tx, rx) = mpsc::channel::<ignore::DirEntry>();
let print_thread = thread::spawn(move || {
let term = print_args.stdout();
let mut printer = print_args.printer(term);
let mut file_count = 0;
for dent in rx.iter() {
printer.path(dent.path());
file_count += 1;
}
file_count
});
let no_messages = args.no_messages();
args.walker_parallel().run(move || {
let tx = tx.clone();
Box::new(move |result| {
if let Some(dent) = get_or_log_dir_entry(result, no_messages) {
tx.send(dent).unwrap();
}
ignore::WalkState::Continue
})
});
Ok(print_thread.join().unwrap())
}
fn run_files_one_thread(args: Arc<Args>) -> Result<u64> {
let term = args.stdout(); let term = args.stdout();
let mut printer = args.printer(term); let mut printer = args.printer(term);
let mut file_count = 0; let mut file_count = 0;
for dent in args.walker() { for result in args.walker() {
let dent = match get_or_log_dir_entry(result, args.no_messages()) {
None => continue,
Some(dent) => dent,
};
printer.path(dent.path()); printer.path(dent.path());
file_count += 1; file_count += 1;
} }
@@ -222,163 +254,69 @@ fn run_types(args: Arc<Args>) -> Result<u64> {
Ok(ty_count) Ok(ty_count)
} }
enum Work { fn get_or_log_dir_entry(
Stdin, result: result::Result<ignore::DirEntry, ignore::Error>,
File(DirEntry), no_messages: bool,
Quit, ) -> Option<ignore::DirEntry> {
} match result {
Err(err) => {
enum WorkReady { if !no_messages {
Stdin,
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>,
inpbuf: InputBuffer,
grep: Grep,
match_count: u64,
}
impl MultiWorker {
fn run(mut self) -> u64 {
loop {
if self.quiet_matched.has_match() {
break;
}
let work = match self.chan_work.steal() {
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::DirFile(ent, file),
Err(err) => {
eprintln!("{}: {}", ent.path().display(), err);
continue;
}
}
}
};
let mut outbuf = self.outbuf.take().unwrap();
outbuf.clear();
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();
out.write(&outbuf);
}
self.outbuf = Some(outbuf);
}
self.worker.match_count
}
}
impl Worker {
fn do_work<W: Terminal + Send>(
&mut self,
printer: &mut Printer<W>,
work: WorkReady,
) {
let result = match work {
WorkReady::Stdin => {
let stdin = io::stdin();
let stdin = stdin.lock();
self.search(printer, &Path::new("<stdin>"), stdin)
}
WorkReady::DirFile(ent, file) => {
let mut path = ent.path();
if let Some(p) = strip_prefix("./", path) {
path = p;
}
if self.args.mmap() {
self.search_mmap(printer, path, &file)
} else {
self.search(printer, path, file)
}
}
};
match result {
Ok(count) => {
self.match_count += count;
}
Err(err) => {
eprintln!("{}", err); eprintln!("{}", err);
} }
None
} }
} Ok(dent) => {
if let Some(err) = dent.error() {
fn search<R: io::Read, W: Terminal + Send>( if !no_messages {
&mut self, eprintln!("{}", err);
printer: &mut Printer<W>, }
path: &Path, }
rdr: R, if !dent.file_type().map_or(true, |x| x.is_file()) {
) -> Result<u64> { None
self.args.searcher( } else {
&mut self.inpbuf, Some(dent)
printer, }
&self.grep,
path,
rdr,
).run().map_err(From::from)
}
fn search_mmap<W: Terminal + Send>(
&mut self,
printer: &mut Printer<W>,
path: &Path,
file: &File,
) -> Result<u64> {
if try!(file.metadata()).len() == 0 {
// Opening a memory map with an empty file results in an error.
// 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(
printer,
&self.grep,
path,
unsafe { mmap.as_slice() },
).run())
} }
} }
fn eprint_nothing_searched() {
eprintln!("No files were searched, which means ripgrep probably \
applied a filter you didn't expect. \
Try running again with --debug.");
}
/// A simple thread safe abstraction for determining whether a search should
/// stop if the user has requested quiet mode.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct QuietMatched(Arc<Option<AtomicBool>>); pub struct QuietMatched(Arc<Option<AtomicBool>>);
impl QuietMatched { impl QuietMatched {
fn new(quiet: bool) -> QuietMatched { /// Create a new QuietMatched value.
///
/// If quiet is true, then set_match and has_match will reflect whether
/// a search should quit or not because it found a match.
///
/// If quiet is false, then set_match is always a no-op and has_match
/// always returns false.
pub fn new(quiet: bool) -> QuietMatched {
let atomic = if quiet { Some(AtomicBool::new(false)) } else { None }; let atomic = if quiet { Some(AtomicBool::new(false)) } else { None };
QuietMatched(Arc::new(atomic)) QuietMatched(Arc::new(atomic))
} }
fn has_match(&self) -> bool { /// Returns true if and only if quiet mode is enabled and a match has
/// occurred.
pub fn has_match(&self) -> bool {
match *self.0 { match *self.0 {
None => false, None => false,
Some(ref matched) => matched.load(Ordering::SeqCst), Some(ref matched) => matched.load(Ordering::SeqCst),
} }
} }
fn set_match(&self, yes: bool) -> bool { /// Sets whether a match has occurred or not.
///
/// If quiet mode is disabled, then this is a no-op.
pub fn set_match(&self, yes: bool) -> bool {
match *self.0 { match *self.0 {
None => false, None => false,
Some(_) if !yes => false, Some(_) if !yes => false,

View File

@@ -158,6 +158,7 @@ impl<W: Terminal + Send> Printer<W> {
} }
/// Flushes the underlying writer and returns it. /// Flushes the underlying writer and returns it.
#[allow(dead_code)]
pub fn into_inner(mut self) -> W { pub fn into_inner(mut self) -> W {
let _ = self.wtr.flush(); let _ = self.wtr.flush();
self.wtr self.wtr

View File

@@ -81,6 +81,14 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
self self
} }
/// Limit the number of matches to the given count.
///
/// The default is None, which corresponds to no limit.
pub fn max_count(mut self, count: Option<u64>) -> Self {
self.opts.max_count = count;
self
}
/// If enabled, don't show any output and quit searching after the first /// If enabled, don't show any output and quit searching after the first
/// match is found. /// match is found.
pub fn quiet(mut self, yes: bool) -> Self { pub fn quiet(mut self, yes: bool) -> Self {
@@ -111,11 +119,11 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
self.print_match(m.start(), m.end()); self.print_match(m.start(), m.end());
} }
last_end = m.end(); last_end = m.end();
if self.opts.stop_after_first_match() { if self.opts.terminate(self.match_count) {
break; break;
} }
} }
if self.opts.invert_match { if self.opts.invert_match && !self.opts.terminate(self.match_count) {
let upto = self.buf.len(); let upto = self.buf.len();
self.print_inverted_matches(last_end, upto); self.print_inverted_matches(last_end, upto);
} }
@@ -146,6 +154,9 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
debug_assert!(self.opts.invert_match); debug_assert!(self.opts.invert_match);
let mut it = IterLines::new(self.opts.eol, start); let mut it = IterLines::new(self.opts.eol, start);
while let Some((s, e)) = it.next(&self.buf[..end]) { while let Some((s, e)) = it.next(&self.buf[..end]) {
if self.opts.terminate(self.match_count) {
return;
}
self.print_match(s, e); self.print_match(s, e);
} }
} }
@@ -266,6 +277,26 @@ and exhibited clearly, with a label attached.\
assert_eq!(out, "/baz.rs\n"); assert_eq!(out, "/baz.rs\n");
} }
#[test]
fn max_count() {
let (count, out) = search(
"Sherlock", SHERLOCK, |s| s.max_count(Some(1)));
assert_eq!(1, count);
assert_eq!(out, "\
/baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock
");
}
#[test]
fn invert_match_max_count() {
let (count, out) = search(
"zzzz", SHERLOCK, |s| s.invert_match(true).max_count(Some(1)));
assert_eq!(1, count);
assert_eq!(out, "\
/baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock
");
}
#[test] #[test]
fn invert_match() { fn invert_match() {
let (count, out) = search( let (count, out) = search(

View File

@@ -4,6 +4,8 @@ printing matches. In particular, it searches the file in a streaming fashion
using `read` calls and a (roughly) fixed size buffer. using `read` calls and a (roughly) fixed size buffer.
*/ */
extern crate bytecount;
use std::cmp; use std::cmp;
use std::error::Error as StdError; use std::error::Error as StdError;
use std::fmt; use std::fmt;
@@ -84,6 +86,7 @@ pub struct Options {
pub eol: u8, pub eol: u8,
pub invert_match: bool, pub invert_match: bool,
pub line_number: bool, pub line_number: bool,
pub max_count: Option<u64>,
pub quiet: bool, pub quiet: bool,
pub text: bool, pub text: bool,
} }
@@ -98,6 +101,7 @@ impl Default for Options {
eol: b'\n', eol: b'\n',
invert_match: false, invert_match: false,
line_number: false, line_number: false,
max_count: None,
quiet: false, quiet: false,
text: false, text: false,
} }
@@ -117,6 +121,17 @@ impl Options {
pub fn stop_after_first_match(&self) -> bool { pub fn stop_after_first_match(&self) -> bool {
self.files_with_matches || self.quiet self.files_with_matches || self.quiet
} }
/// Returns true if the search should terminate based on the match count.
pub fn terminate(&self, match_count: u64) -> bool {
if match_count > 0 && self.stop_after_first_match() {
return true;
}
if self.max_count.map_or(false, |max| match_count >= max) {
return true;
}
false
}
} }
impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> { impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
@@ -205,6 +220,14 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
self self
} }
/// Limit the number of matches to the given count.
///
/// The default is None, which corresponds to no limit.
pub fn max_count(mut self, count: Option<u64>) -> Self {
self.opts.max_count = count;
self
}
/// If enabled, don't show any output and quit searching after the first /// If enabled, don't show any output and quit searching after the first
/// match is found. /// match is found.
pub fn quiet(mut self, yes: bool) -> Self { pub fn quiet(mut self, yes: bool) -> Self {
@@ -280,7 +303,7 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
#[inline(always)] #[inline(always)]
fn terminate(&self) -> bool { fn terminate(&self) -> bool {
self.match_count > 0 && self.opts.stop_after_first_match() self.opts.terminate(self.match_count)
} }
#[inline(always)] #[inline(always)]
@@ -317,6 +340,9 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
debug_assert!(self.opts.invert_match); debug_assert!(self.opts.invert_match);
let mut it = IterLines::new(self.opts.eol, self.inp.pos); let mut it = IterLines::new(self.opts.eol, self.inp.pos);
while let Some((start, end)) = it.next(&self.inp.buf[..upto]) { while let Some((start, end)) = it.next(&self.inp.buf[..upto]) {
if self.terminate() {
return;
}
self.print_match(start, end); self.print_match(start, end);
self.inp.pos = end; self.inp.pos = end;
} }
@@ -582,89 +608,9 @@ pub fn is_binary(buf: &[u8]) -> bool {
} }
/// Count the number of lines in the given buffer. /// Count the number of lines in the given buffer.
#[inline(never)]
#[inline(never)] #[inline(never)]
pub fn count_lines(buf: &[u8], eol: u8) -> u64 { pub fn count_lines(buf: &[u8], eol: u8) -> u64 {
// This was adapted from code in the memchr crate. The specific benefit bytecount::count(buf, eol) as u64
// here is that we can avoid a branch in the inner loop because all we're
// doing is counting.
// The technique to count EOL bytes was adapted from:
// http://bits.stephan-brumme.com/null.html
const LO_U64: u64 = 0x0101010101010101;
const HI_U64: u64 = 0x8080808080808080;
// use truncation
const LO_USIZE: usize = LO_U64 as usize;
const HI_USIZE: usize = HI_U64 as usize;
#[cfg(target_pointer_width = "32")]
const USIZE_BYTES: usize = 4;
#[cfg(target_pointer_width = "64")]
const USIZE_BYTES: usize = 8;
fn count_eol(eol: usize) -> u64 {
// Ideally, this would compile down to a POPCNT instruction, but
// it looks like you need to set RUSTFLAGS="-C target-cpu=native"
// (or target-feature=+popcnt) to get that to work. Bummer.
(eol.wrapping_sub(LO_USIZE) & !eol & HI_USIZE).count_ones() as u64
}
#[cfg(target_pointer_width = "32")]
fn repeat_byte(b: u8) -> usize {
let mut rep = (b as usize) << 8 | b as usize;
rep = rep << 16 | rep;
rep
}
#[cfg(target_pointer_width = "64")]
fn repeat_byte(b: u8) -> usize {
let mut rep = (b as usize) << 8 | b as usize;
rep = rep << 16 | rep;
rep = rep << 32 | rep;
rep
}
fn count_lines_slow(mut buf: &[u8], eol: u8) -> u64 {
let mut count = 0;
while let Some(pos) = memchr(eol, buf) {
count += 1;
buf = &buf[pos + 1..];
}
count
}
let len = buf.len();
let ptr = buf.as_ptr();
let mut count = 0;
// Search up to an aligned boundary...
let align = (ptr as usize) & (USIZE_BYTES - 1);
let mut i = 0;
if align > 0 {
i = cmp::min(USIZE_BYTES - align, len);
count += count_lines_slow(&buf[..i], eol);
}
// ... and search the rest.
let repeated_eol = repeat_byte(eol);
if len >= 2 * USIZE_BYTES {
while i <= len - (2 * USIZE_BYTES) {
unsafe {
let u = *(ptr.offset(i as isize) as *const usize);
let v = *(ptr.offset((i + USIZE_BYTES) as isize)
as *const usize);
count += count_eol(u ^ repeated_eol);
count += count_eol(v ^ repeated_eol);
}
i += USIZE_BYTES * 2;
}
}
count += count_lines_slow(&buf[i..], eol);
count
} }
/// Replaces a with b in buf. /// Replaces a with b in buf.
@@ -1040,6 +986,26 @@ fn main() {
assert_eq!(out, "/baz.rs\n"); assert_eq!(out, "/baz.rs\n");
} }
#[test]
fn max_count() {
let (count, out) = search_smallcap(
"Sherlock", SHERLOCK, |s| s.max_count(Some(1)));
assert_eq!(1, count);
assert_eq!(out, "\
/baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock
");
}
#[test]
fn invert_match_max_count() {
let (count, out) = search(
"zzzz", SHERLOCK, |s| s.invert_match(true).max_count(Some(1)));
assert_eq!(1, count);
assert_eq!(out, "\
/baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock
");
}
#[test] #[test]
fn invert_match() { fn invert_match() {
let (count, out) = search_smallcap( let (count, out) = search_smallcap(

271
src/worker.rs Normal file
View File

@@ -0,0 +1,271 @@
use std::fs::File;
use std::io;
use std::path::Path;
use grep::Grep;
use ignore::DirEntry;
use memmap::{Mmap, Protection};
use term::Terminal;
use pathutil::strip_prefix;
use printer::Printer;
use search_buffer::BufferSearcher;
use search_stream::{InputBuffer, Searcher};
use Result;
pub enum Work {
Stdin,
DirEntry(DirEntry),
}
pub struct WorkerBuilder {
grep: Grep,
opts: Options,
}
#[derive(Clone, Debug)]
struct Options {
mmap: bool,
after_context: usize,
before_context: usize,
count: bool,
files_with_matches: bool,
eol: u8,
invert_match: bool,
line_number: bool,
max_count: Option<u64>,
no_messages: bool,
quiet: bool,
text: bool,
}
impl Default for Options {
fn default() -> Options {
Options {
mmap: false,
after_context: 0,
before_context: 0,
count: false,
files_with_matches: false,
eol: b'\n',
invert_match: false,
line_number: false,
max_count: None,
no_messages: false,
quiet: false,
text: false,
}
}
}
impl WorkerBuilder {
/// Create a new builder for a worker.
///
/// A reusable input buffer and a grep matcher are required, but there
/// are numerous additional options that can be configured on this builder.
pub fn new(grep: Grep) -> WorkerBuilder {
WorkerBuilder {
grep: grep,
opts: Options::default(),
}
}
/// Create the worker from this builder.
pub fn build(self) -> Worker {
let mut inpbuf = InputBuffer::new();
inpbuf.eol(self.opts.eol);
Worker {
grep: self.grep,
inpbuf: inpbuf,
opts: self.opts,
}
}
/// The number of contextual lines to show after each match. The default
/// is zero.
pub fn after_context(mut self, count: usize) -> Self {
self.opts.after_context = count;
self
}
/// The number of contextual lines to show before each match. The default
/// is zero.
pub fn before_context(mut self, count: usize) -> Self {
self.opts.before_context = count;
self
}
/// If enabled, searching will print a count instead of each match.
///
/// Disabled by default.
pub fn count(mut self, yes: bool) -> Self {
self.opts.count = yes;
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;
self
}
/// If enabled, matching is inverted so that lines that *don't* match the
/// given pattern are treated as matches.
pub fn invert_match(mut self, yes: bool) -> Self {
self.opts.invert_match = yes;
self
}
/// If enabled, compute line numbers and prefix each line of output with
/// them.
pub fn line_number(mut self, yes: bool) -> Self {
self.opts.line_number = yes;
self
}
/// Limit the number of matches to the given count.
///
/// The default is None, which corresponds to no limit.
pub fn max_count(mut self, count: Option<u64>) -> Self {
self.opts.max_count = count;
self
}
/// If enabled, try to use memory maps for searching if possible.
pub fn mmap(mut self, yes: bool) -> Self {
self.opts.mmap = yes;
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;
self
}
}
/// Worker is responsible for executing searches on file paths, while choosing
/// streaming search or memory map search as appropriate.
pub struct Worker {
inpbuf: InputBuffer,
grep: Grep,
opts: Options,
}
impl Worker {
/// Execute the worker with the given printer and work item.
///
/// A work item can either be stdin or a file path.
pub fn run<W: Terminal + Send>(
&mut self,
printer: &mut Printer<W>,
work: Work,
) -> u64 {
let result = match work {
Work::Stdin => {
let stdin = io::stdin();
let stdin = stdin.lock();
self.search(printer, &Path::new("<stdin>"), stdin)
}
Work::DirEntry(dent) => {
let mut path = dent.path();
let file = match File::open(path) {
Ok(file) => file,
Err(err) => {
if !self.opts.no_messages {
eprintln!("{}: {}", path.display(), err);
}
return 0;
}
};
if let Some(p) = strip_prefix("./", path) {
path = p;
}
if self.opts.mmap {
self.search_mmap(printer, path, &file)
} else {
self.search(printer, path, file)
}
}
};
match result {
Ok(count) => {
count
}
Err(err) => {
if !self.opts.no_messages {
eprintln!("{}", err);
}
0
}
}
}
fn search<R: io::Read, W: Terminal + Send>(
&mut self,
printer: &mut Printer<W>,
path: &Path,
rdr: R,
) -> Result<u64> {
let searcher = Searcher::new(
&mut self.inpbuf, printer, &self.grep, path, rdr);
searcher
.after_context(self.opts.after_context)
.before_context(self.opts.before_context)
.count(self.opts.count)
.files_with_matches(self.opts.files_with_matches)
.eol(self.opts.eol)
.line_number(self.opts.line_number)
.invert_match(self.opts.invert_match)
.max_count(self.opts.max_count)
.quiet(self.opts.quiet)
.text(self.opts.text)
.run()
.map_err(From::from)
}
fn search_mmap<W: Terminal + Send>(
&mut self,
printer: &mut Printer<W>,
path: &Path,
file: &File,
) -> Result<u64> {
if try!(file.metadata()).len() == 0 {
// Opening a memory map with an empty file results in an error.
// 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));
let searcher = BufferSearcher::new(
printer, &self.grep, path, unsafe { mmap.as_slice() });
Ok(searcher
.count(self.opts.count)
.files_with_matches(self.opts.files_with_matches)
.eol(self.opts.eol)
.line_number(self.opts.line_number)
.invert_match(self.opts.invert_match)
.max_count(self.opts.max_count)
.quiet(self.opts.quiet)
.text(self.opts.text)
.run())
}
}

View File

@@ -789,6 +789,15 @@ clean!(regression_127, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
assert_eq!(lines, expected); assert_eq!(lines, expected);
}); });
// See: https://github.com/BurntSushi/ripgrep/issues/128
clean!(regression_128, "x", ".", |wd: WorkDir, mut cmd: Command| {
wd.create_bytes("foo", b"01234567\x0b\n\x0b\n\x0b\n\x0b\nx");
cmd.arg("-n");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "foo:5:x\n");
});
// See: https://github.com/BurntSushi/ripgrep/issues/131 // See: https://github.com/BurntSushi/ripgrep/issues/131
// //
// TODO(burntsushi): Darwin doesn't like this test for some reason. // TODO(burntsushi): Darwin doesn't like this test for some reason.
@@ -865,6 +874,25 @@ clean!(regression_184, "test", ".", |wd: WorkDir, mut cmd: Command| {
assert_eq!(lines, "baz:test\n"); assert_eq!(lines, "baz:test\n");
}); });
// See: https://github.com/BurntSushi/ripgrep/issues/199
clean!(regression_199, r"\btest\b", ".", |wd: WorkDir, mut cmd: Command| {
wd.create("foo", "tEsT");
cmd.arg("--smart-case");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "foo:tEsT\n");
});
// See: https://github.com/BurntSushi/ripgrep/issues/206
clean!(regression_206, "test", ".", |wd: WorkDir, mut cmd: Command| {
wd.create_dir("foo");
wd.create("foo/bar.txt", "test");
cmd.arg("-g").arg("*.txt");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, format!("{}:test\n", path("foo/bar.txt")));
});
// See: https://github.com/BurntSushi/ripgrep/issues/20 // See: https://github.com/BurntSushi/ripgrep/issues/20
sherlock!(feature_20_no_filename, "Sherlock", ".", sherlock!(feature_20_no_filename, "Sherlock", ".",
|wd: WorkDir, mut cmd: Command| { |wd: WorkDir, mut cmd: Command| {
@@ -1043,6 +1071,21 @@ clean!(feature_109_case_sensitive_part2, "test", ".",
wd.assert_err(&mut cmd); wd.assert_err(&mut cmd);
}); });
// See: https://github.com/BurntSushi/ripgrep/issues/159
clean!(feature_159_works, "test", ".", |wd: WorkDir, mut cmd: Command| {
wd.create("foo", "test\ntest");
cmd.arg("-m1");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "foo:test\n");
});
// See: https://github.com/BurntSushi/ripgrep/issues/159
clean!(feature_159_zero_max, "test", ".", |wd: WorkDir, mut cmd: Command| {
wd.create("foo", "test\ntest");
cmd.arg("-m0");
wd.assert_err(&mut cmd);
});
#[test] #[test]
fn binary_nosearch() { fn binary_nosearch() {
let wd = WorkDir::new("binary_nosearch"); let wd = WorkDir::new("binary_nosearch");

View File

@@ -43,9 +43,14 @@ impl WorkDir {
/// Create a new file with the given name and contents in this directory. /// Create a new file with the given name and contents in this directory.
pub fn create<P: AsRef<Path>>(&self, name: P, contents: &str) { pub fn create<P: AsRef<Path>>(&self, name: P, contents: &str) {
self.create_bytes(name, contents.as_bytes());
}
/// Create a new file with the given name and contents in this directory.
pub fn create_bytes<P: AsRef<Path>>(&self, name: P, contents: &[u8]) {
let path = self.dir.join(name); let path = self.dir.join(name);
let mut file = nice_err(&path, File::create(&path)); let mut file = nice_err(&path, File::create(&path));
nice_err(&path, file.write_all(contents.as_bytes())); nice_err(&path, file.write_all(contents));
nice_err(&path, file.flush()); nice_err(&path, file.flush());
} }