Compare commits

...

59 Commits
0.2.1 ... 0.2.2

Author SHA1 Message Date
Andrew Gallant
4981991a6e 0.2.2 2016-10-10 22:24:36 -04:00
Andrew Gallant
51440f59cd Don't include HomebrewFormula in crate. 2016-10-10 22:24:28 -04:00
Andrew Gallant
7b8a8d77d0 changelog 0.2.2 2016-10-10 22:18:21 -04:00
Andrew Gallant
4737326ed3 Update regex-syntax for bug fix.
The bug fix was in expression pretty printing. ripgrep parses the regex
into an AST and may do some modifications to it, which requires the
ability to go from string -> AST -> string' -> AST' where string == string'
implies AST == AST'.

Also, add a regression test for the specific regex that tripped the bug.

Fixes #156.
2016-10-10 22:04:29 -04:00
Andrew Gallant
a3537aa32a Update darwin cfg attributes. 2016-10-10 21:48:47 -04:00
Andrew Gallant
d3e118a786 Fix debug expression statement. 2016-10-10 21:48:34 -04:00
Andrew Gallant
4e52059ad6 Disable regression_131 test on darwin.
It's not clear why it's failing. Maybe it doesn't permit certain
characters in file paths?
2016-10-10 21:03:11 -04:00
Andrew Gallant
60c016c243 Fix docopt usage string. Gah. 2016-10-10 20:49:39 -04:00
Andrew Gallant
4665128f25 Clarify documentation for --replace.
Also add a minor clarification for --type-add.

Fixes #147
2016-10-10 20:19:45 -04:00
Andrew Gallant
dde5bd5a80 globset-0.1.0 2016-10-10 20:07:13 -04:00
Andrew Gallant
762ad44f71 add version marker 2016-10-10 20:06:35 -04:00
Andrew Gallant
705386934d Fill in globset/Cargo.toml with more details. 2016-10-10 19:50:21 -04:00
Andrew Gallant
97bbc6ef11 Update appveyor to test subcrates. 2016-10-10 19:35:47 -04:00
Andrew Gallant
27a980c1bc Fix symlink test.
We attempt to run it on Windows, but I'm getting "access denied" errors
when trying to create a file symlink. So we disable the test on Windows.
2016-10-10 19:34:57 -04:00
Andrew Gallant
e8645dc8ae style nits 2016-10-10 19:27:12 -04:00
Andrew Gallant
e96d93034a Finish overhaul of glob matching.
This commit completes the initial move of glob matching to an external
crate, including fixing up cross platform support, polishing the
external crate for others to use and fixing a number of bugs in the
process.

Fixes #87, #127, #131
2016-10-10 19:24:18 -04:00
Andrew Gallant
bc5accc035 Merge pull request #161 from moshen/update-homebrew-readme
Update Homebrew instructions in the README
2016-10-10 19:11:50 -04:00
Andrew Gallant
c9d0ca8257 Merge pull request #157 from CannedYerins/follow-explicit-args
Always follow symlinks on explicit file arguments
2016-10-10 19:11:10 -04:00
Andrew Gallant
45fe4aab96 Merge pull request #155 from theamazingfedex/adding-extra-md-filetype
Adding extra .md filetype for ease of access to Markdown filetypes
2016-10-10 19:09:15 -04:00
Andrew Gallant
97f981fbcb Merge pull request #154 from theamazingfedex/adding-spark-filetype
Adding .spark filetype
2016-10-10 19:08:20 -04:00
Andrew Gallant
59329dcc61 Merge pull request #153 from theamazingfedex/master
Adding .config filetype
2016-10-10 19:08:06 -04:00
Colin Kennedy
604da8eb86 Update Homebrew instructions in the README 2016-10-09 23:45:02 -05:00
Ian Kerins
1c964372ad Always follow symlinks on explicit file arguments. 2016-10-08 22:40:03 -04:00
Daniel Wood
50a961960e added extra .md filetype for ease of access 2016-10-07 14:37:29 -06:00
Daniel Wood
7481c5fe29 added .spark filetype 2016-10-07 14:35:20 -06:00
Daniel Wood
3ae37b0937 added .config filetype 2016-10-07 13:50:26 -06:00
Andrew Gallant
4ee6dbe422 Merge pull request #148 from munyari/patch-1
Change Arch Linux instructions
2016-10-05 09:53:17 -04:00
Panashe Fundira
cd4bdcf810 Change Arch Linux instructions
The `-Syu` flag will do a full system upgrade and then install the package, which is not necessarily the desired behavior. Only the `-S` flag is necessary to install a single package.
See https://wiki.archlinux.org/index.php/Pacman#Installing_specific_packages
https://wiki.archlinux.org/index.php/Pacman#Upgrading_packages
2016-10-05 08:26:19 -04:00
Andrew Gallant
175406df01 Refactor and test glob sets.
This commit goes a long way toward refactoring glob sets so that the
code is easier to maintain going forward. In particular, it makes the
literal optimizations that glob sets used a lot more structured and much
easier to extend. Tests have also been modified to include glob sets.

There's still a bit of polish work left to do before a release.

This also fixes the immediate issue where large gitignore files were
causing ripgrep to slow way down. While we don't technically fix it for
good, we're a lot better about reducing the number of regexes we
compile. In particular, if a gitignore file contains thousands of
patterns that can't be matched more simply using literals, then ripgrep
will slow down again. We could fix this for good by avoiding RegexSet if
the number of regexes grows too large.

Fixes #134.
2016-10-04 20:28:56 -04:00
Andrew Gallant
89811d43d4 Merge pull request #146 from samuelcolvin/add-jinja-type
add jinja type for *.jinja and *.jinja2
2016-10-04 08:25:22 -04:00
Samuel Colvin
f0053682c0 add jinja type for *.jinja and *.jinja2 2016-10-04 13:15:31 +01:00
Andrew Gallant
35045d6105 Merge pull request #143 from moshen/change-brew-formula-name
Fix brew formula name to not conflict with core
2016-10-04 06:49:16 -04:00
Colin Kennedy
95f552fc06 Fix brew formula name to not conflict with core
Since the homebrew-core formula was accepted, we should differentiate
the prebuilt formula available in this tap
2016-10-03 22:30:26 -05:00
Andrew Gallant
48353bea17 Merge pull request #144 from bitshifter/dotcmake
Added *.cmake extension to cmake file type.
2016-10-03 20:51:05 -04:00
Cameron Hart
703d5b558e Added *.cmake extension to cmake file type. 2016-10-04 11:28:49 +11:00
Andrew Gallant
47efea234f Remove i686-darwin.
Apparently 32 bit Mac CPUs are really old at this point. Also, it has
been causing CI to fail lately. It's not worth it.
2016-10-03 17:16:28 -04:00
Andrew Gallant
ca0d8998a2 Merge pull request #139 from moshen/make-a-tap
Make the repo a Homebrew Tap
2016-10-03 17:14:09 -04:00
Andrew Gallant
fdf24317ac Move glob implementation to new crate.
It is isolated and complex enough that it deserves attention all on its
own. It's also eminently reusable.
2016-09-30 19:42:41 -04:00
Andrew Gallant
b9d5f22a4d Stopgap measure for projects with huge gitignore files.
This helps #134 by avoiding a slow regex execution path, but doesn't
actually fix the problem. Namely, we've gone from "so slow I'm not going
to keep waiting for rg to finish" to "wow that was slow but at least it
finished before I lost my patience."
2016-09-30 19:29:52 -04:00
Colin Kennedy
67bb4f040f Make the repo a Homebrew Tap 2016-09-30 12:51:37 -05:00
Andrew Gallant
cee2f09a6d Merge pull request #121 from lilydjwg/master
if --color always, always print with color, even when --vimgrep is given
2016-09-29 16:49:44 -04:00
Andrew Gallant
ced777e91f Merge pull request #133 from akien-mga/pr-appveyor
AppVeyor: Change release description to fit Travis binaries
2016-09-29 09:35:44 -04:00
Rémi Verschelde
e9d9083898 AppVeyor: Change release description to fit Travis binaries 2016-09-29 15:29:59 +02:00
Andrew Gallant
46dff8f4be Be better with short circuiting with --quiet.
It didn't make sense for --quiet to be part of the printer, because --quiet
doesn't just mean "don't print," it also means, "stop after the first
match is found." This needs to be wired all the way up through directory
traversal, and it also needs to cause all of the search workers to quit
as well. We do it with an atomic that is only checked with --quiet is
given.

Fixes #116.
2016-09-28 20:50:50 -04:00
Andrew Gallant
7aa6e87952 clarify 2016-09-28 16:47:10 -04:00
Andrew Gallant
925d0db9f0 Add -s/--case-sensitive flag.
This flag overrides both --smart-case and --ignore-case.

Closes #124.
2016-09-28 16:32:29 -04:00
Andrew Gallant
316ffd87b3 bump docopt to 0.6.86 2016-09-28 15:56:59 -04:00
依云
5943b1effe if --color always, always print with color, even when --vimgrep is given 2016-09-28 20:10:07 +08:00
Andrew Gallant
c42f97b4da Merge pull request #122 from lilydjwg/color-filename
colorize filepath at the beginning of line too
2016-09-28 07:06:34 -04:00
依云
0d9bba7816 colorize filepath at the beginning of line too 2016-09-28 11:54:43 +08:00
Andrew Gallant
3550f2e29a Merge pull request #111 from gsquire/max-depth
Max depth option
2016-09-27 19:46:29 -04:00
Garrett Squire
babe80d498 add a max-depth option for directory traversal
CR and add integration test
2016-09-27 16:14:53 -07:00
Andrew Gallant
3e892a7a80 Correct example with --type-add.
Fixes #118.
2016-09-27 18:35:06 -04:00
Andrew Gallant
1df3f0b793 Merge pull request #114 from cetra3/colorChoice
Create Colour Choice struct to adjust colours depending on platform
2016-09-27 09:43:47 -04:00
cetra3
b3935935cb Add colour choice 2016-09-27 22:51:07 +09:30
Andrew Gallant
67abbf6f22 Merge pull request #115 from nickstenning/update-brew-hashes
Update brew 0.2.1 package hashes
2016-09-27 07:01:27 -04:00
Nick Stenning
7b9f7d7dc6 Update brew 0.2.1 package hashes 2016-09-27 12:01:22 +02:00
Andrew Gallant
7ab29a91d0 fix use of --type-add 2016-09-26 20:58:28 -04:00
Andrew Gallant
9fa38c6232 brew 0.2.1 2016-09-26 20:42:12 -04:00
31 changed files with 3005 additions and 1413 deletions

1
.gitignore vendored
View File

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

View File

@@ -15,9 +15,6 @@ matrix:
- os: linux - os: linux
rust: nightly rust: nightly
env: TARGET=x86_64-unknown-linux-musl env: TARGET=x86_64-unknown-linux-musl
- os: osx
rust: nightly
env: TARGET=i686-apple-darwin
- os: osx - os: osx
rust: nightly rust: nightly
env: TARGET=x86_64-apple-darwin env: TARGET=x86_64-apple-darwin

View File

@@ -1,3 +1,47 @@
0.2.2
=====
Packaging updates:
* `ripgrep` is now in homebrew-core. `brew install ripgrep` will do the trick
on a Mac.
* `ripgrep` is now in the Archlinux community repository.
`pacman -S ripgrep` will do the trick on Archlinux.
* Support has been discontinued for i686-darwin.
* Glob matching has been moved out into its own crate:
[`globset`](https://crates.io/crates/globset).
Feature enhancements:
* Added or improved file type filtering for CMake, config, Jinja, Markdown,
Spark,
* [FEATURE #109](https://github.com/BurntSushi/ripgrep/issues/109):
Add a --max-depth flag for directory traversal.
* [FEATURE #124](https://github.com/BurntSushi/ripgrep/issues/124):
Add -s/--case-sensitive flag. Overrides --smart-case.
* [FEATURE #139](https://github.com/BurntSushi/ripgrep/pull/139):
The `ripgrep` repo is now a Homebrew tap. This is useful for installing
SIMD accelerated binaries, which aren't available in homebrew-core.
Bug fixes:
* [BUG #87](https://github.com/BurntSushi/ripgrep/issues/87),
[BUG #127](https://github.com/BurntSushi/ripgrep/issues/127),
[BUG #131](https://github.com/BurntSushi/ripgrep/issues/131):
Various issues related to glob matching.
* [BUG #116](https://github.com/BurntSushi/ripgrep/issues/116):
--quiet should stop search after first match.
* [BUG #121](https://github.com/BurntSushi/ripgrep/pull/121):
--color always should show colors, even when --vimgrep is used.
* [BUG #122](https://github.com/BurntSushi/ripgrep/pull/122):
Colorize file path at beginning of line.
* [BUG #134](https://github.com/BurntSushi/ripgrep/issues/134):
Processing a large ignore file (thousands of globs) was very slow.
* [BUG #137](https://github.com/BurntSushi/ripgrep/issues/137):
Always follow symlinks when given as an explicit argument.
* [BUG #147](https://github.com/BurntSushi/ripgrep/issues/147):
Clarify documentation for --replace.
0.2.1 0.2.1
===== =====
Feature enhancements: Feature enhancements:

31
Cargo.lock generated
View File

@@ -3,10 +3,9 @@ name = "ripgrep"
version = "0.2.1" version = "0.2.1"
dependencies = [ dependencies = [
"deque 0.3.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.85 (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)",
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "globset 0.1.0",
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"grep 0.1.3", "grep 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)",
@@ -40,7 +39,7 @@ dependencies = [
[[package]] [[package]]
name = "docopt" name = "docopt"
version = "0.6.85" version = "0.6.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"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)",
@@ -74,9 +73,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "glob" name = "globset"
version = "0.2.11" version = "0.1.0"
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)",
"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]] [[package]]
name = "grep" name = "grep"
@@ -86,7 +92,7 @@ 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)",
"memmap 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "memmap 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@@ -155,7 +161,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
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)",
"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-syntax 0.3.5 (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)", "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)", "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)", "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -163,7 +169,7 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.3.5" version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@@ -234,11 +240,10 @@ 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 deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf" "checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf"
"checksum docopt 0.6.85 (registry+https://github.com/rust-lang/crates.io-index)" = "1b88d783674021c5570e7238e17985b9b8c7141d90f33de49031b8d56e7f0bf9" "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"
"checksum fs2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bcd414e5a1a979b931bb92f41b7a54106d3f6d2e6c253e9ce943b7cd468251ef" "checksum fs2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bcd414e5a1a979b931bb92f41b7a54106d3f6d2e6c253e9ce943b7cd468251ef"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "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 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 libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "408014cace30ee0f767b1c4517980646a573ec61a57957aeeabcac8ac0a02e8d"
@@ -248,7 +253,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"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 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 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)" = "64b03446c466d35b42f2a8b203c8e03ed8b91c0f17b56e1f84f7210a257aa665"
"checksum regex-syntax 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279401017ae31cf4e15344aa3f085d0e2e5c1e70067289ef906906fdbe92c8fd" "checksum regex-syntax 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "48f0573bcee95a48da786f8823465b5f2a1fae288a55407aca991e5b3e0eae11"
"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"
"checksum simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63b5847c2d766ca7ce7227672850955802fabd779ba616aeabead4c2c3877023" "checksum simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63b5847c2d766ca7ce7227672850955802fabd779ba616aeabead4c2c3877023"
"checksum strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "50c069df92e4b01425a8bf3576d5d417943a6a7272fbabaf5bd80b1aaa76442e" "checksum strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "50c069df92e4b01425a8bf3576d5d417943a6a7272fbabaf5bd80b1aaa76442e"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ripgrep" name = "ripgrep"
version = "0.2.1" #:version version = "0.2.2" #: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
@@ -12,6 +12,7 @@ repository = "https://github.com/BurntSushi/ripgrep"
readme = "README.md" readme = "README.md"
keywords = ["regex", "grep", "egrep", "search", "pattern"] keywords = ["regex", "grep", "egrep", "search", "pattern"]
license = "Unlicense/MIT" license = "Unlicense/MIT"
exclude = ["HomebrewFormula"]
[[bin]] [[bin]]
bench = false bench = false
@@ -26,7 +27,7 @@ path = "tests/tests.rs"
deque = "0.3" deque = "0.3"
docopt = "0.6" docopt = "0.6"
env_logger = "0.3" env_logger = "0.3"
fnv = "1.0" globset = { version = "0.1.0", path = "globset" }
grep = { version = "0.1.3", path = "grep" } grep = { version = "0.1.3", path = "grep" }
lazy_static = "0.2" lazy_static = "0.2"
libc = "0.2" libc = "0.2"
@@ -46,8 +47,5 @@ winapi = "0.2"
[features] [features]
simd-accel = ["regex/simd-accel"] simd-accel = ["regex/simd-accel"]
[dev-dependencies]
glob = "0.2"
[profile.release] [profile.release]
debug = true debug = true

1
HomebrewFormula Symbolic link
View File

@@ -0,0 +1 @@
pkg/brew

View File

@@ -30,7 +30,7 @@ for a very detailed comparison with more benchmarks and analysis.
| ripgrep | `rg -n -w '[A-Z]+_SUSPEND'` | 450 | **0.245s** | | ripgrep | `rg -n -w '[A-Z]+_SUSPEND'` | 450 | **0.245s** |
| [The Silver Searcher](https://github.com/ggreer/the_silver_searcher) | `ag -w '[A-Z]+_SUSPEND'` | 450 | 0.753s | | [The Silver Searcher](https://github.com/ggreer/the_silver_searcher) | `ag -w '[A-Z]+_SUSPEND'` | 450 | 0.753s |
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=C git grep -E -n -w '[A-Z]+_SUSPEND'` | 450 | 0.823s | | [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=C git grep -E -n -w '[A-Z]+_SUSPEND'` | 450 | 0.823s |
| [git grep](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=en_US.UTF-8 git grep -E -n -w '[A-Z]+_SUSPEND'` | 450 | 2.880s | | [git grep (Unicode)](https://www.kernel.org/pub/software/scm/git/docs/git-grep.html) | `LC_ALL=en_US.UTF-8 git grep -E -n -w '[A-Z]+_SUSPEND'` | 450 | 2.880s |
| [sift](https://github.com/svent/sift) | `sift --git -n -w '[A-Z]+_SUSPEND'` | 450 | 3.656s | | [sift](https://github.com/svent/sift) | `sift --git -n -w '[A-Z]+_SUSPEND'` | 450 | 3.656s |
| [The Platinum Searcher](https://github.com/monochromegane/the_platinum_searcher) | `pt -w -e '[A-Z]+_SUSPEND'` | 450 | 12.369s | | [The Platinum Searcher](https://github.com/monochromegane/the_platinum_searcher) | `pt -w -e '[A-Z]+_SUSPEND'` | 450 | 12.369s |
| [ack](http://beyondgrep.com/) | `ack -w '[A-Z]+_SUSPEND'` | 1878 | 16.952s | | [ack](http://beyondgrep.com/) | `ack -w '[A-Z]+_SUSPEND'` | 1878 | 16.952s |
@@ -97,18 +97,25 @@ but you'll need to have the
[Microsoft VC++ 2015 redistributable](https://www.microsoft.com/en-us/download/details.aspx?id=48145) [Microsoft VC++ 2015 redistributable](https://www.microsoft.com/en-us/download/details.aspx?id=48145)
installed. installed.
If you're a **Homebrew** user, then you can install it with a custom formula If you're a **Mac OS X Homebrew** user, then you can install ripgrep either
(N.B. `ripgrep` isn't actually in Homebrew yet. This just installs the binary from homebrew-core, (compiled with rust stable, no SIMD):
directly):
``` ```
$ brew install https://raw.githubusercontent.com/BurntSushi/ripgrep/master/pkg/brew/ripgrep.rb $ brew install ripgrep
```
or you can install a binary compiled with rust nightly (including SIMD and all
optimizations) by utilizing a custom tap:
```
$ brew tap burntsushi/ripgrep https://github.com/BurntSushi/ripgrep.git
$ brew install burntsushi/ripgrep/ripgrep-bin
``` ```
If you're an **Arch Linux** user, then you can install `ripgrep` from the official repos: If you're an **Arch Linux** user, then you can install `ripgrep` from the official repos:
``` ```
$ pacman -Syu ripgrep $ pacman -S ripgrep
``` ```
If you're a **Rust programmer**, `ripgrep` can be installed with `cargo`: If you're a **Rust programmer**, `ripgrep` can be installed with `cargo`:
@@ -214,10 +221,11 @@ $ rg -Tjs foobar
``` ```
To see a list of types supported, run `rg --type-list`. To add a new type, use To see a list of types supported, run `rg --type-list`. To add a new type, use
`--type-add`: `--type-add`, which must be accompanied by a pattern for searching (`rg` won't
persist your type settings):
``` ```
$ rg --type-add 'foo:*.foo,*.foobar' $ rg --type-add 'foo:*.{foo,foobar}' -tfoo bar
``` ```
The type `foo` will now match any file ending with the `.foo` or `.foobar` The type `foo` will now match any file ending with the `.foo` or `.foobar`

View File

@@ -28,6 +28,8 @@ build: false
# TODO modify this phase as you see fit # TODO modify this phase as you see fit
test_script: test_script:
- cargo test --verbose - cargo test --verbose
- cargo test --verbose --manifest-path grep/Cargo.toml
- cargo test --verbose --manifest-path globset/Cargo.toml
before_deploy: before_deploy:
# Generate artifacts for release # Generate artifacts for release
@@ -41,7 +43,7 @@ before_deploy:
- appveyor PushArtifact ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip - appveyor PushArtifact ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip
deploy: deploy:
description: 'Windows release' description: 'Automatically deployed release'
# All the zipped artifacts will be deployed # All the zipped artifacts will be deployed
artifact: /.*\.zip/ artifact: /.*\.zip/
auth_token: auth_token:

View File

@@ -1,5 +0,0 @@
These are internal microbenchmarks for tracking the peformance of individual
components inside of ripgrep. At the moment, they aren't heavily used.
For performance benchmarks of ripgrep proper, see the sibling `benchsuite`
directory.

View File

@@ -19,6 +19,10 @@ run_test_suite() {
cargo clean --target $TARGET --verbose cargo clean --target $TARGET --verbose
cargo build --target $TARGET --verbose cargo build --target $TARGET --verbose
cargo test --target $TARGET --verbose cargo test --target $TARGET --verbose
cargo build --target $TARGET --verbose --manifest-path grep/Cargo.toml
cargo test --target $TARGET --verbose --manifest-path grep/Cargo.toml
cargo build --target $TARGET --verbose --manifest-path globset/Cargo.toml
cargo test --target $TARGET --verbose --manifest-path globset/Cargo.toml
# sanity check the file type # sanity check the file type
file target/$TARGET/debug/rg file target/$TARGET/debug/rg

View File

@@ -70,6 +70,7 @@ Show this usage message.
.TP .TP
.B \-i, \-\-ignore\-case .B \-i, \-\-ignore\-case
Case insensitive search. Case insensitive search.
Overridden by \-\-case\-sensitive.
.RS .RS
.RE .RE
.TP .TP
@@ -90,12 +91,6 @@ If a match is found in a file, stop searching that file.
.RS .RS
.RE .RE
.TP .TP
.B \-r, \-\-replace \f[I]ARG\f[]
Replace every match with the string given.
Capture group indices (e.g., $5) and names (e.g., $foo) are supported.
.RS
.RE
.TP
.B \-t, \-\-type \f[I]TYPE\f[] ... .B \-t, \-\-type \f[I]TYPE\f[] ...
Only search files matching TYPE. Only search files matching TYPE.
Multiple type flags may be provided. Multiple type flags may be provided.
@@ -209,6 +204,12 @@ Follow symlinks.
.RS .RS
.RE .RE
.TP .TP
.B \-\-maxdepth \f[I]NUM\f[]
Descend at most NUM directories below the command line arguments.
A value of zero searches only the starting\-points themselves.
.RS
.RE
.TP
.B \-\-mmap .B \-\-mmap
Search using memory maps when possible. Search using memory maps when possible.
This is enabled by default when ripgrep thinks it will be faster. This is enabled by default when ripgrep thinks it will be faster.
@@ -252,9 +253,25 @@ Alias for \-\-color=always \-\-heading \-n.
.RS .RS
.RE .RE
.TP .TP
.B \-r, \-\-replace \f[I]ARG\f[]
Replace every match with the string given when printing search results.
Neither this flag nor any other flag will modify your files.
.RS
.PP
Capture group indices (e.g., $5) and names (e.g., $foo) are supported in
the replacement string.
.RE
.TP
.B \-s, \-\-case\-sensitive
Search case sensitively.
This overrides \-\-ignore\-case and \-\-smart\-case.
.RS
.RE
.TP
.B \-S, \-\-smart\-case .B \-S, \-\-smart\-case
Search case insensitively if the pattern is all lowercase. Search case insensitively if the pattern is all lowercase.
Search case sensitively otherwise. Search case sensitively otherwise.
This is overridden by either \-\-case\-sensitive or \-\-ignore\-case.
.RS .RS
.RE .RE
.TP .TP
@@ -291,10 +308,12 @@ Multiple \-\-type\-add flags can be provided.
Unless \-\-type\-clear is used, globs are added to any existing globs Unless \-\-type\-clear is used, globs are added to any existing globs
inside of ripgrep. inside of ripgrep.
Note that this must be passed to every invocation of rg. Note that this must be passed to every invocation of rg.
Type settings are NOT persisted.
.RS .RS
.RE
.PP .PP
Example: \f[C]\-\-type\-add\ html:*.html\f[] Example:
\f[C]rg\ \-\-type\-add\ \[aq]foo:*.foo\[aq]\ \-tfoo\ PATTERN\f[]
.RE
.TP .TP
.B \-\-type\-clear \f[I]TYPE\f[] ... .B \-\-type\-clear \f[I]TYPE\f[] ...
Clear the file type globs previously defined for TYPE. Clear the file type globs previously defined for TYPE.

View File

@@ -49,7 +49,7 @@ the raw speed of grep.
: Show this usage message. : Show this usage message.
-i, --ignore-case -i, --ignore-case
: Case insensitive search. : Case insensitive search. Overridden by --case-sensitive.
-n, --line-number -n, --line-number
: Show line numbers (1-based). This is enabled by default at a tty. : Show line numbers (1-based). This is enabled by default at a tty.
@@ -61,10 +61,6 @@ the raw speed of grep.
: Do not print anything to stdout. If a match is found in a file, stop : Do not print anything to stdout. If a match is found in a file, stop
searching that file. searching that file.
-r, --replace *ARG*
: Replace every match with the string given. Capture group indices (e.g., $5)
and names (e.g., $foo) are supported.
-t, --type *TYPE* ... -t, --type *TYPE* ...
: Only search files matching TYPE. Multiple type flags may be provided. Use the : Only search files matching TYPE. Multiple type flags may be provided. Use the
--type-list flag to list all available types. --type-list flag to list all available types.
@@ -136,6 +132,10 @@ the raw speed of grep.
-L, --follow -L, --follow
: Follow symlinks. : Follow symlinks.
--maxdepth *NUM*
: Descend at most NUM directories below the command line arguments.
A value of zero searches only the starting-points themselves.
--mmap --mmap
: Search using memory maps when possible. This is enabled by default : Search using memory maps when possible. This is enabled by default
when ripgrep thinks it will be faster. (Note that mmap searching when ripgrep thinks it will be faster. (Note that mmap searching
@@ -164,9 +164,20 @@ the raw speed of grep.
-p, --pretty -p, --pretty
: Alias for --color=always --heading -n. : Alias for --color=always --heading -n.
-r, --replace *ARG*
: Replace every match with the string given when printing search results.
Neither this flag nor any other flag will modify your files.
Capture group indices (e.g., $5) and names (e.g., $foo) are supported
in the replacement string.
-s, --case-sensitive
: Search case sensitively. This overrides --ignore-case and --smart-case.
-S, --smart-case -S, --smart-case
: Search case insensitively if the pattern is all lowercase. : Search case insensitively if the pattern is all lowercase.
Search case sensitively otherwise. Search case sensitively otherwise. This is overridden by either
--case-sensitive or --ignore-case.
-j, --threads *ARG* -j, --threads *ARG*
: The number of threads to use. Defaults to the number of logical CPUs : The number of threads to use. Defaults to the number of logical CPUs
@@ -189,9 +200,10 @@ the raw speed of grep.
: Add a new glob for a particular file type. Only one glob can be added : Add a new glob for a particular file type. Only one glob can be added
at a time. Multiple --type-add flags can be provided. Unless --type-clear at a time. Multiple --type-add flags can be provided. Unless --type-clear
is used, globs are added to any existing globs inside of ripgrep. Note that is used, globs are added to any existing globs inside of ripgrep. Note that
this must be passed to every invocation of rg. this must be passed to every invocation of rg. Type settings are NOT
persisted.
Example: `--type-add html:*.html` Example: `rg --type-add 'foo:*.foo' -tfoo PATTERN`
--type-clear *TYPE* ... --type-clear *TYPE* ...
: Clear the file type globs previously defined for TYPE. This only clears : Clear the file type globs previously defined for TYPE. This only clears

30
globset/Cargo.toml Normal file
View File

@@ -0,0 +1,30 @@
[package]
name = "globset"
version = "0.1.0" #:version
authors = ["Andrew Gallant <jamslam@gmail.com>"]
description = """
Cross platform single glob and glob set matching. Glob set matching is the
process of matching one or more glob patterns against a single candidate path
simultaneously, and returning all of the globs that matched.
"""
documentation = "https://docs.rs/globset"
homepage = "https://github.com/BurntSushi/ripgrep/tree/master/globset"
repository = "https://github.com/BurntSushi/ripgrep/tree/master/globset"
readme = "README.md"
keywords = ["regex", "glob", "multiple", "set", "pattern"]
license = "Unlicense/MIT"
[lib]
name = "globset"
bench = false
[dependencies]
aho-corasick = "0.5.3"
fnv = "1.0"
lazy_static = "0.2"
log = "0.3"
memchr = "0.1"
regex = "0.1.77"
[dev-dependencies]
glob = "0.2"

122
globset/README.md Normal file
View File

@@ -0,0 +1,122 @@
globset
=======
Cross platform single glob and glob set matching. Glob set matching is the
process of matching one or more glob patterns against a single candidate path
simultaneously, and returning all of the globs that matched.
[![Linux build status](https://api.travis-ci.org/BurntSushi/ripgrep.png)](https://travis-ci.org/BurntSushi/ripgrep)
[![Windows build status](https://ci.appveyor.com/api/projects/status/github/BurntSushi/ripgrep?svg=true)](https://ci.appveyor.com/project/BurntSushi/ripgrep)
[![](https://img.shields.io/crates/v/globset.svg)](https://crates.io/crates/globset)
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
### Documentation
[https://docs.rs/globset](https://docs.rs/globset)
### Usage
Add this to your `Cargo.toml`:
```toml
[dependencies]
globset = "0.1"
```
and this to your crate root:
```rust
extern crate globset;
```
### Example: one glob
This example shows how to match a single glob against a single file path.
```rust
use globset::Glob;
let glob = try!(Glob::new("*.rs")).compile_matcher();
assert!(glob.is_match("foo.rs"));
assert!(glob.is_match("foo/bar.rs"));
assert!(!glob.is_match("Cargo.toml"));
```
### Example: configuring a glob matcher
This example shows how to use a `GlobBuilder` to configure aspects of match
semantics. In this example, we prevent wildcards from matching path separators.
```rust
use globset::GlobBuilder;
let glob = try!(GlobBuilder::new("*.rs")
.literal_separator(true).build()).compile_matcher();
assert!(glob.is_match("foo.rs"));
assert!(!glob.is_match("foo/bar.rs")); // no longer matches
assert!(!glob.is_match("Cargo.toml"));
```
### Example: match multiple globs at once
This example shows how to match multiple glob patterns at once.
```rust
use globset::{Glob, GlobSetBuilder};
let mut builder = GlobSetBuilder::new();
// A GlobBuilder can be used to configure each glob's match semantics
// independently.
builder.add(try!(Glob::new("*.rs")));
builder.add(try!(Glob::new("src/lib.rs")));
builder.add(try!(Glob::new("src/**/foo.rs")));
let set = try!(builder.build());
assert_eq!(set.matches("src/bar/baz/foo.rs"), vec![0, 2]);
```
### Performance
This crate implements globs by converting them to regular expressions, and
executing them with the
[`regex`](https://github.com/rust-lang-nursery/regex)
crate.
For single glob matching, performance of this crate should be roughly on par
with the performance of the
[`glob`](https://github.com/rust-lang-nursery/glob)
crate. (`*_regex` correspond to benchmarks for this library while `*_glob`
correspond to benchmarks for the `glob` library.)
Optimizations in the `regex` crate may propel this library past `glob`,
particularly when matching longer paths.
```
test ext_glob ... bench: 425 ns/iter (+/- 21)
test ext_regex ... bench: 175 ns/iter (+/- 10)
test long_glob ... bench: 182 ns/iter (+/- 11)
test long_regex ... bench: 173 ns/iter (+/- 10)
test short_glob ... bench: 69 ns/iter (+/- 4)
test short_regex ... bench: 83 ns/iter (+/- 2)
```
The primary performance advantage of this crate is when matching multiple
globs against a single path. With the `glob` crate, one must match each glob
synchronously, one after the other. In this crate, many can be matched
simultaneously. For example:
```
test many_short_glob ... bench: 1,063 ns/iter (+/- 47)
test many_short_regex_set ... bench: 186 ns/iter (+/- 11)
```
### Comparison with the [`glob`](https://github.com/rust-lang-nursery/glob) crate
* Supports alternate "or" globs, e.g., `*.{foo,bar}`.
* Can match non-UTF-8 file paths correctly.
* Supports matching multiple globs at once.
* Doesn't provide a recursive directory iterator of matching file paths,
although I believe this crate should grow one eventually.
* Supports case insensitive and require-literal-separator match options, but
**doesn't** support the require-literal-leading-dot option.

View File

@@ -5,37 +5,50 @@ tool itself, see the benchsuite directory.
#![feature(test)] #![feature(test)]
extern crate glob; extern crate glob;
extern crate globset;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
extern crate regex; extern crate regex;
extern crate test; extern crate test;
use globset::{Candidate, Glob, GlobMatcher, GlobSet, GlobSetBuilder};
const EXT: &'static str = "some/a/bigger/path/to/the/crazy/needle.txt";
const EXT_PAT: &'static str = "*.txt";
const SHORT: &'static str = "some/needle.txt"; const SHORT: &'static str = "some/needle.txt";
const SHORT_PAT: &'static str = "some/**/needle.txt"; const SHORT_PAT: &'static str = "some/**/needle.txt";
const LONG: &'static str = "some/a/bigger/path/to/the/crazy/needle.txt"; const LONG: &'static str = "some/a/bigger/path/to/the/crazy/needle.txt";
const LONG_PAT: &'static str = "some/**/needle.txt"; const LONG_PAT: &'static str = "some/**/needle.txt";
#[allow(dead_code, unused_variables)]
#[path = "../src/glob.rs"]
mod reglob;
fn new_glob(pat: &str) -> glob::Pattern { fn new_glob(pat: &str) -> glob::Pattern {
glob::Pattern::new(pat).unwrap() glob::Pattern::new(pat).unwrap()
} }
fn new_reglob(pat: &str) -> reglob::Set { fn new_reglob(pat: &str) -> GlobMatcher {
let mut builder = reglob::SetBuilder::new(); Glob::new(pat).unwrap().compile_matcher()
builder.add(pat).unwrap(); }
fn new_reglob_many(pats: &[&str]) -> GlobSet {
let mut builder = GlobSetBuilder::new();
for pat in pats {
builder.add(Glob::new(pat).unwrap());
}
builder.build().unwrap() builder.build().unwrap()
} }
fn new_reglob_many(pats: &[&str]) -> reglob::Set { #[bench]
let mut builder = reglob::SetBuilder::new(); fn ext_glob(b: &mut test::Bencher) {
for pat in pats { let pat = new_glob(EXT_PAT);
builder.add(pat).unwrap(); b.iter(|| assert!(pat.matches(EXT)));
} }
builder.build().unwrap()
#[bench]
fn ext_regex(b: &mut test::Bencher) {
let set = new_reglob(EXT_PAT);
let cand = Candidate::new(EXT);
b.iter(|| assert!(set.is_match_candidate(&cand)));
} }
#[bench] #[bench]
@@ -47,7 +60,8 @@ fn short_glob(b: &mut test::Bencher) {
#[bench] #[bench]
fn short_regex(b: &mut test::Bencher) { fn short_regex(b: &mut test::Bencher) {
let set = new_reglob(SHORT_PAT); let set = new_reglob(SHORT_PAT);
b.iter(|| assert!(set.is_match(SHORT))); let cand = Candidate::new(SHORT);
b.iter(|| assert!(set.is_match_candidate(&cand)));
} }
#[bench] #[bench]
@@ -59,7 +73,8 @@ fn long_glob(b: &mut test::Bencher) {
#[bench] #[bench]
fn long_regex(b: &mut test::Bencher) { fn long_regex(b: &mut test::Bencher) {
let set = new_reglob(LONG_PAT); let set = new_reglob(LONG_PAT);
b.iter(|| assert!(set.is_match(LONG))); let cand = Candidate::new(LONG);
b.iter(|| assert!(set.is_match_candidate(&cand)));
} }
const MANY_SHORT_GLOBS: &'static [&'static str] = &[ const MANY_SHORT_GLOBS: &'static [&'static str] = &[
@@ -101,26 +116,3 @@ fn many_short_regex_set(b: &mut test::Bencher) {
let set = new_reglob_many(MANY_SHORT_GLOBS); let set = new_reglob_many(MANY_SHORT_GLOBS);
b.iter(|| assert_eq!(2, set.matches(MANY_SHORT_SEARCH).iter().count())); b.iter(|| assert_eq!(2, set.matches(MANY_SHORT_SEARCH).iter().count()));
} }
// This is the fastest on my system (beating many_glob by about 2x). This
// suggests that a RegexSet needs quite a few regexes (or a larger haystack)
// in order for it to scale.
//
// TODO(burntsushi): come up with a benchmark that uses more complex patterns
// or a longer haystack.
#[bench]
fn many_short_regex_pattern(b: &mut test::Bencher) {
let pats: Vec<_> = MANY_SHORT_GLOBS.iter().map(|&s| {
let pat = reglob::Pattern::new(s).unwrap();
regex::Regex::new(&pat.to_regex()).unwrap()
}).collect();
b.iter(|| {
let mut count = 0;
for pat in &pats {
if pat.is_match(MANY_SHORT_SEARCH) {
count += 1;
}
}
assert_eq!(2, count);
})
}

1300
globset/src/glob.rs Normal file

File diff suppressed because it is too large Load Diff

753
globset/src/lib.rs Normal file
View File

@@ -0,0 +1,753 @@
/*!
The globset crate provides cross platform single glob and glob set matching.
Glob set matching is the process of matching one or more glob patterns against
a single candidate path simultaneously, and returning all of the globs that
matched. For example, given this set of globs:
```ignore
*.rs
src/lib.rs
src/**/foo.rs
```
and a path `src/bar/baz/foo.rs`, then the set would report the first and third
globs as matching.
Single glob matching is also provided and is done by converting globs to
# Example: one glob
This example shows how to match a single glob against a single file path.
```
# fn example() -> Result<(), globset::Error> {
use globset::Glob;
let glob = try!(Glob::new("*.rs")).compile_matcher();
assert!(glob.is_match("foo.rs"));
assert!(glob.is_match("foo/bar.rs"));
assert!(!glob.is_match("Cargo.toml"));
# Ok(()) } example().unwrap();
```
# Example: configuring a glob matcher
This example shows how to use a `GlobBuilder` to configure aspects of match
semantics. In this example, we prevent wildcards from matching path separators.
```
# fn example() -> Result<(), globset::Error> {
use globset::GlobBuilder;
let glob = try!(GlobBuilder::new("*.rs")
.literal_separator(true).build()).compile_matcher();
assert!(glob.is_match("foo.rs"));
assert!(!glob.is_match("foo/bar.rs")); // no longer matches
assert!(!glob.is_match("Cargo.toml"));
# Ok(()) } example().unwrap();
```
# Example: match multiple globs at once
This example shows how to match multiple glob patterns at once.
```
# fn example() -> Result<(), globset::Error> {
use globset::{Glob, GlobSetBuilder};
let mut builder = GlobSetBuilder::new();
// A GlobBuilder can be used to configure each glob's match semantics
// independently.
builder.add(try!(Glob::new("*.rs")));
builder.add(try!(Glob::new("src/lib.rs")));
builder.add(try!(Glob::new("src/**/foo.rs")));
let set = try!(builder.build());
assert_eq!(set.matches("src/bar/baz/foo.rs"), vec![0, 2]);
# Ok(()) } example().unwrap();
```
# Syntax
Standard Unix-style glob syntax is supported:
* `?` matches any single character. (If the `literal_separator` option is
enabled, then `?` can never match a path separator.)
* `*` matches zero or more characters. (If the `literal_separator` option is
enabled, then `*` can never match a path separator.)
* `**` recursively matches directories but are only legal in three situations.
First, if the glob starts with <code>\*\*&#x2F;</code>, then it matches
all directories. For example, <code>\*\*&#x2F;foo</code> matches `foo`
and `bar/foo` but not `foo/bar`. Secondly, if the glob ends with
<code>&#x2F;\*\*</code>, then it matches all sub-entries. For example,
<code>foo&#x2F;\*\*</code> matches `foo/a` and `foo/a/b`, but not `foo`.
Thirdly, if the glob contains <code>&#x2F;\*\*&#x2F;</code> anywhere within
the pattern, then it matches zero or more directories. Using `**` anywhere
else is illegal (N.B. the glob `**` is allowed and means "match everything").
* `{a,b}` matches `a` or `b` where `a` and `b` are arbitrary glob patterns.
(N.B. Nesting `{...}` is not currently allowed.)
* `[ab]` matches `a` or `b` where `a` and `b` are characters. Use
`[!ab]` to match any character except for `a` and `b`.
* Metacharacters such as `*` and `?` can be escaped with character class
notation. e.g., `[*]` matches `*`.
A `GlobBuilder` can be used to prevent wildcards from matching path separators,
or to enable case insensitive matching.
*/
#![deny(missing_docs)]
extern crate aho_corasick;
extern crate fnv;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate memchr;
extern crate regex;
use std::borrow::Cow;
use std::collections::{BTreeMap, HashMap};
use std::error::Error as StdError;
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::hash;
use std::path::Path;
use std::str;
use aho_corasick::{Automaton, AcAutomaton, FullAcAutomaton};
use regex::bytes::{Regex, RegexBuilder, RegexSet};
use pathutil::{
file_name, file_name_ext, normalize_path, os_str_bytes, path_bytes,
};
use glob::MatchStrategy;
pub use glob::{Glob, GlobBuilder, GlobMatcher};
mod glob;
mod pathutil;
macro_rules! eprintln {
($($tt:tt)*) => {{
use std::io::Write;
let _ = writeln!(&mut ::std::io::stderr(), $($tt)*);
}}
}
/// Represents an error that can occur when parsing a glob pattern.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Error {
/// Occurs when a use of `**` is invalid. Namely, `**` can only appear
/// adjacent to a path separator, or the beginning/end of a glob.
InvalidRecursive,
/// Occurs when a character class (e.g., `[abc]`) is not closed.
UnclosedClass,
/// Occurs when a range in a character (e.g., `[a-z]`) is invalid. For
/// example, if the range starts with a lexicographically larger character
/// than it ends with.
InvalidRange(char, char),
/// Occurs when a `}` is found without a matching `{`.
UnopenedAlternates,
/// Occurs when a `{` is found without a matching `}`.
UnclosedAlternates,
/// Occurs when an alternating group is nested inside another alternating
/// group, e.g., `{{a,b},{c,d}}`.
NestedAlternates,
/// An error associated with parsing or compiling a regex.
Regex(String),
}
impl StdError for Error {
fn description(&self) -> &str {
match *self {
Error::InvalidRecursive => {
"invalid use of **; must be one path component"
}
Error::UnclosedClass => {
"unclosed character class; missing ']'"
}
Error::InvalidRange(_, _) => {
"invalid character range"
}
Error::UnopenedAlternates => {
"unopened alternate group; missing '{' \
(maybe escape '}' with '[}]'?)"
}
Error::UnclosedAlternates => {
"unclosed alternate group; missing '}' \
(maybe escape '{' with '[{]'?)"
}
Error::NestedAlternates => {
"nested alternate groups are not allowed"
}
Error::Regex(ref err) => err,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::InvalidRecursive
| Error::UnclosedClass
| Error::UnopenedAlternates
| Error::UnclosedAlternates
| Error::NestedAlternates
| Error::Regex(_) => {
write!(f, "{}", self.description())
}
Error::InvalidRange(s, e) => {
write!(f, "invalid range; '{}' > '{}'", s, e)
}
}
}
}
fn new_regex(pat: &str) -> Result<Regex, Error> {
RegexBuilder::new(pat)
.dot_matches_new_line(true)
.size_limit(10 * (1 << 20))
.dfa_size_limit(10 * (1 << 20))
.compile()
.map_err(|err| Error::Regex(err.to_string()))
}
fn new_regex_set<I, S>(pats: I) -> Result<RegexSet, Error>
where S: AsRef<str>, I: IntoIterator<Item=S> {
RegexSet::new(pats).map_err(|err| Error::Regex(err.to_string()))
}
type Fnv = hash::BuildHasherDefault<fnv::FnvHasher>;
/// GlobSet represents a group of globs that can be matched together in a
/// single pass.
#[derive(Clone, Debug)]
pub struct GlobSet {
strats: Vec<GlobSetMatchStrategy>,
}
impl GlobSet {
/// Returns true if any glob in this set matches the path given.
pub fn is_match<P: AsRef<Path>>(&self, path: P) -> bool {
self.is_match_candidate(&Candidate::new(path.as_ref()))
}
/// Returns true if any glob in this set matches the path given.
///
/// This takes a Candidate as input, which can be used to amortize the
/// cost of preparing a path for matching.
pub fn is_match_candidate(&self, path: &Candidate) -> bool {
for strat in &self.strats {
if strat.is_match(path) {
return true;
}
}
false
}
/// Returns the sequence number of every glob pattern that matches the
/// given path.
///
/// This takes a Candidate as input, which can be used to amortize the
/// cost of preparing a path for matching.
pub fn matches<P: AsRef<Path>>(&self, path: P) -> Vec<usize> {
self.matches_candidate(&Candidate::new(path.as_ref()))
}
/// Returns the sequence number of every glob pattern that matches the
/// given path.
///
/// This takes a Candidate as input, which can be used to amortize the
/// cost of preparing a path for matching.
pub fn matches_candidate(&self, path: &Candidate) -> Vec<usize> {
let mut into = vec![];
self.matches_candidate_into(path, &mut into);
into
}
/// Adds the sequence number of every glob pattern that matches the given
/// path to the vec given.
///
/// `into` is is cleared before matching begins, and contains the set of
/// sequence numbers (in ascending order) after matching ends. If no globs
/// were matched, then `into` will be empty.
pub fn matches_candidate_into(
&self,
path: &Candidate,
into: &mut Vec<usize>,
) {
into.clear();
for strat in &self.strats {
strat.matches_into(path, into);
}
into.sort();
into.dedup();
}
fn new(pats: &[Glob]) -> Result<GlobSet, Error> {
let mut lits = LiteralStrategy::new();
let mut base_lits = BasenameLiteralStrategy::new();
let mut exts = ExtensionStrategy::new();
let mut prefixes = MultiStrategyBuilder::new();
let mut suffixes = MultiStrategyBuilder::new();
let mut required_exts = RequiredExtensionStrategyBuilder::new();
let mut regexes = MultiStrategyBuilder::new();
for (i, p) in pats.iter().enumerate() {
match MatchStrategy::new(p) {
MatchStrategy::Literal(lit) => {
lits.add(i, lit);
}
MatchStrategy::BasenameLiteral(lit) => {
base_lits.add(i, lit);
}
MatchStrategy::Extension(ext) => {
exts.add(i, ext);
}
MatchStrategy::Prefix(prefix) => {
prefixes.add(i, prefix);
}
MatchStrategy::Suffix { suffix, component } => {
if component {
lits.add(i, suffix[1..].to_string());
}
suffixes.add(i, suffix);
}
MatchStrategy::RequiredExtension(ext) => {
required_exts.add(i, ext, p.regex().to_owned());
}
MatchStrategy::Regex => {
debug!("glob converted to regex: {:?}", p);
regexes.add(i, p.regex().to_owned());
}
}
}
debug!("built glob set; {} literals, {} basenames, {} extensions, \
{} prefixes, {} suffixes, {} required extensions, {} regexes",
lits.0.len(), base_lits.0.len(), exts.0.len(),
prefixes.literals.len(), suffixes.literals.len(),
required_exts.0.len(), regexes.literals.len());
Ok(GlobSet {
strats: vec![
GlobSetMatchStrategy::Extension(exts),
GlobSetMatchStrategy::BasenameLiteral(base_lits),
GlobSetMatchStrategy::Literal(lits),
GlobSetMatchStrategy::Suffix(suffixes.suffix()),
GlobSetMatchStrategy::Prefix(prefixes.prefix()),
GlobSetMatchStrategy::RequiredExtension(
try!(required_exts.build())),
GlobSetMatchStrategy::Regex(try!(regexes.regex_set())),
],
})
}
}
/// GlobSetBuilder builds a group of patterns that can be used to
/// simultaneously match a file path.
pub struct GlobSetBuilder {
pats: Vec<Glob>,
}
impl GlobSetBuilder {
/// Create a new GlobSetBuilder. A GlobSetBuilder can be used to add new
/// patterns. Once all patterns have been added, `build` should be called
/// to produce a `GlobSet`, which can then be used for matching.
pub fn new() -> GlobSetBuilder {
GlobSetBuilder { pats: vec![] }
}
/// Builds a new matcher from all of the glob patterns added so far.
///
/// Once a matcher is built, no new patterns can be added to it.
pub fn build(&self) -> Result<GlobSet, Error> {
GlobSet::new(&self.pats)
}
/// Add a new pattern to this set.
#[allow(dead_code)]
pub fn add(&mut self, pat: Glob) -> &mut GlobSetBuilder {
self.pats.push(pat);
self
}
}
/// A candidate path for matching.
///
/// All glob matching in this crate operates on `Candidate` values.
/// Constructing candidates has a very small cost associated with it, so
/// callers may find it beneficial to amortize that cost when matching a single
/// path against multiple globs or sets of globs.
#[derive(Clone, Debug)]
pub struct Candidate<'a> {
path: Cow<'a, [u8]>,
basename: Cow<'a, [u8]>,
ext: &'a OsStr,
}
impl<'a> Candidate<'a> {
/// Create a new candidate for matching from the given path.
pub fn new<P: AsRef<Path> + ?Sized>(path: &'a P) -> Candidate<'a> {
let path = path.as_ref();
let basename = file_name(path).unwrap_or(OsStr::new(""));
Candidate {
path: normalize_path(path_bytes(path)),
basename: os_str_bytes(basename),
ext: file_name_ext(basename).unwrap_or(OsStr::new("")),
}
}
fn path_prefix(&self, max: usize) -> &[u8] {
if self.path.len() <= max {
&*self.path
} else {
&self.path[..max]
}
}
fn path_suffix(&self, max: usize) -> &[u8] {
if self.path.len() <= max {
&*self.path
} else {
&self.path[self.path.len() - max..]
}
}
}
#[derive(Clone, Debug)]
enum GlobSetMatchStrategy {
Literal(LiteralStrategy),
BasenameLiteral(BasenameLiteralStrategy),
Extension(ExtensionStrategy),
Prefix(PrefixStrategy),
Suffix(SuffixStrategy),
RequiredExtension(RequiredExtensionStrategy),
Regex(RegexSetStrategy),
}
impl GlobSetMatchStrategy {
fn is_match(&self, candidate: &Candidate) -> bool {
use self::GlobSetMatchStrategy::*;
match *self {
Literal(ref s) => s.is_match(candidate),
BasenameLiteral(ref s) => s.is_match(candidate),
Extension(ref s) => s.is_match(candidate),
Prefix(ref s) => s.is_match(candidate),
Suffix(ref s) => s.is_match(candidate),
RequiredExtension(ref s) => s.is_match(candidate),
Regex(ref s) => s.is_match(candidate),
}
}
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
use self::GlobSetMatchStrategy::*;
match *self {
Literal(ref s) => s.matches_into(candidate, matches),
BasenameLiteral(ref s) => s.matches_into(candidate, matches),
Extension(ref s) => s.matches_into(candidate, matches),
Prefix(ref s) => s.matches_into(candidate, matches),
Suffix(ref s) => s.matches_into(candidate, matches),
RequiredExtension(ref s) => s.matches_into(candidate, matches),
Regex(ref s) => s.matches_into(candidate, matches),
}
}
}
#[derive(Clone, Debug)]
struct LiteralStrategy(BTreeMap<Vec<u8>, Vec<usize>>);
impl LiteralStrategy {
fn new() -> LiteralStrategy {
LiteralStrategy(BTreeMap::new())
}
fn add(&mut self, global_index: usize, lit: String) {
self.0.entry(lit.into_bytes()).or_insert(vec![]).push(global_index);
}
fn is_match(&self, candidate: &Candidate) -> bool {
self.0.contains_key(&*candidate.path)
}
#[inline(never)]
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
if let Some(hits) = self.0.get(&*candidate.path) {
matches.extend(hits);
}
}
}
#[derive(Clone, Debug)]
struct BasenameLiteralStrategy(BTreeMap<Vec<u8>, Vec<usize>>);
impl BasenameLiteralStrategy {
fn new() -> BasenameLiteralStrategy {
BasenameLiteralStrategy(BTreeMap::new())
}
fn add(&mut self, global_index: usize, lit: String) {
self.0.entry(lit.into_bytes()).or_insert(vec![]).push(global_index);
}
fn is_match(&self, candidate: &Candidate) -> bool {
if candidate.basename.is_empty() {
return false;
}
self.0.contains_key(&*candidate.basename)
}
#[inline(never)]
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
if candidate.basename.is_empty() {
return;
}
if let Some(hits) = self.0.get(&*candidate.basename) {
matches.extend(hits);
}
}
}
#[derive(Clone, Debug)]
struct ExtensionStrategy(HashMap<OsString, Vec<usize>, Fnv>);
impl ExtensionStrategy {
fn new() -> ExtensionStrategy {
ExtensionStrategy(HashMap::with_hasher(Fnv::default()))
}
fn add(&mut self, global_index: usize, ext: OsString) {
self.0.entry(ext).or_insert(vec![]).push(global_index);
}
fn is_match(&self, candidate: &Candidate) -> bool {
if candidate.ext.is_empty() {
return false;
}
self.0.contains_key(candidate.ext)
}
#[inline(never)]
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
if candidate.ext.is_empty() {
return;
}
if let Some(hits) = self.0.get(candidate.ext) {
matches.extend(hits);
}
}
}
#[derive(Clone, Debug)]
struct PrefixStrategy {
matcher: FullAcAutomaton<Vec<u8>>,
map: Vec<usize>,
longest: usize,
}
impl PrefixStrategy {
fn is_match(&self, candidate: &Candidate) -> bool {
let path = candidate.path_prefix(self.longest);
for m in self.matcher.find_overlapping(path) {
if m.start == 0 {
return true;
}
}
false
}
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
let path = candidate.path_prefix(self.longest);
for m in self.matcher.find_overlapping(path) {
if m.start == 0 {
matches.push(self.map[m.pati]);
}
}
}
}
#[derive(Clone, Debug)]
struct SuffixStrategy {
matcher: FullAcAutomaton<Vec<u8>>,
map: Vec<usize>,
longest: usize,
}
impl SuffixStrategy {
fn is_match(&self, candidate: &Candidate) -> bool {
let path = candidate.path_suffix(self.longest);
for m in self.matcher.find_overlapping(path) {
if m.end == path.len() {
return true;
}
}
false
}
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
let path = candidate.path_suffix(self.longest);
for m in self.matcher.find_overlapping(path) {
if m.end == path.len() {
matches.push(self.map[m.pati]);
}
}
}
}
#[derive(Clone, Debug)]
struct RequiredExtensionStrategy(HashMap<OsString, Vec<(usize, Regex)>, Fnv>);
impl RequiredExtensionStrategy {
fn is_match(&self, candidate: &Candidate) -> bool {
if candidate.ext.is_empty() {
return false;
}
match self.0.get(candidate.ext) {
None => false,
Some(regexes) => {
for &(_, ref re) in regexes {
if re.is_match(&*candidate.path) {
return true;
}
}
false
}
}
}
#[inline(never)]
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
if candidate.ext.is_empty() {
return;
}
if let Some(regexes) = self.0.get(candidate.ext) {
for &(global_index, ref re) in regexes {
if re.is_match(&*candidate.path) {
matches.push(global_index);
}
}
}
}
}
#[derive(Clone, Debug)]
struct RegexSetStrategy {
matcher: RegexSet,
map: Vec<usize>,
}
impl RegexSetStrategy {
fn is_match(&self, candidate: &Candidate) -> bool {
self.matcher.is_match(&*candidate.path)
}
fn matches_into(&self, candidate: &Candidate, matches: &mut Vec<usize>) {
for i in self.matcher.matches(&*candidate.path) {
matches.push(self.map[i]);
}
}
}
#[derive(Clone, Debug)]
struct MultiStrategyBuilder {
literals: Vec<String>,
map: Vec<usize>,
longest: usize,
}
impl MultiStrategyBuilder {
fn new() -> MultiStrategyBuilder {
MultiStrategyBuilder {
literals: vec![],
map: vec![],
longest: 0,
}
}
fn add(&mut self, global_index: usize, literal: String) {
if literal.len() > self.longest {
self.longest = literal.len();
}
self.map.push(global_index);
self.literals.push(literal);
}
fn prefix(self) -> PrefixStrategy {
let it = self.literals.into_iter().map(|s| s.into_bytes());
PrefixStrategy {
matcher: AcAutomaton::new(it).into_full(),
map: self.map,
longest: self.longest,
}
}
fn suffix(self) -> SuffixStrategy {
let it = self.literals.into_iter().map(|s| s.into_bytes());
SuffixStrategy {
matcher: AcAutomaton::new(it).into_full(),
map: self.map,
longest: self.longest,
}
}
fn regex_set(self) -> Result<RegexSetStrategy, Error> {
Ok(RegexSetStrategy {
matcher: try!(new_regex_set(self.literals)),
map: self.map,
})
}
}
#[derive(Clone, Debug)]
struct RequiredExtensionStrategyBuilder(
HashMap<OsString, Vec<(usize, String)>>,
);
impl RequiredExtensionStrategyBuilder {
fn new() -> RequiredExtensionStrategyBuilder {
RequiredExtensionStrategyBuilder(HashMap::new())
}
fn add(&mut self, global_index: usize, ext: OsString, regex: String) {
self.0.entry(ext).or_insert(vec![]).push((global_index, regex));
}
fn build(self) -> Result<RequiredExtensionStrategy, Error> {
let mut exts = HashMap::with_hasher(Fnv::default());
for (ext, regexes) in self.0.into_iter() {
exts.insert(ext.clone(), vec![]);
for (global_index, regex) in regexes {
let compiled = try!(new_regex(&regex));
exts.get_mut(&ext).unwrap().push((global_index, compiled));
}
}
Ok(RequiredExtensionStrategy(exts))
}
}
#[cfg(test)]
mod tests {
use super::GlobSetBuilder;
use glob::Glob;
#[test]
fn set_works() {
let mut builder = GlobSetBuilder::new();
builder.add(Glob::new("src/**/*.rs").unwrap());
builder.add(Glob::new("*.c").unwrap());
builder.add(Glob::new("src/lib.rs").unwrap());
let set = builder.build().unwrap();
assert!(set.is_match("foo.c"));
assert!(set.is_match("src/foo.c"));
assert!(!set.is_match("foo.rs"));
assert!(!set.is_match("tests/foo.rs"));
assert!(set.is_match("src/foo.rs"));
assert!(set.is_match("src/grep/src/main.rs"));
let matches = set.matches("src/lib.rs");
assert_eq!(2, matches.len());
assert_eq!(0, matches[0]);
assert_eq!(2, matches[1]);
}
}

180
globset/src/pathutil.rs Normal file
View File

@@ -0,0 +1,180 @@
use std::borrow::Cow;
use std::ffi::OsStr;
use std::path::Path;
/// The final component of the path, if it is a normal file.
///
/// If the path terminates in ., .., or consists solely of a root of prefix,
/// file_name will return None.
#[cfg(unix)]
pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
path: &'a P,
) -> Option<&'a OsStr> {
use std::os::unix::ffi::OsStrExt;
use memchr::memrchr;
let path = path.as_ref().as_os_str().as_bytes();
if path.is_empty() {
return None;
} else if path.len() == 1 && path[0] == b'.' {
return None;
} else if path.last() == Some(&b'.') {
return None;
} else if path.len() >= 2 && &path[path.len() - 2..] == &b".."[..] {
return None;
}
let last_slash = memrchr(b'/', path).map(|i| i + 1).unwrap_or(0);
Some(OsStr::from_bytes(&path[last_slash..]))
}
/// The final component of the path, if it is a normal file.
///
/// If the path terminates in ., .., or consists solely of a root of prefix,
/// file_name will return None.
#[cfg(not(unix))]
pub fn file_name<'a, P: AsRef<Path> + ?Sized>(
path: &'a P,
) -> Option<&'a OsStr> {
path.as_ref().file_name()
}
/// Return a file extension given a path's file name.
///
/// Note that this does NOT match the semantics of std::path::Path::extension.
/// Namely, the extension includes the `.` and matching is otherwise more
/// liberal. Specifically, the extenion is:
///
/// * None, if the file name given is empty;
/// * None, if there is no embedded `.`;
/// * Otherwise, the portion of the file name starting with the final `.`.
///
/// e.g., A file name of `.rs` has an extension `.rs`.
///
/// N.B. This is done to make certain glob match optimizations easier. Namely,
/// a pattern like `*.rs` is obviously trying to match files with a `rs`
/// extension, but it also matches files like `.rs`, which doesn't have an
/// extension according to std::path::Path::extension.
pub fn file_name_ext(name: &OsStr) -> Option<&OsStr> {
// Yes, these functions are awful, and yes, we are completely violating
// the abstraction barrier of std::ffi. The barrier we're violating is
// that an OsStr's encoding is *ASCII compatible*. While this is obviously
// true on Unix systems, it's also true on Windows because an OsStr uses
// WTF-8 internally: https://simonsapin.github.io/wtf-8/
//
// We should consider doing the same for the other path utility functions.
// Right now, we don't break any barriers, but Windows users are paying
// for it.
//
// Got any better ideas that don't cost anything? Hit me up. ---AG
unsafe fn os_str_as_u8_slice(s: &OsStr) -> &[u8] {
::std::mem::transmute(s)
}
unsafe fn u8_slice_as_os_str(s: &[u8]) -> &OsStr {
::std::mem::transmute(s)
}
if name.is_empty() {
return None;
}
let name = unsafe { os_str_as_u8_slice(name) };
for (i, &b) in name.iter().enumerate().rev() {
if b == b'.' {
return Some(unsafe { u8_slice_as_os_str(&name[i..]) });
}
}
None
}
/// Return raw bytes of a path, transcoded to UTF-8 if necessary.
pub fn path_bytes(path: &Path) -> Cow<[u8]> {
os_str_bytes(path.as_os_str())
}
/// Return the raw bytes of the given OS string, transcoded to UTF-8 if
/// necessary.
#[cfg(unix)]
pub fn os_str_bytes(s: &OsStr) -> Cow<[u8]> {
use std::os::unix::ffi::OsStrExt;
Cow::Borrowed(s.as_bytes())
}
/// Return the raw bytes of the given OS string, transcoded to UTF-8 if
/// necessary.
#[cfg(not(unix))]
pub fn os_str_bytes(s: &OsStr) -> Cow<[u8]> {
// TODO(burntsushi): On Windows, OS strings are WTF-8, which is a superset
// of UTF-8, so even if we could get at the raw bytes, they wouldn't
// be useful. We *must* convert to UTF-8 before doing path matching.
// Unfortunate, but necessary.
match s.to_string_lossy() {
Cow::Owned(s) => Cow::Owned(s.into_bytes()),
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
}
}
/// Normalizes a path to use `/` as a separator everywhere, even on platforms
/// that recognize other characters as separators.
#[cfg(unix)]
pub fn normalize_path(path: Cow<[u8]>) -> Cow<[u8]> {
// UNIX only uses /, so we're good.
path
}
/// Normalizes a path to use `/` as a separator everywhere, even on platforms
/// that recognize other characters as separators.
#[cfg(not(unix))]
pub fn normalize_path(mut path: Cow<[u8]>) -> Cow<[u8]> {
use std::path::is_separator;
for i in 0..path.len() {
if path[i] == b'/' || !is_separator(path[i] as char) {
continue;
}
path.to_mut()[i] = b'/';
}
path
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use std::ffi::OsStr;
use super::{file_name_ext, normalize_path};
macro_rules! ext {
($name:ident, $file_name:expr, $ext:expr) => {
#[test]
fn $name() {
let got = file_name_ext(OsStr::new($file_name));
assert_eq!($ext.map(OsStr::new), got);
}
};
}
ext!(ext1, "foo.rs", Some(".rs"));
ext!(ext2, ".rs", Some(".rs"));
ext!(ext3, "..rs", Some(".rs"));
ext!(ext4, "", None::<&str>);
ext!(ext5, "foo", None::<&str>);
macro_rules! normalize {
($name:ident, $path:expr, $expected:expr) => {
#[test]
fn $name() {
let got = normalize_path(Cow::Owned($path.to_vec()));
assert_eq!($expected.to_vec(), got.into_owned());
}
};
}
normalize!(normal1, b"foo", b"foo");
normalize!(normal2, b"foo/bar", b"foo/bar");
#[cfg(unix)]
normalize!(normal3, b"foo\\bar", b"foo\\bar");
#[cfg(not(unix))]
normalize!(normal3, b"foo\\bar", b"foo/bar");
#[cfg(unix)]
normalize!(normal4, b"foo\\bar/baz", b"foo\\bar/baz");
#[cfg(not(unix))]
normalize!(normal4, b"foo\\bar/baz", b"foo/bar/baz");
}

View File

@@ -184,8 +184,9 @@ impl GrepBuilder {
.unicode(true) .unicode(true)
.case_insensitive(self.opts.case_insensitive) .case_insensitive(self.opts.case_insensitive)
.parse(&self.pattern)); .parse(&self.pattern));
let expr = try!(nonl::remove(expr, self.opts.line_terminator));
debug!("regex ast:\n{:#?}", expr); debug!("regex ast:\n{:#?}", expr);
Ok(try!(nonl::remove(expr, self.opts.line_terminator))) Ok(expr)
} }
} }

14
pkg/brew/ripgrep-bin.rb Normal file
View File

@@ -0,0 +1,14 @@
class RipgrepBin < Formula
version '0.2.1'
desc "Search tool like grep and The Silver Searcher."
homepage "https://github.com/BurntSushi/ripgrep"
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-apple-darwin.tar.gz"
sha256 "f8b208239b988708da2e58f848a75bf70ad144e201b3ed99cd323cc5a699625f"
conflicts_with "ripgrep"
def install
bin.install "rg"
man1.install "rg.1"
end
end

View File

@@ -1,19 +0,0 @@
require 'formula'
class Ripgrep < Formula
version '0.2.0'
desc "Search tool like grep and The Silver Searcher."
homepage "https://github.com/BurntSushi/ripgrep"
if Hardware::CPU.is_64_bit?
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-apple-darwin.tar.gz"
sha256 "f55ef5dac04178bcae0d6c5ba2d09690d326e8c7c3f28e561025b04e1ab81d80"
else
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-i686-apple-darwin.tar.gz"
sha256 "d901d55ccb48c19067f563d42652dfd8642bf50d28a40c0e2a4d3e866857a93b"
end
def install
bin.install "rg"
man1.install "rg.1"
end
end

View File

@@ -62,14 +62,12 @@ Common options:
Precede a glob with a '!' to exclude it. Precede a glob with a '!' to exclude it.
-h, --help Show this usage message. -h, --help Show this usage message.
-i, --ignore-case Case insensitive search. -i, --ignore-case Case insensitive search.
Overridden by --case-sensitive.
-n, --line-number Show line numbers (1-based). This is enabled -n, --line-number Show line numbers (1-based). This is enabled
by default at a tty. by default at a tty.
-N, --no-line-number Suppress line numbers. -N, --no-line-number Suppress line numbers.
-q, --quiet Do not print anything to stdout. If a match is -q, --quiet Do not print anything to stdout. If a match is
found in a file, stop searching that file. found in a file, stop searching that file.
-r, --replace ARG Replace every match with the string given.
Capture group indices (e.g., $5) and names
(e.g., $foo) are supported.
-t, --type TYPE ... Only search files matching TYPE. Multiple type -t, --type TYPE ... Only search files matching TYPE. Multiple type
flags may be provided. Use the --type-list flag flags may be provided. Use the --type-list flag
to list all available types. to list all available types.
@@ -136,6 +134,10 @@ Less common options:
-L, --follow -L, --follow
Follow symlinks. Follow symlinks.
--maxdepth NUM
Descend at most NUM directories below the command line arguments.
A value of zero only searches the starting-points themselves.
--mmap --mmap
Search using memory maps when possible. This is enabled by default Search using memory maps when possible. This is enabled by default
when ripgrep thinks it will be faster. (Note that mmap searching when ripgrep thinks it will be faster. (Note that mmap searching
@@ -164,9 +166,20 @@ Less common options:
-p, --pretty -p, --pretty
Alias for --color=always --heading -n. Alias for --color=always --heading -n.
-r, --replace ARG
Replace every match with the string given when printing search results.
Neither this flag nor any other flag will modify your files.
Capture group indices (e.g., $5) and names (e.g., $foo) are supported
in the replacement string.
-s, --case-sensitive
Search case sensitively. This overrides --ignore-case and --smart-case.
-S, --smart-case -S, --smart-case
Search case insensitively if the pattern is all lowercase. Search case insensitively if the pattern is all lowercase.
Search case sensitively otherwise. Search case sensitively otherwise. This is overridden by
either --case-sensitive or --ignore-case.
-j, --threads ARG -j, --threads ARG
The number of threads to use. Defaults to the number of logical CPUs The number of threads to use. Defaults to the number of logical CPUs
@@ -185,12 +198,13 @@ File type management options:
Show all supported file types and their associated globs. Show all supported file types and their associated globs.
--type-add ARG ... --type-add ARG ...
Add a new glob for a particular file type. Only one glob can be added Add a new glob for a particular file type. Only one glob can be
at a time. Multiple type-add flags can be provided. Unless type-clear added at a time. Multiple --type-add flags can be provided.
is used, globs are added to any existing globs inside of ripgrep. Note Unless --type-clear is used, globs are added to any existing globs
that this must be passed to every invocation of rg. inside of ripgrep. Note that this must be passed to every invocation of
rg. Type settings are NOT persisted.
Example: `--type-add html:*.html` Example: `rg --type-add 'foo:*.foo' -tfoo PATTERN`
--type-clear TYPE ... --type-clear TYPE ...
Clear the file type globs previously defined for TYPE. This only clears Clear the file type globs previously defined for TYPE. This only clears
@@ -206,6 +220,7 @@ pub struct RawArgs {
arg_path: Vec<String>, arg_path: Vec<String>,
flag_after_context: usize, flag_after_context: usize,
flag_before_context: usize, flag_before_context: usize,
flag_case_sensitive: bool,
flag_color: String, flag_color: String,
flag_column: bool, flag_column: bool,
flag_context: usize, flag_context: usize,
@@ -222,6 +237,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_maxdepth: Option<usize>,
flag_mmap: bool, flag_mmap: bool,
flag_no_heading: bool, flag_no_heading: bool,
flag_no_ignore: bool, flag_no_ignore: bool,
@@ -252,7 +268,6 @@ pub struct RawArgs {
/// Args are transformed/normalized from RawArgs. /// Args are transformed/normalized from RawArgs.
#[derive(Debug)] #[derive(Debug)]
pub struct Args { pub struct Args {
pattern: String,
paths: Vec<PathBuf>, paths: Vec<PathBuf>,
after_context: usize, after_context: usize,
before_context: usize, before_context: usize,
@@ -272,6 +287,7 @@ pub struct Args {
invert_match: bool, invert_match: bool,
line_number: bool, line_number: bool,
line_per_match: bool, line_per_match: bool,
maxdepth: Option<usize>,
mmap: bool, mmap: bool,
no_ignore: bool, no_ignore: bool,
no_ignore_parent: bool, no_ignore_parent: bool,
@@ -281,7 +297,6 @@ pub struct Args {
replace: Option<Vec<u8>>, replace: Option<Vec<u8>>,
text: bool, text: bool,
threads: usize, threads: usize,
type_defs: Vec<FileTypeDef>,
type_list: bool, type_list: bool,
types: Types, types: Types,
with_filename: bool, with_filename: bool,
@@ -290,7 +305,6 @@ pub struct Args {
impl RawArgs { impl RawArgs {
/// Convert arguments parsed into a configuration used by ripgrep. /// Convert arguments parsed into a configuration used by ripgrep.
fn to_args(&self) -> Result<Args> { fn to_args(&self) -> Result<Args> {
let pattern = self.pattern();
let paths = let paths =
if self.arg_path.is_empty() { if self.arg_path.is_empty() {
if atty::on_stdin() if atty::on_stdin()
@@ -320,7 +334,7 @@ impl RawArgs {
} else if cfg!(windows) { } else if cfg!(windows) {
// On Windows, memory maps appear faster than read calls. Neat. // On Windows, memory maps appear faster than read calls. Neat.
true true
} else if cfg!(darwin) { } else if cfg!(target_os = "macos") {
// On Mac, memory maps appear to suck. Neat. // On Mac, memory maps appear to suck. Neat.
false false
} else { } else {
@@ -349,14 +363,15 @@ impl RawArgs {
self.flag_threads self.flag_threads
}; };
let color = let color =
if self.flag_vimgrep { if self.flag_color == "always" {
true
} else if self.flag_vimgrep {
false false
} else if self.flag_color == "auto" { } else if self.flag_color == "auto" {
atty::on_stdout() || self.flag_pretty atty::on_stdout() || self.flag_pretty
} else { } else {
self.flag_color == "always" false
}; };
let eol = b'\n';
let mut with_filename = self.flag_with_filename; let mut with_filename = self.flag_with_filename;
if !with_filename { if !with_filename {
@@ -364,22 +379,10 @@ impl RawArgs {
} }
with_filename = with_filename && !self.flag_no_filename; with_filename = with_filename && !self.flag_no_filename;
let mut btypes = TypesBuilder::new();
btypes.add_defaults();
try!(self.add_types(&mut btypes));
let types = try!(btypes.build());
let grep = try!(
GrepBuilder::new(&pattern)
.case_smart(self.flag_smart_case)
.case_insensitive(self.flag_ignore_case)
.line_terminator(eol)
.build()
);
let no_ignore = self.flag_no_ignore || self.flag_unrestricted >= 1; let no_ignore = self.flag_no_ignore || self.flag_unrestricted >= 1;
let hidden = self.flag_hidden || self.flag_unrestricted >= 2; let hidden = self.flag_hidden || self.flag_unrestricted >= 2;
let text = self.flag_text || self.flag_unrestricted >= 3; let text = self.flag_text || self.flag_unrestricted >= 3;
let mut args = Args { let mut args = Args {
pattern: pattern,
paths: paths, paths: paths,
after_context: after_context, after_context: after_context,
before_context: before_context, before_context: before_context,
@@ -388,17 +391,18 @@ impl RawArgs {
context_separator: unescape(&self.flag_context_separator), context_separator: unescape(&self.flag_context_separator),
count: self.flag_count, count: self.flag_count,
files_with_matches: self.flag_files_with_matches, files_with_matches: self.flag_files_with_matches,
eol: eol, eol: self.eol(),
files: self.flag_files, files: self.flag_files,
follow: self.flag_follow, follow: self.flag_follow,
glob_overrides: glob_overrides, glob_overrides: glob_overrides,
grep: grep, grep: try!(self.grep()),
heading: !self.flag_no_heading && self.flag_heading, heading: !self.flag_no_heading && self.flag_heading,
hidden: hidden, hidden: hidden,
ignore_case: self.flag_ignore_case, ignore_case: self.flag_ignore_case,
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,
maxdepth: self.flag_maxdepth,
mmap: mmap, mmap: mmap,
no_ignore: no_ignore, no_ignore: no_ignore,
no_ignore_parent: no_ignore_parent:
@@ -412,9 +416,8 @@ impl RawArgs {
replace: self.flag_replace.clone().map(|s| s.into_bytes()), replace: self.flag_replace.clone().map(|s| s.into_bytes()),
text: text, text: text,
threads: threads, threads: threads,
type_defs: btypes.definitions(),
type_list: self.flag_type_list, type_list: self.flag_type_list,
types: types, types: try!(self.types()),
with_filename: with_filename, with_filename: with_filename,
}; };
// If stdout is a tty, then apply some special default options. // If stdout is a tty, then apply some special default options.
@@ -433,20 +436,22 @@ impl RawArgs {
Ok(args) Ok(args)
} }
fn add_types(&self, types: &mut TypesBuilder) -> Result<()> { fn types(&self) -> Result<Types> {
let mut btypes = TypesBuilder::new();
btypes.add_defaults();
for ty in &self.flag_type_clear { for ty in &self.flag_type_clear {
types.clear(ty); btypes.clear(ty);
} }
for def in &self.flag_type_add { for def in &self.flag_type_add {
try!(types.add_def(def)); try!(btypes.add_def(def));
} }
for ty in &self.flag_type { for ty in &self.flag_type {
types.select(ty); btypes.select(ty);
} }
for ty in &self.flag_type_not { for ty in &self.flag_type_not {
types.negate(ty); btypes.negate(ty);
} }
Ok(()) btypes.build().map_err(From::from)
} }
fn pattern(&self) -> String { fn pattern(&self) -> String {
@@ -476,6 +481,27 @@ impl RawArgs {
s s
} }
} }
fn eol(&self) -> u8 {
// We might want to make this configurable.
b'\n'
}
fn grep(&self) -> Result<Grep> {
let smart =
self.flag_smart_case
&& !self.flag_ignore_case
&& !self.flag_case_sensitive;
let casei =
self.flag_ignore_case
&& !self.flag_case_sensitive;
GrepBuilder::new(&self.pattern())
.case_smart(smart)
.case_insensitive(casei)
.line_terminator(self.eol())
.build()
.map_err(From::from)
}
} }
impl Args { impl Args {
@@ -552,6 +578,11 @@ impl Args {
self.mmap self.mmap
} }
/// Whether ripgrep should be quiet or not.
pub fn quiet(&self) -> bool {
self.quiet
}
/// Create a new printer of individual search results that writes to the /// Create a new printer of individual search results that writes to the
/// writer given. /// writer given.
pub fn printer<W: Terminal + Send>(&self, wtr: W) -> Printer<W> { pub fn printer<W: Terminal + Send>(&self, wtr: W) -> Printer<W> {
@@ -561,7 +592,6 @@ impl Args {
.eol(self.eol) .eol(self.eol)
.heading(self.heading) .heading(self.heading)
.line_per_match(self.line_per_match) .line_per_match(self.line_per_match)
.quiet(self.quiet)
.null(self.null) .null(self.null)
.with_filename(self.with_filename); .with_filename(self.with_filename);
if let Some(ref rep) = self.replace { if let Some(ref rep) = self.replace {
@@ -641,6 +671,7 @@ 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)
.quiet(self.quiet)
.text(self.text) .text(self.text)
} }
@@ -660,6 +691,7 @@ 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)
.quiet(self.quiet)
.text(self.text) .text(self.text)
} }
@@ -670,7 +702,7 @@ impl Args {
/// Returns a list of type definitions currently loaded. /// Returns a list of type definitions currently loaded.
pub fn type_defs(&self) -> &[FileTypeDef] { pub fn type_defs(&self) -> &[FileTypeDef] {
&self.type_defs self.types.definitions()
} }
/// Returns true if ripgrep should print the type definitions currently /// Returns true if ripgrep should print the type definitions currently
@@ -681,7 +713,12 @@ impl Args {
/// Create a new recursive directory iterator at the path given. /// Create a new recursive directory iterator at the path given.
pub fn walker(&self, path: &Path) -> Result<walk::Iter> { pub fn walker(&self, path: &Path) -> Result<walk::Iter> {
let wd = WalkDir::new(path).follow_links(self.follow); // Always follow symlinks for explicitly specified files.
let mut wd = WalkDir::new(path).follow_links(
self.follow || path.is_file());
if let Some(maxdepth) = self.maxdepth {
wd = wd.max_depth(maxdepth);
}
let mut ig = Ignore::new(); let mut ig = Ignore::new();
// Only register ignore rules if this is a directory. If it's a file, // Only register ignore rules if this is a directory. If it's a file,
// then it was explicitly given by the end user, so we always search // then it was explicitly given by the end user, so we always search

View File

@@ -28,15 +28,15 @@ use std::fs::File;
use std::io::{self, BufRead}; use std::io::{self, BufRead};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use globset::{self, Candidate, GlobBuilder, GlobSet, GlobSetBuilder};
use regex; use regex;
use glob;
use pathutil::{is_file_name, strip_prefix}; use pathutil::{is_file_name, strip_prefix};
/// Represents an error that can occur when parsing a gitignore file. /// Represents an error that can occur when parsing a gitignore file.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
Glob(glob::Error), Glob(globset::Error),
Regex(regex::Error), Regex(regex::Error),
Io(io::Error), Io(io::Error),
} }
@@ -61,8 +61,8 @@ impl fmt::Display for Error {
} }
} }
impl From<glob::Error> for Error { impl From<globset::Error> for Error {
fn from(err: glob::Error) -> Error { fn from(err: globset::Error) -> Error {
Error::Glob(err) Error::Glob(err)
} }
} }
@@ -82,7 +82,7 @@ impl From<io::Error> for Error {
/// Gitignore is a matcher for the glob patterns in a single gitignore file. /// Gitignore is a matcher for the glob patterns in a single gitignore file.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Gitignore { pub struct Gitignore {
set: glob::Set, set: GlobSet,
root: PathBuf, root: PathBuf,
patterns: Vec<Pattern>, patterns: Vec<Pattern>,
num_ignores: u64, num_ignores: u64,
@@ -140,7 +140,8 @@ impl Gitignore {
}; };
MATCHES.with(|matches| { MATCHES.with(|matches| {
let mut matches = matches.borrow_mut(); let mut matches = matches.borrow_mut();
self.set.matches_into(path, &mut *matches); let candidate = Candidate::new(path);
self.set.matches_candidate_into(&candidate, &mut *matches);
for &i in matches.iter().rev() { for &i in matches.iter().rev() {
let pat = &self.patterns[i]; let pat = &self.patterns[i];
if !pat.only_dir || is_dir { if !pat.only_dir || is_dir {
@@ -207,7 +208,7 @@ impl<'a> Match<'a> {
/// GitignoreBuilder constructs a matcher for a single set of globs from a /// GitignoreBuilder constructs a matcher for a single set of globs from a
/// .gitignore file. /// .gitignore file.
pub struct GitignoreBuilder { pub struct GitignoreBuilder {
builder: glob::SetBuilder, builder: GlobSetBuilder,
root: PathBuf, root: PathBuf,
patterns: Vec<Pattern>, patterns: Vec<Pattern>,
} }
@@ -237,7 +238,7 @@ impl GitignoreBuilder {
pub fn new<P: AsRef<Path>>(root: P) -> GitignoreBuilder { pub fn new<P: AsRef<Path>>(root: P) -> GitignoreBuilder {
let root = strip_prefix("./", root.as_ref()).unwrap_or(root.as_ref()); let root = strip_prefix("./", root.as_ref()).unwrap_or(root.as_ref());
GitignoreBuilder { GitignoreBuilder {
builder: glob::SetBuilder::new(), builder: GlobSetBuilder::new(),
root: root.to_path_buf(), root: root.to_path_buf(),
patterns: vec![], patterns: vec![],
} }
@@ -261,8 +262,19 @@ impl GitignoreBuilder {
/// Add each pattern line from the file path given. /// Add each pattern line from the file path given.
pub fn add_path<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> { pub fn add_path<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
let rdr = io::BufReader::new(try!(File::open(&path))); let rdr = io::BufReader::new(try!(File::open(&path)));
for line in rdr.lines() { debug!("gitignore: {}", path.as_ref().display());
try!(self.add(&path, &try!(line))); for (i, line) in rdr.lines().enumerate() {
let line = match line {
Ok(line) => line,
Err(err) => {
debug!("error reading line {} in {}: {}",
i, path.as_ref().display(), err);
continue;
}
};
if let Err(err) = self.add(&path, &line) {
debug!("error adding gitignore pattern: '{}': {}", line, err);
}
} }
Ok(()) Ok(())
} }
@@ -299,7 +311,7 @@ impl GitignoreBuilder {
whitelist: false, whitelist: false,
only_dir: false, only_dir: false,
}; };
let mut opts = glob::MatchOptions::default(); let mut literal_separator = false;
let has_slash = line.chars().any(|c| c == '/'); let has_slash = line.chars().any(|c| c == '/');
let is_absolute = line.chars().nth(0).unwrap() == '/'; let is_absolute = line.chars().nth(0).unwrap() == '/';
if line.starts_with("\\!") || line.starts_with("\\#") { if line.starts_with("\\!") || line.starts_with("\\#") {
@@ -314,7 +326,7 @@ impl GitignoreBuilder {
// then the glob can only match the beginning of a path // then the glob can only match the beginning of a path
// (relative to the location of gitignore). We achieve this by // (relative to the location of gitignore). We achieve this by
// simply banning wildcards from matching /. // simply banning wildcards from matching /.
opts.require_literal_separator = true; literal_separator = true;
line = &line[1..]; line = &line[1..];
} }
} }
@@ -330,7 +342,7 @@ impl GitignoreBuilder {
// doesn't let wildcards match slashes. // doesn't let wildcards match slashes.
pat.pat = line.to_string(); pat.pat = line.to_string();
if has_slash { if has_slash {
opts.require_literal_separator = true; literal_separator = true;
} }
// If there was a leading slash, then this is a pattern that must // If there was a leading slash, then this is a pattern that must
// match the entire path name. Otherwise, we should let it match // match the entire path name. Otherwise, we should let it match
@@ -347,7 +359,11 @@ impl GitignoreBuilder {
if pat.pat.ends_with("/**") { if pat.pat.ends_with("/**") {
pat.pat = format!("{}/*", pat.pat); pat.pat = format!("{}/*", pat.pat);
} }
try!(self.builder.add_with(&pat.pat, &opts)); let parsed = try!(
GlobBuilder::new(&pat.pat)
.literal_separator(literal_separator)
.build());
self.builder.add(parsed);
self.patterns.push(pat); self.patterns.push(pat);
Ok(()) Ok(())
} }
@@ -429,6 +445,9 @@ mod tests {
not_ignored!(ignot11, ROOT, "#foo", "#foo"); not_ignored!(ignot11, ROOT, "#foo", "#foo");
not_ignored!(ignot12, ROOT, "\n\n\n", "foo"); not_ignored!(ignot12, ROOT, "\n\n\n", "foo");
not_ignored!(ignot13, ROOT, "foo/**", "foo", true); not_ignored!(ignot13, ROOT, "foo/**", "foo", true);
not_ignored!(
ignot14, "./third_party/protobuf", "m4/ltoptions.m4",
"./third_party/protobuf/csharp/src/packages/repositories.config");
// See: https://github.com/BurntSushi/ripgrep/issues/106 // See: https://github.com/BurntSushi/ripgrep/issues/106
#[test] #[test]

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
extern crate deque; extern crate deque;
extern crate docopt; extern crate docopt;
extern crate env_logger; extern crate env_logger;
extern crate fnv; extern crate globset;
extern crate grep; extern crate grep;
#[cfg(windows)] #[cfg(windows)]
extern crate kernel32; extern crate kernel32;
@@ -27,6 +27,7 @@ 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::thread; use std::thread;
use std::cmp; use std::cmp;
@@ -60,7 +61,6 @@ macro_rules! eprintln {
mod args; mod args;
mod atty; mod atty;
mod gitignore; mod gitignore;
mod glob;
mod ignore; mod ignore;
mod out; mod out;
mod pathutil; mod pathutil;
@@ -102,6 +102,7 @@ fn run(args: 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 mut workers = vec![]; let mut workers = vec![];
let workq = { let workq = {
@@ -109,6 +110,7 @@ fn run(args: Args) -> Result<u64> {
for _ in 0..threads { for _ in 0..threads {
let worker = MultiWorker { let worker = MultiWorker {
chan_work: stealer.clone(), chan_work: stealer.clone(),
quiet_matched: quiet_matched.clone(),
out: out.clone(), out: out.clone(),
outbuf: Some(args.outbuf()), outbuf: Some(args.outbuf()),
worker: Worker { worker: Worker {
@@ -124,11 +126,17 @@ fn run(args: Args) -> Result<u64> {
}; };
let mut paths_searched: u64 = 0; let mut paths_searched: u64 = 0;
for p in paths { for p in paths {
if quiet_matched.has_match() {
break;
}
if p == Path::new("-") { if p == Path::new("-") {
paths_searched += 1; paths_searched += 1;
workq.push(Work::Stdin); workq.push(Work::Stdin);
} else { } else {
for ent in try!(args.walker(p)) { for ent in try!(args.walker(p)) {
if quiet_matched.has_match() {
break;
}
paths_searched += 1; paths_searched += 1;
workq.push(Work::File(ent)); workq.push(Work::File(ent));
} }
@@ -161,6 +169,9 @@ fn run_one_thread(args: Arc<Args>) -> Result<u64> {
let mut paths_searched: u64 = 0; let mut paths_searched: u64 = 0;
for p in paths { for p in paths {
if args.quiet() && worker.match_count > 0 {
break;
}
if p == Path::new("-") { if p == Path::new("-") {
paths_searched += 1; paths_searched += 1;
let mut printer = args.printer(&mut term); let mut printer = args.printer(&mut term);
@@ -175,6 +186,9 @@ fn run_one_thread(args: Arc<Args>) -> Result<u64> {
paths_searched += 1; paths_searched += 1;
let mut printer = args.printer(&mut term); let mut printer = args.printer(&mut term);
if worker.match_count > 0 { if worker.match_count > 0 {
if args.quiet() {
break;
}
if let Some(sep) = args.file_separator() { if let Some(sep) = args.file_separator() {
printer = printer.file_separator(sep); printer = printer.file_separator(sep);
} }
@@ -240,6 +254,7 @@ enum WorkReady {
struct MultiWorker { struct MultiWorker {
chan_work: Stealer<Work>, chan_work: Stealer<Work>,
quiet_matched: QuietMatched,
out: Arc<Mutex<Out>>, out: Arc<Mutex<Out>>,
#[cfg(not(windows))] #[cfg(not(windows))]
outbuf: Option<ColoredTerminal<term::TerminfoTerminal<Vec<u8>>>>, outbuf: Option<ColoredTerminal<term::TerminfoTerminal<Vec<u8>>>>,
@@ -258,6 +273,9 @@ struct Worker {
impl MultiWorker { impl MultiWorker {
fn run(mut self) -> u64 { fn run(mut self) -> u64 {
loop { loop {
if self.quiet_matched.has_match() {
break;
}
let work = match self.chan_work.steal() { let work = match self.chan_work.steal() {
Stolen::Empty | Stolen::Abort => continue, Stolen::Empty | Stolen::Abort => continue,
Stolen::Data(Work::Quit) => break, Stolen::Data(Work::Quit) => break,
@@ -276,6 +294,9 @@ impl MultiWorker {
outbuf.clear(); outbuf.clear();
let mut printer = self.worker.args.printer(outbuf); let mut printer = self.worker.args.printer(outbuf);
self.worker.do_work(&mut printer, work); self.worker.do_work(&mut printer, work);
if self.quiet_matched.set_match(self.worker.match_count > 0) {
break;
}
let outbuf = printer.into_inner(); let outbuf = printer.into_inner();
if !outbuf.get_ref().is_empty() { if !outbuf.get_ref().is_empty() {
let mut out = self.out.lock().unwrap(); let mut out = self.out.lock().unwrap();
@@ -359,3 +380,28 @@ impl Worker {
).run()) ).run())
} }
} }
#[derive(Clone, Debug)]
struct QuietMatched(Arc<Option<AtomicBool>>);
impl QuietMatched {
fn new(quiet: bool) -> QuietMatched {
let atomic = if quiet { Some(AtomicBool::new(false)) } else { None };
QuietMatched(Arc::new(atomic))
}
fn has_match(&self) -> bool {
match *self.0 {
None => false,
Some(ref matched) => matched.load(Ordering::SeqCst),
}
}
fn set_match(&self, yes: bool) -> bool {
match *self.0 {
None => false,
Some(_) if !yes => false,
Some(ref m) => { m.store(true, Ordering::SeqCst); true }
}
}
}

View File

@@ -33,8 +33,6 @@ pub struct Printer<W> {
heading: bool, heading: bool,
/// Whether to show every match on its own line. /// Whether to show every match on its own line.
line_per_match: bool, line_per_match: bool,
/// Whether to suppress all output.
quiet: bool,
/// Whether to print NUL bytes after a file path instead of new lines /// Whether to print NUL bytes after a file path instead of new lines
/// or `:`. /// or `:`.
null: bool, null: bool,
@@ -42,6 +40,34 @@ pub struct Printer<W> {
replace: Option<Vec<u8>>, replace: Option<Vec<u8>>,
/// Whether to prefix each match with the corresponding file name. /// Whether to prefix each match with the corresponding file name.
with_filename: bool, with_filename: bool,
/// The choice of colors.
color_choice: ColorChoice
}
struct ColorChoice {
matched_line: color::Color,
heading: color::Color,
line_number: color::Color
}
impl ColorChoice {
#[cfg(unix)]
pub fn new() -> ColorChoice {
ColorChoice {
matched_line: color::RED,
heading: color::GREEN,
line_number: color::BLUE
}
}
#[cfg(not(unix))]
pub fn new() -> ColorChoice {
ColorChoice {
matched_line: color::BRIGHT_RED,
heading: color::BRIGHT_GREEN,
line_number: color::BRIGHT_BLUE
}
}
} }
impl<W: Terminal + Send> Printer<W> { impl<W: Terminal + Send> Printer<W> {
@@ -56,10 +82,10 @@ impl<W: Terminal + Send> Printer<W> {
file_separator: None, file_separator: None,
heading: false, heading: false,
line_per_match: false, line_per_match: false,
quiet: false,
null: false, null: false,
replace: None, replace: None,
with_filename: false, with_filename: false,
color_choice: ColorChoice::new()
} }
} }
@@ -110,12 +136,6 @@ impl<W: Terminal + Send> Printer<W> {
self self
} }
/// When set, all output is suppressed.
pub fn quiet(mut self, yes: bool) -> Printer<W> {
self.quiet = yes;
self
}
/// Replace every match in each matching line with the replacement string /// Replace every match in each matching line with the replacement string
/// given. /// given.
/// ///
@@ -137,11 +157,6 @@ impl<W: Terminal + Send> Printer<W> {
self.has_printed self.has_printed
} }
/// Returns true if the printer has been configured to be quiet.
pub fn is_quiet(&self) -> bool {
self.quiet
}
/// Flushes the underlying writer and returns it. /// Flushes the underlying writer and returns it.
pub fn into_inner(mut self) -> W { pub fn into_inner(mut self) -> W {
let _ = self.wtr.flush(); let _ = self.wtr.flush();
@@ -191,9 +206,6 @@ impl<W: Terminal + Send> Printer<W> {
/// Prints the context separator. /// Prints the context separator.
pub fn context_separate(&mut self) { pub fn context_separate(&mut self) {
// N.B. We can't use `write` here because of borrowing restrictions. // N.B. We can't use `write` here because of borrowing restrictions.
if self.quiet {
return;
}
if self.context_separator.is_empty() { if self.context_separator.is_empty() {
return; return;
} }
@@ -243,12 +255,7 @@ impl<W: Terminal + Send> Printer<W> {
self.write_file_sep(); self.write_file_sep();
self.write_heading(path.as_ref()); self.write_heading(path.as_ref());
} else if !self.heading && self.with_filename { } else if !self.heading && self.with_filename {
self.write_path(path.as_ref()); self.write_non_heading_path(path.as_ref());
if self.null {
self.write(b"\x00");
} else {
self.write(b":");
}
} }
if let Some(line_number) = line_number { if let Some(line_number) = line_number {
self.line_number(line_number, b':'); self.line_number(line_number, b':');
@@ -277,7 +284,7 @@ impl<W: Terminal + Send> Printer<W> {
let mut last_written = 0; let mut last_written = 0;
for (s, e) in re.find_iter(buf) { for (s, e) in re.find_iter(buf) {
self.write(&buf[last_written..s]); self.write(&buf[last_written..s]);
let _ = self.wtr.fg(color::BRIGHT_RED); let _ = self.wtr.fg(self.color_choice.matched_line);
let _ = self.wtr.attr(Attr::Bold); let _ = self.wtr.attr(Attr::Bold);
self.write(&buf[s..e]); self.write(&buf[s..e]);
let _ = self.wtr.reset(); let _ = self.wtr.reset();
@@ -316,7 +323,7 @@ impl<W: Terminal + Send> Printer<W> {
fn write_heading<P: AsRef<Path>>(&mut self, path: P) { fn write_heading<P: AsRef<Path>>(&mut self, path: P) {
if self.wtr.supports_color() { if self.wtr.supports_color() {
let _ = self.wtr.fg(color::BRIGHT_GREEN); let _ = self.wtr.fg(self.color_choice.heading);
let _ = self.wtr.attr(Attr::Bold); let _ = self.wtr.attr(Attr::Bold);
} }
self.write_path(path.as_ref()); self.write_path(path.as_ref());
@@ -330,9 +337,25 @@ impl<W: Terminal + Send> Printer<W> {
} }
} }
fn write_non_heading_path<P: AsRef<Path>>(&mut self, path: P) {
if self.wtr.supports_color() {
let _ = self.wtr.fg(self.color_choice.heading);
let _ = self.wtr.attr(Attr::Bold);
}
self.write_path(path.as_ref());
if self.wtr.supports_color() {
let _ = self.wtr.reset();
}
if self.null {
self.write(b"\x00");
} else {
self.write(b":");
}
}
fn line_number(&mut self, n: u64, sep: u8) { fn line_number(&mut self, n: u64, sep: u8) {
if self.wtr.supports_color() { if self.wtr.supports_color() {
let _ = self.wtr.fg(color::BRIGHT_BLUE); let _ = self.wtr.fg(self.color_choice.line_number);
let _ = self.wtr.attr(Attr::Bold); let _ = self.wtr.attr(Attr::Bold);
} }
self.write(n.to_string().as_bytes()); self.write(n.to_string().as_bytes());
@@ -356,9 +379,6 @@ impl<W: Terminal + Send> Printer<W> {
} }
fn write(&mut self, buf: &[u8]) { fn write(&mut self, buf: &[u8]) {
if self.quiet {
return;
}
self.has_printed = true; self.has_printed = true;
let _ = self.wtr.write_all(buf); let _ = self.wtr.write_all(buf);
} }
@@ -369,9 +389,6 @@ impl<W: Terminal + Send> Printer<W> {
} }
fn write_file_sep(&mut self) { fn write_file_sep(&mut self) {
if self.quiet {
return;
}
if let Some(ref sep) = self.file_separator { if let Some(ref sep) = self.file_separator {
self.has_printed = true; self.has_printed = true;
let _ = self.wtr.write_all(sep); let _ = self.wtr.write_all(sep);

View File

@@ -81,6 +81,13 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
self 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. /// If enabled, search binary files as if they were text.
pub fn text(mut self, yes: bool) -> Self { pub fn text(mut self, yes: bool) -> Self {
self.opts.text = yes; self.opts.text = yes;
@@ -104,7 +111,7 @@ 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.printer.is_quiet() || self.opts.files_with_matches { if self.opts.stop_after_first_match() {
break; break;
} }
} }

View File

@@ -84,6 +84,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 quiet: bool,
pub text: bool, pub text: bool,
} }
@@ -97,6 +98,7 @@ impl Default for Options {
eol: b'\n', eol: b'\n',
invert_match: false, invert_match: false,
line_number: false, line_number: false,
quiet: false,
text: false, text: false,
} }
} }
@@ -104,10 +106,16 @@ impl Default for Options {
} }
impl Options { impl Options {
/// Both --count and --files-with-matches options imply that we should not /// Several options (--quiet, --count, --files-with-matches) imply that
/// display matches at all. /// we shouldn't ever display matches.
pub fn skip_matches(&self) -> bool { pub fn skip_matches(&self) -> bool {
return self.count || self.files_with_matches; self.count || self.files_with_matches || self.quiet
}
/// Some options (--quiet, --files-with-matches) imply that we can stop
/// searching after the first match.
pub fn stop_after_first_match(&self) -> bool {
self.files_with_matches || self.quiet
} }
} }
@@ -197,6 +205,13 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
self 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. /// If enabled, search binary files as if they were text.
pub fn text(mut self, yes: bool) -> Self { pub fn text(mut self, yes: bool) -> Self {
self.opts.text = yes; self.opts.text = yes;
@@ -265,8 +280,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.match_count > 0 && self.opts.stop_after_first_match()
&& (self.printer.is_quiet() || self.opts.files_with_matches)
} }
#[inline(always)] #[inline(always)]

View File

@@ -11,7 +11,7 @@ use std::path::Path;
use regex; use regex;
use gitignore::{Match, Pattern}; use gitignore::{Match, Pattern};
use glob::{self, MatchOptions}; use globset::{self, GlobBuilder, GlobSet, GlobSetBuilder};
const TYPE_EXTENSIONS: &'static [(&'static str, &'static [&'static str])] = &[ const TYPE_EXTENSIONS: &'static [(&'static str, &'static [&'static str])] = &[
("asm", &["*.asm", "*.s", "*.S"]), ("asm", &["*.asm", "*.s", "*.S"]),
@@ -19,8 +19,9 @@ const TYPE_EXTENSIONS: &'static [(&'static str, &'static [&'static str])] = &[
("c", &["*.c", "*.h", "*.H"]), ("c", &["*.c", "*.h", "*.H"]),
("cbor", &["*.cbor"]), ("cbor", &["*.cbor"]),
("clojure", &["*.clj", "*.cljc", "*.cljs", "*.cljx"]), ("clojure", &["*.clj", "*.cljc", "*.cljs", "*.cljx"]),
("cmake", &["CMakeLists.txt"]), ("cmake", &["*.cmake", "CMakeLists.txt"]),
("coffeescript", &["*.coffee"]), ("coffeescript", &["*.coffee"]),
("config", &["*.config"]),
("cpp", &[ ("cpp", &[
"*.C", "*.cc", "*.cpp", "*.cxx", "*.C", "*.cc", "*.cpp", "*.cxx",
"*.h", "*.H", "*.hh", "*.hpp", "*.h", "*.H", "*.hh", "*.hpp",
@@ -42,6 +43,7 @@ const TYPE_EXTENSIONS: &'static [(&'static str, &'static [&'static str])] = &[
("haskell", &["*.hs", "*.lhs"]), ("haskell", &["*.hs", "*.lhs"]),
("html", &["*.htm", "*.html"]), ("html", &["*.htm", "*.html"]),
("java", &["*.java"]), ("java", &["*.java"]),
("jinja", &["*.jinja", "*.jinja2"]),
("js", &[ ("js", &[
"*.js", "*.jsx", "*.vue", "*.js", "*.jsx", "*.vue",
]), ]),
@@ -52,6 +54,7 @@ const TYPE_EXTENSIONS: &'static [(&'static str, &'static [&'static str])] = &[
("m4", &["*.ac", "*.m4"]), ("m4", &["*.ac", "*.m4"]),
("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk"]), ("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk"]),
("markdown", &["*.md"]), ("markdown", &["*.md"]),
("md", &["*.md"]),
("matlab", &["*.m"]), ("matlab", &["*.m"]),
("mk", &["mkfile"]), ("mk", &["mkfile"]),
("ml", &["*.ml"]), ("ml", &["*.ml"]),
@@ -69,6 +72,7 @@ const TYPE_EXTENSIONS: &'static [(&'static str, &'static [&'static str])] = &[
("rust", &["*.rs"]), ("rust", &["*.rs"]),
("scala", &["*.scala"]), ("scala", &["*.scala"]),
("sh", &["*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh"]), ("sh", &["*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh"]),
("spark", &["*.spark"]),
("sql", &["*.sql"]), ("sql", &["*.sql"]),
("sv", &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]), ("sv", &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
("swift", &["*.swift"]), ("swift", &["*.swift"]),
@@ -93,7 +97,7 @@ pub enum Error {
/// A user specified file type definition could not be parsed. /// A user specified file type definition could not be parsed.
InvalidDefinition, InvalidDefinition,
/// There was an error building the matcher (probably a bad glob). /// There was an error building the matcher (probably a bad glob).
Glob(glob::Error), Glob(globset::Error),
/// There was an error compiling a glob as a regex. /// There was an error compiling a glob as a regex.
Regex(regex::Error), Regex(regex::Error),
} }
@@ -125,8 +129,8 @@ impl fmt::Display for Error {
} }
} }
impl From<glob::Error> for Error { impl From<globset::Error> for Error {
fn from(err: glob::Error) -> Error { fn from(err: globset::Error) -> Error {
Error::Glob(err) Error::Glob(err)
} }
} }
@@ -159,8 +163,9 @@ impl FileTypeDef {
/// Types is a file type matcher. /// Types is a file type matcher.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Types { pub struct Types {
selected: Option<glob::SetYesNo>, defs: Vec<FileTypeDef>,
negated: Option<glob::SetYesNo>, selected: Option<GlobSet>,
negated: Option<GlobSet>,
has_selected: bool, has_selected: bool,
unmatched_pat: Pattern, unmatched_pat: Pattern,
} }
@@ -173,11 +178,13 @@ impl Types {
/// If has_selected is true, then at least one file type was selected. /// If has_selected is true, then at least one file type was selected.
/// Therefore, any non-matches should be ignored. /// Therefore, any non-matches should be ignored.
fn new( fn new(
selected: Option<glob::SetYesNo>, selected: Option<GlobSet>,
negated: Option<glob::SetYesNo>, negated: Option<GlobSet>,
has_selected: bool, has_selected: bool,
defs: Vec<FileTypeDef>,
) -> Types { ) -> Types {
Types { Types {
defs: defs,
selected: selected, selected: selected,
negated: negated, negated: negated,
has_selected: has_selected, has_selected: has_selected,
@@ -193,7 +200,7 @@ impl Types {
/// Creates a new file type matcher that never matches. /// Creates a new file type matcher that never matches.
pub fn empty() -> Types { pub fn empty() -> Types {
Types::new(None, None, false) Types::new(None, None, false, vec![])
} }
/// Returns a match for the given path against this file type matcher. /// Returns a match for the given path against this file type matcher.
@@ -233,6 +240,11 @@ impl Types {
Match::None Match::None
} }
} }
/// Return the set of current file type definitions.
pub fn definitions(&self) -> &[FileTypeDef] {
&self.defs
}
} }
/// TypesBuilder builds a type matcher from a set of file type definitions and /// TypesBuilder builds a type matcher from a set of file type definitions and
@@ -256,14 +268,11 @@ impl TypesBuilder {
/// Build the current set of file type definitions *and* selections into /// Build the current set of file type definitions *and* selections into
/// a file type matcher. /// a file type matcher.
pub fn build(&self) -> Result<Types, Error> { pub fn build(&self) -> Result<Types, Error> {
let opts = MatchOptions {
require_literal_separator: true, ..MatchOptions::default()
};
let selected_globs = let selected_globs =
if self.selected.is_empty() { if self.selected.is_empty() {
None None
} else { } else {
let mut bset = glob::SetBuilder::new(); let mut bset = GlobSetBuilder::new();
for name in &self.selected { for name in &self.selected {
let globs = match self.types.get(name) { let globs = match self.types.get(name) {
Some(globs) => globs, Some(globs) => globs,
@@ -273,16 +282,19 @@ impl TypesBuilder {
} }
}; };
for glob in globs { for glob in globs {
try!(bset.add_with(glob, &opts)); let pat = try!(
GlobBuilder::new(glob)
.literal_separator(true).build());
bset.add(pat);
} }
} }
Some(try!(bset.build_yesno())) Some(try!(bset.build()))
}; };
let negated_globs = let negated_globs =
if self.negated.is_empty() { if self.negated.is_empty() {
None None
} else { } else {
let mut bset = glob::SetBuilder::new(); let mut bset = GlobSetBuilder::new();
for name in &self.negated { for name in &self.negated {
let globs = match self.types.get(name) { let globs = match self.types.get(name) {
Some(globs) => globs, Some(globs) => globs,
@@ -292,13 +304,20 @@ impl TypesBuilder {
} }
}; };
for glob in globs { for glob in globs {
try!(bset.add_with(glob, &opts)); let pat = try!(
GlobBuilder::new(glob)
.literal_separator(true).build());
bset.add(pat);
} }
} }
Some(try!(bset.build_yesno())) Some(try!(bset.build()))
}; };
Ok(Types::new( Ok(Types::new(
selected_globs, negated_globs, !self.selected.is_empty())) selected_globs,
negated_globs,
!self.selected.is_empty(),
self.definitions(),
))
} }
/// Return the set of current file type definitions. /// Return the set of current file type definitions.

View File

@@ -542,7 +542,7 @@ sherlock!(symlink_nofollow, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.remove("sherlock"); wd.remove("sherlock");
wd.create_dir("foo"); wd.create_dir("foo");
wd.create_dir("foo/bar"); wd.create_dir("foo/bar");
wd.link("foo/baz", "foo/bar/baz"); wd.link_dir("foo/baz", "foo/bar/baz");
wd.create_dir("foo/baz"); wd.create_dir("foo/baz");
wd.create("foo/baz/sherlock", hay::SHERLOCK); wd.create("foo/baz/sherlock", hay::SHERLOCK);
cmd.current_dir(wd.path().join("foo/bar")); cmd.current_dir(wd.path().join("foo/bar"));
@@ -555,7 +555,7 @@ sherlock!(symlink_follow, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.create_dir("foo/bar"); wd.create_dir("foo/bar");
wd.create_dir("foo/baz"); wd.create_dir("foo/baz");
wd.create("foo/baz/sherlock", hay::SHERLOCK); wd.create("foo/baz/sherlock", hay::SHERLOCK);
wd.link("foo/baz", "foo/bar/baz"); wd.link_dir("foo/baz", "foo/bar/baz");
cmd.arg("-L"); cmd.arg("-L");
cmd.current_dir(wd.path().join("foo/bar")); cmd.current_dir(wd.path().join("foo/bar"));
@@ -592,17 +592,6 @@ sherlock!(unrestricted2, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
assert_eq!(lines, expected); assert_eq!(lines, expected);
}); });
#[cfg(not(windows))]
sherlock!(unrestricted3, "foo", ".", |wd: WorkDir, mut cmd: Command| {
wd.create("file", "foo\x00bar\nfoo\x00baz\n");
cmd.arg("-uuu");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "file:foo\x00bar\nfile:foo\x00baz\n");
});
// On Windows, this test uses memory maps, so the NUL bytes don't get replaced.
#[cfg(windows)]
sherlock!(unrestricted3, "foo", ".", |wd: WorkDir, mut cmd: Command| { sherlock!(unrestricted3, "foo", ".", |wd: WorkDir, mut cmd: Command| {
wd.create("file", "foo\x00bar\nfoo\x00baz\n"); wd.create("file", "foo\x00bar\nfoo\x00baz\n");
cmd.arg("-uuu"); cmd.arg("-uuu");
@@ -659,7 +648,6 @@ clean!(regression_30, "test", ".", |wd: WorkDir, mut cmd: Command| {
} }
wd.create_dir("vendor"); wd.create_dir("vendor");
wd.create("vendor/manifest", "test"); wd.create("vendor/manifest", "test");
cmd.arg("--debug");
let lines: String = wd.stdout(&mut cmd); let lines: String = wd.stdout(&mut cmd);
let expected = path("vendor/manifest:test\n"); let expected = path("vendor/manifest:test\n");
@@ -705,6 +693,13 @@ clean!(regression_67, "test", ".", |wd: WorkDir, mut cmd: Command| {
assert_eq!(lines, path("dir/bar:test\n")); assert_eq!(lines, path("dir/bar:test\n"));
}); });
// See: https://github.com/BurntSushi/ripgrep/issues/87
clean!(regression_87, "test", ".", |wd: WorkDir, mut cmd: Command| {
wd.create(".gitignore", "foo\n**no-vcs**");
wd.create("foo", "test");
wd.assert_err(&mut cmd);
});
// See: https://github.com/BurntSushi/ripgrep/issues/90 // See: https://github.com/BurntSushi/ripgrep/issues/90
clean!(regression_90, "test", ".", |wd: WorkDir, mut cmd: Command| { clean!(regression_90, "test", ".", |wd: WorkDir, mut cmd: Command| {
wd.create(".gitignore", "!.foo"); wd.create(".gitignore", "!.foo");
@@ -753,8 +748,98 @@ clean!(regression_105_part2, "test", ".", |wd: WorkDir, mut cmd: Command| {
assert_eq!(lines, "foo:3:zztest\n"); assert_eq!(lines, "foo:3:zztest\n");
}); });
// See: https://github.com/BurntSushi/ripgrep/issues/127
clean!(regression_127, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
// Set up a directory hierarchy like this:
//
// .gitignore
// foo/
// sherlock
// watson
//
// Where `.gitignore` contains `foo/sherlock`.
//
// ripgrep should ignore 'foo/sherlock' giving us results only from
// 'foo/watson' but on Windows ripgrep will include both 'foo/sherlock' and
// 'foo/watson' in the search results.
wd.create(".gitignore", "foo/sherlock\n");
wd.create_dir("foo");
wd.create("foo/sherlock", hay::SHERLOCK);
wd.create("foo/watson", hay::SHERLOCK);
let lines: String = wd.stdout(&mut cmd);
let expected = format!("\
{path}:For the Doctor Watsons of this world, as opposed to the Sherlock
{path}:be, to a very large extent, the result of luck. Sherlock Holmes
", path=path("foo/watson"));
assert_eq!(lines, expected);
});
// See: https://github.com/BurntSushi/ripgrep/issues/131
//
// TODO(burntsushi): Darwin doesn't like this test for some reason.
#[cfg(not(target_os = "macos"))]
clean!(regression_131, "test", ".", |wd: WorkDir, mut cmd: Command| {
wd.create(".gitignore", "TopÑapa");
wd.create("TopÑapa", "test");
wd.assert_err(&mut cmd);
});
// See: https://github.com/BurntSushi/ripgrep/issues/137
//
// TODO(burntsushi): Figure out why Windows gives "access denied" errors
// when trying to create a file symlink. For now, disable test on Windows.
#[cfg(not(windows))]
sherlock!(regression_137, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
wd.link_file("sherlock", "sym1");
wd.link_file("sherlock", "sym2");
cmd.arg("sym1");
cmd.arg("sym2");
cmd.arg("-j1");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
sym1:For the Doctor Watsons of this world, as opposed to the Sherlock
sym1:be, to a very large extent, the result of luck. Sherlock Holmes
sym2:For the Doctor Watsons of this world, as opposed to the Sherlock
sym2:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, path(expected));
});
// See: https://github.com/BurntSushi/ripgrep/issues/156
clean!(
regression_156,
r#"#(?:parse|include)\s*\(\s*(?:"|')[./A-Za-z_-]+(?:"|')"#,
"testcase.txt",
|wd: WorkDir, mut cmd: Command| {
const TESTCASE: &'static str = r#"#parse('widgets/foo_bar_macros.vm')
#parse ( 'widgets/mobile/foo_bar_macros.vm' )
#parse ("widgets/foobarhiddenformfields.vm")
#parse ( "widgets/foo_bar_legal.vm" )
#include( 'widgets/foo_bar_tips.vm' )
#include('widgets/mobile/foo_bar_macros.vm')
#include ("widgets/mobile/foo_bar_resetpw.vm")
#parse('widgets/foo-bar-macros.vm')
#parse ( 'widgets/mobile/foo-bar-macros.vm' )
#parse ("widgets/foo-bar-hiddenformfields.vm")
#parse ( "widgets/foo-bar-legal.vm" )
#include( 'widgets/foo-bar-tips.vm' )
#include('widgets/mobile/foo-bar-macros.vm')
#include ("widgets/mobile/foo-bar-resetpw.vm")
"#;
wd.create("testcase.txt", TESTCASE);
cmd.arg("-N");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, TESTCASE);
});
// See: https://github.com/BurntSushi/ripgrep/issues/20 // See: https://github.com/BurntSushi/ripgrep/issues/20
sherlock!(feature_20, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| { sherlock!(feature_20_no_filename, "Sherlock", ".",
|wd: WorkDir, mut cmd: Command| {
cmd.arg("--no-filename"); cmd.arg("--no-filename");
let lines: String = wd.stdout(&mut cmd); let lines: String = wd.stdout(&mut cmd);
@@ -766,7 +851,7 @@ be, to a very large extent, the result of luck. Sherlock Holmes
}); });
// See: https://github.com/BurntSushi/ripgrep/issues/68 // See: https://github.com/BurntSushi/ripgrep/issues/68
clean!(feature_68, "test", ".", |wd: WorkDir, mut cmd: Command| { clean!(feature_68_no_ignore_vcs, "test", ".", |wd: WorkDir, mut cmd: Command| {
wd.create(".gitignore", "foo"); wd.create(".gitignore", "foo");
wd.create(".ignore", "bar"); wd.create(".ignore", "bar");
wd.create("foo", "test"); wd.create("foo", "test");
@@ -778,7 +863,8 @@ clean!(feature_68, "test", ".", |wd: WorkDir, mut cmd: Command| {
}); });
// See: https://github.com/BurntSushi/ripgrep/issues/70 // See: https://github.com/BurntSushi/ripgrep/issues/70
sherlock!(feature_70, "sherlock", ".", |wd: WorkDir, mut cmd: Command| { sherlock!(feature_70_smart_case, "sherlock", ".",
|wd: WorkDir, mut cmd: Command| {
cmd.arg("--smart-case"); cmd.arg("--smart-case");
let lines: String = wd.stdout(&mut cmd); let lines: String = wd.stdout(&mut cmd);
@@ -831,6 +917,36 @@ sherlock\x00can extract a clew from a wisp of straw or a flake of cigar ash;
assert_eq!(lines, expected); assert_eq!(lines, expected);
}); });
// See: https://github.com/BurntSushi/ripgrep/issues/109
clean!(feature_109_max_depth, "far", ".", |wd: WorkDir, mut cmd: Command| {
wd.create_dir("one");
wd.create("one/pass", "far");
wd.create_dir("one/too");
wd.create("one/too/many", "far");
cmd.arg("--maxdepth").arg("2");
let lines: String = wd.stdout(&mut cmd);
let expected = path("one/pass:far\n");
assert_eq!(lines, expected);
});
// See: https://github.com/BurntSushi/ripgrep/issues/124
clean!(feature_109_case_sensitive_part1, "test", ".",
|wd: WorkDir, mut cmd: Command| {
wd.create("foo", "tEsT");
cmd.arg("--smart-case").arg("--case-sensitive");
wd.assert_err(&mut cmd);
});
// See: https://github.com/BurntSushi/ripgrep/issues/124
clean!(feature_109_case_sensitive_part2, "test", ".",
|wd: WorkDir, mut cmd: Command| {
wd.create("foo", "tEsT");
cmd.arg("--ignore-case").arg("--case-sensitive");
wd.assert_err(&mut cmd);
});
#[test] #[test]
fn binary_nosearch() { fn binary_nosearch() {
let wd = WorkDir::new("binary_nosearch"); let wd = WorkDir::new("binary_nosearch");

View File

@@ -83,7 +83,7 @@ impl WorkDir {
/// Creates a directory symlink to the src with the given target name /// Creates a directory symlink to the src with the given target name
/// in this directory. /// in this directory.
#[cfg(not(windows))] #[cfg(not(windows))]
pub fn link<S: AsRef<Path>, T: AsRef<Path>>(&self, src: S, target: T) { pub fn link_dir<S: AsRef<Path>, T: AsRef<Path>>(&self, src: S, target: T) {
use std::os::unix::fs::symlink; use std::os::unix::fs::symlink;
let src = self.dir.join(src); let src = self.dir.join(src);
let target = self.dir.join(target); let target = self.dir.join(target);
@@ -91,8 +91,10 @@ impl WorkDir {
nice_err(&target, symlink(&src, &target)); nice_err(&target, symlink(&src, &target));
} }
/// Creates a directory symlink to the src with the given target name
/// in this directory.
#[cfg(windows)] #[cfg(windows)]
pub fn link<S: AsRef<Path>, T: AsRef<Path>>(&self, src: S, target: T) { pub fn link_dir<S: AsRef<Path>, T: AsRef<Path>>(&self, src: S, target: T) {
use std::os::windows::fs::symlink_dir; use std::os::windows::fs::symlink_dir;
let src = self.dir.join(src); let src = self.dir.join(src);
let target = self.dir.join(target); let target = self.dir.join(target);
@@ -100,6 +102,32 @@ impl WorkDir {
nice_err(&target, symlink_dir(&src, &target)); nice_err(&target, symlink_dir(&src, &target));
} }
/// Creates a file symlink to the src with the given target name
/// in this directory.
#[cfg(not(windows))]
pub fn link_file<S: AsRef<Path>, T: AsRef<Path>>(
&self,
src: S,
target: T,
) {
self.link_dir(src, target);
}
/// Creates a file symlink to the src with the given target name
/// in this directory.
#[cfg(windows)]
pub fn link_file<S: AsRef<Path>, T: AsRef<Path>>(
&self,
src: S,
target: T,
) {
use std::os::windows::fs::symlink_file;
let src = self.dir.join(src);
let target = self.dir.join(target);
let _ = fs::remove_file(&target);
nice_err(&target, symlink_file(&src, &target));
}
/// Runs and captures the stdout of the given command. /// Runs and captures the stdout of the given command.
/// ///
/// If the return type could not be created from a string, then this /// If the return type could not be created from a string, then this