mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-07-26 09:42:00 -07:00
Compare commits
94 Commits
termcolor-
...
grep-0.1.9
Author | SHA1 | Date | |
---|---|---|---|
|
0fdab0ec5e | ||
|
74ec5b8932 | ||
|
2913fc4cd0 | ||
|
7c412bb2fa | ||
|
651a0f1ddf | ||
|
45473ba48f | ||
|
0863c75a5a | ||
|
d94d99f657 | ||
|
84585908ac | ||
|
1611c04e6f | ||
|
d857ad6ed3 | ||
|
4dd2f8e40e | ||
|
dca8110da2 | ||
|
0d11497d21 | ||
|
22ac2e056e | ||
|
03af61fc7b | ||
|
560dffd247 | ||
|
e65ca21a6c | ||
|
6771626553 | ||
|
b9c922be53 | ||
|
7a44cad599 | ||
|
6dc09c5b1b | ||
|
209a125ea2 | ||
|
090216cf00 | ||
|
c96a358593 | ||
|
231456c409 | ||
|
1d09d4d31b | ||
|
02f08f3800 | ||
|
470afa1bd7 | ||
|
aa2ce39d14 | ||
|
d11a3b3377 | ||
|
d09e2f6af1 | ||
|
7b6af5a177 | ||
|
9bd1aa1c04 | ||
|
1393ce4b6b | ||
|
8e358ee056 | ||
|
5b5f4e74d9 | ||
|
7829850bf0 | ||
|
06b66efd59 | ||
|
7e5a590276 | ||
|
d17ca45063 | ||
|
df469fe1b4 | ||
|
b64475aeac | ||
|
fca9709d94 | ||
|
62b4813b8a | ||
|
b38b101c77 | ||
|
ac90316e35 | ||
|
a6467f880a | ||
|
004bb35694 | ||
|
cd6c190967 | ||
|
d5139228e5 | ||
|
bb16ba6311 | ||
|
5e85f2577b | ||
|
ca23a170f7 | ||
|
223d7d9846 | ||
|
e4bce86111 | ||
|
15fa77cdb3 | ||
|
c3f97513d6 | ||
|
c21b9b20cf | ||
|
ce145c6a2a | ||
|
a383d5c4e9 | ||
|
64317bda9f | ||
|
7f3a0f0828 | ||
|
49f36c7dcd | ||
|
83b4fdb8d6 | ||
|
8b57d78b96 | ||
|
a2d8c49d6f | ||
|
6ffb4b7466 | ||
|
198d1fede9 | ||
|
667b9a7d62 | ||
|
1f528f1641 | ||
|
ab64da73ab | ||
|
1266de3d4c | ||
|
bf51058eb2 | ||
|
3dc6fe6f05 | ||
|
06438d5360 | ||
|
ae6f871491 | ||
|
ed059559cd | ||
|
b75526bd7f | ||
|
507801c1f2 | ||
|
2a9d007261 | ||
|
0ee0b160b5 | ||
|
b4781e2f91 | ||
|
8cb03941e6 | ||
|
6b15ce2342 | ||
|
4c0b0c6c9d | ||
|
6c8b1e93d5 | ||
|
ebdb7c1d4c | ||
|
58bd0c67da | ||
|
1503b3175f | ||
|
0345e089aa | ||
|
0911ab1546 | ||
|
c4dd927a13 | ||
|
34abed597f |
@@ -16,6 +16,7 @@ addons:
|
||||
- zsh
|
||||
# Needed for testing decompression search.
|
||||
- xz-utils
|
||||
- liblz4-tool
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
@@ -59,13 +60,13 @@ matrix:
|
||||
# Minimum Rust supported channel. We enable these to make sure ripgrep
|
||||
# continues to work on the advertised minimum Rust version.
|
||||
- os: linux
|
||||
rust: 1.20.0
|
||||
rust: 1.23.0
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
- os: linux
|
||||
rust: 1.20.0
|
||||
rust: 1.23.0
|
||||
env: TARGET=x86_64-unknown-linux-musl
|
||||
- os: linux
|
||||
rust: 1.20.0
|
||||
rust: 1.23.0
|
||||
env: TARGET=arm-unknown-linux-gnueabihf GCC_VERSION=4.8
|
||||
addons:
|
||||
apt:
|
||||
|
62
CHANGELOG.md
62
CHANGELOG.md
@@ -6,6 +6,9 @@ Releases provided on Github for `x86` and `x86_64` will now work on all target
|
||||
CPUs, and will also automatically take advantage of features found on modern
|
||||
CPUs (such as AVX2) for additional optimizations.
|
||||
|
||||
This release increases the **minimum supported Rust version** from 1.20.0 to
|
||||
1.23.0.
|
||||
|
||||
**BREAKING CHANGES**:
|
||||
|
||||
* When `--count` and `--only-matching` are provided simultaneously, the
|
||||
@@ -17,17 +20,43 @@ CPUs (such as AVX2) for additional optimizations.
|
||||
* Octal syntax is no longer supported. ripgrep previously accepted expressions
|
||||
like `\1` as syntax for matching `U+0001`, but ripgrep will now report an
|
||||
error instead.
|
||||
* The `--line-number-width` flag has been removed. Its functionality was not
|
||||
carefully considered with all ripgrep output formats.
|
||||
See [#795](https://github.com/BurntSushi/ripgrep/issues/795) for more
|
||||
details.
|
||||
|
||||
Feature enhancements:
|
||||
|
||||
* Added or improved file type filtering for Android, Bazel, Fuschia, Haskell,
|
||||
Java and Puppet.
|
||||
* [FEATURE #411](https://github.com/BurntSushi/ripgrep/issues/411):
|
||||
Add a `--stats` flag, which emits aggregate statistics after search results.
|
||||
* [FEATURE #646](https://github.com/BurntSushi/ripgrep/issues/646):
|
||||
Add a `--no-ignore-messages` flag, which suppresses parse errors from reading
|
||||
`.ignore` and `.gitignore` files.
|
||||
* [FEATURE #702](https://github.com/BurntSushi/ripgrep/issues/702):
|
||||
Support `\u{..}` Unicode escape sequences.
|
||||
* [FEATURE #812](https://github.com/BurntSushi/ripgrep/issues/812):
|
||||
Add `-b/--byte-offset` flag that reports byte offset of each matching line.
|
||||
* [FEATURE #814](https://github.com/BurntSushi/ripgrep/issues/814):
|
||||
Add `--count-matches` flag, which is like `--count`, but for each match.
|
||||
* [FEATURE #880](https://github.com/BurntSushi/ripgrep/issues/880):
|
||||
Add a `--no-column` flag, which disables column numbers in the output.
|
||||
* [FEATURE #898](https://github.com/BurntSushi/ripgrep/issues/898):
|
||||
Add support for `lz4` when using the `-z/--search-zip` flag.
|
||||
* [FEATURE #924](https://github.com/BurntSushi/ripgrep/issues/924):
|
||||
`termcolor` has moved to its own repository:
|
||||
https://github.com/BurntSushi/termcolor
|
||||
* [FEATURE #934](https://github.com/BurntSushi/ripgrep/issues/934):
|
||||
Add a new flag, `--no-ignore-global`, that permits disabling global
|
||||
gitignores.
|
||||
* [FEATURE #967](https://github.com/BurntSushi/ripgrep/issues/967):
|
||||
Rename `--maxdepth` to `--max-depth` for consistency. Keep `--maxdepth` for
|
||||
backwards compatibility.
|
||||
* [FEATURE #978](https://github.com/BurntSushi/ripgrep/issues/978):
|
||||
Add a `--pre` option to filter inputs with an arbitrary program.
|
||||
* [FEATURE fca9709d](https://github.com/BurntSushi/ripgrep/commit/fca9709d):
|
||||
Improve zsh completion.
|
||||
|
||||
Bug fixes:
|
||||
|
||||
@@ -41,14 +70,47 @@ Bug fixes:
|
||||
Show comprehensible error messages for regexes like `\s*{`.
|
||||
* [BUG #526](https://github.com/BurntSushi/ripgrep/issues/526):
|
||||
Support backslash escapes in globs.
|
||||
* [BUG #795](https://github.com/BurntSushi/ripgrep/issues/795):
|
||||
Fix problems with `--line-number-width` by removing it.
|
||||
* [BUG #832](https://github.com/BurntSushi/ripgrep/issues/832):
|
||||
Clarify usage instructions for `-f/--file` flag.
|
||||
* [BUG #835](https://github.com/BurntSushi/ripgrep/issues/835):
|
||||
Fix small performance regression while crawling very large directory trees.
|
||||
* [BUG #851](https://github.com/BurntSushi/ripgrep/issues/851):
|
||||
Fix `-S/--smart-case` detection once and for all.
|
||||
* [BUG #852](https://github.com/BurntSushi/ripgrep/issues/852):
|
||||
Be robust with respect to `ENOMEM` errors returned by `mmap`.
|
||||
* [BUG #853](https://github.com/BurntSushi/ripgrep/issues/853):
|
||||
Upgrade `grep` crate to `regex-syntax 0.5.0`.
|
||||
* [BUG #893](https://github.com/BurntSushi/ripgrep/issues/893):
|
||||
Improve support for git submodules.
|
||||
* [BUG #900](https://github.com/BurntSushi/ripgrep/issues/900):
|
||||
When no patterns are given, ripgrep should never match anything.
|
||||
* [BUG #907](https://github.com/BurntSushi/ripgrep/issues/907):
|
||||
ripgrep will now stop traversing after the first file when `--quiet --files`
|
||||
is used.
|
||||
* [BUG #918](https://github.com/BurntSushi/ripgrep/issues/918):
|
||||
Don't skip tar archives when `-z/--search-zip` is used.
|
||||
* [BUG #934](https://github.com/BurntSushi/ripgrep/issues/934):
|
||||
Don't respect gitignore files when searching outside git repositories.
|
||||
* [BUG #948](https://github.com/BurntSushi/ripgrep/issues/948):
|
||||
Use exit code 2 to indicate error, and use exit code 1 to indicate no
|
||||
matches.
|
||||
* [BUG #951](https://github.com/BurntSushi/ripgrep/issues/951):
|
||||
Add stdin example to ripgrep usage documentation.
|
||||
* [BUG #955](https://github.com/BurntSushi/ripgrep/issues/955):
|
||||
Use buffered writing when not printing to a tty, which fixes a performance
|
||||
regression.
|
||||
* [BUG #957](https://github.com/BurntSushi/ripgrep/issues/957):
|
||||
Improve the error message shown for `--path separator /` in some Windows
|
||||
shells.
|
||||
* [BUG #964](https://github.com/BurntSushi/ripgrep/issues/964):
|
||||
Add a `--no-fixed-strings` flag to disable `-F/--fixed-strings`.
|
||||
* [BUG #988](https://github.com/BurntSushi/ripgrep/issues/988):
|
||||
Fix a bug in the `ignore` crate that prevented the use of explicit ignore
|
||||
files after disabling all other ignore rules.
|
||||
* [BUG #995](https://github.com/BurntSushi/ripgrep/issues/995):
|
||||
Respect `$XDG_CONFIG_DIR/git/config` for detecting `core.excludesFile`.
|
||||
|
||||
|
||||
0.8.1 (2018-02-20)
|
||||
|
221
Cargo.lock
generated
221
Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.6.4"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -11,22 +11,22 @@ name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.8"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.1"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -34,25 +34,25 @@ name = "bytecount"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"simd 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.2"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.31.1"
|
||||
version = "2.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -62,11 +62,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.7.2"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"simd 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs_io"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"encoding_rs 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -79,7 +87,7 @@ name = "fuchsia-zircon"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -95,59 +103,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.3.0"
|
||||
version = "0.4.1"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grep"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
dependencies = [
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.1"
|
||||
version = "0.4.3"
|
||||
dependencies = [
|
||||
"crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.3.0",
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.1",
|
||||
"lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.39"
|
||||
version = "0.2.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.1"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -155,7 +163,7 @@ name = "memchr"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -163,8 +171,8 @@ name = "memmap"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -172,17 +180,7 @@ name = "num_cpus"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -191,13 +189,13 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.37"
|
||||
version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -205,50 +203,59 @@ name = "redox_termios"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "0.2.10"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.5.3"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ripgrep"
|
||||
version = "0.8.1"
|
||||
dependencies = [
|
||||
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bytecount 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.31.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.3.0",
|
||||
"grep 0.1.8",
|
||||
"ignore 0.4.1",
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_rs 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding_rs_io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"globset 0.4.1",
|
||||
"grep 0.1.9",
|
||||
"ignore 0.4.3",
|
||||
"lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termcolor 0.3.6",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termcolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -256,12 +263,12 @@ name = "same-file"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -271,17 +278,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "tempdir"
|
||||
version = "0.3.5"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "0.3.6"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"wincolor 0.1.6",
|
||||
"wincolor 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -289,17 +298,17 @@ name = "termion"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -307,7 +316,7 @@ name = "thread_local"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -318,7 +327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -345,12 +354,12 @@ version = "2.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -369,50 +378,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "wincolor"
|
||||
version = "0.1.6"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
|
||||
"checksum aho-corasick 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c1c6d463cbe7ed28720b5b489e7c083eeb8f90d08be2a0d6bb9e1ffea9ce1afa"
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "af80143d6f7608d746df1520709e5d141c96f240b0e62b0aa41bdfb53374d9d4"
|
||||
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
|
||||
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
||||
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
|
||||
"checksum bytecount 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "882585cd7ec84e902472df34a5e01891202db3bf62614e1f0afe459c1afcf744"
|
||||
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
|
||||
"checksum clap 2.31.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dc18f6f4005132120d9711636b32c46a233fad94df6217fa1d81c5e97a9f200"
|
||||
"checksum cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efe5c877e17a9c717a0bf3613b2709f723202c4e4675cc8f12926ded29bcb17e"
|
||||
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
|
||||
"checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19"
|
||||
"checksum encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98fd0f24d1fb71a4a6b9330c8ca04cbd4e7cc5d846b54ca74ff376bc7c9f798d"
|
||||
"checksum encoding_rs 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88a1b66a0d28af4b03a8c8278c6dcb90e6e600d89c14500a9e7a02e64b9ee3ac"
|
||||
"checksum encoding_rs_io 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad0ffe753ba194ef1bc070e8d61edaadb1536c05e364fc9178ca6cbde10922c4"
|
||||
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
|
||||
"checksum libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "f54263ad99207254cf58b5f701ecb432c717445ea2ee8af387334bdd1a03fdff"
|
||||
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
|
||||
"checksum lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fb497c35d362b6a331cfd94956a07fc2c78a4604cdbee844a81170386b996dd3"
|
||||
"checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1"
|
||||
"checksum log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "61bd98ae7f7b754bc53dca7d44b604f733c6bba044ea6f41bc8d89272d8161d2"
|
||||
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
||||
"checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff"
|
||||
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
|
||||
"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
|
||||
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
|
||||
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
|
||||
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
|
||||
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||
"checksum regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "aec3f58d903a7d2a9dc2bf0e41a746f4530e0cab6b615494e058f67a3ef947fb"
|
||||
"checksum regex-syntax 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b2550876c31dc914696a6c2e01cbce8afba79a93c8ae979d2fe051c0230b3756"
|
||||
"checksum regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5bbbea44c5490a1e84357ff28b7d518b4619a159fed5d25f6c1de2d19cc42814"
|
||||
"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d"
|
||||
"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
|
||||
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
|
||||
"checksum simd 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3dd0805c7363ab51a829a1511ad24b6ed0349feaa756c4bc2f977f9f496e6673"
|
||||
"checksum simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3686dd9418ebcc3a26a0c0ae56deab0681e53fe899af91f5bbcee667ebffb1"
|
||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
|
||||
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||
"checksum termcolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "722426c4a0539da2c4ffd9b419d90ad540b4cff4a053be9069c908d4d07e2836"
|
||||
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
|
||||
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
|
||||
"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
|
||||
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
|
||||
"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
|
||||
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
|
||||
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
||||
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63636bd0eb3d00ccb8b9036381b526efac53caf112b7783b730ab3f8e44da369"
|
||||
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
|
||||
"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
|
||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
"checksum wincolor 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9dc3aa9dcda98b5a16150c54619c1ead22e3d3a5d458778ae914be760aa981a"
|
||||
|
27
Cargo.toml
27
Cargo.toml
@@ -3,8 +3,9 @@ name = "ripgrep"
|
||||
version = "0.8.1" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Line oriented search tool using Rust's regex library. Combines the raw
|
||||
performance of grep with the usability of the silver searcher.
|
||||
ripgrep is a line-oriented search tool that recursively searches your current
|
||||
directory for a regex pattern while respecting your gitignore rules. ripgrep
|
||||
has first class support on Windows, macOS and Linux
|
||||
"""
|
||||
documentation = "https://github.com/BurntSushi/ripgrep"
|
||||
homepage = "https://github.com/BurntSushi/ripgrep"
|
||||
@@ -12,9 +13,10 @@ repository = "https://github.com/BurntSushi/ripgrep"
|
||||
readme = "README.md"
|
||||
keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
||||
categories = ["command-line-utilities", "text-processing"]
|
||||
license = "Unlicense/MIT"
|
||||
license = "Unlicense OR MIT"
|
||||
exclude = ["HomebrewFormula"]
|
||||
build = "build.rs"
|
||||
autotests = false
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "BurntSushi/ripgrep" }
|
||||
@@ -30,13 +32,14 @@ name = "integration"
|
||||
path = "tests/tests.rs"
|
||||
|
||||
[workspace]
|
||||
members = ["grep", "globset", "ignore", "termcolor", "wincolor"]
|
||||
members = ["grep", "globset", "ignore"]
|
||||
|
||||
[dependencies]
|
||||
atty = "0.2.2"
|
||||
atty = "0.2.11"
|
||||
bytecount = "0.3.1"
|
||||
encoding_rs = "0.7"
|
||||
globset = { version = "0.3.0", path = "globset" }
|
||||
encoding_rs = "0.8"
|
||||
encoding_rs_io = "0.1"
|
||||
globset = { version = "0.4.0", path = "globset" }
|
||||
grep = { version = "0.1.8", path = "grep" }
|
||||
ignore = { version = "0.4.0", path = "ignore" }
|
||||
lazy_static = "1"
|
||||
@@ -45,9 +48,9 @@ log = "0.4"
|
||||
memchr = "2"
|
||||
memmap = "0.6"
|
||||
num_cpus = "1"
|
||||
regex = "0.2.10"
|
||||
regex = "1"
|
||||
same-file = "1"
|
||||
termcolor = { version = "0.3.4", path = "termcolor" }
|
||||
termcolor = "1"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "2.29.4"
|
||||
@@ -67,13 +70,13 @@ default-features = false
|
||||
features = ["suggestions", "color"]
|
||||
|
||||
[features]
|
||||
avx-accel = ["bytecount/avx-accel", "regex/unstable"]
|
||||
avx-accel = [
|
||||
"bytecount/avx-accel",
|
||||
]
|
||||
simd-accel = [
|
||||
"bytecount/simd-accel",
|
||||
"encoding_rs/simd-accel",
|
||||
"regex/unstable",
|
||||
]
|
||||
unstable = ["regex/unstable"]
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
67
FAQ.md
67
FAQ.md
@@ -20,6 +20,7 @@
|
||||
* [How do I create an alias for ripgrep on Windows?](#rg-alias-windows)
|
||||
* [How do I create a PowerShell profile?](#powershell-profile)
|
||||
* [How do I pipe non-ASCII content to ripgrep on Windows?](#pipe-non-ascii-windows)
|
||||
* [How can I search and replace with ripgrep?](#search-and-replace)
|
||||
* [How is ripgrep licensed?](#license)
|
||||
* [Can ripgrep replace grep?](#posix4ever)
|
||||
* [What does the "rip" in ripgrep mean?](#intentcountsforsomething)
|
||||
@@ -134,7 +135,7 @@ How do I search compressed files?
|
||||
</h3>
|
||||
|
||||
ripgrep's `-z/--search-zip` flag will cause it to search compressed files
|
||||
automatically. Currently, this supports gzip, bzip2, lzma and xz only and
|
||||
automatically. Currently, this supports gzip, bzip2, lzma, lz4 and xz only and
|
||||
requires the corresponding `gzip`, `bzip2` and `xz` binaries to be installed on
|
||||
your system. (That is, ripgrep does decompression by shelling out to another
|
||||
process.)
|
||||
@@ -470,6 +471,70 @@ that the console will use for printing to UTF-8 with
|
||||
will also reset when PowerShell is restarted, so you can add that line
|
||||
to your profile as well if you want to make the setting permanent.
|
||||
|
||||
<h3 name="search-and-replace">
|
||||
How can I search and replace with ripgrep?
|
||||
</h3>
|
||||
|
||||
Using ripgrep alone, you can't. ripgrep is a search tool that will never
|
||||
touch your files. However, the output of ripgrep can be piped to other tools
|
||||
that do modify files on disk. See
|
||||
[this issue](https://github.com/BurntSushi/ripgrep/issues/74) for more
|
||||
information.
|
||||
|
||||
sed is one such tool that can modify files on disk. sed can take a filename
|
||||
and a substitution command to search and replace in the specified file.
|
||||
Files containing matching patterns can be provided to sed using
|
||||
|
||||
```
|
||||
rg foo --files-with-matches
|
||||
```
|
||||
|
||||
The output of this command is a list of filenames that contain a match for
|
||||
the `foo` pattern.
|
||||
|
||||
This list can be piped into `xargs`, which will split the filenames from
|
||||
standard input into arguments for the command following xargs. You can use this
|
||||
combination to pipe a list of filenames into sed for replacement. For example:
|
||||
|
||||
```
|
||||
rg foo --files-with-matches | xargs sed -i 's/foo/bar/g'
|
||||
```
|
||||
|
||||
will replace all instances of 'foo' with 'bar' in the files in which
|
||||
ripgrep finds the foo pattern. The `-i` flag to sed indicates that you are
|
||||
editing files in place, and `s/foo/bar/g` says that you are performing a
|
||||
**s**ubstitution of the pattren `foo` for `bar`, and that you are doing this
|
||||
substitution **g**lobally (all occurrences of the pattern in each file).
|
||||
|
||||
Note: the above command assumes that you are using GNU sed. If you are using
|
||||
BSD sed (the default on macOS and FreeBSD) then you must modify the above
|
||||
command to be the following:
|
||||
|
||||
```
|
||||
rg foo --files-with-matches | xargs sed -i '' 's/foo/bar/g'
|
||||
```
|
||||
|
||||
The `-i` flag in BSD sed requires a file extension to be given to make backups
|
||||
for all modified files. Specifying the empty string prevents file backups from
|
||||
being made.
|
||||
|
||||
Finally, if any of your file paths contain whitespace in them, then you might
|
||||
need to delimit your file paths with a NUL terminator. This requires telling
|
||||
ripgrep to output NUL bytes between each path, and telling xargs to read paths
|
||||
delimited by NUL bytes:
|
||||
|
||||
```
|
||||
rg foo --files-with-matches -0 | xargs -0 sed -i 's/foo/bar/g'
|
||||
```
|
||||
|
||||
To learn more about sed, see the sed manual
|
||||
[here](https://www.gnu.org/software/sed/manual/sed.html).
|
||||
|
||||
Additionally, Facebook has a tool called
|
||||
[fastmod](https://github.com/facebookincubator/fastmod)
|
||||
that uses some of the same libraries as ripgrep and might provide a more
|
||||
ergonomic search-and-replace experience.
|
||||
|
||||
|
||||
<h3 name="license">
|
||||
How is ripgrep licensed?
|
||||
|
7
GUIDE.md
7
GUIDE.md
@@ -539,6 +539,13 @@ $ cat $HOME/.ripgreprc
|
||||
--type-add
|
||||
web:*.{html,css,js}*
|
||||
|
||||
# Using glob patterns to include/exclude files or folders
|
||||
--glob=!git/*
|
||||
|
||||
# or
|
||||
--glob
|
||||
!git/*
|
||||
|
||||
# Set the colors.
|
||||
--colors=line:none
|
||||
--colors=line:style:bold
|
||||
|
@@ -2,6 +2,12 @@
|
||||
|
||||
Replace this text with the output of `rg --version`.
|
||||
|
||||
#### How did you install ripgrep?
|
||||
|
||||
If you installed ripgrep with snap and are getting strange file permission or
|
||||
file not found errors, then please do not file a bug. Instead, use one of the
|
||||
Github binary releases.
|
||||
|
||||
#### What operating system are you using ripgrep on?
|
||||
|
||||
Replace this text with your operating system and version.
|
||||
|
70
README.md
70
README.md
@@ -9,7 +9,7 @@ ack and grep.
|
||||
|
||||
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||
[](https://crates.io/crates/ripgrep)
|
||||
[](https://crates.io/crates/ripgrep)
|
||||
|
||||
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||
|
||||
@@ -106,7 +106,10 @@ increases the times to `2.640s` for ripgrep and `10.277s` for GNU grep.
|
||||
automatically detecting UTF-16 is provided. Other text encodings must be
|
||||
specifically specified with the `-E/--encoding` flag.)
|
||||
* ripgrep supports searching files compressed in a common format (gzip, xz,
|
||||
lzma or bzip2 current) with the `-z/--search-zip` flag.
|
||||
lzma, bzip2 or lz4) with the `-z/--search-zip` flag.
|
||||
* ripgrep supports arbitrary input preprocessing filters which could be PDF
|
||||
text extraction, less supported decompression, decrypting, automatic encoding
|
||||
detection and so on.
|
||||
|
||||
In other words, use ripgrep if you like speed, filtering by default, fewer
|
||||
bugs, and Unicode support.
|
||||
@@ -151,7 +154,7 @@ Summarizing, ripgrep is fast because:
|
||||
latter is better for large directories. ripgrep chooses the best searching
|
||||
strategy for you automatically.
|
||||
* Applies your ignore patterns in `.gitignore` files using a
|
||||
[`RegexSet`](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html).
|
||||
[`RegexSet`](https://docs.rs/regex/1.0.0/regex/struct.RegexSet.html).
|
||||
That means a single file path can be matched against multiple glob patterns
|
||||
simultaneously.
|
||||
* It uses a lock-free parallel recursive directory iterator, courtesy of
|
||||
@@ -197,6 +200,13 @@ $ brew tap burntsushi/ripgrep https://github.com/BurntSushi/ripgrep.git
|
||||
$ brew install ripgrep-bin
|
||||
```
|
||||
|
||||
If you're a **MacPorts** user, then you can install ripgrep from the
|
||||
[official ports](https://www.macports.org/ports.php?by=name&substr=ripgrep):
|
||||
|
||||
```
|
||||
$ sudo port install ripgrep
|
||||
```
|
||||
|
||||
If you're a **Windows Chocolatey** user, then you can install ripgrep from the [official repo](https://chocolatey.org/packages/ripgrep):
|
||||
|
||||
```
|
||||
@@ -234,6 +244,12 @@ $ sudo dnf copr enable carlwgeorge/ripgrep
|
||||
$ sudo dnf install ripgrep
|
||||
```
|
||||
|
||||
If you're an **openSUSE Tumbleweed** user, you can install ripgrep from the [official repo](http://software.opensuse.org/package/ripgrep):
|
||||
|
||||
```
|
||||
$ sudo zypper install ripgrep
|
||||
```
|
||||
|
||||
If you're a **RHEL/CentOS 7** user, you can install ripgrep from [copr](https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/):
|
||||
|
||||
```
|
||||
@@ -259,24 +275,31 @@ $ curl -LO https://github.com/BurntSushi/ripgrep/releases/download/0.8.1/ripgrep
|
||||
$ sudo dpkg -i ripgrep_0.8.1_amd64.deb
|
||||
```
|
||||
|
||||
If you're an **Ubuntu** user, ripgrep can be installed from the `snap` store.
|
||||
* Note that if you are using `16.04 LTS` or later, snap is already installed.
|
||||
* For older versions you can install snap using
|
||||
[this guide](https://docs.snapcraft.io/core/install-ubuntu).
|
||||
* If you get permission errors when running ripgrep after installation, try
|
||||
uninstalling and then re-installing with the `--classic` flag.
|
||||
(N.B. Various snaps for ripgrep on Ubuntu are also available, but none of them
|
||||
seem to work right and generate a number of very strange bug reports that I
|
||||
don't know how to fix and don't have the time to fix. Therefore, it is no
|
||||
longer a recommended installation option.)
|
||||
|
||||
If you're a **FreeBSD** user, then you can install ripgrep from the [official ports](https://www.freshports.org/textproc/ripgrep/):
|
||||
|
||||
For the latest stable release:
|
||||
```
|
||||
$ sudo snap install rg
|
||||
# pkg install ripgrep
|
||||
```
|
||||
For the most recent release candidate:
|
||||
|
||||
If you're an **OpenBSD** user, then you can install ripgrep from the [official ports](http://openports.se/textproc/ripgrep):
|
||||
|
||||
```
|
||||
$ sudo snap install --candidate rg
|
||||
$ doas pkg_add ripgrep
|
||||
```
|
||||
|
||||
If you're a **NetBSD** user, then you can install ripgrep from [pkgsrc](http://pkgsrc.se/textproc/ripgrep):
|
||||
|
||||
```
|
||||
# pkgin install ripgrep
|
||||
```
|
||||
|
||||
If you're a **Rust programmer**, ripgrep can be installed with `cargo`.
|
||||
* Note that the minimum supported version of Rust for ripgrep is **1.20**,
|
||||
* Note that the minimum supported version of Rust for ripgrep is **1.23.0**,
|
||||
although ripgrep may work with older versions.
|
||||
* Note that the binary may be bigger than expected because it contains debug
|
||||
symbols. This is intentional. To remove debug symbols and therefore reduce
|
||||
@@ -286,13 +309,8 @@ If you're a **Rust programmer**, ripgrep can be installed with `cargo`.
|
||||
$ cargo install ripgrep
|
||||
```
|
||||
|
||||
If you're using Rust nightly, then use
|
||||
|
||||
```
|
||||
$ cargo install ripgrep --features unstable
|
||||
```
|
||||
|
||||
to get SIMD optimizations.
|
||||
When compiling with Rust 1.27 or newer, this will automatically enable SIMD
|
||||
optimizations for search.
|
||||
|
||||
ripgrep isn't currently in any other package repositories.
|
||||
[I'd like to change that](https://github.com/BurntSushi/ripgrep/issues/10).
|
||||
@@ -302,7 +320,7 @@ ripgrep isn't currently in any other package repositories.
|
||||
|
||||
ripgrep is written in Rust, so you'll need to grab a
|
||||
[Rust installation](https://www.rust-lang.org/) in order to compile it.
|
||||
ripgrep compiles with Rust 1.20 (stable) or newer. Building is easy:
|
||||
ripgrep compiles with Rust 1.23.0 (stable) or newer. Building is easy:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/BurntSushi/ripgrep
|
||||
@@ -313,7 +331,7 @@ $ ./target/release/rg --version
|
||||
```
|
||||
|
||||
If you have a Rust nightly compiler and a recent Intel CPU, then you can enable
|
||||
optional SIMD acceleration like so:
|
||||
additional optional SIMD acceleration like so:
|
||||
|
||||
```
|
||||
RUSTFLAGS="-C target-cpu=native" cargo build --release --features 'simd-accel avx-accel'
|
||||
@@ -323,6 +341,12 @@ If your machine doesn't support AVX instructions, then simply remove
|
||||
`avx-accel` from the features list. Similarly for SIMD (which corresponds
|
||||
roughly to SSE instructions).
|
||||
|
||||
The `simd-accel` and `avx-accel` features enable SIMD support in certain
|
||||
ripgrep dependencies (responsible for counting lines and transcoding). They
|
||||
are not necessary to get SIMD optimizations for search; those are enabled
|
||||
automatically. Hopefully, some day, the `simd-accel` and `avx-accel` features
|
||||
will similarly become unnecessary.
|
||||
|
||||
|
||||
### Running tests
|
||||
|
||||
|
@@ -50,7 +50,7 @@ test_script:
|
||||
|
||||
before_deploy:
|
||||
# Generate artifacts for release
|
||||
- cargo build --release --features unstable
|
||||
- cargo build --release
|
||||
- mkdir staging
|
||||
- copy target\release\rg.exe staging
|
||||
- ps: copy target\release\build\ripgrep-*\out\_rg.ps1 staging
|
||||
|
@@ -8,7 +8,7 @@ set -ex
|
||||
|
||||
# Generate artifacts for release
|
||||
mk_artifacts() {
|
||||
cargo build --target "$TARGET" --release --features unstable
|
||||
cargo build --target "$TARGET" --release
|
||||
}
|
||||
|
||||
mk_tarball() {
|
||||
|
@@ -1,41 +1,42 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
emulate zsh -o extended_glob -o no_function_argzero -o no_unset
|
||||
|
||||
##
|
||||
# Compares options in `rg --help` output to options in zsh completion function
|
||||
|
||||
emulate -R zsh
|
||||
setopt extended_glob
|
||||
setopt no_function_argzero
|
||||
setopt no_unset
|
||||
|
||||
get_comp_args() {
|
||||
# Technically there are many options that the completion system sets that
|
||||
# our function may rely on, but we'll trust that we've got it mostly right
|
||||
setopt local_options unset
|
||||
|
||||
# Our completion function recognises a special variable which tells it to
|
||||
# dump the _arguments specs and then just return. But do this in a sub-shell
|
||||
# anyway to avoid any weirdness
|
||||
( _RG_COMPLETE_LIST_ARGS=1 source $1 )
|
||||
return $?
|
||||
}
|
||||
|
||||
main() {
|
||||
local diff
|
||||
local rg="${${0:a}:h}/../target/${TARGET:-}/release/rg"
|
||||
local _rg="${${0:a}:h}/../complete/_rg"
|
||||
local rg="${0:a:h}/../target/${TARGET:-}/release/rg"
|
||||
local _rg="${0:a:h}/../complete/_rg"
|
||||
local -a help_args comp_args
|
||||
|
||||
[[ -e $rg ]] || rg=${rg/%\/release\/rg/\/debug\/rg}
|
||||
|
||||
rg=${rg:a}
|
||||
_rg=${_rg:a}
|
||||
|
||||
[[ -e $rg ]] || {
|
||||
printf >&2 'File not found: %s\n' $rg
|
||||
print -r >&2 "File not found: $rg"
|
||||
return 1
|
||||
}
|
||||
[[ -e $_rg ]] || {
|
||||
printf >&2 'File not found: %s\n' $_rg
|
||||
print -r >&2 "File not found: $_rg"
|
||||
return 1
|
||||
}
|
||||
|
||||
printf 'Comparing options:\n-%s\n+%s\n' $rg $_rg
|
||||
print -rl - 'Comparing options:' "-$rg" "+$_rg"
|
||||
|
||||
# 'Parse' options out of the `--help` output. To prevent false positives we
|
||||
# only look at lines where the first non-white-space character is `-`
|
||||
@@ -50,6 +51,8 @@ main() {
|
||||
# 'Parse' options out of the completion function
|
||||
comp_args=( ${(f)"$( get_comp_args $_rg )"} )
|
||||
|
||||
# Note that we currently exclude hidden (!...) options; matching these
|
||||
# properly against the `--help` output could be irritating
|
||||
comp_args=( ${comp_args#\(*\)} ) # Strip excluded options
|
||||
comp_args=( ${comp_args#\*} ) # Strip repetition indicator
|
||||
comp_args=( ${comp_args%%-[:[]*} ) # Strip everything after -optname-
|
||||
@@ -57,14 +60,14 @@ main() {
|
||||
comp_args=( ${comp_args##[^-]*} ) # Remove non-options
|
||||
|
||||
# This probably isn't necessary, but we should ensure the same order
|
||||
comp_args=( ${(f)"$( printf '%s\n' $comp_args | sort -u )"} )
|
||||
comp_args=( ${(f)"$( print -rl - $comp_args | sort -u )"} )
|
||||
|
||||
(( $#help_args )) || {
|
||||
printf >&2 'Failed to get help_args\n'
|
||||
print -r >&2 'Failed to get help_args'
|
||||
return 1
|
||||
}
|
||||
(( $#comp_args )) || {
|
||||
printf >&2 'Failed to get comp_args\n'
|
||||
print -r >&2 'Failed to get comp_args'
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -73,12 +76,12 @@ main() {
|
||||
diff -U2 \
|
||||
--label '`rg --help`' \
|
||||
--label '`_rg`' \
|
||||
=( printf '%s\n' $help_args ) =( printf '%s\n' $comp_args )
|
||||
=( print -rl - $help_args ) =( print -rl - $comp_args )
|
||||
else
|
||||
diff -U2 \
|
||||
-L '`rg --help`' \
|
||||
-L '`_rg`' \
|
||||
=( printf '%s\n' $help_args ) =( printf '%s\n' $comp_args )
|
||||
=( print -rl - $help_args ) =( print -rl - $comp_args )
|
||||
fi
|
||||
)"
|
||||
|
||||
@@ -91,4 +94,4 @@ main() {
|
||||
return 0
|
||||
}
|
||||
|
||||
main "${@}"
|
||||
main "$@"
|
||||
|
392
complete/_rg
392
complete/_rg
@@ -6,163 +6,277 @@
|
||||
# Run ci/test_complete.sh after building to ensure that the options supported by
|
||||
# this function stay in synch with the `rg` binary.
|
||||
#
|
||||
# @see http://zsh.sourceforge.net/Doc/Release/Completion-System.html
|
||||
# @see https://github.com/zsh-users/zsh/blob/master/Etc/completion-style-guide
|
||||
#
|
||||
# Based on code from the zsh-users project — see copyright notice below.
|
||||
# Originally based on code from the zsh-users project — see copyright notice
|
||||
# below.
|
||||
|
||||
_rg() {
|
||||
local state_descr ret curcontext="${curcontext:-}"
|
||||
local -a context line state
|
||||
local -A opt_args val_args
|
||||
local -a rg_args
|
||||
local curcontext=$curcontext no='!' descr ret=1
|
||||
local -a context line state state_descr args tmp suf
|
||||
local -A opt_args
|
||||
|
||||
# Sort by long option name to match `rg --help`
|
||||
rg_args=(
|
||||
'(-A -C --after-context --context)'{-A+,--after-context=}'[specify lines to show after each match]:number of lines'
|
||||
'(-B -C --before-context --context)'{-B+,--before-context=}'[specify lines to show before each match]:number of lines'
|
||||
'(-i -s -S --ignore-case --case-sensitive --smart-case)'{-s,--case-sensitive}'[search case-sensitively]'
|
||||
'--color=[specify when to use colors in output]:when:( never auto always ansi )'
|
||||
'*--colors=[specify color settings and styles]: :->colorspec'
|
||||
'--column[show column numbers]'
|
||||
'(-A -B -C --after-context --before-context --context)'{-C+,--context=}'[specify lines to show before and after each match]:number of lines'
|
||||
'(-b --byte-offset)'{-b,--byte-offset}'[print the 0-based byte offset for each matching line]'
|
||||
'--context-separator=[specify string used to separate non-continuous context lines in output]:separator'
|
||||
'(-c --count --count-matches --passthrough --passthru)'{-c,--count}'[only show count of matching lines for each file]'
|
||||
'(--count-matches -c --count --passthrough --passthru)--count-matches[only show count of individual matches for each file]'
|
||||
'--debug[show debug messages]'
|
||||
'--dfa-size-limit=[specify upper size limit of generated DFA]:DFA size'
|
||||
'(-E --encoding)'{-E+,--encoding=}'[specify text encoding of files to search]: :_rg_encodings'
|
||||
'*'{-f+,--file=}'[specify file containing patterns to search for]:file:_files'
|
||||
"(1)--files[show each file that would be searched (but don't search)]"
|
||||
'(-l --files-with-matches --files-without-match)'{-l,--files-with-matches}'[only show names of files with matches]'
|
||||
'(-l --files-with-matches --files-without-match)--files-without-match[only show names of files without matches]'
|
||||
'(-F --fixed-strings)'{-F,--fixed-strings}'[treat pattern as literal string instead of regular expression]'
|
||||
'(-L --follow)'{-L,--follow}'[follow symlinks]'
|
||||
'*'{-g+,--glob=}'[include or exclude files for searching that match the specified glob]:glob'
|
||||
'(: -)'{-h,--help}'[display help information]'
|
||||
'(-p --no-heading --pretty --vimgrep)--heading[show matches grouped by file name]'
|
||||
# ripgrep has many options which negate the effect of a more common one — for
|
||||
# example, `--no-column` to negate `--column`, and `--messages` to negate
|
||||
# `--no-messages`. There are so many of these, and they're so infrequently
|
||||
# used, that some users will probably find it irritating if they're completed
|
||||
# indiscriminately, so let's not do that unless either the current prefix
|
||||
# matches one of those negation options or the user has the `complete-all`
|
||||
# style set. Note that this prefix check has to be updated manually to account
|
||||
# for all of the potential negation options listed below!
|
||||
if
|
||||
# (--[imn]* => --ignore*, --messages, --no-*)
|
||||
[[ $PREFIX$SUFFIX == --[imn]* ]] ||
|
||||
zstyle -t ":complete:$curcontext:*" complete-all
|
||||
then
|
||||
no=
|
||||
fi
|
||||
|
||||
# We make heavy use of argument groups here to prevent the option specs from
|
||||
# growing unwieldy. These aren't supported in zsh <5.4, though, so we'll strip
|
||||
# them out below if necessary. This makes the exclusions inaccurate on those
|
||||
# older versions, but oh well — it's not that big a deal
|
||||
args=(
|
||||
+ '(exclusive)' # Misc. fully exclusive options
|
||||
'(: * -)'{-h,--help}'[display help information]'
|
||||
'(: * -)'{-V,--version}'[display version information]'
|
||||
|
||||
+ '(case)' # Case-sensitivity options
|
||||
{-i,--ignore-case}'[search case-insensitively]'
|
||||
{-s,--case-sensitive}'[search case-sensitively]'
|
||||
{-S,--smart-case}'[search case-insensitively if pattern is all lowercase]'
|
||||
|
||||
+ '(context-a)' # Context (after) options
|
||||
'(context-c)'{-A+,--after-context=}'[specify lines to show after each match]:number of lines'
|
||||
|
||||
+ '(context-b)' # Context (before) options
|
||||
'(context-c)'{-B+,--before-context=}'[specify lines to show before each match]:number of lines'
|
||||
|
||||
+ '(context-c)' # Context (combined) options
|
||||
'(context-a context-b)'{-C+,--context=}'[specify lines to show before and after each match]:number of lines'
|
||||
|
||||
+ '(column)' # Column options
|
||||
'--column[show column numbers for matches]'
|
||||
$no"--no-column[don't show column numbers for matches]"
|
||||
|
||||
+ '(count)' # Counting options
|
||||
'(passthru)'{-c,--count}'[only show count of matching lines for each file]'
|
||||
'(passthru)--count-matches[only show count of individual matches for each file]'
|
||||
|
||||
+ file # File-input options
|
||||
'*'{-f+,--file=}'[specify file containing patterns to search for]: :_files'
|
||||
|
||||
+ '(file-match)' # Files with/without match options
|
||||
'(stats)'{-l,--files-with-matches}'[only show names of files with matches]'
|
||||
'(stats)--files-without-match[only show names of files without matches]'
|
||||
|
||||
+ '(file-name)' # File-name options
|
||||
{-H,--with-filename}'[show file name for matches]'
|
||||
"--no-filename[don't show file name for matches]"
|
||||
|
||||
+ '(fixed)' # Fixed-string options
|
||||
{-F,--fixed-strings}'[treat pattern as literal string instead of regular expression]'
|
||||
$no"--no-fixed-strings[don't treat pattern as literal string]"
|
||||
|
||||
+ '(follow)' # Symlink-following options
|
||||
{-L,--follow}'[follow symlinks]'
|
||||
$no"--no-follow[don't follow symlinks]"
|
||||
|
||||
+ glob # File-glob options
|
||||
'*'{-g+,--glob=}'[include/exclude files matching specified glob]:glob'
|
||||
'*--iglob=[include/exclude files matching specified case-insensitive glob]:glob'
|
||||
|
||||
+ '(heading)' # Heading options
|
||||
'(pretty-vimgrep)--heading[show matches grouped by file name]'
|
||||
"(pretty-vimgrep)--no-heading[don't show matches grouped by file name]"
|
||||
|
||||
+ '(hidden)' # Hidden-file options
|
||||
'--hidden[search hidden files and directories]'
|
||||
'*--iglob=[include or exclude files for searching that match the specified case-insensitive glob]:glob'
|
||||
'(-i -s -S --case-sensitive --ignore-case --smart-case)'{-i,--ignore-case}'[search case-insensitively]'
|
||||
'--ignore-file=[specify additional ignore file]:file:_files'
|
||||
'(-v --invert-match)'{-v,--invert-match}'[invert matching]'
|
||||
'(-n -N --line-number --no-line-number)'{-n,--line-number}'[show line numbers]'
|
||||
'(-N --no-line-number)--line-number-width=[specify width of displayed line number]:number of columns'
|
||||
'(-w -x --line-regexp --word-regexp)'{-x,--line-regexp}'[only show matches surrounded by line boundaries]'
|
||||
'(-M --max-columns)'{-M+,--max-columns=}'[specify max length of lines to print]:number of bytes'
|
||||
'(-m --max-count)'{-m+,--max-count=}'[specify max number of matches per file]:number of matches'
|
||||
'--max-filesize=[specify size above which files should be ignored]:file size'
|
||||
'--maxdepth=[specify max number of directories to descend]:number of directories'
|
||||
'(--mmap --no-mmap)--mmap[search using memory maps when possible]'
|
||||
'(-H --with-filename --no-filename)--no-filename[suppress all file names]'
|
||||
"(-p --heading --pretty --vimgrep)--no-heading[don't group matches by file name]"
|
||||
"--no-config[don't load configuration files]"
|
||||
"(--no-ignore-parent)--no-ignore[don't respect ignore files]"
|
||||
$no"--no-hidden[don't search hidden files and directories]"
|
||||
|
||||
+ '(ignore)' # Ignore-file options
|
||||
"(--no-ignore-global --no-ignore-parent --no-ignore-vcs)--no-ignore[don't respect ignore files]"
|
||||
$no'(--ignore-global --ignore-parent --ignore-vcs)--ignore[respect ignore files]'
|
||||
|
||||
+ '(ignore-global)' # Global ignore-file options
|
||||
"--no-ignore-global[don't respect global ignore files]"
|
||||
$no'--ignore-global[respect global ignore files]'
|
||||
|
||||
+ '(ignore-parent)' # Parent ignore-file options
|
||||
"--no-ignore-parent[don't respect ignore files in parent directories]"
|
||||
$no'--ignore-parent[respect ignore files in parent directories]'
|
||||
|
||||
+ '(ignore-vcs)' # VCS ignore-file options
|
||||
"--no-ignore-vcs[don't respect version control ignore files]"
|
||||
'(-n -N --line-number --no-line-number)'{-N,--no-line-number}'[suppress line numbers]'
|
||||
'--no-messages[suppress all error messages]'
|
||||
"(--mmap --no-mmap)--no-mmap[don't search using memory maps]"
|
||||
'(-0 --null)'{-0,--null}'[print NUL byte after file names]'
|
||||
'(-o -r --only-matching --passthrough --passthru --replace)'{-o,--only-matching}'[show only matching part of each line]'
|
||||
'(-c -o -r --count --only-matching --passthrough --replace)--passthru[show both matching and non-matching lines]'
|
||||
'!(-c -o -r --count --only-matching --passthru --replace)--passthrough'
|
||||
'--path-separator=[specify path separator to use when printing file names]:separator'
|
||||
'(-p --heading --no-heading --pretty --vimgrep)'{-p,--pretty}'[alias for --color=always --heading -n]'
|
||||
'(-q --quiet)'{-q,--quiet}'[suppress normal output]'
|
||||
'--regex-size-limit=[specify upper size limit of compiled regex]:regex size'
|
||||
'(1 -f --file)*'{-e+,--regexp=}'[specify pattern]:pattern'
|
||||
'(-c -o -r --count --only-matching --passthrough --passthru --replace)'{-r+,--replace=}'[specify string used to replace matches]:replace string'
|
||||
'(-i -s -S --ignore-case --case-sensitive --smart-case)'{-S,--smart-case}'[search case-insensitively if the pattern is all lowercase]'
|
||||
'(-j --threads)--sort-files[sort results by file path (disables parallelism)]'
|
||||
'(-a --text)'{-a,--text}'[search binary files as if they were text]'
|
||||
'(-j --sort-files --threads)'{-j+,--threads=}'[specify approximate number of threads to use]:number of threads'
|
||||
$no'--ignore-vcs[respect version control ignore files]'
|
||||
|
||||
+ '(line)' # Line-number options
|
||||
{-n,--line-number}'[show line numbers for matches]'
|
||||
{-N,--no-line-number}"[don't show line numbers for matches]"
|
||||
|
||||
+ '(max-depth)' # Directory-depth options
|
||||
'--max-depth=[specify max number of directories to descend]:number of directories'
|
||||
'!--maxdepth=:number of directories'
|
||||
|
||||
+ '(messages)' # Error-message options
|
||||
'(--no-ignore-messages)--no-messages[suppress some error messages]'
|
||||
$no"--messages[don't suppress error messages affected by --no-messages]"
|
||||
|
||||
+ '(messages-ignore)' # Ignore-error message options
|
||||
"--no-ignore-messages[don't show ignore-file parse error messages]"
|
||||
$no'--ignore-messages[show ignore-file parse error messages]'
|
||||
|
||||
+ '(mmap)' # mmap options
|
||||
'--mmap[search using memory maps when possible]'
|
||||
"--no-mmap[don't search using memory maps]"
|
||||
|
||||
+ '(only)' # Only-match options
|
||||
'(passthru replace)'{-o,--only-matching}'[show only matching part of each line]'
|
||||
|
||||
+ '(passthru)' # Pass-through options
|
||||
'(--vimgrep count only replace)--passthru[show both matching and non-matching lines]'
|
||||
'!(--vimgrep count only replace)--passthrough'
|
||||
|
||||
+ '(pre)' # Preprocessing options
|
||||
'(-z --search-zip)--pre=[specify preprocessor utility]:preprocessor utility:_command_names -e'
|
||||
$no'--no-pre[disable preprocessor utility]'
|
||||
|
||||
+ '(pretty-vimgrep)' # Pretty/vimgrep display options
|
||||
'(heading)'{-p,--pretty}'[alias for --color=always --heading -n]'
|
||||
'(heading passthru)--vimgrep[show results in vim-compatible format]'
|
||||
|
||||
+ regexp # Explicit pattern options
|
||||
'(1 file)*'{-e+,--regexp=}'[specify pattern]:pattern'
|
||||
|
||||
+ '(replace)' # Replacement options
|
||||
'(count only passthru)'{-r+,--replace=}'[specify string used to replace matches]:replace string'
|
||||
|
||||
+ '(sort)' # File-sorting options
|
||||
'(threads)--sort-files[sort results by file path (disables parallelism)]'
|
||||
$no"--no-sort-files[don't sort results by file path]"
|
||||
|
||||
+ stats # Statistics options
|
||||
'(--files file-match)--stats[show search statistics]'
|
||||
|
||||
+ '(text)' # Binary-search options
|
||||
{-a,--text}'[search binary files as if they were text]'
|
||||
$no"--no-text[don't search binary files as if they were text]"
|
||||
|
||||
+ '(threads)' # Thread-count options
|
||||
'(--sort-files)'{-j+,--threads=}'[specify approximate number of threads to use]:number of threads'
|
||||
|
||||
+ type # Type options
|
||||
'*'{-t+,--type=}'[only search files matching specified type]: :_rg_types'
|
||||
'*--type-add=[add new glob for file type]: :->typespec'
|
||||
'*--type-add=[add new glob for specified file type]: :->typespec'
|
||||
'*--type-clear=[clear globs previously defined for specified file type]: :_rg_types'
|
||||
# This should actually be exclusive with everything but other type options
|
||||
'(:)--type-list[show all supported file types and their associated globs]'
|
||||
'*'{-T+,--type-not=}"[don't search files matching specified type]: :_rg_types"
|
||||
'(: *)--type-list[show all supported file types and their associated globs]'
|
||||
'*'{-T+,--type-not=}"[don't search files matching specified file type]: :_rg_types"
|
||||
|
||||
+ '(word-line)' # Whole-word/line match options
|
||||
{-w,--word-regexp}'[only show matches surrounded by word boundaries]'
|
||||
{-x,--line-regexp}'[only show matches surrounded by line boundaries]'
|
||||
|
||||
+ '(zip)' # Compression options
|
||||
'(--pre)'{-z,--search-zip}'[search in compressed files]'
|
||||
$no"--no-search-zip[don't search in compressed files]"
|
||||
|
||||
+ misc # Other options — no need to separate these at the moment
|
||||
'(-b --byte-offset)'{-b,--byte-offset}'[show 0-based byte offset for each matching line]'
|
||||
'--color=[specify when to use colors in output]:when:((
|
||||
never\:"never use colors"
|
||||
auto\:"use colors or not based on stdout, TERM, etc."
|
||||
always\:"always use colors"
|
||||
ansi\:"always use ANSI colors (even on Windows)"
|
||||
))'
|
||||
'*--colors=[specify color and style settings]: :->colorspec'
|
||||
'--context-separator=[specify string used to separate non-continuous context lines in output]:separator'
|
||||
'--debug[show debug messages]'
|
||||
'--dfa-size-limit=[specify upper size limit of generated DFA]:DFA size (bytes)'
|
||||
'(-E --encoding)'{-E+,--encoding=}'[specify text encoding of files to search]: :_rg_encodings'
|
||||
"(1 stats)--files[show each file that would be searched (but don't search)]"
|
||||
'*--ignore-file=[specify additional ignore file]:ignore file:_files'
|
||||
'(-v --invert-match)'{-v,--invert-match}'[invert matching]'
|
||||
'(-M --max-columns)'{-M+,--max-columns=}'[specify max length of lines to print]:number of bytes'
|
||||
'(-m --max-count)'{-m+,--max-count=}'[specify max number of matches per file]:number of matches'
|
||||
'--max-filesize=[specify size above which files should be ignored]:file size (bytes)'
|
||||
"--no-config[don't load configuration files]"
|
||||
'(-0 --null)'{-0,--null}'[print NUL byte after file names]'
|
||||
'--path-separator=[specify path separator to use when printing file names]:separator'
|
||||
'(-q --quiet)'{-q,--quiet}'[suppress normal output]'
|
||||
'--regex-size-limit=[specify upper size limit of compiled regex]:regex size (bytes)'
|
||||
'*'{-u,--unrestricted}'[reduce level of "smart" searching]'
|
||||
'(: -)'{-V,--version}'[display version information]'
|
||||
'(-p --heading --no-heading --pretty)--vimgrep[show results in vim-compatible format]'
|
||||
'(-H --no-filename --with-filename)'{-H,--with-filename}'[display the file name for matches]'
|
||||
'(-w -x --line-regexp --word-regexp)'{-w,--word-regexp}'[only show matches surrounded by word boundaries]'
|
||||
'(-e -f --file --files --regexp --type-list)1: :_rg_pattern'
|
||||
'(--type-list)*:file:_files'
|
||||
'(-z --search-zip)'{-z,--search-zip}'[search in compressed files]'
|
||||
"(--stats)--stats[print stats about this search]"
|
||||
|
||||
+ operand # Operands
|
||||
'(--files --type-list file regexp)1: :_guard "^-*" pattern'
|
||||
'(--type-list)*: :_files'
|
||||
)
|
||||
|
||||
[[ ${_RG_COMPLETE_LIST_ARGS:-} == (1|t*|y*) ]] && {
|
||||
printf '%s\n' "${rg_args[@]}"
|
||||
# This is used with test_complete.sh to verify that there are no options
|
||||
# listed in the help output that aren't also defined here
|
||||
[[ $_RG_COMPLETE_LIST_ARGS == (1|t*|y*) ]] && {
|
||||
print -rl - $args
|
||||
return 0
|
||||
}
|
||||
|
||||
_arguments -s -S : "${rg_args[@]}" && return 0
|
||||
# Strip out argument groups where unsupported (see above)
|
||||
[[ $ZSH_VERSION == (4|5.<0-3>)(.*)# ]] &&
|
||||
args=( ${(@)args:#(#i)(+|[a-z0-9][a-z0-9_-]#|\([a-z0-9][a-z0-9_-]#\))} )
|
||||
|
||||
while (( $#state )); do
|
||||
case "${state[1]}" in
|
||||
colorspec)
|
||||
# @todo I don't like this because it allows you to do weird things like
|
||||
# `line:line:bg:`. Also, i would like the `compadd -q` behaviour
|
||||
[[ -prefix *:none: ]] && return 1
|
||||
[[ -prefix *:*:*:* ]] && return 1
|
||||
_arguments -C -s -S : $args && ret=0
|
||||
|
||||
_values -S ':' 'color/style type' \
|
||||
'column[specify coloring for column numbers]: :->attribute' \
|
||||
'line[specify coloring for line numbers]: :->attribute' \
|
||||
'match[specify coloring for match text]: :->attribute' \
|
||||
'path[specify color for file names]: :->attribute' && return 0
|
||||
case $state in
|
||||
colorspec)
|
||||
if [[ ${IPREFIX#--*=}$PREFIX == [^:]# ]]; then
|
||||
suf=( -qS: )
|
||||
tmp=(
|
||||
'column:specify coloring for column numbers'
|
||||
'line:specify coloring for line numbers'
|
||||
'match:specify coloring for match text'
|
||||
'path:specify coloring for file names'
|
||||
)
|
||||
descr='color/style type'
|
||||
elif [[ ${IPREFIX#--*=}$PREFIX == (column|line|match|path):[^:]# ]]; then
|
||||
suf=( -qS: )
|
||||
tmp=(
|
||||
'none:clear color/style for type'
|
||||
'bg:specify background color'
|
||||
'fg:specify foreground color'
|
||||
'style:specify text style'
|
||||
)
|
||||
descr='color/style attribute'
|
||||
elif [[ ${IPREFIX#--*=}$PREFIX == [^:]##:(bg|fg):[^:]# ]]; then
|
||||
tmp=( black blue green red cyan magenta yellow white )
|
||||
descr='color name or r,g,b'
|
||||
elif [[ ${IPREFIX#--*=}$PREFIX == [^:]##:style:[^:]# ]]; then
|
||||
tmp=( {,no}bold {,no}intense {,no}underline )
|
||||
descr='style name'
|
||||
else
|
||||
_message -e colorspec 'no more arguments'
|
||||
fi
|
||||
|
||||
[[ "${state}" == 'attribute' ]] &&
|
||||
_values -S ':' 'color/style attribute' \
|
||||
'none[clear color/style for type]' \
|
||||
'bg[specify background color]: :->color' \
|
||||
'fg[specify foreground color]: :->color' \
|
||||
'style[specify text style]: :->style' && return 0
|
||||
(( $#tmp )) && {
|
||||
compset -P '*:'
|
||||
_describe -t colorspec $descr tmp $suf && ret=0
|
||||
}
|
||||
;;
|
||||
|
||||
[[ "${state}" == 'color' ]] &&
|
||||
_values -S ':' 'color value' \
|
||||
black blue green red cyan magenta yellow white && return 0
|
||||
typespec)
|
||||
if compset -P '[^:]##:include:'; then
|
||||
_sequence -s , _rg_types && ret=0
|
||||
# @todo This bit in particular could be better, but it's a little
|
||||
# complex, and attempting to solve it seems to run us up against a crash
|
||||
# bug — zsh # 40362
|
||||
elif compset -P '[^:]##:'; then
|
||||
_message 'glob or include directive' && ret=1
|
||||
elif [[ ! -prefix *:* ]]; then
|
||||
_rg_types -qS : && ret=0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ "${state}" == 'style' ]] &&
|
||||
_values -S ':' 'style value' \
|
||||
bold nobold intense nointense underline nounderline && return 0
|
||||
;;
|
||||
|
||||
typespec)
|
||||
if compset -P '[^:]##:include:'; then
|
||||
_sequence -s ',' _rg_types && return 0
|
||||
# @todo This bit in particular could be better, but it's a little
|
||||
# complex, and attempting to solve it seems to run us up against a crash
|
||||
# bug — zsh # 40362
|
||||
elif compset -P '[^:]##:'; then
|
||||
_message 'glob or include directive' && return 1
|
||||
elif [[ ! -prefix *:* ]]; then
|
||||
_rg_types -qS ':' && return 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
shift state
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# zsh 5.1 refuses to complete options if a 'match-less' operand like our pattern
|
||||
# could be 'completed' instead. We can use _guard() to avoid this problem, but
|
||||
# it introduces another one: zsh won't print the message if we try to complete
|
||||
# the pattern after having passed `--`. To work around *that* problem, we can
|
||||
# use this function to bypass the _guard() when `--` is on the command line.
|
||||
# This is inaccurate (it'd get confused by e.g. `rg -e --`), but zsh's handling
|
||||
# of `--` isn't accurate anyway
|
||||
_rg_pattern() {
|
||||
if (( ${words[(I)--]} )); then
|
||||
_message 'pattern'
|
||||
else
|
||||
_guard '^-*' 'pattern'
|
||||
fi
|
||||
return ret
|
||||
}
|
||||
|
||||
# Complete encodings
|
||||
@@ -198,7 +312,7 @@ _rg_encodings() {
|
||||
x-user-defined auto
|
||||
)
|
||||
|
||||
_wanted rg-encodings expl 'encoding' compadd -a "${@}" - _encodings
|
||||
_wanted encodings expl encoding compadd -a "$@" - _encodings
|
||||
}
|
||||
|
||||
# Complete file types
|
||||
@@ -206,12 +320,12 @@ _rg_types() {
|
||||
local -a expl
|
||||
local -aU _types
|
||||
|
||||
_types=( ${${(f)"$( _call_program rg-types rg --type-list )"}%%:*} )
|
||||
_types=( ${(@)${(f)"$( _call_program types rg --type-list )"}%%:*} )
|
||||
|
||||
_wanted rg-types expl 'file type' compadd -a "${@}" - _types
|
||||
_wanted types expl 'file type' compadd -a "$@" - _types
|
||||
}
|
||||
|
||||
_rg "${@}"
|
||||
_rg "$@"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright (c) 2011 Github zsh-users - http://github.com/zsh-users
|
||||
|
@@ -18,6 +18,8 @@ Synopsis
|
||||
|
||||
*rg* [_OPTIONS_] *--type-list*
|
||||
|
||||
*command* | *rg* [_OPTIONS_] _PATTERN_
|
||||
|
||||
*rg* [_OPTIONS_] *--help*
|
||||
|
||||
*rg* [_OPTIONS_] *--version*
|
||||
@@ -98,6 +100,28 @@ would behave identically to the following command
|
||||
|
||||
rg --smart-case foo
|
||||
|
||||
another example is adding types
|
||||
|
||||
--type-add
|
||||
web:*.{html,css,js}*
|
||||
|
||||
would behave identically to the following command
|
||||
|
||||
rg --type-add 'web:*.{html,css,js}*' foo
|
||||
|
||||
same with using globs
|
||||
|
||||
--glob=!git/*
|
||||
|
||||
or
|
||||
|
||||
--glob
|
||||
!git/*
|
||||
|
||||
would behave identically to the following command
|
||||
|
||||
rg --glob '!git/*' foo
|
||||
|
||||
ripgrep also provides a flag, *--no-config*, that when present will suppress
|
||||
any and all support for configuration. This includes any future support
|
||||
for auto-loading configuration files from pre-determined paths.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "globset"
|
||||
version = "0.3.0" #:version
|
||||
version = "0.4.1" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Cross platform single glob and glob set matching. Glob set matching is the
|
||||
@@ -23,10 +23,10 @@ aho-corasick = "0.6.0"
|
||||
fnv = "1.0"
|
||||
log = "0.4"
|
||||
memchr = "2"
|
||||
regex = "0.2.9"
|
||||
regex = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
glob = "0.2"
|
||||
|
||||
[features]
|
||||
simd-accel = ["regex/simd-accel"]
|
||||
simd-accel = []
|
||||
|
@@ -275,6 +275,19 @@ impl Glob {
|
||||
}
|
||||
|
||||
/// Returns the regular expression string for this glob.
|
||||
///
|
||||
/// Note that regular expressions for globs are intended to be matched on
|
||||
/// arbitrary bytes (`&[u8]`) instead of Unicode strings (`&str`). In
|
||||
/// particular, globs are frequently used on file paths, where there is no
|
||||
/// general guarantee that file paths are themselves valid UTF-8. As a
|
||||
/// result, callers will need to ensure that they are using a regex API
|
||||
/// that can match on arbitrary bytes. For example, the
|
||||
/// [`regex`](https://crates.io/regex)
|
||||
/// crate's
|
||||
/// [`Regex`](https://docs.rs/regex/*/regex/struct.Regex.html)
|
||||
/// API is not suitable for this since it matches on `&str`, but its
|
||||
/// [`bytes::Regex`](https://docs.rs/regex/*/regex/bytes/struct.Regex.html)
|
||||
/// API is suitable for this.
|
||||
pub fn regex(&self) -> &str {
|
||||
&self.re
|
||||
}
|
||||
|
@@ -288,6 +288,14 @@ pub struct GlobSet {
|
||||
}
|
||||
|
||||
impl GlobSet {
|
||||
/// Create an empty `GlobSet`. An empty set matches nothing.
|
||||
pub fn empty() -> GlobSet {
|
||||
GlobSet {
|
||||
len: 0,
|
||||
strats: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this set is empty, and therefore matches nothing.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
@@ -441,6 +449,7 @@ impl GlobSet {
|
||||
|
||||
/// GlobSetBuilder builds a group of patterns that can be used to
|
||||
/// simultaneously match a file path.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GlobSetBuilder {
|
||||
pats: Vec<Glob>,
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grep"
|
||||
version = "0.1.8" #:version
|
||||
version = "0.1.9" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
Fast line oriented regex searching as a library.
|
||||
@@ -15,5 +15,5 @@ license = "Unlicense/MIT"
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
memchr = "2"
|
||||
regex = "0.2.9"
|
||||
regex-syntax = "0.5.3"
|
||||
regex = "1"
|
||||
regex-syntax = "0.6"
|
||||
|
@@ -67,6 +67,16 @@ impl LiteralSets {
|
||||
lit = req;
|
||||
}
|
||||
|
||||
// Special case: if we have any literals that are all whitespace,
|
||||
// then this is probably a failing of the literal detection since
|
||||
// whitespace is typically pretty common. In this case, don't bother
|
||||
// with inner literal scanning at all and just defer to the regex.
|
||||
let any_all_white = req_lits.iter()
|
||||
.any(|lit| lit.iter().all(|&b| (b as char).is_whitespace()));
|
||||
if any_all_white {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Special case: if we detected an alternation of inner required
|
||||
// literals and its longest literal is bigger than the longest
|
||||
// prefix/suffix, then choose the alternation. In practice, this
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ignore"
|
||||
version = "0.4.1" #:version
|
||||
version = "0.4.3" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
A fast library for efficiently matching ignore files such as `.gitignore`
|
||||
@@ -19,11 +19,11 @@ bench = false
|
||||
|
||||
[dependencies]
|
||||
crossbeam = "0.3"
|
||||
globset = { version = "0.3.0", path = "../globset" }
|
||||
globset = { version = "0.4.0", path = "../globset" }
|
||||
lazy_static = "1"
|
||||
log = "0.4"
|
||||
memchr = "2"
|
||||
regex = "0.2.9"
|
||||
regex = "1"
|
||||
same-file = "1"
|
||||
thread_local = "0.3.2"
|
||||
walkdir = "2"
|
||||
|
@@ -20,7 +20,7 @@ Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
ignore = "0.3"
|
||||
ignore = "0.4"
|
||||
```
|
||||
|
||||
and this to your crate root:
|
||||
|
@@ -65,6 +65,8 @@ struct IgnoreOptions {
|
||||
hidden: bool,
|
||||
/// Whether to read .ignore files.
|
||||
ignore: bool,
|
||||
/// Whether to respect any ignore files in parent directories.
|
||||
parents: bool,
|
||||
/// Whether to read git's global gitignore file.
|
||||
git_global: bool,
|
||||
/// Whether to read .gitignore files.
|
||||
@@ -151,6 +153,15 @@ impl Ignore {
|
||||
&self,
|
||||
path: P,
|
||||
) -> (Ignore, Option<Error>) {
|
||||
if !self.0.opts.parents
|
||||
&& !self.0.opts.git_ignore
|
||||
&& !self.0.opts.git_exclude
|
||||
&& !self.0.opts.git_global
|
||||
{
|
||||
// If we never need info from parent directories, then don't do
|
||||
// anything.
|
||||
return (self.clone(), None);
|
||||
}
|
||||
if !self.is_root() {
|
||||
panic!("Ignore::add_parents called on non-root matcher");
|
||||
}
|
||||
@@ -183,6 +194,7 @@ impl Ignore {
|
||||
errs.maybe_push(err);
|
||||
igtmp.is_absolute_parent = true;
|
||||
igtmp.absolute_base = Some(absolute_base.clone());
|
||||
igtmp.has_git = parent.join(".git").exists();
|
||||
ig = Ignore(Arc::new(igtmp));
|
||||
compiled.insert(parent.as_os_str().to_os_string(), ig.clone());
|
||||
}
|
||||
@@ -209,7 +221,9 @@ impl Ignore {
|
||||
fn add_child_path(&self, dir: &Path) -> (IgnoreInner, Option<Error>) {
|
||||
let mut errs = PartialErrorBuilder::default();
|
||||
let custom_ig_matcher =
|
||||
{
|
||||
if self.0.custom_ignore_filenames.is_empty() {
|
||||
Gitignore::empty()
|
||||
} else {
|
||||
let (m, err) =
|
||||
create_gitignore(&dir, &self.0.custom_ignore_filenames);
|
||||
errs.maybe_push(err);
|
||||
@@ -254,7 +268,7 @@ impl Ignore {
|
||||
git_global_matcher: self.0.git_global_matcher.clone(),
|
||||
git_ignore_matcher: gi_matcher,
|
||||
git_exclude_matcher: gi_exclude_matcher,
|
||||
has_git: dir.join(".git").is_dir(),
|
||||
has_git: dir.join(".git").exists(),
|
||||
opts: self.0.opts,
|
||||
};
|
||||
(ig, errs.into_error_option())
|
||||
@@ -264,9 +278,11 @@ impl Ignore {
|
||||
fn has_any_ignore_rules(&self) -> bool {
|
||||
let opts = self.0.opts;
|
||||
let has_custom_ignore_files = !self.0.custom_ignore_filenames.is_empty();
|
||||
let has_explicit_ignores = !self.0.explicit_ignores.is_empty();
|
||||
|
||||
opts.ignore || opts.git_global || opts.git_ignore
|
||||
|| opts.git_exclude || has_custom_ignore_files
|
||||
|| has_explicit_ignores
|
||||
}
|
||||
|
||||
/// Returns a match indicating whether the given file path should be
|
||||
@@ -329,6 +345,7 @@ impl Ignore {
|
||||
) -> Match<IgnoreMatch<'a>> {
|
||||
let (mut m_custom_ignore, mut m_ignore, mut m_gi, mut m_gi_exclude, mut m_explicit) =
|
||||
(Match::None, Match::None, Match::None, Match::None, Match::None);
|
||||
let any_git = self.parents().any(|ig| ig.0.has_git);
|
||||
let mut saw_git = false;
|
||||
for ig in self.parents().take_while(|ig| !ig.0.is_absolute_parent) {
|
||||
if m_custom_ignore.is_none() {
|
||||
@@ -341,42 +358,44 @@ impl Ignore {
|
||||
ig.0.ignore_matcher.matched(path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
if !saw_git && m_gi.is_none() {
|
||||
if any_git && !saw_git && m_gi.is_none() {
|
||||
m_gi =
|
||||
ig.0.git_ignore_matcher.matched(path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
if !saw_git && m_gi_exclude.is_none() {
|
||||
if any_git && !saw_git && m_gi_exclude.is_none() {
|
||||
m_gi_exclude =
|
||||
ig.0.git_exclude_matcher.matched(path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
saw_git = saw_git || ig.0.has_git;
|
||||
}
|
||||
if let Some(abs_parent_path) = self.absolute_base() {
|
||||
let path = abs_parent_path.join(path);
|
||||
for ig in self.parents().skip_while(|ig|!ig.0.is_absolute_parent) {
|
||||
if m_custom_ignore.is_none() {
|
||||
m_custom_ignore =
|
||||
ig.0.custom_ignore_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
if self.0.opts.parents {
|
||||
if let Some(abs_parent_path) = self.absolute_base() {
|
||||
let path = abs_parent_path.join(path);
|
||||
for ig in self.parents().skip_while(|ig|!ig.0.is_absolute_parent) {
|
||||
if m_custom_ignore.is_none() {
|
||||
m_custom_ignore =
|
||||
ig.0.custom_ignore_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
if m_ignore.is_none() {
|
||||
m_ignore =
|
||||
ig.0.ignore_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
if any_git && !saw_git && m_gi.is_none() {
|
||||
m_gi =
|
||||
ig.0.git_ignore_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
if any_git && !saw_git && m_gi_exclude.is_none() {
|
||||
m_gi_exclude =
|
||||
ig.0.git_exclude_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
saw_git = saw_git || ig.0.has_git;
|
||||
}
|
||||
if m_ignore.is_none() {
|
||||
m_ignore =
|
||||
ig.0.ignore_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
if !saw_git && m_gi.is_none() {
|
||||
m_gi =
|
||||
ig.0.git_ignore_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
if !saw_git && m_gi_exclude.is_none() {
|
||||
m_gi_exclude =
|
||||
ig.0.git_exclude_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
}
|
||||
saw_git = saw_git || ig.0.has_git;
|
||||
}
|
||||
}
|
||||
for gi in self.0.explicit_ignores.iter().rev() {
|
||||
@@ -385,8 +404,14 @@ impl Ignore {
|
||||
}
|
||||
m_explicit = gi.matched(&path, is_dir).map(IgnoreMatch::gitignore);
|
||||
}
|
||||
let m_global = self.0.git_global_matcher.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore);
|
||||
let m_global =
|
||||
if any_git {
|
||||
self.0.git_global_matcher
|
||||
.matched(&path, is_dir)
|
||||
.map(IgnoreMatch::gitignore)
|
||||
} else {
|
||||
Match::None
|
||||
};
|
||||
|
||||
m_custom_ignore.or(m_ignore).or(m_gi).or(m_gi_exclude).or(m_global).or(m_explicit)
|
||||
}
|
||||
@@ -454,6 +479,7 @@ impl IgnoreBuilder {
|
||||
opts: IgnoreOptions {
|
||||
hidden: true,
|
||||
ignore: true,
|
||||
parents: true,
|
||||
git_global: true,
|
||||
git_ignore: true,
|
||||
git_exclude: true,
|
||||
@@ -556,6 +582,17 @@ impl IgnoreBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables reading ignore files from parent directories.
|
||||
///
|
||||
/// If this is enabled, then .gitignore files in parent directories of each
|
||||
/// file path given are respected. Otherwise, they are ignored.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn parents(&mut self, yes: bool) -> &mut IgnoreBuilder {
|
||||
self.opts.parents = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a global gitignore matcher.
|
||||
///
|
||||
/// Its precedence is lower than both normal `.gitignore` files and
|
||||
@@ -677,6 +714,7 @@ mod tests {
|
||||
#[test]
|
||||
fn gitignore() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
mkdirp(td.path().join(".git"));
|
||||
wfile(td.path().join(".gitignore"), "foo\n!bar");
|
||||
|
||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
@@ -686,6 +724,18 @@ mod tests {
|
||||
assert!(ig.matched("baz", false).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gitignore_no_git() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
wfile(td.path().join(".gitignore"), "foo\n!bar");
|
||||
|
||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
assert!(err.is_none());
|
||||
assert!(ig.matched("foo", false).is_none());
|
||||
assert!(ig.matched("bar", false).is_none());
|
||||
assert!(ig.matched("baz", false).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
@@ -795,6 +845,7 @@ mod tests {
|
||||
#[test]
|
||||
fn errored_partial() {
|
||||
let td = TempDir::new("ignore-test-").unwrap();
|
||||
mkdirp(td.path().join(".git"));
|
||||
wfile(td.path().join(".gitignore"), "f**oo\nbar");
|
||||
|
||||
let (ig, err) = IgnoreBuilder::new().build().add_child(td.path());
|
||||
|
@@ -83,7 +83,7 @@ pub struct Gitignore {
|
||||
globs: Vec<Glob>,
|
||||
num_ignores: u64,
|
||||
num_whitelists: u64,
|
||||
matches: Arc<ThreadLocal<RefCell<Vec<usize>>>>,
|
||||
matches: Option<Arc<ThreadLocal<RefCell<Vec<usize>>>>>,
|
||||
}
|
||||
|
||||
impl Gitignore {
|
||||
@@ -143,7 +143,14 @@ impl Gitignore {
|
||||
///
|
||||
/// Its path is empty.
|
||||
pub fn empty() -> Gitignore {
|
||||
GitignoreBuilder::new("").build().unwrap()
|
||||
Gitignore {
|
||||
set: GlobSet::empty(),
|
||||
root: PathBuf::from(""),
|
||||
globs: vec![],
|
||||
num_ignores: 0,
|
||||
num_whitelists: 0,
|
||||
matches: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the directory containing this gitignore matcher.
|
||||
@@ -213,6 +220,11 @@ impl Gitignore {
|
||||
/// determined by a common suffix of the directory containing this
|
||||
/// gitignore) is stripped. If there is no common suffix/prefix overlap,
|
||||
/// then `path` is assumed to be relative to this matcher.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method panics if the given file path is not under the root path
|
||||
/// of this matcher.
|
||||
pub fn matched_path_or_any_parents<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
@@ -222,10 +234,8 @@ impl Gitignore {
|
||||
return Match::None;
|
||||
}
|
||||
let mut path = self.strip(path.as_ref());
|
||||
debug_assert!(
|
||||
!path.has_root(),
|
||||
"path is expect to be under the root"
|
||||
);
|
||||
assert!(!path.has_root(), "path is expected to be under the root");
|
||||
|
||||
match self.matched_stripped(path, is_dir) {
|
||||
Match::None => (), // walk up
|
||||
a_match => return a_match,
|
||||
@@ -249,7 +259,7 @@ impl Gitignore {
|
||||
return Match::None;
|
||||
}
|
||||
let path = path.as_ref();
|
||||
let _matches = self.matches.get_default();
|
||||
let _matches = self.matches.as_ref().unwrap().get_default();
|
||||
let mut matches = _matches.borrow_mut();
|
||||
let candidate = Candidate::new(path);
|
||||
self.set.matches_candidate_into(&candidate, &mut *matches);
|
||||
@@ -301,6 +311,7 @@ impl Gitignore {
|
||||
}
|
||||
|
||||
/// Builds a matcher for a single set of globs from a .gitignore file.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GitignoreBuilder {
|
||||
builder: GlobSetBuilder,
|
||||
root: PathBuf,
|
||||
@@ -344,7 +355,7 @@ impl GitignoreBuilder {
|
||||
globs: self.globs.clone(),
|
||||
num_ignores: nignore as u64,
|
||||
num_whitelists: nwhite as u64,
|
||||
matches: Arc::new(ThreadLocal::default()),
|
||||
matches: Some(Arc::new(ThreadLocal::default())),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -492,6 +503,8 @@ impl GitignoreBuilder {
|
||||
|
||||
/// Toggle whether the globs should be matched case insensitively or not.
|
||||
///
|
||||
/// When this option is changed, only globs added after the change will be affected.
|
||||
///
|
||||
/// This is disabled by default.
|
||||
pub fn case_insensitive(
|
||||
&mut self, yes: bool
|
||||
@@ -505,16 +518,27 @@ impl GitignoreBuilder {
|
||||
///
|
||||
/// Note that the file path returned may not exist.
|
||||
fn gitconfig_excludes_path() -> Option<PathBuf> {
|
||||
gitconfig_contents()
|
||||
.and_then(|data| parse_excludes_file(&data))
|
||||
.or_else(excludes_file_default)
|
||||
// git supports $HOME/.gitconfig and $XDG_CONFIG_DIR/git/config. Notably,
|
||||
// both can be active at the same time, where $HOME/.gitconfig takes
|
||||
// precedent. So if $HOME/.gitconfig defines a `core.excludesFile`, then
|
||||
// we're done.
|
||||
match gitconfig_home_contents().and_then(|x| parse_excludes_file(&x)) {
|
||||
Some(path) => return Some(path),
|
||||
None => {}
|
||||
}
|
||||
match gitconfig_xdg_contents().and_then(|x| parse_excludes_file(&x)) {
|
||||
Some(path) => return Some(path),
|
||||
None => {}
|
||||
}
|
||||
excludes_file_default()
|
||||
}
|
||||
|
||||
/// Returns the file contents of git's global config file, if one exists.
|
||||
fn gitconfig_contents() -> Option<Vec<u8>> {
|
||||
let home = match env::var_os("HOME") {
|
||||
/// Returns the file contents of git's global config file, if one exists, in
|
||||
/// the user's home directory.
|
||||
fn gitconfig_home_contents() -> Option<Vec<u8>> {
|
||||
let home = match home_dir() {
|
||||
None => return None,
|
||||
Some(home) => PathBuf::from(home),
|
||||
Some(home) => home,
|
||||
};
|
||||
let mut file = match File::open(home.join(".gitconfig")) {
|
||||
Err(_) => return None,
|
||||
@@ -524,13 +548,28 @@ fn gitconfig_contents() -> Option<Vec<u8>> {
|
||||
file.read_to_end(&mut contents).ok().map(|_| contents)
|
||||
}
|
||||
|
||||
/// Returns the file contents of git's global config file, if one exists, in
|
||||
/// the user's XDG_CONFIG_DIR directory.
|
||||
fn gitconfig_xdg_contents() -> Option<Vec<u8>> {
|
||||
let path = env::var_os("XDG_CONFIG_HOME")
|
||||
.and_then(|x| if x.is_empty() { None } else { Some(PathBuf::from(x)) })
|
||||
.or_else(|| home_dir().map(|p| p.join(".config")))
|
||||
.map(|x| x.join("git/config"));
|
||||
let mut file = match path.and_then(|p| File::open(p).ok()) {
|
||||
None => return None,
|
||||
Some(file) => io::BufReader::new(file),
|
||||
};
|
||||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents).ok().map(|_| contents)
|
||||
}
|
||||
|
||||
/// Returns the default file path for a global .gitignore file.
|
||||
///
|
||||
/// Specifically, this respects XDG_CONFIG_HOME.
|
||||
fn excludes_file_default() -> Option<PathBuf> {
|
||||
env::var_os("XDG_CONFIG_HOME")
|
||||
.and_then(|x| if x.is_empty() { None } else { Some(PathBuf::from(x)) })
|
||||
.or_else(|| env::home_dir().map(|p| p.join(".config")))
|
||||
.or_else(|| home_dir().map(|p| p.join(".config")))
|
||||
.map(|x| x.join("git/ignore"))
|
||||
}
|
||||
|
||||
@@ -542,7 +581,8 @@ fn parse_excludes_file(data: &[u8]) -> Option<PathBuf> {
|
||||
// a full INI parser. Yuck.
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(
|
||||
r"(?ium)^\s*excludesfile\s*=\s*(.+)\s*$").unwrap();
|
||||
r"(?im)^\s*excludesfile\s*=\s*(.+)\s*$"
|
||||
).unwrap();
|
||||
};
|
||||
let caps = match RE.captures(data) {
|
||||
None => return None,
|
||||
@@ -553,13 +593,22 @@ fn parse_excludes_file(data: &[u8]) -> Option<PathBuf> {
|
||||
|
||||
/// Expands ~ in file paths to the value of $HOME.
|
||||
fn expand_tilde(path: &str) -> String {
|
||||
let home = match env::var("HOME") {
|
||||
Err(_) => return path.to_string(),
|
||||
Ok(home) => home,
|
||||
let home = match home_dir() {
|
||||
None => return path.to_string(),
|
||||
Some(home) => home.to_string_lossy().into_owned(),
|
||||
};
|
||||
path.replace("~", &home)
|
||||
}
|
||||
|
||||
/// Returns the location of the user's home directory.
|
||||
fn home_dir() -> Option<PathBuf> {
|
||||
// We're fine with using env::home_dir for now. Its bugs are, IMO, pretty
|
||||
// minor corner cases. We should still probably eventually migrate to
|
||||
// the `dirs` crate to get a proper implementation.
|
||||
#![allow(deprecated)]
|
||||
env::home_dir()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
@@ -132,6 +132,44 @@ pub enum Error {
|
||||
InvalidDefinition,
|
||||
}
|
||||
|
||||
impl Clone for Error {
|
||||
fn clone(&self) -> Error {
|
||||
match *self {
|
||||
Error::Partial(ref errs) => Error::Partial(errs.clone()),
|
||||
Error::WithLineNumber { line, ref err } => {
|
||||
Error::WithLineNumber { line: line, err: err.clone() }
|
||||
}
|
||||
Error::WithPath { ref path, ref err } => {
|
||||
Error::WithPath { path: path.clone(), err: err.clone() }
|
||||
}
|
||||
Error::WithDepth { depth, ref err } => {
|
||||
Error::WithDepth { depth: depth, err: err.clone() }
|
||||
}
|
||||
Error::Loop { ref ancestor, ref child } => {
|
||||
Error::Loop {
|
||||
ancestor: ancestor.clone(),
|
||||
child: child.clone()
|
||||
}
|
||||
}
|
||||
Error::Io(ref err) => {
|
||||
match err.raw_os_error() {
|
||||
Some(e) => Error::Io(io::Error::from_raw_os_error(e)),
|
||||
None => {
|
||||
Error::Io(io::Error::new(err.kind(), err.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
Error::Glob { ref glob, ref err } => {
|
||||
Error::Glob { glob: glob.clone(), err: err.clone() }
|
||||
}
|
||||
Error::UnrecognizedFileType(ref err) => {
|
||||
Error::UnrecognizedFileType(err.clone())
|
||||
}
|
||||
Error::InvalidDefinition => Error::InvalidDefinition,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Returns true if this is a partial error.
|
||||
///
|
||||
|
@@ -140,6 +140,8 @@ impl OverrideBuilder {
|
||||
|
||||
/// Toggle whether the globs should be matched case insensitively or not.
|
||||
///
|
||||
/// When this option is changed, only globs added after the change will be affected.
|
||||
///
|
||||
/// This is disabled by default.
|
||||
pub fn case_insensitive(
|
||||
&mut self, yes: bool
|
||||
|
@@ -98,10 +98,13 @@ use {Error, Match};
|
||||
|
||||
const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
||||
("agda", &["*.agda", "*.lagda"]),
|
||||
("aidl", &["*.aidl"]),
|
||||
("amake", &["*.mk", "*.bp"]),
|
||||
("asciidoc", &["*.adoc", "*.asc", "*.asciidoc"]),
|
||||
("asm", &["*.asm", "*.s", "*.S"]),
|
||||
("avro", &["*.avdl", "*.avpr", "*.avsc"]),
|
||||
("awk", &["*.awk"]),
|
||||
("bazel", &["*.bzl", "WORKSPACE", "BUILD"]),
|
||||
("bitbake", &["*.bb", "*.bbappend", "*.bbclass", "*.conf", "*.inc"]),
|
||||
("bzip2", &["*.bz2"]),
|
||||
("c", &["*.c", "*.h", "*.H"]),
|
||||
@@ -131,6 +134,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
||||
("elixir", &["*.ex", "*.eex", "*.exs"]),
|
||||
("elm", &["*.elm"]),
|
||||
("erlang", &["*.erl", "*.hrl"]),
|
||||
("fidl", &["*.fidl"]),
|
||||
("fish", &["*.fish"]),
|
||||
("fortran", &[
|
||||
"*.f", "*.F", "*.f77", "*.F77", "*.pfo",
|
||||
@@ -144,8 +148,9 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
||||
("h", &["*.h", "*.hpp"]),
|
||||
("hbs", &["*.hbs"]),
|
||||
("haskell", &["*.hs", "*.lhs"]),
|
||||
("hs", &["*.hs", "*.lhs"]),
|
||||
("html", &["*.htm", "*.html", "*.ejs"]),
|
||||
("java", &["*.java"]),
|
||||
("java", &["*.java", "*.jsp"]),
|
||||
("jinja", &["*.j2", "*.jinja", "*.jinja2"]),
|
||||
("js", &[
|
||||
"*.js", "*.jsx", "*.vue",
|
||||
@@ -188,6 +193,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
||||
("log", &["*.log"]),
|
||||
("lua", &["*.lua"]),
|
||||
("lzma", &["*.lzma"]),
|
||||
("lz4", &["*.lz4"]),
|
||||
("m4", &["*.ac", "*.m4"]),
|
||||
("make", &[
|
||||
"gnumakefile", "Gnumakefile", "GNUmakefile",
|
||||
@@ -215,6 +221,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
||||
("pod", &["*.pod"]),
|
||||
("protobuf", &["*.proto"]),
|
||||
("ps", &["*.cdxml", "*.ps1", "*.ps1xml", "*.psd1", "*.psm1"]),
|
||||
("puppet", &["*.erb", "*.pp", "*.rb"]),
|
||||
("purs", &["*.purs"]),
|
||||
("py", &["*.py"]),
|
||||
("qmake", &["*.pro", "*.pri", "*.prf"]),
|
||||
@@ -275,6 +282,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
||||
("twig", &["*.twig"]),
|
||||
("vala", &["*.vala"]),
|
||||
("vb", &["*.vb"]),
|
||||
("verilog", &["*.v", "*.vh", "*.sv", "*.svh"]),
|
||||
("vhdl", &["*.vhd", "*.vhdl"]),
|
||||
("vim", &["*.vim"]),
|
||||
("vimscript", &["*.vim"]),
|
||||
|
@@ -24,7 +24,7 @@ use {Error, PartialErrorBuilder};
|
||||
///
|
||||
/// The error typically refers to a problem parsing ignore files in a
|
||||
/// particular directory.
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DirEntry {
|
||||
dent: DirEntryInner,
|
||||
err: Option<Error>,
|
||||
@@ -126,7 +126,7 @@ impl DirEntry {
|
||||
///
|
||||
/// Specifically, (3) has to essentially re-create the DirEntry implementation
|
||||
/// from WalkDir.
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
enum DirEntryInner {
|
||||
Stdin,
|
||||
Walkdir(walkdir::DirEntry),
|
||||
@@ -235,6 +235,7 @@ impl DirEntryInner {
|
||||
|
||||
/// DirEntryRaw is essentially copied from the walkdir crate so that we can
|
||||
/// build `DirEntry`s from whole cloth in the parallel iterator.
|
||||
#[derive(Clone)]
|
||||
struct DirEntryRaw {
|
||||
/// The path as reported by the `fs::ReadDir` iterator (even if it's a
|
||||
/// symbolic link).
|
||||
@@ -456,7 +457,6 @@ impl DirEntryRaw {
|
||||
pub struct WalkBuilder {
|
||||
paths: Vec<PathBuf>,
|
||||
ig_builder: IgnoreBuilder,
|
||||
parents: bool,
|
||||
max_depth: Option<usize>,
|
||||
max_filesize: Option<u64>,
|
||||
follow_links: bool,
|
||||
@@ -471,7 +471,6 @@ impl fmt::Debug for WalkBuilder {
|
||||
f.debug_struct("WalkBuilder")
|
||||
.field("paths", &self.paths)
|
||||
.field("ig_builder", &self.ig_builder)
|
||||
.field("parents", &self.parents)
|
||||
.field("max_depth", &self.max_depth)
|
||||
.field("max_filesize", &self.max_filesize)
|
||||
.field("follow_links", &self.follow_links)
|
||||
@@ -491,7 +490,6 @@ impl WalkBuilder {
|
||||
WalkBuilder {
|
||||
paths: vec![path.as_ref().to_path_buf()],
|
||||
ig_builder: IgnoreBuilder::new(),
|
||||
parents: true,
|
||||
max_depth: None,
|
||||
max_filesize: None,
|
||||
follow_links: false,
|
||||
@@ -530,7 +528,6 @@ impl WalkBuilder {
|
||||
ig_root: ig_root.clone(),
|
||||
ig: ig_root.clone(),
|
||||
max_filesize: self.max_filesize,
|
||||
parents: self.parents,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,7 +543,6 @@ impl WalkBuilder {
|
||||
max_depth: self.max_depth,
|
||||
max_filesize: self.max_filesize,
|
||||
follow_links: self.follow_links,
|
||||
parents: self.parents,
|
||||
threads: self.threads,
|
||||
}
|
||||
}
|
||||
@@ -678,14 +674,12 @@ impl WalkBuilder {
|
||||
|
||||
/// Enables reading ignore files from parent directories.
|
||||
///
|
||||
/// If this is enabled, then the parent directories of each file path given
|
||||
/// are traversed for ignore files (subject to the ignore settings on
|
||||
/// this builder). Note that file paths are canonicalized with respect to
|
||||
/// the current working directory in order to determine parent directories.
|
||||
/// If this is enabled, then .gitignore files in parent directories of each
|
||||
/// file path given are respected. Otherwise, they are ignored.
|
||||
///
|
||||
/// This is enabled by default.
|
||||
pub fn parents(&mut self, yes: bool) -> &mut WalkBuilder {
|
||||
self.parents = yes;
|
||||
self.ig_builder.parents(yes);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -764,7 +758,6 @@ pub struct Walk {
|
||||
ig_root: Ignore,
|
||||
ig: Ignore,
|
||||
max_filesize: Option<u64>,
|
||||
parents: bool,
|
||||
}
|
||||
|
||||
impl Walk {
|
||||
@@ -811,7 +804,7 @@ impl Iterator for Walk {
|
||||
}
|
||||
Some((path, Some(it))) => {
|
||||
self.it = Some(it);
|
||||
if self.parents && path_is_dir(&path) {
|
||||
if path_is_dir(&path) {
|
||||
let (ig, err) = self.ig_root.add_parents(path);
|
||||
self.ig = ig;
|
||||
if let Some(err) = err {
|
||||
@@ -947,7 +940,6 @@ impl WalkState {
|
||||
pub struct WalkParallel {
|
||||
paths: vec::IntoIter<PathBuf>,
|
||||
ig_root: Ignore,
|
||||
parents: bool,
|
||||
max_filesize: Option<u64>,
|
||||
max_depth: Option<usize>,
|
||||
follow_links: bool,
|
||||
@@ -1009,7 +1001,6 @@ impl WalkParallel {
|
||||
num_waiting: num_waiting.clone(),
|
||||
num_quitting: num_quitting.clone(),
|
||||
threads: threads,
|
||||
parents: self.parents,
|
||||
max_depth: self.max_depth,
|
||||
max_filesize: self.max_filesize,
|
||||
follow_links: self.follow_links,
|
||||
@@ -1125,9 +1116,6 @@ struct Worker {
|
||||
num_quitting: Arc<AtomicUsize>,
|
||||
/// The total number of workers.
|
||||
threads: usize,
|
||||
/// Whether to create ignore matchers for parents of caller specified
|
||||
/// directories.
|
||||
parents: bool,
|
||||
/// The maximum depth of directories to descend. A value of `0` means no
|
||||
/// descension at all.
|
||||
max_depth: Option<usize>,
|
||||
@@ -1155,12 +1143,10 @@ impl Worker {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if self.parents {
|
||||
if let Some(err) = work.add_parents() {
|
||||
if (self.f)(Err(err)).is_quit() {
|
||||
self.quit_now();
|
||||
return;
|
||||
}
|
||||
if let Some(err) = work.add_parents() {
|
||||
if (self.f)(Err(err)).is_quit() {
|
||||
self.quit_now();
|
||||
return;
|
||||
}
|
||||
}
|
||||
let readdir = match work.read_dir() {
|
||||
@@ -1644,6 +1630,7 @@ mod tests {
|
||||
#[test]
|
||||
fn gitignore() {
|
||||
let td = TempDir::new("walk-test-").unwrap();
|
||||
mkdirp(td.path().join(".git"));
|
||||
mkdirp(td.path().join("a"));
|
||||
wfile(td.path().join(".gitignore"), "foo");
|
||||
wfile(td.path().join("foo"), "");
|
||||
@@ -1672,9 +1659,28 @@ mod tests {
|
||||
assert_paths(td.path(), &builder, &["bar", "a", "a/bar"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_ignore_exclusive_use() {
|
||||
let td = TempDir::new("walk-test-").unwrap();
|
||||
let igpath = td.path().join(".not-an-ignore");
|
||||
mkdirp(td.path().join("a"));
|
||||
wfile(&igpath, "foo");
|
||||
wfile(td.path().join("foo"), "");
|
||||
wfile(td.path().join("a/foo"), "");
|
||||
wfile(td.path().join("bar"), "");
|
||||
wfile(td.path().join("a/bar"), "");
|
||||
|
||||
let mut builder = WalkBuilder::new(td.path());
|
||||
builder.standard_filters(false);
|
||||
assert!(builder.add_ignore(&igpath).is_none());
|
||||
assert_paths(td.path(), &builder,
|
||||
&[".not-an-ignore", "bar", "a", "a/bar"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gitignore_parent() {
|
||||
let td = TempDir::new("walk-test-").unwrap();
|
||||
mkdirp(td.path().join(".git"));
|
||||
mkdirp(td.path().join("a"));
|
||||
wfile(td.path().join(".gitignore"), "foo");
|
||||
wfile(td.path().join("a/foo"), "");
|
||||
|
@@ -1,13 +1,11 @@
|
||||
extern crate ignore;
|
||||
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use ignore::gitignore::{Gitignore, GitignoreBuilder};
|
||||
|
||||
|
||||
const IGNORE_FILE: &'static str = "tests/gitignore_matched_path_or_any_parents_tests.gitignore";
|
||||
|
||||
const IGNORE_FILE: &'static str =
|
||||
"tests/gitignore_matched_path_or_any_parents_tests.gitignore";
|
||||
|
||||
fn get_gitignore() -> Gitignore {
|
||||
let mut builder = GitignoreBuilder::new("ROOT");
|
||||
@@ -16,9 +14,8 @@ fn get_gitignore() -> Gitignore {
|
||||
builder.build().unwrap()
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "path is expect to be under the root")]
|
||||
#[should_panic(expected = "path is expected to be under the root")]
|
||||
fn test_path_should_be_under_root() {
|
||||
let gitignore = get_gitignore();
|
||||
let path = "/tmp/some_file";
|
||||
@@ -26,11 +23,12 @@ fn test_path_should_be_under_root() {
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_files_in_root() {
|
||||
let gitignore = get_gitignore();
|
||||
let m = |path: &str| gitignore.matched_path_or_any_parents(Path::new(path), false);
|
||||
let m = |path: &str| {
|
||||
gitignore.matched_path_or_any_parents(Path::new(path), false)
|
||||
};
|
||||
|
||||
// 0x
|
||||
assert!(m("ROOT/file_root_00").is_ignore());
|
||||
@@ -61,7 +59,9 @@ fn test_files_in_root() {
|
||||
#[test]
|
||||
fn test_files_in_deep() {
|
||||
let gitignore = get_gitignore();
|
||||
let m = |path: &str| gitignore.matched_path_or_any_parents(Path::new(path), false);
|
||||
let m = |path: &str| {
|
||||
gitignore.matched_path_or_any_parents(Path::new(path), false)
|
||||
};
|
||||
|
||||
// 0x
|
||||
assert!(m("ROOT/parent_dir/file_deep_00").is_ignore());
|
||||
@@ -92,8 +92,9 @@ fn test_files_in_deep() {
|
||||
#[test]
|
||||
fn test_dirs_in_root() {
|
||||
let gitignore = get_gitignore();
|
||||
let m =
|
||||
|path: &str, is_dir: bool| gitignore.matched_path_or_any_parents(Path::new(path), is_dir);
|
||||
let m = |path: &str, is_dir: bool| {
|
||||
gitignore.matched_path_or_any_parents(Path::new(path), is_dir)
|
||||
};
|
||||
|
||||
// 00
|
||||
assert!(m("ROOT/dir_root_00", true).is_ignore());
|
||||
@@ -196,20 +197,25 @@ fn test_dirs_in_root() {
|
||||
#[test]
|
||||
fn test_dirs_in_deep() {
|
||||
let gitignore = get_gitignore();
|
||||
let m =
|
||||
|path: &str, is_dir: bool| gitignore.matched_path_or_any_parents(Path::new(path), is_dir);
|
||||
let m = |path: &str, is_dir: bool| {
|
||||
gitignore.matched_path_or_any_parents(Path::new(path), is_dir)
|
||||
};
|
||||
|
||||
// 00
|
||||
assert!(m("ROOT/parent_dir/dir_deep_00", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_00/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_00/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_00/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_00/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 01
|
||||
assert!(m("ROOT/parent_dir/dir_deep_01", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_01/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_01/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_01/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_01/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 02
|
||||
assert!(m("ROOT/parent_dir/dir_deep_02", true).is_none());
|
||||
@@ -251,47 +257,67 @@ fn test_dirs_in_deep() {
|
||||
assert!(m("ROOT/parent_dir/dir_deep_20", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_20/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_20/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_20/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_20/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 21
|
||||
assert!(m("ROOT/parent_dir/dir_deep_21", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_21/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_21/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_21/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_21/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 22
|
||||
assert!(m("ROOT/parent_dir/dir_deep_22", true).is_none()); // dir itself doesn't match
|
||||
// dir itself doesn't match
|
||||
assert!(m("ROOT/parent_dir/dir_deep_22", true).is_none());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_22/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_22/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_22/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_22/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 23
|
||||
assert!(m("ROOT/parent_dir/dir_deep_23", true).is_none()); // dir itself doesn't match
|
||||
// dir itself doesn't match
|
||||
assert!(m("ROOT/parent_dir/dir_deep_23", true).is_none());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_23/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_23/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_23/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_23/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 30
|
||||
assert!(m("ROOT/parent_dir/dir_deep_30", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_30/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_30/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_30/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_30/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 31
|
||||
assert!(m("ROOT/parent_dir/dir_deep_31", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_31/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_31/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_31/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_31/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 32
|
||||
assert!(m("ROOT/parent_dir/dir_deep_32", true).is_none()); // dir itself doesn't match
|
||||
// dir itself doesn't match
|
||||
assert!(m("ROOT/parent_dir/dir_deep_32", true).is_none());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_32/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_32/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_32/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_32/child_dir/file", false).is_ignore()
|
||||
);
|
||||
|
||||
// 33
|
||||
assert!(m("ROOT/parent_dir/dir_deep_33", true).is_none()); // dir itself doesn't match
|
||||
// dir itself doesn't match
|
||||
assert!(m("ROOT/parent_dir/dir_deep_33", true).is_none());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_33/file", false).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_33/child_dir", true).is_ignore());
|
||||
assert!(m("ROOT/parent_dir/dir_deep_33/child_dir/file", false).is_ignore());
|
||||
assert!(
|
||||
m("ROOT/parent_dir/dir_deep_33/child_dir/file", false).is_ignore()
|
||||
);
|
||||
}
|
||||
|
@@ -11,4 +11,5 @@ parts:
|
||||
source: .
|
||||
apps:
|
||||
rg:
|
||||
command: env PATH=$SNAP/bin:$PATH rg
|
||||
adapter: none
|
||||
command: ./bin/rg
|
||||
|
202
src/app.rs
202
src/app.rs
@@ -33,7 +33,8 @@ const USAGE: &str = "
|
||||
rg [OPTIONS] PATTERN [PATH ...]
|
||||
rg [OPTIONS] [-e PATTERN ...] [-f PATTERNFILE ...] [PATH ...]
|
||||
rg [OPTIONS] --files [PATH ...]
|
||||
rg [OPTIONS] --type-list";
|
||||
rg [OPTIONS] --type-list
|
||||
command | rg [OPTIONS] PATTERN";
|
||||
|
||||
const TEMPLATE: &str = "\
|
||||
{bin} {version}
|
||||
@@ -475,23 +476,6 @@ impl RGArg {
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Indicate that any value given to this argument should be a valid
|
||||
/// line number width. A valid line number width cannot start with `0`
|
||||
/// to maintain compatibility with future improvements that add support
|
||||
/// for padding character specifies.
|
||||
fn line_number_width(mut self) -> RGArg {
|
||||
self.claparg = self.claparg.validator(|val| {
|
||||
if val.starts_with("0") {
|
||||
Err(String::from(
|
||||
"Custom padding characters are currently not supported. \
|
||||
Please enter only a numeric value."))
|
||||
} else {
|
||||
val.parse::<usize>().map(|_| ()).map_err(|err| err.to_string())
|
||||
}
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// We add an extra space to long descriptions so that a black line is inserted
|
||||
@@ -535,15 +519,16 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
||||
flag_ignore_file(&mut args);
|
||||
flag_invert_match(&mut args);
|
||||
flag_line_number(&mut args);
|
||||
flag_line_number_width(&mut args);
|
||||
flag_line_regexp(&mut args);
|
||||
flag_max_columns(&mut args);
|
||||
flag_max_count(&mut args);
|
||||
flag_max_depth(&mut args);
|
||||
flag_max_filesize(&mut args);
|
||||
flag_maxdepth(&mut args);
|
||||
flag_mmap(&mut args);
|
||||
flag_no_config(&mut args);
|
||||
flag_no_ignore(&mut args);
|
||||
flag_no_ignore_global(&mut args);
|
||||
flag_no_ignore_messages(&mut args);
|
||||
flag_no_ignore_parent(&mut args);
|
||||
flag_no_ignore_vcs(&mut args);
|
||||
flag_no_messages(&mut args);
|
||||
@@ -551,6 +536,7 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
|
||||
flag_only_matching(&mut args);
|
||||
flag_path_separator(&mut args);
|
||||
flag_passthru(&mut args);
|
||||
flag_pre(&mut args);
|
||||
flag_pretty(&mut args);
|
||||
flag_quiet(&mut args);
|
||||
flag_regex_size_limit(&mut args);
|
||||
@@ -740,9 +726,17 @@ fn flag_column(args: &mut Vec<RGArg>) {
|
||||
Show column numbers (1-based). This only shows the column numbers for the first
|
||||
match on each line. This does not try to account for Unicode. One byte is equal
|
||||
to one column. This implies --line-number.
|
||||
|
||||
This flag can be disabled with --no-column.
|
||||
");
|
||||
let arg = RGArg::switch("column")
|
||||
.help(SHORT).long_help(LONG);
|
||||
.help(SHORT).long_help(LONG)
|
||||
.overrides("no-column");
|
||||
args.push(arg);
|
||||
|
||||
let arg = RGArg::switch("no-column")
|
||||
.hidden()
|
||||
.overrides("column");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
@@ -918,9 +912,17 @@ fn flag_fixed_strings(args: &mut Vec<RGArg>) {
|
||||
Treat the pattern as a literal string instead of a regular expression. When
|
||||
this flag is used, special regular expression meta characters such as .(){}*+
|
||||
do not need to be escaped.
|
||||
|
||||
This flag can be disabled with --no-fixed-strings.
|
||||
");
|
||||
let arg = RGArg::switch("fixed-strings").short("F")
|
||||
.help(SHORT).long_help(LONG);
|
||||
.help(SHORT).long_help(LONG)
|
||||
.overrides("no-fixed-strings");
|
||||
args.push(arg);
|
||||
|
||||
let arg = RGArg::switch("no-fixed-strings")
|
||||
.hidden()
|
||||
.overrides("fixed-strings");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
@@ -1091,18 +1093,6 @@ terminal.
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_line_number_width(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Left pad line numbers up to NUM width.";
|
||||
const LONG: &str = long!("\
|
||||
Left pad line numbers up to NUM width. Space is used as the default padding
|
||||
character. This has no effect if --no-line-number is enabled.
|
||||
");
|
||||
let arg = RGArg::flag("line-number-width", "NUM")
|
||||
.help(SHORT).long_help(LONG)
|
||||
.line_number_width();
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_line_regexp(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Only show matches surrounded by line boundaries.";
|
||||
const LONG: &str = long!("\
|
||||
@@ -1143,6 +1133,23 @@ Limit the number of matching lines per file searched to NUM.
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_max_depth(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Descend at most NUM directories.";
|
||||
const LONG: &str = long!("\
|
||||
Limit the depth of directory traversal to NUM levels beyond the paths given. A
|
||||
value of zero only searches the explicitly given paths themselves.
|
||||
|
||||
For example, 'rg --max-depth 0 dir/' is a no-op because dir/ will not be
|
||||
descended into. 'rg --max-depth 1 dir/' will search only the direct children of
|
||||
'dir'.
|
||||
");
|
||||
let arg = RGArg::flag("max-depth", "NUM")
|
||||
.help(SHORT).long_help(LONG)
|
||||
.alias("maxdepth")
|
||||
.number();
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_max_filesize(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Ignore files larger than NUM in size.";
|
||||
const LONG: &str = long!("\
|
||||
@@ -1159,22 +1166,6 @@ Examples: --max-filesize 50K or --max-filesize 80M
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_maxdepth(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Descend at most NUM directories.";
|
||||
const LONG: &str = long!("\
|
||||
Limit the depth of directory traversal to NUM levels beyond the paths given. A
|
||||
value of zero only searches the explicitly given paths themselves.
|
||||
|
||||
For example, 'rg --maxdepth 0 dir/' is a no-op because dir/ will not be
|
||||
descended into. 'rg --maxdepth 1 dir/' will search only the direct children of
|
||||
'dir'.
|
||||
");
|
||||
let arg = RGArg::flag("maxdepth", "NUM")
|
||||
.help(SHORT).long_help(LONG)
|
||||
.number();
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_mmap(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Search using memory maps when possible.";
|
||||
const LONG: &str = long!("\
|
||||
@@ -1240,6 +1231,45 @@ This flag can be disabled with the --ignore flag.
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_no_ignore_global(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Don't respect global ignore files.";
|
||||
const LONG: &str = long!("\
|
||||
Don't respect ignore files that come from \"global\" sources such as git's
|
||||
`core.excludesFile` configuration option (which defaults to
|
||||
`$HOME/.config/git/ignore).
|
||||
|
||||
This flag can be disabled with the --ignore-global flag.
|
||||
");
|
||||
let arg = RGArg::switch("no-ignore-global")
|
||||
.help(SHORT).long_help(LONG)
|
||||
.overrides("ignore-global");
|
||||
args.push(arg);
|
||||
|
||||
let arg = RGArg::switch("ignore-global")
|
||||
.hidden()
|
||||
.overrides("no-ignore-global");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_no_ignore_messages(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Suppress gitignore parse error messages.";
|
||||
const LONG: &str = long!("\
|
||||
Suppresses all error messages related to parsing ignore files such as .ignore
|
||||
or .gitignore.
|
||||
|
||||
This flag can be disabled with the --ignore-messages flag.
|
||||
");
|
||||
let arg = RGArg::switch("no-ignore-messages")
|
||||
.help(SHORT).long_help(LONG)
|
||||
.overrides("ignore-messages");
|
||||
args.push(arg);
|
||||
|
||||
let arg = RGArg::switch("ignore-messages")
|
||||
.hidden()
|
||||
.overrides("no-ignore-messages");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_no_ignore_parent(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Don't respect ignore files in parent directories.";
|
||||
const LONG: &str = long!("\
|
||||
@@ -1279,10 +1309,10 @@ This flag can be disabled with the --ignore-vcs flag.
|
||||
}
|
||||
|
||||
fn flag_no_messages(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Suppress all error messages.";
|
||||
const SHORT: &str = "Suppress some error messages.";
|
||||
const LONG: &str = long!("\
|
||||
Suppress all error messages. This provides the same behavior as redirecting
|
||||
stderr to /dev/null on Unix-like systems.
|
||||
Suppress all error messages related to opening and reading files. Error
|
||||
messages related to the syntax of the pattern given are still shown.
|
||||
|
||||
This flag can be disabled with the --messages flag.
|
||||
");
|
||||
@@ -1372,6 +1402,9 @@ fn flag_quiet(args: &mut Vec<RGArg>) {
|
||||
Do not print anything to stdout. If a match is found in a file, then ripgrep
|
||||
will stop searching. This is useful when ripgrep is used only for its exit
|
||||
code (which will be an error if no matches are found).
|
||||
|
||||
When --files is used, then ripgrep will stop finding files after finding the
|
||||
first file that matches all ignore rules.
|
||||
");
|
||||
let arg = RGArg::switch("quiet").short("q")
|
||||
.help(SHORT).long_help(LONG);
|
||||
@@ -1438,7 +1471,7 @@ This flag can be used with the -o/--only-matching flag.
|
||||
fn flag_search_zip(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Search in compressed files.";
|
||||
const LONG: &str = long!("\
|
||||
Search in compressed files. Currently gz, bz2, xz, and lzma files are
|
||||
Search in compressed files. Currently gz, bz2, xz, lzma and lz4 files are
|
||||
supported. This option expects the decompression binaries to be available in
|
||||
your PATH.
|
||||
|
||||
@@ -1446,7 +1479,8 @@ This flag can be disabled with --no-search-zip.
|
||||
");
|
||||
let arg = RGArg::switch("search-zip").short("z")
|
||||
.help(SHORT).long_help(LONG)
|
||||
.overrides("no-search-zip");
|
||||
.overrides("no-search-zip")
|
||||
.overrides("pre");
|
||||
args.push(arg);
|
||||
|
||||
let arg = RGArg::switch("no-search-zip")
|
||||
@@ -1455,6 +1489,64 @@ This flag can be disabled with --no-search-zip.
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_pre(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "search outputs of COMMAND FILE for each FILE";
|
||||
const LONG: &str = long!("\
|
||||
For each input FILE, search the standard output of COMMAND FILE rather than the
|
||||
contents of FILE. This option expects the COMMAND program to either be an
|
||||
absolute path or to be available in your PATH. Either an empty string COMMAND
|
||||
or the `--no-pre` flag will disable this behavior.
|
||||
|
||||
WARNING: When this flag is set, ripgrep will unconditionally spawn a
|
||||
process for every file that is searched. Therefore, this can incur an
|
||||
unnecessarily large performance penalty if you don't otherwise need the
|
||||
flexibility offered by this flag.
|
||||
|
||||
A preprocessor is not run when ripgrep is searching stdin.
|
||||
|
||||
When searching over sets of files that may require one of several decoders
|
||||
as preprocessors, COMMAND should be a wrapper program or script which first
|
||||
classifies FILE based on magic numbers/content or based on the FILE name and
|
||||
then dispatches to an appropriate preprocessor. Each COMMAND also has its
|
||||
standard input connected to FILE for convenience.
|
||||
|
||||
For example, a shell script for COMMAND might look like:
|
||||
|
||||
case \"$1\" in
|
||||
*.pdf)
|
||||
exec pdftotext \"$1\" -
|
||||
;;
|
||||
*)
|
||||
case $(file \"$1\") in
|
||||
*Zstandard*)
|
||||
exec pzstd -cdq
|
||||
;;
|
||||
*)
|
||||
exec cat
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
The above script uses `pdftotext` to convert a PDF file to plain text. For
|
||||
all other files, the script uses the `file` utility to sniff the type of the
|
||||
file based on its contents. If it is a compressed file in the Zstandard format,
|
||||
then `pzstd` is used to decompress the contents to stdout.
|
||||
|
||||
This overrides the -z/--search-zip flag.
|
||||
");
|
||||
let arg = RGArg::flag("pre", "COMMAND")
|
||||
.help(SHORT).long_help(LONG)
|
||||
.overrides("no-pre")
|
||||
.overrides("search-zip");
|
||||
args.push(arg);
|
||||
|
||||
let arg = RGArg::switch("no-pre")
|
||||
.hidden()
|
||||
.overrides("pre");
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
fn flag_smart_case(args: &mut Vec<RGArg>) {
|
||||
const SHORT: &str = "Smart case search.";
|
||||
const LONG: &str = long!("\
|
||||
|
101
src/args.rs
101
src/args.rs
@@ -22,7 +22,7 @@ use ignore::overrides::{Override, OverrideBuilder};
|
||||
use ignore::types::{FileTypeDef, Types, TypesBuilder};
|
||||
use ignore;
|
||||
use printer::{ColorSpecs, Printer};
|
||||
use unescape::unescape;
|
||||
use unescape::{escape, unescape};
|
||||
use worker::{Worker, WorkerBuilder};
|
||||
|
||||
use config;
|
||||
@@ -36,6 +36,7 @@ pub struct Args {
|
||||
after_context: usize,
|
||||
before_context: usize,
|
||||
byte_offset: bool,
|
||||
can_match: bool,
|
||||
color_choice: termcolor::ColorChoice,
|
||||
colors: ColorSpecs,
|
||||
column: bool,
|
||||
@@ -56,13 +57,14 @@ pub struct Args {
|
||||
invert_match: bool,
|
||||
line_number: bool,
|
||||
line_per_match: bool,
|
||||
line_number_width: Option<usize>,
|
||||
max_columns: Option<usize>,
|
||||
max_count: Option<u64>,
|
||||
max_depth: Option<usize>,
|
||||
max_filesize: Option<u64>,
|
||||
maxdepth: Option<usize>,
|
||||
mmap: bool,
|
||||
no_ignore: bool,
|
||||
no_ignore_global: bool,
|
||||
no_ignore_messages: bool,
|
||||
no_ignore_parent: bool,
|
||||
no_ignore_vcs: bool,
|
||||
no_messages: bool,
|
||||
@@ -80,6 +82,7 @@ pub struct Args {
|
||||
types: Types,
|
||||
with_filename: bool,
|
||||
search_zip_files: bool,
|
||||
preprocessor: Option<PathBuf>,
|
||||
stats: bool
|
||||
}
|
||||
|
||||
@@ -190,8 +193,7 @@ impl Args {
|
||||
.only_matching(self.only_matching)
|
||||
.path_separator(self.path_separator)
|
||||
.with_filename(self.with_filename)
|
||||
.max_columns(self.max_columns)
|
||||
.line_number_width(self.line_number_width);
|
||||
.max_columns(self.max_columns);
|
||||
if let Some(ref rep) = self.replace {
|
||||
p = p.replace(rep.clone());
|
||||
}
|
||||
@@ -219,18 +221,22 @@ impl Args {
|
||||
|
||||
/// Returns true if the given arguments are known to never produce a match.
|
||||
pub fn never_match(&self) -> bool {
|
||||
self.max_count == Some(0)
|
||||
!self.can_match || self.max_count == Some(0)
|
||||
}
|
||||
|
||||
|
||||
/// Returns whether ripgrep should track stats for this run
|
||||
pub fn stats(&self) -> bool {
|
||||
self.stats
|
||||
}
|
||||
|
||||
/// Create a new writer for single-threaded searching with color support.
|
||||
pub fn stdout(&self) -> termcolor::StandardStream {
|
||||
termcolor::StandardStream::stdout(self.color_choice)
|
||||
pub fn stdout(&self) -> Box<termcolor::WriteColor> {
|
||||
if atty::is(atty::Stream::Stdout) {
|
||||
Box::new(termcolor::StandardStream::stdout(self.color_choice))
|
||||
} else {
|
||||
Box::new(
|
||||
termcolor::BufferedStandardStream::stdout(self.color_choice))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a handle to stdout for filtering search.
|
||||
@@ -284,6 +290,7 @@ impl Args {
|
||||
.quiet(self.quiet)
|
||||
.text(self.text)
|
||||
.search_zip_files(self.search_zip_files)
|
||||
.preprocessor(self.preprocessor.clone())
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -308,6 +315,12 @@ impl Args {
|
||||
self.no_messages
|
||||
}
|
||||
|
||||
/// Returns true if error messages associated with parsing .ignore or
|
||||
/// .gitignore files should be suppressed.
|
||||
pub fn no_ignore_messages(&self) -> bool {
|
||||
self.no_ignore_messages
|
||||
}
|
||||
|
||||
/// Create a new recursive directory iterator over the paths in argv.
|
||||
pub fn walker(&self) -> ignore::Walk {
|
||||
self.walker_builder().build()
|
||||
@@ -327,7 +340,7 @@ impl Args {
|
||||
}
|
||||
for path in &self.ignore_files {
|
||||
if let Some(err) = wd.add_ignore(path) {
|
||||
if !self.no_messages {
|
||||
if !self.no_messages && !self.no_ignore_messages {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
}
|
||||
@@ -335,11 +348,13 @@ impl Args {
|
||||
|
||||
wd.follow_links(self.follow);
|
||||
wd.hidden(!self.hidden);
|
||||
wd.max_depth(self.maxdepth);
|
||||
wd.max_depth(self.max_depth);
|
||||
wd.max_filesize(self.max_filesize);
|
||||
wd.overrides(self.glob_overrides.clone());
|
||||
wd.types(self.types.clone());
|
||||
wd.git_global(!self.no_ignore && !self.no_ignore_vcs);
|
||||
wd.git_global(
|
||||
!self.no_ignore && !self.no_ignore_vcs && !self.no_ignore_global
|
||||
);
|
||||
wd.git_ignore(!self.no_ignore && !self.no_ignore_vcs);
|
||||
wd.git_exclude(!self.no_ignore && !self.no_ignore_vcs);
|
||||
wd.ignore(!self.no_ignore);
|
||||
@@ -370,11 +385,13 @@ impl<'a> ArgMatches<'a> {
|
||||
let (before_context, after_context) = self.contexts()?;
|
||||
let (count, count_matches) = self.counts();
|
||||
let quiet = self.is_present("quiet");
|
||||
let (grep, can_match) = self.grep()?;
|
||||
let args = Args {
|
||||
paths: paths,
|
||||
after_context: after_context,
|
||||
before_context: before_context,
|
||||
byte_offset: self.is_present("byte-offset"),
|
||||
can_match: can_match,
|
||||
color_choice: self.color_choice(),
|
||||
colors: self.color_specs()?,
|
||||
column: self.column(),
|
||||
@@ -388,20 +405,21 @@ impl<'a> ArgMatches<'a> {
|
||||
files: self.is_present("files"),
|
||||
follow: self.is_present("follow"),
|
||||
glob_overrides: self.overrides()?,
|
||||
grep: self.grep()?,
|
||||
grep: grep,
|
||||
heading: self.heading(),
|
||||
hidden: self.hidden(),
|
||||
ignore_files: self.ignore_files(),
|
||||
invert_match: self.is_present("invert-match"),
|
||||
line_number: line_number,
|
||||
line_number_width: try!(self.usize_of("line-number-width")),
|
||||
line_per_match: self.is_present("vimgrep"),
|
||||
max_columns: self.usize_of_nonzero("max-columns")?,
|
||||
max_count: self.usize_of("max-count")?.map(|n| n as u64),
|
||||
max_depth: self.usize_of("max-depth")?,
|
||||
max_filesize: self.max_filesize()?,
|
||||
maxdepth: self.usize_of("maxdepth")?,
|
||||
mmap: mmap,
|
||||
no_ignore: self.no_ignore(),
|
||||
no_ignore_global: self.no_ignore_global(),
|
||||
no_ignore_messages: self.is_present("no-ignore-messages"),
|
||||
no_ignore_parent: self.no_ignore_parent(),
|
||||
no_ignore_vcs: self.no_ignore_vcs(),
|
||||
no_messages: self.is_present("no-messages"),
|
||||
@@ -419,6 +437,7 @@ impl<'a> ArgMatches<'a> {
|
||||
types: self.types()?,
|
||||
with_filename: with_filename,
|
||||
search_zip_files: self.is_present("search-zip"),
|
||||
preprocessor: self.preprocessor(),
|
||||
stats: self.stats()
|
||||
};
|
||||
if args.mmap {
|
||||
@@ -474,17 +493,6 @@ impl<'a> ArgMatches<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the pattern that should be used for searching.
|
||||
///
|
||||
/// If multiple -e/--regexp flags are given, then they are all collapsed
|
||||
/// into one pattern.
|
||||
///
|
||||
/// If any part of the pattern isn't valid UTF-8, then an error is
|
||||
/// returned.
|
||||
fn pattern(&self) -> Result<String> {
|
||||
Ok(self.patterns()?.join("|"))
|
||||
}
|
||||
|
||||
/// Get a sequence of all available patterns from the command line.
|
||||
/// This includes reading the -e/--regexp and -f/--file flags.
|
||||
///
|
||||
@@ -534,8 +542,6 @@ impl<'a> ArgMatches<'a> {
|
||||
// match first, and we wouldn't get colours in the output
|
||||
if self.is_present("passthru") && !self.is_present("count") {
|
||||
pats.push("^".to_string())
|
||||
} else if pats.is_empty() {
|
||||
pats.push(self.empty_pattern())
|
||||
}
|
||||
Ok(pats)
|
||||
}
|
||||
@@ -681,6 +687,9 @@ impl<'a> ArgMatches<'a> {
|
||||
|
||||
/// Returns true if and only if column numbers should be shown.
|
||||
fn column(&self) -> bool {
|
||||
if self.is_present("no-column") {
|
||||
return false;
|
||||
}
|
||||
self.is_present("column") || self.is_present("vimgrep")
|
||||
}
|
||||
|
||||
@@ -709,6 +718,19 @@ impl<'a> ArgMatches<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the preprocessor command
|
||||
fn preprocessor(&self) -> Option<PathBuf> {
|
||||
if let Some(path) = self.value_of_os("pre") {
|
||||
if path.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Path::new(path).to_path_buf())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the unescaped path separator in UTF-8 bytes.
|
||||
fn path_separator(&self) -> Result<Option<u8>> {
|
||||
match self.value_of_lossy("path-separator") {
|
||||
@@ -720,7 +742,12 @@ impl<'a> ArgMatches<'a> {
|
||||
} else if sep.len() > 1 {
|
||||
Err(From::from(format!(
|
||||
"A path separator must be exactly one byte, but \
|
||||
the given separator is {} bytes.", sep.len())))
|
||||
the given separator is {} bytes: {}\n\
|
||||
In some shells on Windows '/' is automatically \
|
||||
expanded. Use '//' instead.",
|
||||
sep.len(),
|
||||
escape(&sep),
|
||||
)))
|
||||
} else {
|
||||
Ok(Some(sep[0]))
|
||||
}
|
||||
@@ -863,7 +890,10 @@ impl<'a> ArgMatches<'a> {
|
||||
///
|
||||
/// If there was a problem extracting the pattern from the command line
|
||||
/// flags, then an error is returned.
|
||||
fn grep(&self) -> Result<Grep> {
|
||||
///
|
||||
/// If no match can ever occur, then `false` is returned. Otherwise,
|
||||
/// `true` is returned.
|
||||
fn grep(&self) -> Result<(Grep, bool)> {
|
||||
let smart =
|
||||
self.is_present("smart-case")
|
||||
&& !self.is_present("ignore-case")
|
||||
@@ -871,7 +901,9 @@ impl<'a> ArgMatches<'a> {
|
||||
let casei =
|
||||
self.is_present("ignore-case")
|
||||
&& !self.is_present("case-sensitive");
|
||||
let mut gb = GrepBuilder::new(&self.pattern()?)
|
||||
let pats = self.patterns()?;
|
||||
let ok = !pats.is_empty();
|
||||
let mut gb = GrepBuilder::new(&pats.join("|"))
|
||||
.case_smart(smart)
|
||||
.case_insensitive(casei)
|
||||
.line_terminator(b'\n');
|
||||
@@ -882,7 +914,7 @@ impl<'a> ArgMatches<'a> {
|
||||
if let Some(limit) = self.regex_size_limit()? {
|
||||
gb = gb.size_limit(limit);
|
||||
}
|
||||
Ok(gb.build()?)
|
||||
Ok((gb.build()?, ok))
|
||||
}
|
||||
|
||||
/// Builds the set of glob overrides from the command line flags.
|
||||
@@ -985,6 +1017,11 @@ impl<'a> ArgMatches<'a> {
|
||||
|| self.occurrences_of("unrestricted") >= 1
|
||||
}
|
||||
|
||||
/// Returns true if global ignore files should be ignored.
|
||||
fn no_ignore_global(&self) -> bool {
|
||||
self.is_present("no-ignore-global") || self.no_ignore()
|
||||
}
|
||||
|
||||
/// Returns true if parent ignore files should be ignored.
|
||||
fn no_ignore_parent(&self) -> bool {
|
||||
self.is_present("no-ignore-parent") || self.no_ignore()
|
||||
|
456
src/decoder.rs
456
src/decoder.rs
@@ -1,456 +0,0 @@
|
||||
use std::cmp;
|
||||
use std::io::{self, Read};
|
||||
|
||||
use encoding_rs::{Decoder, Encoding, UTF_8};
|
||||
|
||||
/// A BOM is at least 2 bytes and at most 3 bytes.
|
||||
///
|
||||
/// If fewer than 2 bytes are available to be read at the beginning of a
|
||||
/// reader, then a BOM is `None`.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
struct Bom {
|
||||
bytes: [u8; 3],
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl Bom {
|
||||
fn as_slice(&self) -> &[u8] {
|
||||
&self.bytes[0..self.len]
|
||||
}
|
||||
|
||||
fn decoder(&self) -> Option<Decoder> {
|
||||
let bom = self.as_slice();
|
||||
if bom.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
if let Some((enc, _)) = Encoding::for_bom(bom) {
|
||||
if enc != UTF_8 {
|
||||
return Some(enc.new_decoder_with_bom_removal());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// `BomPeeker` wraps `R` and satisfies the `io::Read` interface while also
|
||||
/// providing a peek at the BOM if one exists. Peeking at the BOM does not
|
||||
/// advance the reader.
|
||||
struct BomPeeker<R> {
|
||||
rdr: R,
|
||||
bom: Option<Bom>,
|
||||
nread: usize,
|
||||
}
|
||||
|
||||
impl<R: io::Read> BomPeeker<R> {
|
||||
/// Create a new BomPeeker.
|
||||
///
|
||||
/// The first three bytes can be read using the `peek_bom` method, but
|
||||
/// will not advance the reader.
|
||||
fn new(rdr: R) -> BomPeeker<R> {
|
||||
BomPeeker { rdr: rdr, bom: None, nread: 0 }
|
||||
}
|
||||
|
||||
/// Peek at the first three bytes of the underlying reader.
|
||||
///
|
||||
/// This does not advance the reader provided by `BomPeeker`.
|
||||
///
|
||||
/// If the underlying reader does not have at least two bytes available,
|
||||
/// then `None` is returned.
|
||||
fn peek_bom(&mut self) -> io::Result<Bom> {
|
||||
if let Some(bom) = self.bom {
|
||||
return Ok(bom);
|
||||
}
|
||||
self.bom = Some(Bom { bytes: [0; 3], len: 0 });
|
||||
let mut buf = [0u8; 3];
|
||||
let bom_len = read_full(&mut self.rdr, &mut buf)?;
|
||||
self.bom = Some(Bom { bytes: buf, len: bom_len });
|
||||
Ok(self.bom.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: io::Read> io::Read for BomPeeker<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
if self.nread < 3 {
|
||||
let bom = self.peek_bom()?;
|
||||
let bom = bom.as_slice();
|
||||
if self.nread < bom.len() {
|
||||
let rest = &bom[self.nread..];
|
||||
let len = cmp::min(buf.len(), rest.len());
|
||||
buf[..len].copy_from_slice(&rest[..len]);
|
||||
self.nread += len;
|
||||
return Ok(len);
|
||||
}
|
||||
}
|
||||
let nread = self.rdr.read(buf)?;
|
||||
self.nread += nread;
|
||||
Ok(nread)
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `io::Read::read_exact`, except it never returns `UnexpectedEof` and
|
||||
/// instead returns the number of bytes read if EOF is seen before filling
|
||||
/// `buf`.
|
||||
fn read_full<R: io::Read>(
|
||||
mut rdr: R,
|
||||
mut buf: &mut [u8],
|
||||
) -> io::Result<usize> {
|
||||
let mut nread = 0;
|
||||
while !buf.is_empty() {
|
||||
match rdr.read(buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
nread += n;
|
||||
let tmp = buf;
|
||||
buf = &mut tmp[n..];
|
||||
}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(nread)
|
||||
}
|
||||
|
||||
/// A reader that transcodes to UTF-8. The source encoding is determined by
|
||||
/// inspecting the BOM from the stream read from `R`, if one exists. If a
|
||||
/// UTF-16 BOM exists, then the source stream is transcoded to UTF-8 with
|
||||
/// invalid UTF-16 sequences translated to the Unicode replacement character.
|
||||
/// In all other cases, the underlying reader is passed through unchanged.
|
||||
///
|
||||
/// `R` is the type of the underlying reader and `B` is the type of an internal
|
||||
/// buffer used to store the results of transcoding.
|
||||
///
|
||||
/// Note that not all methods on `io::Read` work with this implementation.
|
||||
/// For example, the `bytes` adapter method attempts to read a single byte at
|
||||
/// a time, but this implementation requires a buffer of size at least `4`. If
|
||||
/// a buffer of size less than 4 is given, then an error is returned.
|
||||
pub struct DecodeReader<R, B> {
|
||||
/// The underlying reader, wrapped in a peeker for reading a BOM if one
|
||||
/// exists.
|
||||
rdr: BomPeeker<R>,
|
||||
/// The internal buffer to store transcoded bytes before they are read by
|
||||
/// callers.
|
||||
buf: B,
|
||||
/// The current position in `buf`. Subsequent reads start here.
|
||||
pos: usize,
|
||||
/// The number of transcoded bytes in `buf`. Subsequent reads end here.
|
||||
buflen: usize,
|
||||
/// Whether this is the first read or not (in which we inspect the BOM).
|
||||
first: bool,
|
||||
/// Whether a "last" read has occurred. After this point, EOF will always
|
||||
/// be returned.
|
||||
last: bool,
|
||||
/// The underlying text decoder derived from the BOM, if one exists.
|
||||
decoder: Option<Decoder>,
|
||||
}
|
||||
|
||||
impl<R: io::Read, B: AsMut<[u8]>> DecodeReader<R, B> {
|
||||
/// Create a new transcoder that converts a source stream to valid UTF-8.
|
||||
///
|
||||
/// If an encoding is specified, then it is used to transcode `rdr` to
|
||||
/// UTF-8. Otherwise, if no encoding is specified, and if a UTF-16 BOM is
|
||||
/// found, then the corresponding UTF-16 encoding is used to transcode
|
||||
/// `rdr` to UTF-8. In all other cases, `rdr` is assumed to be at least
|
||||
/// ASCII-compatible and passed through untouched.
|
||||
///
|
||||
/// Errors in the encoding of `rdr` are handled with the Unicode
|
||||
/// replacement character. If no encoding of `rdr` is specified, then
|
||||
/// errors are not handled.
|
||||
pub fn new(
|
||||
rdr: R,
|
||||
buf: B,
|
||||
enc: Option<&'static Encoding>,
|
||||
) -> DecodeReader<R, B> {
|
||||
DecodeReader {
|
||||
rdr: BomPeeker::new(rdr),
|
||||
buf: buf,
|
||||
buflen: 0,
|
||||
pos: 0,
|
||||
first: enc.is_none(),
|
||||
last: false,
|
||||
decoder: enc.map(|enc| enc.new_decoder_with_bom_removal()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fill the internal buffer from the underlying reader.
|
||||
///
|
||||
/// If there are unread bytes in the internal buffer, then we move them
|
||||
/// to the beginning of the internal buffer and fill the remainder.
|
||||
///
|
||||
/// If the internal buffer is too small to read additional bytes, then an
|
||||
/// error is returned.
|
||||
#[inline(always)] // massive perf benefit (???)
|
||||
fn fill(&mut self) -> io::Result<()> {
|
||||
if self.pos < self.buflen {
|
||||
if self.buflen >= self.buf.as_mut().len() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"DecodeReader: internal buffer exhausted"));
|
||||
}
|
||||
let newlen = self.buflen - self.pos;
|
||||
let mut tmp = Vec::with_capacity(newlen);
|
||||
tmp.extend_from_slice(&self.buf.as_mut()[self.pos..self.buflen]);
|
||||
self.buf.as_mut()[..newlen].copy_from_slice(&tmp);
|
||||
self.buflen = newlen;
|
||||
} else {
|
||||
self.buflen = 0;
|
||||
}
|
||||
self.pos = 0;
|
||||
self.buflen +=
|
||||
self.rdr.read(&mut self.buf.as_mut()[self.buflen..])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transcode the inner stream to UTF-8 in `buf`. This assumes that there
|
||||
/// is a decoder capable of transcoding the inner stream to UTF-8. This
|
||||
/// returns the number of bytes written to `buf`.
|
||||
///
|
||||
/// When this function returns, exactly one of the following things will
|
||||
/// be true:
|
||||
///
|
||||
/// 1. A non-zero number of bytes were written to `buf`.
|
||||
/// 2. The underlying reader reached EOF.
|
||||
/// 3. An error is returned: the internal buffer ran out of room.
|
||||
/// 4. An I/O error occurred.
|
||||
///
|
||||
/// Note that `buf` must have at least 4 bytes of space.
|
||||
fn transcode(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
assert!(buf.len() >= 4);
|
||||
if self.last {
|
||||
return Ok(0);
|
||||
}
|
||||
if self.pos >= self.buflen {
|
||||
self.fill()?;
|
||||
}
|
||||
let mut nwrite = 0;
|
||||
loop {
|
||||
let (_, nin, nout, _) =
|
||||
self.decoder.as_mut().unwrap().decode_to_utf8(
|
||||
&self.buf.as_mut()[self.pos..self.buflen], buf, false);
|
||||
self.pos += nin;
|
||||
nwrite += nout;
|
||||
// If we've written at least one byte to the caller-provided
|
||||
// buffer, then our mission is complete.
|
||||
if nwrite > 0 {
|
||||
break;
|
||||
}
|
||||
// Otherwise, we know that our internal buffer has insufficient
|
||||
// data to transcode at least one char, so we attempt to refill it.
|
||||
self.fill()?;
|
||||
// Quit on EOF.
|
||||
if self.buflen == 0 {
|
||||
self.pos = 0;
|
||||
self.last = true;
|
||||
let (_, _, nout, _) =
|
||||
self.decoder.as_mut().unwrap().decode_to_utf8(
|
||||
&[], buf, true);
|
||||
return Ok(nout);
|
||||
}
|
||||
}
|
||||
Ok(nwrite)
|
||||
}
|
||||
|
||||
#[inline(never)] // impacts perf...
|
||||
fn detect(&mut self) -> io::Result<()> {
|
||||
let bom = self.rdr.peek_bom()?;
|
||||
self.decoder = bom.decoder();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: io::Read, B: AsMut<[u8]>> io::Read for DecodeReader<R, B> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
if self.first {
|
||||
self.first = false;
|
||||
self.detect()?;
|
||||
}
|
||||
if self.decoder.is_none() {
|
||||
return self.rdr.read(buf);
|
||||
}
|
||||
// When decoding UTF-8, we need at least 4 bytes of space to guarantee
|
||||
// that we can decode at least one codepoint. If we don't have it, we
|
||||
// can either return `0` for the number of bytes read or return an
|
||||
// error. Since `0` would be interpreted as a possibly premature EOF,
|
||||
// we opt for an error.
|
||||
if buf.len() < 4 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"DecodeReader: byte buffer must have length at least 4"));
|
||||
}
|
||||
self.transcode(buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Read;
|
||||
|
||||
use encoding_rs::Encoding;
|
||||
|
||||
use super::{Bom, BomPeeker, DecodeReader};
|
||||
|
||||
fn read_to_string<R: Read>(mut rdr: R) -> String {
|
||||
let mut s = String::new();
|
||||
rdr.read_to_string(&mut s).unwrap();
|
||||
s
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peeker_empty() {
|
||||
let buf = [];
|
||||
let mut peeker = BomPeeker::new(&buf[..]);
|
||||
assert_eq!(Bom { bytes: [0; 3], len: 0}, peeker.peek_bom().unwrap());
|
||||
|
||||
let mut tmp = [0; 100];
|
||||
assert_eq!(0, peeker.read(&mut tmp).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peeker_one() {
|
||||
let buf = [1];
|
||||
let mut peeker = BomPeeker::new(&buf[..]);
|
||||
assert_eq!(
|
||||
Bom { bytes: [1, 0, 0], len: 1},
|
||||
peeker.peek_bom().unwrap());
|
||||
|
||||
let mut tmp = [0; 100];
|
||||
assert_eq!(1, peeker.read(&mut tmp).unwrap());
|
||||
assert_eq!(1, tmp[0]);
|
||||
assert_eq!(0, peeker.read(&mut tmp).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peeker_two() {
|
||||
let buf = [1, 2];
|
||||
let mut peeker = BomPeeker::new(&buf[..]);
|
||||
assert_eq!(
|
||||
Bom { bytes: [1, 2, 0], len: 2},
|
||||
peeker.peek_bom().unwrap());
|
||||
|
||||
let mut tmp = [0; 100];
|
||||
assert_eq!(2, peeker.read(&mut tmp).unwrap());
|
||||
assert_eq!(1, tmp[0]);
|
||||
assert_eq!(2, tmp[1]);
|
||||
assert_eq!(0, peeker.read(&mut tmp).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peeker_three() {
|
||||
let buf = [1, 2, 3];
|
||||
let mut peeker = BomPeeker::new(&buf[..]);
|
||||
assert_eq!(
|
||||
Bom { bytes: [1, 2, 3], len: 3},
|
||||
peeker.peek_bom().unwrap());
|
||||
|
||||
let mut tmp = [0; 100];
|
||||
assert_eq!(3, peeker.read(&mut tmp).unwrap());
|
||||
assert_eq!(1, tmp[0]);
|
||||
assert_eq!(2, tmp[1]);
|
||||
assert_eq!(3, tmp[2]);
|
||||
assert_eq!(0, peeker.read(&mut tmp).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peeker_four() {
|
||||
let buf = [1, 2, 3, 4];
|
||||
let mut peeker = BomPeeker::new(&buf[..]);
|
||||
assert_eq!(
|
||||
Bom { bytes: [1, 2, 3], len: 3},
|
||||
peeker.peek_bom().unwrap());
|
||||
|
||||
let mut tmp = [0; 100];
|
||||
assert_eq!(3, peeker.read(&mut tmp).unwrap());
|
||||
assert_eq!(1, tmp[0]);
|
||||
assert_eq!(2, tmp[1]);
|
||||
assert_eq!(3, tmp[2]);
|
||||
assert_eq!(1, peeker.read(&mut tmp).unwrap());
|
||||
assert_eq!(4, tmp[0]);
|
||||
assert_eq!(0, peeker.read(&mut tmp).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peeker_one_at_a_time() {
|
||||
let buf = [1, 2, 3, 4];
|
||||
let mut peeker = BomPeeker::new(&buf[..]);
|
||||
|
||||
let mut tmp = [0; 1];
|
||||
assert_eq!(0, peeker.read(&mut tmp[..0]).unwrap());
|
||||
assert_eq!(0, tmp[0]);
|
||||
assert_eq!(1, peeker.read(&mut tmp).unwrap());
|
||||
assert_eq!(1, tmp[0]);
|
||||
assert_eq!(1, peeker.read(&mut tmp).unwrap());
|
||||
assert_eq!(2, tmp[0]);
|
||||
assert_eq!(1, peeker.read(&mut tmp).unwrap());
|
||||
assert_eq!(3, tmp[0]);
|
||||
assert_eq!(1, peeker.read(&mut tmp).unwrap());
|
||||
assert_eq!(4, tmp[0]);
|
||||
}
|
||||
|
||||
// In cases where all we have is a bom, we expect the bytes to be
|
||||
// passed through unchanged.
|
||||
#[test]
|
||||
fn trans_utf16_bom() {
|
||||
let srcbuf = vec![0xFF, 0xFE];
|
||||
let mut dstbuf = vec![0; 8 * (1<<10)];
|
||||
let mut rdr = DecodeReader::new(&*srcbuf, vec![0; 8 * (1<<10)], None);
|
||||
let n = rdr.read(&mut dstbuf).unwrap();
|
||||
assert_eq!(&*srcbuf, &dstbuf[..n]);
|
||||
|
||||
let srcbuf = vec![0xFE, 0xFF];
|
||||
let mut rdr = DecodeReader::new(&*srcbuf, vec![0; 8 * (1<<10)], None);
|
||||
let n = rdr.read(&mut dstbuf).unwrap();
|
||||
assert_eq!(&*srcbuf, &dstbuf[..n]);
|
||||
|
||||
let srcbuf = vec![0xEF, 0xBB, 0xBF];
|
||||
let mut rdr = DecodeReader::new(&*srcbuf, vec![0; 8 * (1<<10)], None);
|
||||
let n = rdr.read(&mut dstbuf).unwrap();
|
||||
assert_eq!(&*srcbuf, &dstbuf[..n]);
|
||||
}
|
||||
|
||||
// Test basic UTF-16 decoding.
|
||||
#[test]
|
||||
fn trans_utf16_basic() {
|
||||
let srcbuf = vec![0xFF, 0xFE, 0x61, 0x00];
|
||||
let mut rdr = DecodeReader::new(&*srcbuf, vec![0; 8 * (1<<10)], None);
|
||||
assert_eq!("a", read_to_string(&mut rdr));
|
||||
|
||||
let srcbuf = vec![0xFE, 0xFF, 0x00, 0x61];
|
||||
let mut rdr = DecodeReader::new(&*srcbuf, vec![0; 8 * (1<<10)], None);
|
||||
assert_eq!("a", read_to_string(&mut rdr));
|
||||
}
|
||||
|
||||
// Test incomplete UTF-16 decoding. This ensures we see a replacement char
|
||||
// if the stream ends with an unpaired code unit.
|
||||
#[test]
|
||||
fn trans_utf16_incomplete() {
|
||||
let srcbuf = vec![0xFF, 0xFE, 0x61, 0x00, 0x00];
|
||||
let mut rdr = DecodeReader::new(&*srcbuf, vec![0; 8 * (1<<10)], None);
|
||||
assert_eq!("a\u{FFFD}", read_to_string(&mut rdr));
|
||||
}
|
||||
|
||||
macro_rules! test_trans_simple {
|
||||
($name:ident, $enc:expr, $srcbytes:expr, $dst:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let srcbuf = &$srcbytes[..];
|
||||
let enc = Encoding::for_label($enc.as_bytes());
|
||||
let mut rdr = DecodeReader::new(
|
||||
&*srcbuf, vec![0; 8 * (1<<10)], enc);
|
||||
assert_eq!($dst, read_to_string(&mut rdr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This isn't exhaustive obviously, but it lets us test base level support.
|
||||
test_trans_simple!(trans_simple_auto, "does not exist", b"\xD0\x96", "Ж");
|
||||
test_trans_simple!(trans_simple_utf8, "utf-8", b"\xD0\x96", "Ж");
|
||||
test_trans_simple!(trans_simple_utf16le, "utf-16le", b"\x16\x04", "Ж");
|
||||
test_trans_simple!(trans_simple_utf16be, "utf-16be", b"\x04\x16", "Ж");
|
||||
test_trans_simple!(trans_simple_chinese, "chinese", b"\xA7\xA8", "Ж");
|
||||
test_trans_simple!(trans_simple_korean, "korean", b"\xAC\xA8", "Ж");
|
||||
test_trans_simple!(
|
||||
trans_simple_big5_hkscs, "big5-hkscs", b"\xC7\xFA", "Ж");
|
||||
test_trans_simple!(trans_simple_gbk, "gbk", b"\xA7\xA8", "Ж");
|
||||
test_trans_simple!(trans_simple_sjis, "sjis", b"\x84\x47", "Ж");
|
||||
test_trans_simple!(trans_simple_eucjp, "euc-jp", b"\xA7\xA8", "Ж");
|
||||
test_trans_simple!(trans_simple_latin1, "latin1", b"\xA9", "©");
|
||||
}
|
@@ -44,6 +44,7 @@ lazy_static! {
|
||||
m.insert("gz", DecompressionCommand::new("gzip", ARGS));
|
||||
m.insert("bz2", DecompressionCommand::new("bzip2", ARGS));
|
||||
m.insert("xz", DecompressionCommand::new("xz", ARGS));
|
||||
m.insert("lz4", DecompressionCommand::new("lz4", ARGS));
|
||||
|
||||
const LZMA_ARGS: &[&str] = &["--format=lzma", "-d", "-c"];
|
||||
m.insert("lzma", DecompressionCommand::new("xz", LZMA_ARGS));
|
||||
@@ -55,6 +56,7 @@ lazy_static! {
|
||||
builder.add(Glob::new("*.gz").unwrap());
|
||||
builder.add(Glob::new("*.bz2").unwrap());
|
||||
builder.add(Glob::new("*.xz").unwrap());
|
||||
builder.add(Glob::new("*.lz4").unwrap());
|
||||
builder.add(Glob::new("*.lzma").unwrap());
|
||||
builder.build().unwrap()
|
||||
};
|
||||
@@ -63,6 +65,7 @@ lazy_static! {
|
||||
builder.add(Glob::new("*.tar.gz").unwrap());
|
||||
builder.add(Glob::new("*.tar.xz").unwrap());
|
||||
builder.add(Glob::new("*.tar.bz2").unwrap());
|
||||
builder.add(Glob::new("*.tar.lz4").unwrap());
|
||||
builder.add(Glob::new("*.tgz").unwrap());
|
||||
builder.add(Glob::new("*.txz").unwrap());
|
||||
builder.add(Glob::new("*.tbz2").unwrap());
|
||||
@@ -89,10 +92,6 @@ impl DecompressionReader {
|
||||
/// If there is any error in spawning the decompression command, then
|
||||
/// return `None`, after outputting any necessary debug or error messages.
|
||||
pub fn from_path(path: &Path) -> Option<DecompressionReader> {
|
||||
if is_tar_archive(path) {
|
||||
debug!("{}: skipping tar archive", path.display());
|
||||
return None;
|
||||
}
|
||||
let extension = match path.extension().and_then(OsStr::to_str) {
|
||||
Some(extension) => extension,
|
||||
None => {
|
||||
|
33
src/main.rs
33
src/main.rs
@@ -3,6 +3,7 @@ extern crate bytecount;
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
extern crate encoding_rs;
|
||||
extern crate encoding_rs_io;
|
||||
extern crate globset;
|
||||
extern crate grep;
|
||||
extern crate ignore;
|
||||
@@ -41,8 +42,8 @@ macro_rules! errored {
|
||||
mod app;
|
||||
mod args;
|
||||
mod config;
|
||||
mod decoder;
|
||||
mod decompressor;
|
||||
mod preprocessor;
|
||||
mod logger;
|
||||
mod pathutil;
|
||||
mod printer;
|
||||
@@ -60,7 +61,7 @@ fn main() {
|
||||
Ok(_) => process::exit(0),
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
process::exit(1);
|
||||
process::exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,6 +114,7 @@ fn run_parallel(args: &Arc<Args>) -> Result<u64> {
|
||||
args.stdout_handle(),
|
||||
args.files(),
|
||||
args.no_messages(),
|
||||
args.no_ignore_messages(),
|
||||
) {
|
||||
None => return Continue,
|
||||
Some(dent) => dent,
|
||||
@@ -164,8 +166,7 @@ fn run_parallel(args: &Arc<Args>) -> Result<u64> {
|
||||
|
||||
fn run_one_thread(args: &Arc<Args>) -> Result<u64> {
|
||||
let start_time = Instant::now();
|
||||
let stdout = args.stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let mut stdout = args.stdout();
|
||||
let mut worker = args.worker();
|
||||
let mut paths_searched: u64 = 0;
|
||||
let mut match_line_count = 0;
|
||||
@@ -176,6 +177,7 @@ fn run_one_thread(args: &Arc<Args>) -> Result<u64> {
|
||||
args.stdout_handle(),
|
||||
args.files(),
|
||||
args.no_messages(),
|
||||
args.no_ignore_messages(),
|
||||
) {
|
||||
None => continue,
|
||||
Some(dent) => dent,
|
||||
@@ -221,8 +223,7 @@ fn run_files_parallel(args: Arc<Args>) -> Result<u64> {
|
||||
let print_args = Arc::clone(&args);
|
||||
let (tx, rx) = mpsc::channel::<ignore::DirEntry>();
|
||||
let print_thread = thread::spawn(move || {
|
||||
let stdout = print_args.stdout();
|
||||
let mut printer = print_args.printer(stdout.lock());
|
||||
let mut printer = print_args.printer(print_args.stdout());
|
||||
let mut file_count = 0;
|
||||
for dent in rx.iter() {
|
||||
if !print_args.quiet() {
|
||||
@@ -241,8 +242,12 @@ fn run_files_parallel(args: Arc<Args>) -> Result<u64> {
|
||||
args.stdout_handle(),
|
||||
args.files(),
|
||||
args.no_messages(),
|
||||
args.no_ignore_messages(),
|
||||
) {
|
||||
tx.send(dent).unwrap();
|
||||
if args.quiet() {
|
||||
return ignore::WalkState::Quit
|
||||
}
|
||||
}
|
||||
ignore::WalkState::Continue
|
||||
})
|
||||
@@ -251,8 +256,7 @@ fn run_files_parallel(args: Arc<Args>) -> Result<u64> {
|
||||
}
|
||||
|
||||
fn run_files_one_thread(args: &Arc<Args>) -> Result<u64> {
|
||||
let stdout = args.stdout();
|
||||
let mut printer = args.printer(stdout.lock());
|
||||
let mut printer = args.printer(args.stdout());
|
||||
let mut file_count = 0;
|
||||
for result in args.walker() {
|
||||
let dent = match get_or_log_dir_entry(
|
||||
@@ -260,21 +264,23 @@ fn run_files_one_thread(args: &Arc<Args>) -> Result<u64> {
|
||||
args.stdout_handle(),
|
||||
args.files(),
|
||||
args.no_messages(),
|
||||
args.no_ignore_messages(),
|
||||
) {
|
||||
None => continue,
|
||||
Some(dent) => dent,
|
||||
};
|
||||
if !args.quiet() {
|
||||
file_count += 1;
|
||||
if args.quiet() {
|
||||
break;
|
||||
} else {
|
||||
printer.path(dent.path());
|
||||
}
|
||||
file_count += 1;
|
||||
}
|
||||
Ok(file_count)
|
||||
}
|
||||
|
||||
fn run_types(args: &Arc<Args>) -> Result<u64> {
|
||||
let stdout = args.stdout();
|
||||
let mut printer = args.printer(stdout.lock());
|
||||
let mut printer = args.printer(args.stdout());
|
||||
let mut ty_count = 0;
|
||||
for def in args.type_defs() {
|
||||
printer.type_def(def);
|
||||
@@ -288,6 +294,7 @@ fn get_or_log_dir_entry(
|
||||
stdout_handle: Option<&same_file::Handle>,
|
||||
files_only: bool,
|
||||
no_messages: bool,
|
||||
no_ignore_messages: bool,
|
||||
) -> Option<ignore::DirEntry> {
|
||||
match result {
|
||||
Err(err) => {
|
||||
@@ -298,7 +305,7 @@ fn get_or_log_dir_entry(
|
||||
}
|
||||
Ok(dent) => {
|
||||
if let Some(err) = dent.error() {
|
||||
if !no_messages {
|
||||
if !no_messages && !no_ignore_messages {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
}
|
||||
|
92
src/preprocessor.rs
Normal file
92
src/preprocessor.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{self, Stdio};
|
||||
|
||||
use Result;
|
||||
|
||||
/// PreprocessorReader provides an `io::Read` impl to read kids output.
|
||||
#[derive(Debug)]
|
||||
pub struct PreprocessorReader {
|
||||
cmd: PathBuf,
|
||||
path: PathBuf,
|
||||
child: process::Child,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl PreprocessorReader {
|
||||
/// Returns a handle to the stdout of the spawned preprocessor process for
|
||||
/// `path`, which can be directly searched in the worker. When the returned
|
||||
/// value is exhausted, the underlying process is reaped. If the underlying
|
||||
/// process fails, then its stderr is read and converted into a normal
|
||||
/// io::Error.
|
||||
///
|
||||
/// If there is any error in spawning the preprocessor command, then
|
||||
/// return the corresponding error.
|
||||
pub fn from_cmd_path(
|
||||
cmd: PathBuf,
|
||||
path: &Path,
|
||||
) -> Result<PreprocessorReader> {
|
||||
let child = process::Command::new(&cmd)
|
||||
.arg(path)
|
||||
.stdin(Stdio::from(File::open(path)?))
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"error running preprocessor command '{}': {}",
|
||||
cmd.display(),
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
Ok(PreprocessorReader {
|
||||
cmd: cmd,
|
||||
path: path.to_path_buf(),
|
||||
child: child,
|
||||
done: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_error(&mut self) -> io::Result<io::Error> {
|
||||
let mut errbytes = vec![];
|
||||
self.child.stderr.as_mut().unwrap().read_to_end(&mut errbytes)?;
|
||||
let errstr = String::from_utf8_lossy(&errbytes);
|
||||
let errstr = errstr.trim();
|
||||
|
||||
Ok(if errstr.is_empty() {
|
||||
let msg = format!(
|
||||
"preprocessor command failed: '{} {}'",
|
||||
self.cmd.display(),
|
||||
self.path.display(),
|
||||
);
|
||||
io::Error::new(io::ErrorKind::Other, msg)
|
||||
} else {
|
||||
let msg = format!(
|
||||
"preprocessor command failed: '{} {}': {}",
|
||||
self.cmd.display(),
|
||||
self.path.display(),
|
||||
errstr,
|
||||
);
|
||||
io::Error::new(io::ErrorKind::Other, msg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for PreprocessorReader {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
if self.done {
|
||||
return Ok(0);
|
||||
}
|
||||
let nread = self.child.stdout.as_mut().unwrap().read(buf)?;
|
||||
if nread == 0 {
|
||||
self.done = true;
|
||||
// Reap the child now that we're done reading.
|
||||
// If the command failed, report stderr as an error.
|
||||
if !self.child.wait()?.success() {
|
||||
return Err(self.read_error()?);
|
||||
}
|
||||
}
|
||||
Ok(nread)
|
||||
}
|
||||
}
|
@@ -99,10 +99,6 @@ pub struct Printer<W> {
|
||||
path_separator: Option<u8>,
|
||||
/// Restrict lines to this many columns.
|
||||
max_columns: Option<usize>,
|
||||
/// Width of line number displayed. If the number of digits in the
|
||||
/// line number is less than this, it is left padded with
|
||||
/// spaces.
|
||||
line_number_width: Option<usize>
|
||||
}
|
||||
|
||||
impl<W: WriteColor> Printer<W> {
|
||||
@@ -124,7 +120,6 @@ impl<W: WriteColor> Printer<W> {
|
||||
colors: ColorSpecs::default(),
|
||||
path_separator: None,
|
||||
max_columns: None,
|
||||
line_number_width: None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,12 +208,6 @@ impl<W: WriteColor> Printer<W> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure the width of the displayed line number
|
||||
pub fn line_number_width(mut self, line_number_width: Option<usize>) -> Printer<W> {
|
||||
self.line_number_width = line_number_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns true if and only if something has been printed.
|
||||
pub fn has_printed(&self) -> bool {
|
||||
self.has_printed
|
||||
@@ -283,10 +272,14 @@ impl<W: WriteColor> Printer<W> {
|
||||
byte_offset: Option<u64>
|
||||
) {
|
||||
if !self.line_per_match && !self.only_matching {
|
||||
let mat = re
|
||||
.find(&buf[start..end])
|
||||
.map(|m| (m.start(), m.end()))
|
||||
.unwrap_or((0, 0));
|
||||
let mat =
|
||||
if !self.needs_match() {
|
||||
(0, 0)
|
||||
} else {
|
||||
re.find(&buf[start..end])
|
||||
.map(|m| (m.start(), m.end()))
|
||||
.unwrap_or((0, 0))
|
||||
};
|
||||
return self.write_match(
|
||||
re, path, buf, start, end, line_number,
|
||||
byte_offset, mat.0, mat.1);
|
||||
@@ -298,6 +291,12 @@ impl<W: WriteColor> Printer<W> {
|
||||
}
|
||||
}
|
||||
|
||||
fn needs_match(&self) -> bool {
|
||||
self.column
|
||||
|| self.replace.is_some()
|
||||
|| self.only_matching
|
||||
}
|
||||
|
||||
fn write_match<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
re: &Regex,
|
||||
@@ -483,10 +482,7 @@ impl<W: WriteColor> Printer<W> {
|
||||
}
|
||||
|
||||
fn line_number(&mut self, n: u64, sep: u8) {
|
||||
let mut line_number = n.to_string();
|
||||
if let Some(width) = self.line_number_width {
|
||||
line_number = format!("{:>width$}", line_number, width = width);
|
||||
}
|
||||
let line_number = n.to_string();
|
||||
self.write_colored(line_number.as_bytes(), |colors| colors.line());
|
||||
self.separator(&[sep]);
|
||||
}
|
||||
|
@@ -11,6 +11,15 @@ enum State {
|
||||
Literal,
|
||||
}
|
||||
|
||||
/// Escapes an arbitrary byte slice such that it can be presented as a human
|
||||
/// readable string.
|
||||
pub fn escape(bytes: &[u8]) -> String {
|
||||
use std::ascii::escape_default;
|
||||
|
||||
let escaped = bytes.iter().flat_map(|&b| escape_default(b)).collect();
|
||||
String::from_utf8(escaped).unwrap()
|
||||
}
|
||||
|
||||
/// Unescapes a string given on the command line. It supports a limited set of
|
||||
/// escape sequences:
|
||||
///
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use encoding_rs::Encoding;
|
||||
use grep::Grep;
|
||||
@@ -8,8 +8,10 @@ use ignore::DirEntry;
|
||||
use memmap::Mmap;
|
||||
use termcolor::WriteColor;
|
||||
|
||||
use decoder::DecodeReader;
|
||||
// use decoder::DecodeReader;
|
||||
use encoding_rs_io::DecodeReaderBytesBuilder;
|
||||
use decompressor::{self, DecompressionReader};
|
||||
use preprocessor::PreprocessorReader;
|
||||
use pathutil::strip_prefix;
|
||||
use printer::Printer;
|
||||
use search_buffer::BufferSearcher;
|
||||
@@ -45,6 +47,7 @@ struct Options {
|
||||
no_messages: bool,
|
||||
quiet: bool,
|
||||
text: bool,
|
||||
preprocessor: Option<PathBuf>,
|
||||
search_zip_files: bool
|
||||
}
|
||||
|
||||
@@ -68,6 +71,7 @@ impl Default for Options {
|
||||
quiet: false,
|
||||
text: false,
|
||||
search_zip_files: false,
|
||||
preprocessor: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,6 +226,12 @@ impl WorkerBuilder {
|
||||
self.opts.search_zip_files = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// If non-empty, search output of preprocessor run on each file
|
||||
pub fn preprocessor(mut self, command: Option<PathBuf>) -> Self {
|
||||
self.opts.preprocessor = command;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Worker is responsible for executing searches on file paths, while choosing
|
||||
@@ -250,7 +260,18 @@ impl Worker {
|
||||
}
|
||||
Work::DirEntry(dent) => {
|
||||
let mut path = dent.path();
|
||||
if self.opts.search_zip_files
|
||||
if self.opts.preprocessor.is_some() {
|
||||
let cmd = self.opts.preprocessor.clone().unwrap();
|
||||
match PreprocessorReader::from_cmd_path(cmd, path) {
|
||||
Ok(reader) => self.search(printer, path, reader),
|
||||
Err(err) => {
|
||||
if !self.opts.no_messages {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else if self.opts.search_zip_files
|
||||
&& decompressor::is_compressed(path)
|
||||
{
|
||||
match DecompressionReader::from_path(path) {
|
||||
@@ -299,8 +320,10 @@ impl Worker {
|
||||
path: &Path,
|
||||
rdr: R,
|
||||
) -> Result<u64> {
|
||||
let rdr = DecodeReader::new(
|
||||
rdr, &mut self.decodebuf, self.opts.encoding);
|
||||
let rdr = DecodeReaderBytesBuilder::new()
|
||||
.encoding(self.opts.encoding)
|
||||
.utf8_passthru(true)
|
||||
.build_with_buffer(rdr, &mut self.decodebuf)?;
|
||||
let searcher = Searcher::new(
|
||||
&mut self.inpbuf, printer, &self.grep, path, rdr);
|
||||
searcher
|
||||
|
@@ -1,3 +0,0 @@
|
||||
This project is dual-licensed under the Unlicense and MIT licenses.
|
||||
|
||||
You may use this code under the terms of either license.
|
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "termcolor"
|
||||
version = "0.3.6" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
A simple cross platform library for writing colored text to a terminal.
|
||||
"""
|
||||
documentation = "https://docs.rs/termcolor"
|
||||
homepage = "https://github.com/BurntSushi/ripgrep/tree/master/termcolor"
|
||||
repository = "https://github.com/BurntSushi/ripgrep/tree/master/termcolor"
|
||||
readme = "README.md"
|
||||
keywords = ["windows", "win", "color", "ansi", "console"]
|
||||
license = "Unlicense/MIT"
|
||||
|
||||
[lib]
|
||||
name = "termcolor"
|
||||
bench = false
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
wincolor = { version = "0.1.6", path = "../wincolor" }
|
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Andrew Gallant
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
@@ -1,86 +1,2 @@
|
||||
termcolor
|
||||
=========
|
||||
A simple cross platform library for writing colored text to a terminal. This
|
||||
library writes colored text either using standard ANSI escape sequences or
|
||||
by interacting with the Windows console. Several convenient abstractions
|
||||
are provided for use in single-threaded or multi-threaded command line
|
||||
applications.
|
||||
|
||||
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||
[](https://crates.io/crates/termcolor)
|
||||
|
||||
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||
|
||||
### Documentation
|
||||
|
||||
[https://docs.rs/termcolor](https://docs.rs/termcolor)
|
||||
|
||||
### Usage
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
termcolor = "0.3"
|
||||
```
|
||||
|
||||
and this to your crate root:
|
||||
|
||||
```rust
|
||||
extern crate termcolor;
|
||||
```
|
||||
|
||||
### Organization
|
||||
|
||||
The `WriteColor` trait extends the `io::Write` trait with methods for setting
|
||||
colors or resetting them.
|
||||
|
||||
`StandardStream` and `StandardStreamLock` both satisfy `WriteColor` and are
|
||||
analogous to `std::io::Stdout` and `std::io::StdoutLock`, or `std::io::Stderr`
|
||||
and `std::io::StderrLock`.
|
||||
|
||||
`Buffer` is an in memory buffer that supports colored text. In a parallel
|
||||
program, each thread might write to its own buffer. A buffer can be printed to
|
||||
stdout or stderr using a `BufferWriter`. The advantage of this design is that
|
||||
each thread can work in parallel on a buffer without having to synchronize
|
||||
access to global resources such as the Windows console. Moreover, this design
|
||||
also prevents interleaving of buffer output.
|
||||
|
||||
`Ansi` and `NoColor` both satisfy `WriteColor` for arbitrary implementors of
|
||||
`io::Write`. These types are useful when you know exactly what you need. An
|
||||
analogous type for the Windows console is not provided since it cannot exist.
|
||||
|
||||
### Example: using `StandardStream`
|
||||
|
||||
The `StandardStream` type in this crate works similarly to `std::io::Stdout`,
|
||||
except it is augmented with methods for coloring by the `WriteColor` trait.
|
||||
For example, to write some green text:
|
||||
|
||||
```rust
|
||||
use std::io::Write;
|
||||
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
|
||||
|
||||
let mut stdout = StandardStream::stdout(ColorChoice::Always);
|
||||
stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
|
||||
writeln!(&mut stdout, "green text!")?;
|
||||
```
|
||||
|
||||
### Example: using `BufferWriter`
|
||||
|
||||
A `BufferWriter` can create buffers and write buffers to stdout or stderr. It
|
||||
does *not* implement `io::Write` or `WriteColor` itself. Instead, `Buffer`
|
||||
implements `io::Write` and `termcolor::WriteColor`.
|
||||
|
||||
This example shows how to print some green text to stderr.
|
||||
|
||||
```rust
|
||||
use std::io::Write;
|
||||
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
|
||||
|
||||
let mut bufwtr = BufferWriter::stderr(ColorChoice::Always);
|
||||
let mut buffer = bufwtr.buffer();
|
||||
buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
|
||||
writeln!(&mut buffer, "green text!")?;
|
||||
bufwtr.print(&buffer)?;
|
||||
```
|
||||
termcolor has moved to its own repository:
|
||||
https://github.com/BurntSushi/termcolor
|
||||
|
@@ -1,24 +0,0 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
1697
termcolor/src/lib.rs
1697
termcolor/src/lib.rs
File diff suppressed because it is too large
Load Diff
BIN
tests/data/sherlock.lz4
Normal file
BIN
tests/data/sherlock.lz4
Normal file
Binary file not shown.
173
tests/tests.rs
173
tests/tests.rs
@@ -107,22 +107,6 @@ sherlock!(line_numbers, |wd: WorkDir, mut cmd: Command| {
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(line_number_width, |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-n");
|
||||
cmd.arg("--line-number-width").arg("2");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = " 1:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
3:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(line_number_width_padding_character_error, |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("-n");
|
||||
cmd.arg("--line-number-width").arg("02");
|
||||
wd.assert_non_empty_stderr(&mut cmd);
|
||||
});
|
||||
|
||||
sherlock!(columns, |wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--column");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
@@ -606,6 +590,7 @@ sherlock!(no_ignore_hidden, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
});
|
||||
|
||||
sherlock!(ignore_git, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create_dir(".git");
|
||||
wd.create(".gitignore", "sherlock\n");
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
@@ -651,7 +636,7 @@ sherlock!(ignore_git_parent_stop, "Sherlock", ".",
|
||||
//
|
||||
// .gitignore (contains `sherlock`)
|
||||
// foo/
|
||||
// .git
|
||||
// .git/
|
||||
// bar/
|
||||
// sherlock
|
||||
//
|
||||
@@ -674,6 +659,39 @@ sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
// Like ignore_git_parent_stop, but with a .git file instead of a .git
|
||||
// directory.
|
||||
sherlock!(ignore_git_parent_stop_file, "Sherlock", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
// This tests that searching parent directories for .gitignore files stops
|
||||
// after it sees a .git *file*. A .git file is used for submodules. To test
|
||||
// this, we create this directory hierarchy:
|
||||
//
|
||||
// .gitignore (contains `sherlock`)
|
||||
// foo/
|
||||
// .git
|
||||
// bar/
|
||||
// sherlock
|
||||
//
|
||||
// And we perform the search inside `foo/bar/`. ripgrep will stop looking
|
||||
// for .gitignore files after it sees `foo/.git`, and therefore not
|
||||
// respect the top-level `.gitignore` containing `sherlock`.
|
||||
wd.remove("sherlock");
|
||||
wd.create(".gitignore", "sherlock\n");
|
||||
wd.create_dir("foo");
|
||||
wd.create("foo/.git", "");
|
||||
wd.create_dir("foo/bar");
|
||||
wd.create("foo/bar/sherlock", hay::SHERLOCK);
|
||||
cmd.current_dir(wd.path().join("foo").join("bar"));
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(ignore_ripgrep_parent_no_stop, "Sherlock", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
// This is like the `ignore_git_parent_stop` test, except it checks that
|
||||
@@ -693,6 +711,7 @@ sherlock!(no_parent_ignore_git, "Sherlock", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
// Set up a directory hierarchy like this:
|
||||
//
|
||||
// .git/
|
||||
// .gitignore
|
||||
// foo/
|
||||
// .gitignore
|
||||
@@ -710,6 +729,7 @@ sherlock!(no_parent_ignore_git, "Sherlock", ".",
|
||||
// In other words, we should only see results from `sherlock`, not from
|
||||
// `watson`.
|
||||
wd.remove("sherlock");
|
||||
wd.create_dir(".git");
|
||||
wd.create(".gitignore", "sherlock\n");
|
||||
wd.create_dir("foo");
|
||||
wd.create("foo/.gitignore", "watson\n");
|
||||
@@ -803,8 +823,37 @@ sherlock:5:12:but Doctor Watson has to have it taken out for him and dusted,
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(vimgrep_no_line, "Sherlock|Watson", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--vimgrep").arg("-N");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:16:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:57:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:49:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
sherlock:12:but Doctor Watson has to have it taken out for him and dusted,
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
sherlock!(vimgrep_no_line_no_column, "Sherlock|Watson", ".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
cmd.arg("--vimgrep").arg("-N").arg("--no-column");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
sherlock:but Doctor Watson has to have it taken out for him and dusted,
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/16
|
||||
clean!(regression_16, "xyz", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create_dir(".git");
|
||||
wd.create(".gitignore", "ghi/");
|
||||
wd.create_dir("ghi");
|
||||
wd.create_dir("def/ghi");
|
||||
@@ -860,6 +909,7 @@ clean!(regression_50, "xyz", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/65
|
||||
clean!(regression_65, "xyz", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create_dir(".git");
|
||||
wd.create(".gitignore", "a/");
|
||||
wd.create_dir("a");
|
||||
wd.create("a/foo", "xyz");
|
||||
@@ -869,6 +919,7 @@ clean!(regression_65, "xyz", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/67
|
||||
clean!(regression_67, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create_dir(".git");
|
||||
wd.create(".gitignore", "/*\n!/dir");
|
||||
wd.create_dir("dir");
|
||||
wd.create_dir("foo");
|
||||
@@ -881,6 +932,7 @@ clean!(regression_67, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/87
|
||||
clean!(regression_87, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create_dir(".git");
|
||||
wd.create(".gitignore", "foo\n**no-vcs**");
|
||||
wd.create("foo", "test");
|
||||
wd.assert_err(&mut cmd);
|
||||
@@ -888,6 +940,7 @@ clean!(regression_87, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/90
|
||||
clean!(regression_90, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create_dir(".git");
|
||||
wd.create(".gitignore", "!.foo");
|
||||
wd.create(".foo", "test");
|
||||
|
||||
@@ -948,6 +1001,7 @@ clean!(regression_127, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
// 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_dir(".git");
|
||||
wd.create(".gitignore", "foo/sherlock\n");
|
||||
wd.create_dir("foo");
|
||||
wd.create("foo/sherlock", hay::SHERLOCK);
|
||||
@@ -975,6 +1029,7 @@ clean!(regression_128, "x", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
// 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_dir(".git");
|
||||
wd.create(".gitignore", "TopÑapa");
|
||||
wd.create("TopÑapa", "test");
|
||||
wd.assert_err(&mut cmd);
|
||||
@@ -1262,6 +1317,7 @@ clean!(regression_599, "^$", "input.txt", |wd: WorkDir, mut cmd: Command| {
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/807
|
||||
clean!(regression_807, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create_dir(".git");
|
||||
wd.create(".gitignore", ".a/b");
|
||||
wd.create_dir(".a/b");
|
||||
wd.create_dir(".a/c");
|
||||
@@ -1273,6 +1329,12 @@ clean!(regression_807, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
assert_eq!(lines, format!("{}:test\n", path(".a/c/file")));
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/900
|
||||
sherlock!(regression_900, "-fpat", "sherlock", |wd: WorkDir, mut cmd: Command| {
|
||||
wd.create("pat", "");
|
||||
wd.assert_err(&mut cmd);
|
||||
});
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||
clean!(feature_1_sjis, "Шерлок Холмс", ".", |wd: WorkDir, mut cmd: Command| {
|
||||
let sherlock =
|
||||
@@ -1687,6 +1749,26 @@ sherlock!(feature_419_zero_as_shortcut_for_null, "Sherlock", ".",
|
||||
assert_eq!(lines, "sherlock\x002\n");
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn preprocessing() {
|
||||
if !cmd_exists("xzcat") {
|
||||
return;
|
||||
}
|
||||
let xz_file = include_bytes!("./data/sherlock.xz");
|
||||
|
||||
let wd = WorkDir::new("feature_preprocessing");
|
||||
wd.create_bytes("sherlock.xz", xz_file);
|
||||
|
||||
let mut cmd = wd.command();
|
||||
cmd.arg("--pre").arg("xzcat").arg("Sherlock").arg("sherlock.xz");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compressed_gzip() {
|
||||
if !cmd_exists("gzip") {
|
||||
@@ -1747,6 +1829,26 @@ be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
assert_eq!(lines, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compressed_lz4() {
|
||||
if !cmd_exists("lz4") {
|
||||
return;
|
||||
}
|
||||
let lz4_file = include_bytes!("./data/sherlock.lz4");
|
||||
|
||||
let wd = WorkDir::new("feature_search_compressed");
|
||||
wd.create_bytes("sherlock.lz4", lz4_file);
|
||||
|
||||
let mut cmd = wd.command();
|
||||
cmd.arg("-z").arg("Sherlock").arg("sherlock.lz4");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||
be, to a very large extent, the result of luck. Sherlock Holmes
|
||||
";
|
||||
assert_eq!(lines, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compressed_lzma() {
|
||||
if !cmd_exists("xz") {
|
||||
@@ -1782,7 +1884,7 @@ fn compressed_failing_gzip() {
|
||||
|
||||
let output = cmd.output().unwrap();
|
||||
let err = String::from_utf8_lossy(&output.stderr);
|
||||
assert_eq!(err.contains("not in gzip format"), true);
|
||||
assert!(!err.is_empty());
|
||||
}
|
||||
|
||||
sherlock!(feature_196_persistent_config, "sherlock",
|
||||
@@ -1821,6 +1923,7 @@ fn feature_411_parallel_search_stats() {
|
||||
let mut cmd = wd.command();
|
||||
cmd.arg("--stats");
|
||||
cmd.arg("Sherlock");
|
||||
cmd.arg("./");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines.contains("4 matched lines"), true);
|
||||
@@ -1976,7 +2079,7 @@ fn regression_270() {
|
||||
wd.create("foo", "-test");
|
||||
|
||||
let mut cmd = wd.command();
|
||||
cmd.arg("-e").arg("-test");
|
||||
cmd.arg("-e").arg("-test").arg("./");
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
assert_eq!(lines, path("foo:-test\n"));
|
||||
}
|
||||
@@ -2125,7 +2228,7 @@ fn regression_693_context_option_in_contextless_mode() {
|
||||
wd.create("bar", "xyz\n");
|
||||
|
||||
let mut cmd = wd.command();
|
||||
cmd.arg("-C1").arg("-c").arg("--sort-files").arg("xyz");
|
||||
cmd.arg("-C1").arg("-c").arg("--sort-files").arg("xyz").arg("./");
|
||||
|
||||
let lines: String = wd.stdout(&mut cmd);
|
||||
let expected = "\
|
||||
@@ -2145,3 +2248,33 @@ fn type_list() {
|
||||
// This can change over time, so just make sure we print something.
|
||||
assert!(!lines.is_empty());
|
||||
}
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/948
|
||||
sherlock!(
|
||||
exit_code_match_success,
|
||||
".",
|
||||
".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.assert_exit_code(0, &mut cmd);
|
||||
}
|
||||
);
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/948
|
||||
sherlock!(
|
||||
exit_code_no_match,
|
||||
"6d28e48b5224a42b167e{10}",
|
||||
".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.assert_exit_code(1, &mut cmd);
|
||||
}
|
||||
);
|
||||
|
||||
// See: https://github.com/BurntSushi/ripgrep/issues/948
|
||||
sherlock!(
|
||||
exit_code_error,
|
||||
"*",
|
||||
".",
|
||||
|wd: WorkDir, mut cmd: Command| {
|
||||
wd.assert_exit_code(2, &mut cmd);
|
||||
}
|
||||
);
|
||||
|
101
tests/workdir.rs
101
tests/workdir.rs
@@ -21,7 +21,8 @@ pub struct WorkDir {
|
||||
/// The directory in which this test executable is running.
|
||||
root: PathBuf,
|
||||
/// The directory in which the test should run. If a test needs to create
|
||||
/// files, they should go in here.
|
||||
/// files, they should go in here. This directory is also used as the CWD
|
||||
/// for any processes created by the test.
|
||||
dir: PathBuf,
|
||||
}
|
||||
|
||||
@@ -31,9 +32,15 @@ impl WorkDir {
|
||||
/// to a logical grouping of tests.
|
||||
pub fn new(name: &str) -> WorkDir {
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
|
||||
let root = env::current_exe().unwrap()
|
||||
.parent().expect("executable's directory").to_path_buf();
|
||||
let dir = root.join(TEST_DIR).join(name).join(&format!("{}", id));
|
||||
let root = env::current_exe()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.expect("executable's directory")
|
||||
.to_path_buf();
|
||||
let dir = env::temp_dir()
|
||||
.join(TEST_DIR)
|
||||
.join(name)
|
||||
.join(&format!("{}", id));
|
||||
nice_err(&dir, repeat(|| fs::create_dir_all(&dir)));
|
||||
WorkDir {
|
||||
root: root,
|
||||
@@ -49,7 +56,11 @@ impl WorkDir {
|
||||
|
||||
/// Try to create a new file with the given name and contents in this
|
||||
/// directory.
|
||||
pub fn try_create<P: AsRef<Path>>(&self, name: P, contents: &str) -> io::Result<()> {
|
||||
pub fn try_create<P: AsRef<Path>>(
|
||||
&self,
|
||||
name: P,
|
||||
contents: &str,
|
||||
) -> io::Result<()> {
|
||||
let path = self.dir.join(name);
|
||||
self.try_create_bytes(path, contents.as_bytes())
|
||||
}
|
||||
@@ -70,7 +81,11 @@ impl WorkDir {
|
||||
|
||||
/// Try to create a new file with the given name and contents in this
|
||||
/// directory.
|
||||
fn try_create_bytes<P: AsRef<Path>>(&self, path: P, contents: &[u8]) -> io::Result<()> {
|
||||
fn try_create_bytes<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
contents: &[u8],
|
||||
) -> io::Result<()> {
|
||||
let mut file = File::create(&path)?;
|
||||
file.write_all(contents)?;
|
||||
file.flush()
|
||||
@@ -99,28 +114,11 @@ impl WorkDir {
|
||||
}
|
||||
|
||||
/// Returns the path to the ripgrep executable.
|
||||
#[cfg(not(windows))]
|
||||
pub fn bin(&self) -> PathBuf {
|
||||
let path = self.root.join("rg");
|
||||
if !path.is_file() {
|
||||
// Looks like a recent version of Cargo changed the cwd or the
|
||||
// location of the test executable.
|
||||
self.root.join("../rg")
|
||||
} else {
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the path to the ripgrep executable.
|
||||
#[cfg(windows)]
|
||||
pub fn bin(&self) -> PathBuf {
|
||||
let path = self.root.join("rg.exe");
|
||||
if !path.is_file() {
|
||||
// Looks like a recent version of Cargo changed the cwd or the
|
||||
// location of the test executable.
|
||||
if cfg!(windows) {
|
||||
self.root.join("../rg.exe")
|
||||
} else {
|
||||
path
|
||||
self.root.join("../rg")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +188,11 @@ impl WorkDir {
|
||||
match stdout.parse() {
|
||||
Ok(t) => t,
|
||||
Err(err) => {
|
||||
panic!("could not convert from string: {:?}\n\n{}", err, stdout);
|
||||
panic!(
|
||||
"could not convert from string: {:?}\n\n{}",
|
||||
err,
|
||||
stdout
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +223,10 @@ impl WorkDir {
|
||||
write!(stdin, "{}", input)
|
||||
});
|
||||
|
||||
let output = self.expect_success(cmd, child.wait_with_output().unwrap());
|
||||
let output = self.expect_success(
|
||||
cmd,
|
||||
child.wait_with_output().unwrap(),
|
||||
);
|
||||
worker.join().unwrap().unwrap();
|
||||
output
|
||||
}
|
||||
@@ -261,18 +266,42 @@ impl WorkDir {
|
||||
pub fn assert_err(&self, cmd: &mut process::Command) {
|
||||
let o = cmd.output().unwrap();
|
||||
if o.status.success() {
|
||||
panic!("\n\n===== {:?} =====\n\
|
||||
command succeeded but expected failure!\
|
||||
\n\ncwd: {}\
|
||||
\n\nstatus: {}\
|
||||
\n\nstdout: {}\n\nstderr: {}\
|
||||
\n\n=====\n",
|
||||
cmd, self.dir.display(), o.status,
|
||||
String::from_utf8_lossy(&o.stdout),
|
||||
String::from_utf8_lossy(&o.stderr));
|
||||
panic!(
|
||||
"\n\n===== {:?} =====\n\
|
||||
command succeeded but expected failure!\
|
||||
\n\ncwd: {}\
|
||||
\n\nstatus: {}\
|
||||
\n\nstdout: {}\n\nstderr: {}\
|
||||
\n\n=====\n",
|
||||
cmd,
|
||||
self.dir.display(),
|
||||
o.status,
|
||||
String::from_utf8_lossy(&o.stdout),
|
||||
String::from_utf8_lossy(&o.stderr)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the given command and asserts that its exit code matches expected
|
||||
/// exit code.
|
||||
pub fn assert_exit_code(
|
||||
&self,
|
||||
expected_code: i32,
|
||||
cmd: &mut process::Command,
|
||||
) {
|
||||
let code = cmd.status().unwrap().code().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_code, code,
|
||||
"\n\n===== {:?} =====\n\
|
||||
expected exit code did not match\
|
||||
\n\nexpected: {}\
|
||||
\n\nfound: {}\
|
||||
\n\n=====\n",
|
||||
cmd, expected_code, code
|
||||
);
|
||||
}
|
||||
|
||||
/// Runs the given command and asserts that something was printed to
|
||||
/// stderr.
|
||||
pub fn assert_non_empty_stderr(&self, cmd: &mut process::Command) {
|
||||
|
@@ -1,3 +0,0 @@
|
||||
This project is dual-licensed under the Unlicense and MIT licenses.
|
||||
|
||||
You may use this code under the terms of either license.
|
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "wincolor"
|
||||
version = "0.1.6" #:version
|
||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||
description = """
|
||||
A simple Windows specific API for controlling text color in a Windows console.
|
||||
"""
|
||||
documentation = "https://docs.rs/wincolor"
|
||||
homepage = "https://github.com/BurntSushi/ripgrep/tree/master/wincolor"
|
||||
repository = "https://github.com/BurntSushi/ripgrep/tree/master/wincolor"
|
||||
readme = "README.md"
|
||||
keywords = ["windows", "win", "color", "ansi", "console"]
|
||||
license = "Unlicense/MIT"
|
||||
|
||||
[lib]
|
||||
name = "wincolor"
|
||||
bench = false
|
||||
|
||||
[dependencies.winapi]
|
||||
version = "0.3"
|
||||
features = ["consoleapi", "minwindef", "processenv", "winbase", "wincon"]
|
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Andrew Gallant
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
@@ -1,44 +1,2 @@
|
||||
wincolor
|
||||
========
|
||||
A simple Windows specific API for controlling text color in a Windows console.
|
||||
The purpose of this crate is to expose the full inflexibility of the Windows
|
||||
console without any platform independent abstraction.
|
||||
|
||||
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||
[](https://crates.io/crates/wincolor)
|
||||
|
||||
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||
|
||||
### Documentation
|
||||
|
||||
[https://docs.rs/wincolor](https://docs.rs/wincolor)
|
||||
|
||||
### Usage
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
wincolor = "0.1"
|
||||
```
|
||||
|
||||
and this to your crate root:
|
||||
|
||||
```rust
|
||||
extern crate wincolor;
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
This is a simple example that shows how to write text with a foreground color
|
||||
of cyan and the intense attribute set:
|
||||
|
||||
```rust
|
||||
use wincolor::{Console, Color, Intense};
|
||||
|
||||
let mut con = Console::stdout().unwrap();
|
||||
con.fg(Intense::Yes, Color::Cyan).unwrap();
|
||||
println!("This text will be intense cyan.");
|
||||
con.reset().unwrap();
|
||||
println!("This text will be normal.");
|
||||
```
|
||||
wincolor has moved to the termcolor repository:
|
||||
https://github.com/BurntSushi/termcolor
|
||||
|
@@ -1,24 +0,0 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
@@ -1,33 +0,0 @@
|
||||
/*!
|
||||
This crate provides a safe and simple Windows specific API to control
|
||||
text attributes in the Windows console. Text attributes are limited to
|
||||
foreground/background colors, as well as whether to make colors intense or not.
|
||||
|
||||
Note that on non-Windows platforms, this crate is empty but will compile.
|
||||
|
||||
# Example
|
||||
|
||||
```no_run
|
||||
# #[cfg(windows)]
|
||||
# {
|
||||
use wincolor::{Console, Color, Intense};
|
||||
|
||||
let mut con = Console::stdout().unwrap();
|
||||
con.fg(Intense::Yes, Color::Cyan).unwrap();
|
||||
println!("This text will be intense cyan.");
|
||||
con.reset().unwrap();
|
||||
println!("This text will be normal.");
|
||||
# }
|
||||
```
|
||||
*/
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[cfg(windows)]
|
||||
extern crate winapi;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use win::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod win;
|
@@ -1,266 +0,0 @@
|
||||
use std::io;
|
||||
use std::mem;
|
||||
|
||||
use winapi::shared::minwindef::{DWORD, WORD};
|
||||
use winapi::um::consoleapi;
|
||||
use winapi::um::processenv;
|
||||
use winapi::um::winbase::{STD_ERROR_HANDLE, STD_OUTPUT_HANDLE};
|
||||
use winapi::um::wincon::{
|
||||
self,
|
||||
FOREGROUND_BLUE as FG_BLUE,
|
||||
FOREGROUND_GREEN as FG_GREEN,
|
||||
FOREGROUND_RED as FG_RED,
|
||||
FOREGROUND_INTENSITY as FG_INTENSITY,
|
||||
};
|
||||
|
||||
const FG_CYAN: WORD = FG_BLUE | FG_GREEN;
|
||||
const FG_MAGENTA: WORD = FG_BLUE | FG_RED;
|
||||
const FG_YELLOW: WORD = FG_GREEN | FG_RED;
|
||||
const FG_WHITE: WORD = FG_BLUE | FG_GREEN | FG_RED;
|
||||
|
||||
/// A Windows console.
|
||||
///
|
||||
/// This represents a very limited set of functionality available to a Windows
|
||||
/// console. In particular, it can only change text attributes such as color
|
||||
/// and intensity.
|
||||
///
|
||||
/// There is no way to "write" to this console. Simply write to
|
||||
/// stdout or stderr instead, while interleaving instructions to the console
|
||||
/// to change text attributes.
|
||||
///
|
||||
/// A common pitfall when using a console is to forget to flush writes to
|
||||
/// stdout before setting new text attributes.
|
||||
#[derive(Debug)]
|
||||
pub struct Console {
|
||||
handle_id: DWORD,
|
||||
start_attr: TextAttributes,
|
||||
cur_attr: TextAttributes,
|
||||
}
|
||||
|
||||
impl Console {
|
||||
/// Get a console for a standard I/O stream.
|
||||
fn create_for_stream(handle_id: DWORD) -> io::Result<Console> {
|
||||
let mut info = unsafe { mem::zeroed() };
|
||||
let res = unsafe {
|
||||
let handle = processenv::GetStdHandle(handle_id);
|
||||
wincon::GetConsoleScreenBufferInfo(handle, &mut info)
|
||||
};
|
||||
if res == 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
let attr = TextAttributes::from_word(info.wAttributes);
|
||||
Ok(Console {
|
||||
handle_id: handle_id,
|
||||
start_attr: attr,
|
||||
cur_attr: attr,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new Console to stdout.
|
||||
///
|
||||
/// If there was a problem creating the console, then an error is returned.
|
||||
pub fn stdout() -> io::Result<Console> {
|
||||
Self::create_for_stream(STD_OUTPUT_HANDLE)
|
||||
}
|
||||
|
||||
/// Create a new Console to stderr.
|
||||
///
|
||||
/// If there was a problem creating the console, then an error is returned.
|
||||
pub fn stderr() -> io::Result<Console> {
|
||||
Self::create_for_stream(STD_ERROR_HANDLE)
|
||||
}
|
||||
|
||||
/// Applies the current text attributes.
|
||||
fn set(&mut self) -> io::Result<()> {
|
||||
let attr = self.cur_attr.to_word();
|
||||
let res = unsafe {
|
||||
let handle = processenv::GetStdHandle(self.handle_id);
|
||||
wincon::SetConsoleTextAttribute(handle, attr)
|
||||
};
|
||||
if res == 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply the given intensity and color attributes to the console
|
||||
/// foreground.
|
||||
///
|
||||
/// If there was a problem setting attributes on the console, then an error
|
||||
/// is returned.
|
||||
pub fn fg(&mut self, intense: Intense, color: Color) -> io::Result<()> {
|
||||
self.cur_attr.fg_color = color;
|
||||
self.cur_attr.fg_intense = intense;
|
||||
self.set()
|
||||
}
|
||||
|
||||
/// Apply the given intensity and color attributes to the console
|
||||
/// background.
|
||||
///
|
||||
/// If there was a problem setting attributes on the console, then an error
|
||||
/// is returned.
|
||||
pub fn bg(&mut self, intense: Intense, color: Color) -> io::Result<()> {
|
||||
self.cur_attr.bg_color = color;
|
||||
self.cur_attr.bg_intense = intense;
|
||||
self.set()
|
||||
}
|
||||
|
||||
/// Reset the console text attributes to their original settings.
|
||||
///
|
||||
/// The original settings correspond to the text attributes on the console
|
||||
/// when this `Console` value was created.
|
||||
///
|
||||
/// If there was a problem setting attributes on the console, then an error
|
||||
/// is returned.
|
||||
pub fn reset(&mut self) -> io::Result<()> {
|
||||
self.cur_attr = self.start_attr;
|
||||
self.set()
|
||||
}
|
||||
|
||||
/// Toggle virtual terminal processing.
|
||||
///
|
||||
/// This method attempts to toggle virtual terminal processing for this
|
||||
/// console. If there was a problem toggling it, then an error returned.
|
||||
/// On success, the caller may assume that toggling it was successful.
|
||||
///
|
||||
/// When virtual terminal processing is enabled, characters emitted to the
|
||||
/// console are parsed for VT100 and similar control character sequences
|
||||
/// that control color and other similar operations.
|
||||
pub fn set_virtual_terminal_processing(
|
||||
&mut self,
|
||||
yes: bool,
|
||||
) -> io::Result<()> {
|
||||
let vt = wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
|
||||
let mut old_mode = 0;
|
||||
let handle = unsafe { processenv::GetStdHandle(self.handle_id) };
|
||||
if unsafe { consoleapi::GetConsoleMode(handle, &mut old_mode) } == 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
let new_mode =
|
||||
if yes {
|
||||
old_mode | vt
|
||||
} else {
|
||||
old_mode & !vt
|
||||
};
|
||||
if old_mode == new_mode {
|
||||
return Ok(());
|
||||
}
|
||||
if unsafe { consoleapi::SetConsoleMode(handle, new_mode) } == 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A representation of text attributes for the Windows console.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
struct TextAttributes {
|
||||
fg_color: Color,
|
||||
fg_intense: Intense,
|
||||
bg_color: Color,
|
||||
bg_intense: Intense,
|
||||
}
|
||||
|
||||
impl TextAttributes {
|
||||
fn to_word(&self) -> WORD {
|
||||
let mut w = 0;
|
||||
w |= self.fg_color.to_fg();
|
||||
w |= self.fg_intense.to_fg();
|
||||
w |= self.bg_color.to_bg();
|
||||
w |= self.bg_intense.to_bg();
|
||||
w
|
||||
}
|
||||
|
||||
fn from_word(word: WORD) -> TextAttributes {
|
||||
TextAttributes {
|
||||
fg_color: Color::from_fg(word),
|
||||
fg_intense: Intense::from_fg(word),
|
||||
bg_color: Color::from_bg(word),
|
||||
bg_intense: Intense::from_bg(word),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to use intense colors or not.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Intense {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
impl Intense {
|
||||
fn to_bg(&self) -> WORD {
|
||||
self.to_fg() << 4
|
||||
}
|
||||
|
||||
fn from_bg(word: WORD) -> Intense {
|
||||
Intense::from_fg(word >> 4)
|
||||
}
|
||||
|
||||
fn to_fg(&self) -> WORD {
|
||||
match *self {
|
||||
Intense::No => 0,
|
||||
Intense::Yes => FG_INTENSITY,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_fg(word: WORD) -> Intense {
|
||||
if word & FG_INTENSITY > 0 {
|
||||
Intense::Yes
|
||||
} else {
|
||||
Intense::No
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The set of available colors for use with a Windows console.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Color {
|
||||
Black,
|
||||
Blue,
|
||||
Green,
|
||||
Red,
|
||||
Cyan,
|
||||
Magenta,
|
||||
Yellow,
|
||||
White,
|
||||
}
|
||||
|
||||
impl Color {
|
||||
fn to_bg(&self) -> WORD {
|
||||
self.to_fg() << 4
|
||||
}
|
||||
|
||||
fn from_bg(word: WORD) -> Color {
|
||||
Color::from_fg(word >> 4)
|
||||
}
|
||||
|
||||
fn to_fg(&self) -> WORD {
|
||||
match *self {
|
||||
Color::Black => 0,
|
||||
Color::Blue => FG_BLUE,
|
||||
Color::Green => FG_GREEN,
|
||||
Color::Red => FG_RED,
|
||||
Color::Cyan => FG_CYAN,
|
||||
Color::Magenta => FG_MAGENTA,
|
||||
Color::Yellow => FG_YELLOW,
|
||||
Color::White => FG_WHITE,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_fg(word: WORD) -> Color {
|
||||
match word & 0b111 {
|
||||
FG_BLUE => Color::Blue,
|
||||
FG_GREEN => Color::Green,
|
||||
FG_RED => Color::Red,
|
||||
FG_CYAN => Color::Cyan,
|
||||
FG_MAGENTA => Color::Magenta,
|
||||
FG_YELLOW => Color::Yellow,
|
||||
FG_WHITE => Color::White,
|
||||
_ => Color::Black,
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user