mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-08-02 05:02:01 -07:00
Compare commits
84 Commits
0.4.0
...
ignore-0.1
Author | SHA1 | Date | |
---|---|---|---|
|
487713aa34 | ||
|
e300541701 | ||
|
e9df420d2f | ||
|
201b4fc757 | ||
|
90a11dec5e | ||
|
9456d95e8f | ||
|
0c298f60a6 | ||
|
79271fcb33 | ||
|
fc975af8e9 | ||
|
1425d6735e | ||
|
aed3ccb9c7 | ||
|
33c95d2919 | ||
|
01deac9427 | ||
|
b4bc3b6349 | ||
|
685cc6c562 | ||
|
08c017330f | ||
|
2f3a8c7f69 | ||
|
3ac1b68e54 | ||
|
0ebd5465b7 | ||
|
5cb4bb9ea0 | ||
|
c8a179b4da | ||
|
46f94826fd | ||
|
75f1855a91 | ||
|
fd9870d668 | ||
|
a3a2708067 | ||
|
78847b65c8 | ||
|
e962eea1cc | ||
|
95bc678403 | ||
|
68af3bbdc4 | ||
|
70b6bdb104 | ||
|
c648eadbaa | ||
|
d352b79294 | ||
|
23aec58669 | ||
|
ae863bc7aa | ||
|
f0d3cae569 | ||
|
4ef4818130 | ||
|
8db24e1353 | ||
|
8bbe58d623 | ||
|
b3fd0df94b | ||
|
c1b841e934 | ||
|
f5ede0e319 | ||
|
6ecffec537 | ||
|
80e91a1f1d | ||
|
d570f78144 | ||
|
7c37065911 | ||
|
50f7a60a8d | ||
|
33ec988d70 | ||
|
adff43fbb4 | ||
|
71585f6d47 | ||
|
714ae82241 | ||
|
49fd668712 | ||
|
066f97d855 | ||
|
df1bf4a042 | ||
|
4e8c0fc4ad | ||
|
da1764dfd1 | ||
|
48a8a3a691 | ||
|
796eaab0d7 | ||
|
bf49448e1e | ||
|
cffba53379 | ||
|
79d40d0e20 | ||
|
525b278049 | ||
|
16de47920c | ||
|
a114b86063 | ||
|
a5a16ebb27 | ||
|
8ac5bc0147 | ||
|
cf750a190f | ||
|
d825648b86 | ||
|
22cb644eb6 | ||
|
e424f87487 | ||
|
f5b2c96b77 | ||
|
6e209b6fdb | ||
|
72e3c54e0a | ||
|
b67886264f | ||
|
e67ab459d3 | ||
|
7a926d090d | ||
|
596f94aa7f | ||
|
de55d37bea | ||
|
fecef10c1c | ||
|
79e5e6671f | ||
|
b04a68a782 | ||
|
e573ab5c60 | ||
|
f5a2d022ec | ||
|
b1d1cd2366 | ||
|
f26e0f088f |
@@ -9,13 +9,13 @@ matrix:
|
|||||||
# (All *nix releases are done on the nightly channel to take advantage
|
# (All *nix releases are done on the nightly channel to take advantage
|
||||||
# of the regex library's multiple pattern SIMD search.)
|
# of the regex library's multiple pattern SIMD search.)
|
||||||
- os: linux
|
- os: linux
|
||||||
rust: nightly
|
rust: nightly-2017-03-13
|
||||||
env: TARGET=i686-unknown-linux-musl
|
env: TARGET=i686-unknown-linux-musl
|
||||||
- os: linux
|
- os: linux
|
||||||
rust: nightly
|
rust: nightly-2017-03-13
|
||||||
env: TARGET=x86_64-unknown-linux-musl
|
env: TARGET=x86_64-unknown-linux-musl
|
||||||
- os: osx
|
- os: osx
|
||||||
rust: nightly
|
rust: nightly-2017-03-13
|
||||||
env: TARGET=x86_64-apple-darwin
|
env: TARGET=x86_64-apple-darwin
|
||||||
# Beta channel.
|
# Beta channel.
|
||||||
- os: linux
|
- os: linux
|
||||||
@@ -57,7 +57,7 @@ deploy:
|
|||||||
# channel to use to produce the release artifacts
|
# channel to use to produce the release artifacts
|
||||||
# NOTE make sure you only release *once* per target
|
# NOTE make sure you only release *once* per target
|
||||||
# TODO you may want to pick a different channel
|
# TODO you may want to pick a different channel
|
||||||
condition: $TRAVIS_RUST_VERSION = nightly
|
condition: $TRAVIS_RUST_VERSION = nightly-2017-03-13
|
||||||
tags: true
|
tags: true
|
||||||
|
|
||||||
branches:
|
branches:
|
||||||
|
88
CHANGELOG.md
88
CHANGELOG.md
@@ -1,3 +1,88 @@
|
|||||||
|
0.5.1 (2017-04-09)
|
||||||
|
==================
|
||||||
|
Feature enhancements:
|
||||||
|
|
||||||
|
* Added or improved file type filtering for vim.
|
||||||
|
* [FEATURE #34](https://github.com/BurntSushi/ripgrep/issues/34):
|
||||||
|
Add a `-o/--only-matching` flag.
|
||||||
|
* [FEATURE #377](https://github.com/BurntSushi/ripgrep/issues/377):
|
||||||
|
Column numbers can now be customized with a color. (The default is
|
||||||
|
no color.)
|
||||||
|
* [FEATURE #419](https://github.com/BurntSushi/ripgrep/issues/419):
|
||||||
|
Added `-0` short flag option for `--null`.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* [BUG #381](https://github.com/BurntSushi/ripgrep/issues/381):
|
||||||
|
Include license text in all subcrates.
|
||||||
|
* [BUG #418](https://github.com/BurntSushi/ripgrep/issues/418),
|
||||||
|
[BUG #426](https://github.com/BurntSushi/ripgrep/issues/426),
|
||||||
|
[BUG #439](https://github.com/BurntSushi/ripgrep/issues/439):
|
||||||
|
Fix a few bugs with `-h/--help` output.
|
||||||
|
|
||||||
|
|
||||||
|
0.5.0 (2017-03-12)
|
||||||
|
==================
|
||||||
|
This is a new minor version release of ripgrep that includes one minor breaking
|
||||||
|
change, bug fixes and several new features including support for text encodings
|
||||||
|
other than UTF-8.
|
||||||
|
|
||||||
|
A notable accomplishment with respect to Rust is that ripgrep proper now only
|
||||||
|
contains a single `unsafe` use (for accessing the contents of a memory map).
|
||||||
|
|
||||||
|
The **breaking change** is:
|
||||||
|
|
||||||
|
* [FEATURE #380](https://github.com/BurntSushi/ripgrep/issues/380):
|
||||||
|
Line numbers are now hidden by default when ripgrep is printing to a tty
|
||||||
|
**and** the only thing searched is stdin.
|
||||||
|
|
||||||
|
Feature enhancements:
|
||||||
|
|
||||||
|
* Added or improved file type filtering for Ceylon, CSS, Elixir, HTML, log,
|
||||||
|
SASS, SVG, Twig
|
||||||
|
* [FEATURE #1](https://github.com/BurntSushi/ripgrep/issues/1):
|
||||||
|
Add support for additional text encodings, including automatic detection for
|
||||||
|
UTF-16 via BOM sniffing. Explicit text encoding support with the
|
||||||
|
`-E/--encoding` flag was also added for latin-1, GBK, EUC-JP
|
||||||
|
and Shift_JIS, among others. The full list can be found here:
|
||||||
|
https://encoding.spec.whatwg.org/#concept-encoding-get
|
||||||
|
* [FEATURE #129](https://github.com/BurntSushi/ripgrep/issues/129):
|
||||||
|
Add a new `-M/--max-columns` flag that omits lines longer than the given
|
||||||
|
number of bytes. (Disabled by default!)
|
||||||
|
* [FEATURE #369](https://github.com/BurntSushi/ripgrep/issues/369):
|
||||||
|
A new flag, `--max-filesize`, was added for limiting searches to files with
|
||||||
|
a maximum file size.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
|
||||||
|
* [BUG #52](https://github.com/BurntSushi/ripgrep/issues/52),
|
||||||
|
[BUG #311](https://github.com/BurntSushi/ripgrep/issues/311):
|
||||||
|
Tweak how binary files are detected and handled. (We are slightly less
|
||||||
|
conservative and will no longer use memory without bound.)
|
||||||
|
* [BUG #326](https://github.com/BurntSushi/ripgrep/issues/326):
|
||||||
|
When --files flag is given, we should never attempt to parse positional
|
||||||
|
arguments as regexes.
|
||||||
|
* [BUG #327](https://github.com/BurntSushi/ripgrep/issues/327):
|
||||||
|
Permit the --heading flag to override the --no-heading flag.
|
||||||
|
* [BUG #340](https://github.com/BurntSushi/ripgrep/pull/340):
|
||||||
|
Clarify that the `-u/--unrestricted` flags are aliases.
|
||||||
|
* [BUG #343](https://github.com/BurntSushi/ripgrep/pull/343):
|
||||||
|
Global git ignore config should use `$HOME/.config/git/ignore` and not
|
||||||
|
`$HOME/git/ignore`.
|
||||||
|
* [BUG #345](https://github.com/BurntSushi/ripgrep/pull/345):
|
||||||
|
Clarify docs for `-g/--glob` flag.
|
||||||
|
* [BUG #381](https://github.com/BurntSushi/ripgrep/issues/381):
|
||||||
|
Add license files to each sub-crate.
|
||||||
|
* [BUG #383](https://github.com/BurntSushi/ripgrep/issues/383):
|
||||||
|
Use latest version of clap (for argv parsing).
|
||||||
|
* [BUG #392](https://github.com/BurntSushi/ripgrep/issues/391):
|
||||||
|
Fix translation of set globs (e.g., `{foo,bar,quux}`) to regexes.
|
||||||
|
* [BUG #401](https://github.com/BurntSushi/ripgrep/pull/401):
|
||||||
|
Add PowerShell completion file to Windows release.
|
||||||
|
* [BUG #405](https://github.com/BurntSushi/ripgrep/issues/405):
|
||||||
|
Fix bug when excluding absolute paths with the `-g/--glob` flag.
|
||||||
|
|
||||||
|
|
||||||
0.4.0
|
0.4.0
|
||||||
=====
|
=====
|
||||||
This is a new minor version release of ripgrep that includes a couple very
|
This is a new minor version release of ripgrep that includes a couple very
|
||||||
@@ -39,6 +124,9 @@ Bug fixes:
|
|||||||
Fix bug that caused ripgrep's parallel iterator to spin and burn CPU.
|
Fix bug that caused ripgrep's parallel iterator to spin and burn CPU.
|
||||||
* [BUG #262](https://github.com/BurntSushi/ripgrep/issues/262):
|
* [BUG #262](https://github.com/BurntSushi/ripgrep/issues/262):
|
||||||
Document how to install shell completion files.
|
Document how to install shell completion files.
|
||||||
|
* [BUG #266](https://github.com/BurntSushi/ripgrep/issues/266),
|
||||||
|
[BUG #293](https://github.com/BurntSushi/ripgrep/issues/293):
|
||||||
|
Fix handling of bold styling and change the default colors.
|
||||||
* [BUG #268](https://github.com/BurntSushi/ripgrep/issues/268):
|
* [BUG #268](https://github.com/BurntSushi/ripgrep/issues/268):
|
||||||
Make lack of backreference support more explicit.
|
Make lack of backreference support more explicit.
|
||||||
* [BUG #271](https://github.com/BurntSushi/ripgrep/issues/271):
|
* [BUG #271](https://github.com/BurntSushi/ripgrep/issues/271):
|
||||||
|
182
Cargo.lock
generated
182
Cargo.lock
generated
@@ -1,28 +1,28 @@
|
|||||||
[root]
|
[root]
|
||||||
name = "ripgrep"
|
name = "ripgrep"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bytecount 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bytecount 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"clap 2.19.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.23.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"encoding_rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grep 0.1.5",
|
"env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ignore 0.1.7",
|
"grep 0.1.6",
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ignore 0.1.9",
|
||||||
"lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memmap 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memmap 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num_cpus 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num_cpus 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"same-file 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"termcolor 0.2.0",
|
"termcolor 0.3.2",
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.6.1"
|
version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -33,9 +33,19 @@ name = "ansi_term"
|
|||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "0.7.0"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -46,19 +56,24 @@ dependencies = [
|
|||||||
"simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "2.19.3"
|
version = "2.23.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"term_size 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"unicode-segmentation 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"vec_map 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -67,11 +82,19 @@ version = "0.2.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "encoding_rs"
|
||||||
version = "0.3.5"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -81,31 +104,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fs2"
|
name = "fs2"
|
||||||
version = "0.3.0"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grep"
|
name = "grep"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -113,15 +135,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ignore"
|
name = "ignore"
|
||||||
version = "0.1.7"
|
version = "0.1.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"globset 0.1.3",
|
"globset 0.1.4",
|
||||||
"lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"thread_local 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -136,17 +158,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "0.2.2"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.19"
|
version = "0.2.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.3.6"
|
version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -154,26 +176,26 @@ name = "memchr"
|
|||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memmap"
|
name = "memmap"
|
||||||
version = "0.5.0"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fs2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fs2 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.2.1"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -181,11 +203,11 @@ name = "regex"
|
|||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"thread_local 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -196,11 +218,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -211,24 +232,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.5.2"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "term_size"
|
name = "term_size"
|
||||||
version = "0.2.1"
|
version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "0.2.0"
|
version = "0.3.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wincolor 0.1.1",
|
"wincolor 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -237,12 +258,12 @@ version = "3.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -251,7 +272,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "0.1.3"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -274,7 +295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vec_map"
|
name = "vec_map"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -288,7 +309,7 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"same-file 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -304,42 +325,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wincolor"
|
name = "wincolor"
|
||||||
version = "0.1.1"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
"checksum aho-corasick 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4f660b942762979b56c9f07b4b36bb559776fbad102f05d6771e1b629e8fd5bf"
|
"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
|
||||||
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
|
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
|
||||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159"
|
||||||
|
"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4"
|
||||||
"checksum bytecount 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1e8f09fbc8c6726a4b616dcfbd4f54491068d6bb1b93ac03c78ac18ff9a5924a"
|
"checksum bytecount 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1e8f09fbc8c6726a4b616dcfbd4f54491068d6bb1b93ac03c78ac18ff9a5924a"
|
||||||
"checksum clap 2.19.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95b78f3fe0fc94c13c731714363260e04b557a637166f33a4570d3189d642374"
|
"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c"
|
||||||
|
"checksum clap 2.23.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d480c39a2e5f9b3a3798c661613e1b0e7a7ae71e005102d4aa910fc3289df484"
|
||||||
"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97"
|
"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97"
|
||||||
"checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f"
|
"checksum encoding_rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a1cca0a26f904955d80d70b9bff1019e4f4cbc06f2fcbccf8bd3d889cc1c9b7"
|
||||||
|
"checksum env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e3856f1697098606fc6cb97a93de88ca3f3bc35bb878c725920e6e82ecf05e83"
|
||||||
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
|
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
|
||||||
"checksum fs2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "640001e1bd865c7c32806292822445af576a6866175b5225aa2087ca5e3de551"
|
"checksum fs2 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "34edaee07555859dc13ca387e6ae05686bb4d0364c95d649b6dab959511f4baf"
|
||||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||||
"checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b"
|
"checksum lazy_static 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4732c563b9a21a406565c4747daa7b46742f082911ae4753f390dc9ec7ee1a97"
|
||||||
"checksum libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = "9e030dc72013ed68994d1b2cbf36a94dd0e58418ba949c4b0db7eeb70a7a6352"
|
"checksum libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "88ee81885f9f04bff991e306fea7c1c60a5f0f9e409e99f6b40e3311a3363135"
|
||||||
"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054"
|
"checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad"
|
||||||
"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
|
"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
|
||||||
"checksum memmap 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "065ce59af31c18ea2c419100bda6247dd4ec3099423202b12f0bd32e529fabd2"
|
"checksum memmap 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46f3c7359028b31999287dae4e5047ddfe90a23b7dca2282ce759b491080c99b"
|
||||||
"checksum num_cpus 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a225d1e2717567599c24f88e49f00856c6e825a12125181ee42c4257e3688d39"
|
"checksum num_cpus 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a18c392466409c50b87369414a2680c93e739aedeb498eb2bff7d7eb569744e2"
|
||||||
"checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01"
|
"checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01"
|
||||||
"checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457"
|
"checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457"
|
||||||
"checksum same-file 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c722bde68d432ad7982a6431b13264cc558af1707c0f321820e238c5671856ea"
|
"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
|
||||||
"checksum simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63b5847c2d766ca7ce7227672850955802fabd779ba616aeabead4c2c3877023"
|
"checksum simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63b5847c2d766ca7ce7227672850955802fabd779ba616aeabead4c2c3877023"
|
||||||
"checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c"
|
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
|
||||||
"checksum term_size 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7f5f3f71b0040cecc71af239414c23fd3c73570f5ff54cf50e03cef637f2a0"
|
"checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a"
|
||||||
"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a"
|
"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a"
|
||||||
"checksum thread_local 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7793b722f0f77ce716e7f1acf416359ca32ff24d04ffbac4269f44a4a83be05d"
|
"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7"
|
||||||
"checksum unicode-segmentation 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c3bc443ded17b11305ffffe6b37e2076f328a5a8cb6aa877b1b98f77699e98b5"
|
"checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3"
|
||||||
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
|
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
|
||||||
"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91"
|
"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91"
|
||||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
||||||
"checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f"
|
"checksum vec_map 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8cdc8b93bd0198ed872357fb2e667f7125646b1762f16d60b2c96350d361897"
|
||||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
"checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
|
"checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
|
||||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||||
|
19
Cargo.toml
19
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ripgrep"
|
name = "ripgrep"
|
||||||
version = "0.4.0" #:version
|
version = "0.5.0" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
Line oriented search tool using Rust's regex library. Combines the raw
|
Line oriented search tool using Rust's regex library. Combines the raw
|
||||||
@@ -11,6 +11,7 @@ homepage = "https://github.com/BurntSushi/ripgrep"
|
|||||||
repository = "https://github.com/BurntSushi/ripgrep"
|
repository = "https://github.com/BurntSushi/ripgrep"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
keywords = ["regex", "grep", "egrep", "search", "pattern"]
|
||||||
|
categories = ["command-line-utilities", "text-processing"]
|
||||||
license = "Unlicense/MIT"
|
license = "Unlicense/MIT"
|
||||||
exclude = ["HomebrewFormula"]
|
exclude = ["HomebrewFormula"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
@@ -25,11 +26,13 @@ name = "integration"
|
|||||||
path = "tests/tests.rs"
|
path = "tests/tests.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
atty = "0.2.2"
|
||||||
bytecount = "0.1.4"
|
bytecount = "0.1.4"
|
||||||
clap = "2.19.0"
|
clap = "2.23.1"
|
||||||
env_logger = { version = "0.3", default-features = false }
|
encoding_rs = "0.5.0"
|
||||||
|
env_logger = { version = "0.4", default-features = false }
|
||||||
grep = { version = "0.1.5", path = "grep" }
|
grep = { version = "0.1.5", path = "grep" }
|
||||||
ignore = { version = "0.1.7", path = "ignore" }
|
ignore = { version = "0.1.9", path = "ignore" }
|
||||||
lazy_static = "0.2"
|
lazy_static = "0.2"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
@@ -38,14 +41,10 @@ memmap = "0.5"
|
|||||||
num_cpus = "1"
|
num_cpus = "1"
|
||||||
regex = "0.2.1"
|
regex = "0.2.1"
|
||||||
same-file = "0.1.1"
|
same-file = "0.1.1"
|
||||||
termcolor = { version = "0.2.0", path = "termcolor" }
|
termcolor = { version = "0.3.0", path = "termcolor" }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
|
||||||
kernel32-sys = "0.2.2"
|
|
||||||
winapi = "0.2.8"
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
clap = "2.18"
|
clap = "2.23.1"
|
||||||
lazy_static = "0.2"
|
lazy_static = "0.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
97
README.md
97
README.md
@@ -1,7 +1,7 @@
|
|||||||
ripgrep (rg)
|
ripgrep (rg)
|
||||||
------------
|
------------
|
||||||
`ripgrep` is a line oriented search tool that combines the usability of The
|
`ripgrep` is a line oriented search tool that combines the usability of The
|
||||||
Silver Searcher (an `ack` clone) with the raw speed of GNU grep. `ripgrep`
|
Silver Searcher (similar to `ack`) with the raw speed of GNU grep. `ripgrep`
|
||||||
works by recursively searching your current directory for a regex pattern.
|
works by recursively searching your current directory for a regex pattern.
|
||||||
`ripgrep` has first class support on Windows, Mac and Linux, with binary
|
`ripgrep` has first class support on Windows, Mac and Linux, with binary
|
||||||
downloads available for
|
downloads available for
|
||||||
@@ -13,6 +13,10 @@ downloads available for
|
|||||||
|
|
||||||
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
|
||||||
|
|
||||||
|
### CHANGELOG
|
||||||
|
|
||||||
|
Please see the [CHANGELOG](CHANGELOG.md) for a release history.
|
||||||
|
|
||||||
### Screenshot of search results
|
### Screenshot of search results
|
||||||
|
|
||||||
[](http://burntsushi.net/stuff/ripgrep1.png)
|
[](http://burntsushi.net/stuff/ripgrep1.png)
|
||||||
@@ -83,6 +87,10 @@ increases the times to `3.081s` for ripgrep and `11.403s` for GNU grep.
|
|||||||
of search results, searching multiple patterns, highlighting matches with
|
of search results, searching multiple patterns, highlighting matches with
|
||||||
color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast while
|
color and full Unicode support. Unlike GNU grep, `ripgrep` stays fast while
|
||||||
supporting Unicode (which is always on).
|
supporting Unicode (which is always on).
|
||||||
|
* `ripgrep` supports searching files in text encodings other than UTF-8, such
|
||||||
|
as UTF-16, latin-1, GBK, EUC-JP, Shift_JIS and more. (Some support for
|
||||||
|
automatically detecting UTF-16 is provided. Other text encodings must be
|
||||||
|
specifically specified with the `-E/--encoding` flag.)
|
||||||
|
|
||||||
In other words, use `ripgrep` if you like speed, filtering by default, fewer
|
In other words, use `ripgrep` if you like speed, filtering by default, fewer
|
||||||
bugs and Unicode support.
|
bugs and Unicode support.
|
||||||
@@ -101,18 +109,12 @@ give you a glimpse at some important downsides or missing features of
|
|||||||
support for Unicode categories (e.g., `\p{Sc}` to match currency symbols or
|
support for Unicode categories (e.g., `\p{Sc}` to match currency symbols or
|
||||||
`\p{Lu}` to match any uppercase letter). (Fancier regexes will never be
|
`\p{Lu}` to match any uppercase letter). (Fancier regexes will never be
|
||||||
supported.)
|
supported.)
|
||||||
* If you need to search files with text encodings other than UTF-8 (like
|
|
||||||
UTF-16), then `ripgrep` won't work. `ripgrep` will still work on ASCII
|
|
||||||
compatible encodings like latin1 or otherwise partially valid UTF-8.
|
|
||||||
`ripgrep` *can* search for arbitrary bytes though, which might work in
|
|
||||||
a pinch. (Likely to be supported in the future.)
|
|
||||||
* `ripgrep` doesn't yet support searching compressed files. (Likely to be
|
* `ripgrep` doesn't yet support searching compressed files. (Likely to be
|
||||||
supported in the future.)
|
supported in the future.)
|
||||||
* `ripgrep` doesn't have multiline search. (Unlikely to ever be supported.)
|
* `ripgrep` doesn't have multiline search. (Unlikely to ever be supported.)
|
||||||
|
|
||||||
In other words, if you like fancy regexes, non-UTF-8 character encodings,
|
In other words, if you like fancy regexes, searching compressed files or
|
||||||
searching compressed files or multiline search, then `ripgrep` may not quite
|
multiline search, then `ripgrep` may not quite meet your needs (yet).
|
||||||
meet your needs (yet).
|
|
||||||
|
|
||||||
### Is it really faster than everything else?
|
### Is it really faster than everything else?
|
||||||
|
|
||||||
@@ -167,6 +169,12 @@ $ brew tap burntsushi/ripgrep https://github.com/BurntSushi/ripgrep.git
|
|||||||
$ brew install burntsushi/ripgrep/ripgrep-bin
|
$ brew install burntsushi/ripgrep/ripgrep-bin
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you're a **Windows Chocolatey** user, then you can install `ripgrep` from the [official repo](https://chocolatey.org/packages/ripgrep):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ choco install ripgrep
|
||||||
|
```
|
||||||
|
|
||||||
If you're an **Arch Linux** user, then you can install `ripgrep` from the official repos:
|
If you're an **Arch Linux** user, then you can install `ripgrep` from the official repos:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -201,7 +209,8 @@ $ nix-env --install ripgrep
|
|||||||
$ # (Or using the attribute name, which is also `ripgrep`.)
|
$ # (Or using the attribute name, which is also `ripgrep`.)
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're a **Rust programmer**, `ripgrep` can be installed with `cargo`:
|
If you're a **Rust programmer**, `ripgrep` can be installed with `cargo`. Note
|
||||||
|
that this requires you to have **Rust 1.12 or newer** installed.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cargo install ripgrep
|
$ cargo install ripgrep
|
||||||
@@ -221,11 +230,10 @@ colorize your output and show line numbers, just like The Silver Searcher.
|
|||||||
Coloring works on Windows too! Colors can be controlled more granularly with
|
Coloring works on Windows too! Colors can be controlled more granularly with
|
||||||
the `--color` flag.
|
the `--color` flag.
|
||||||
|
|
||||||
One last thing before we get started: `ripgrep` assumes UTF-8 *everywhere*. It
|
One last thing before we get started: generally speaking, `ripgrep` assumes the
|
||||||
can still search files that are invalid UTF-8 (like, say, latin-1), but it will
|
input is reading is UTF-8. However, if ripgrep notices a file is encoded as
|
||||||
simply not work on UTF-16 encoded files or other more exotic encodings.
|
UTF-16, then it will know how to search it. For other encodings, you'll need to
|
||||||
[Support for other encodings may
|
explicitly specify them with the `-E/--encoding` flag.
|
||||||
happen.](https://github.com/BurntSushi/ripgrep/issues/1)
|
|
||||||
|
|
||||||
To recursively search the current directory, while respecting all `.gitignore`
|
To recursively search the current directory, while respecting all `.gitignore`
|
||||||
files, ignore hidden files and directories and skip binary files:
|
files, ignore hidden files and directories and skip binary files:
|
||||||
@@ -369,3 +377,62 @@ $ cargo test
|
|||||||
```
|
```
|
||||||
|
|
||||||
from the repository root.
|
from the repository root.
|
||||||
|
|
||||||
|
### Tips
|
||||||
|
|
||||||
|
#### Windows Powershell
|
||||||
|
|
||||||
|
##### Powershell Profile
|
||||||
|
|
||||||
|
To customize powershell on start-up there is a special powershell script that has to be created.
|
||||||
|
In order to find its location run command `Get-Command $profile | Select-Object -ExpandProperty Definition`
|
||||||
|
See [more](https://technet.microsoft.com/en-us/library/bb613488(v=vs.85).aspx) for profile details.
|
||||||
|
|
||||||
|
Any powershell code in this file gets evaluated at the start of console.
|
||||||
|
This way you can have own aliases to be created at start.
|
||||||
|
|
||||||
|
##### Setup function alias
|
||||||
|
|
||||||
|
Often you can find a need to make alias for the favourite utility.
|
||||||
|
|
||||||
|
But powershell function aliases do not behave like your typical linux shell alias.
|
||||||
|
|
||||||
|
You always need to propagate arguments and **Stdin** input.
|
||||||
|
But it cannot be done simply as `function grep() { $input | rg.exe --hidden $args }`
|
||||||
|
|
||||||
|
Use below example as reference to how setup alias in powershell.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
function grep {
|
||||||
|
$count = @($input).Count
|
||||||
|
$input.Reset()
|
||||||
|
|
||||||
|
if ($count) {
|
||||||
|
$input | rg.exe --hidden $args
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rg.exe --hidden $args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Powershell special variables:
|
||||||
|
* input - is powershell **Stdin** object that allows you to access its content.
|
||||||
|
* args - is array of arguments passed to this function.
|
||||||
|
|
||||||
|
This alias checks whether there is **Stdin** input and propagates only if there is some lines.
|
||||||
|
Otherwise empty `$input` will make powershell to trigger `rg` to search empty **Stdin**
|
||||||
|
|
||||||
|
### Known issues
|
||||||
|
|
||||||
|
#### I just hit Ctrl+C in the middle of ripgrep's output and now my terminal's foreground color is wrong!
|
||||||
|
|
||||||
|
Type in `color` on Windows and `echo -ne "\033[0m"` on Unix to restore your
|
||||||
|
original foreground color.
|
||||||
|
|
||||||
|
PR [#187](https://github.com/BurntSushi/ripgrep/pull/187) fixed this, and it
|
||||||
|
was later deprecated in
|
||||||
|
[#281](https://github.com/BurntSushi/ripgrep/issues/281). A full explanation is
|
||||||
|
available [here][msys issue explanation].
|
||||||
|
|
||||||
|
[msys issue explanation]: https://github.com/BurntSushi/ripgrep/issues/281#issuecomment-269093893
|
||||||
|
@@ -40,6 +40,7 @@ before_deploy:
|
|||||||
- cargo build --release
|
- cargo build --release
|
||||||
- mkdir staging
|
- mkdir staging
|
||||||
- copy target\release\rg.exe staging
|
- copy target\release\rg.exe staging
|
||||||
|
- ps: copy target\release\build\ripgrep-*\out\_rg.ps1 staging
|
||||||
- cd staging
|
- cd staging
|
||||||
# release zipfile will look like 'rust-everywhere-v1.2.3-x86_64-pc-windows-msvc'
|
# release zipfile will look like 'rust-everywhere-v1.2.3-x86_64-pc-windows-msvc'
|
||||||
- 7z a ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip *
|
- 7z a ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip *
|
||||||
|
2
build.rs
2
build.rs
@@ -19,7 +19,7 @@ fn main() {
|
|||||||
};
|
};
|
||||||
fs::create_dir_all(&outdir).unwrap();
|
fs::create_dir_all(&outdir).unwrap();
|
||||||
|
|
||||||
let mut app = app::app_short();
|
let mut app = app::app();
|
||||||
app.gen_completions("rg", Shell::Bash, &outdir);
|
app.gen_completions("rg", Shell::Bash, &outdir);
|
||||||
app.gen_completions("rg", Shell::Fish, &outdir);
|
app.gen_completions("rg", Shell::Fish, &outdir);
|
||||||
app.gen_completions("rg", Shell::Zsh, &outdir);
|
app.gen_completions("rg", Shell::Zsh, &outdir);
|
||||||
|
@@ -17,9 +17,6 @@ install_c_toolchain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install_rustup() {
|
install_rustup() {
|
||||||
# uninstall the rust toolchain installed by travis, we are going to use rustup
|
|
||||||
sh ~/rust/lib/rustlib/uninstall.sh
|
|
||||||
|
|
||||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain=$TRAVIS_RUST_VERSION
|
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain=$TRAVIS_RUST_VERSION
|
||||||
|
|
||||||
rustc -V
|
rustc -V
|
||||||
|
73
doc/rg.1
73
doc/rg.1
@@ -1,4 +1,4 @@
|
|||||||
.\" Automatically generated by Pandoc 1.19.1
|
.\" Automatically generated by Pandoc 1.19.2.1
|
||||||
.\"
|
.\"
|
||||||
.TH "rg" "1"
|
.TH "rg" "1"
|
||||||
.hy
|
.hy
|
||||||
@@ -64,20 +64,29 @@ Treat the pattern as a literal string instead of a regular expression.
|
|||||||
.TP
|
.TP
|
||||||
.B \-g, \-\-glob \f[I]GLOB\f[] ...
|
.B \-g, \-\-glob \f[I]GLOB\f[] ...
|
||||||
Include or exclude files for searching that match the given glob.
|
Include or exclude files for searching that match the given glob.
|
||||||
This always overrides any other ignore logic.
|
This always overrides any other ignore logic if there is a conflict, but
|
||||||
|
is otherwise applied in addition to ignore files (e.g., .gitignore or
|
||||||
|
\&.ignore).
|
||||||
Multiple glob flags may be used.
|
Multiple glob flags may be used.
|
||||||
Globbing rules match .gitignore globs.
|
Globbing rules match .gitignore globs.
|
||||||
Precede a glob with a \[aq]!\[aq] to exclude it.
|
Precede a glob with a \[aq]!\[aq] to exclude it.
|
||||||
.RS
|
.RS
|
||||||
.PP
|
|
||||||
Values given to \-g must be quoted or your shell will expand them and
|
|
||||||
result in unexpected behavior.
|
|
||||||
.PP
|
|
||||||
Combine with the \-\-files flag to return matched filenames (i.e., to
|
|
||||||
replicate ack/ag\[aq]s \-g flag).
|
|
||||||
.PP
|
|
||||||
For example: rg \-g \[aq]<glob>\[aq] \-\-files
|
|
||||||
.RE
|
.RE
|
||||||
|
.PP
|
||||||
|
The \-\-glob flag subsumes the functionality of both the \-\-include and
|
||||||
|
\-\-exclude flags commonly found in other tools.
|
||||||
|
.IP
|
||||||
|
.nf
|
||||||
|
\f[C]
|
||||||
|
Values\ given\ to\ \-g\ must\ be\ quoted\ or\ your\ shell\ will\ expand\ them\ and\ result
|
||||||
|
in\ unexpected\ behavior.
|
||||||
|
|
||||||
|
Combine\ with\ the\ \-\-files\ flag\ to\ return\ matched\ filenames
|
||||||
|
(i.e.,\ to\ replicate\ ack/ag\[aq]s\ \-g\ flag).
|
||||||
|
|
||||||
|
For\ example:\ rg\ \-g\ \[aq]\\<glob\\>\[aq]\ \-\-files
|
||||||
|
\f[]
|
||||||
|
.fi
|
||||||
.TP
|
.TP
|
||||||
.B \-h, \-\-help
|
.B \-h, \-\-help
|
||||||
Show this usage message.
|
Show this usage message.
|
||||||
@@ -127,6 +136,12 @@ Two \-u flags will search hidden files and directories.
|
|||||||
Three \-u flags will search binary files.
|
Three \-u flags will search binary files.
|
||||||
\-uu is equivalent to grep \-r, and \-uuu is equivalent to grep \-a \-r.
|
\-uu is equivalent to grep \-r, and \-uuu is equivalent to grep \-a \-r.
|
||||||
.RS
|
.RS
|
||||||
|
.PP
|
||||||
|
Note that the \-u flags are convenient aliases for other combinations of
|
||||||
|
flags.
|
||||||
|
\-u aliases \[aq]\-\-no\-ignore\[aq].
|
||||||
|
\-uu aliases \[aq]\-\-no\-ignore \-\-hidden\[aq].
|
||||||
|
\-uuu aliases \[aq]\-\-no\-ignore \-\-hidden \-\-text\[aq].
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B \-v, \-\-invert\-match
|
.B \-v, \-\-invert\-match
|
||||||
@@ -166,7 +181,7 @@ Styles are limited to nobold, bold, nointense or intense.
|
|||||||
.RS
|
.RS
|
||||||
.PP
|
.PP
|
||||||
The format of the flag is {type}:{attribute}:{value}.
|
The format of the flag is {type}:{attribute}:{value}.
|
||||||
{type} should be one of path, line or match.
|
{type} should be one of path, line, column or match.
|
||||||
{attribute} can be fg, bg or style.
|
{attribute} can be fg, bg or style.
|
||||||
Value is either a color (for fg and bg) or a text style.
|
Value is either a color (for fg and bg) or a text style.
|
||||||
A special format, {type}:none, will clear all color settings for {type}.
|
A special format, {type}:none, will clear all color settings for {type}.
|
||||||
@@ -199,6 +214,15 @@ Show debug messages.
|
|||||||
.RS
|
.RS
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
|
.B \-E, \-\-encoding \f[I]ENCODING\f[]
|
||||||
|
Specify the text encoding that ripgrep will use on all files searched.
|
||||||
|
The default value is \[aq]auto\[aq], which will cause ripgrep to do a
|
||||||
|
best effort automatic detection of encoding on a per\-file basis.
|
||||||
|
Other supported values can be found in the list of labels here:
|
||||||
|
https://encoding.spec.whatwg.org/#concept\-encoding\-get
|
||||||
|
.RS
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
.B \-f, \-\-file FILE ...
|
.B \-f, \-\-file FILE ...
|
||||||
Search for patterns from the given file, with one pattern per line.
|
Search for patterns from the given file, with one pattern per line.
|
||||||
When this flag is used or multiple times or in combination with the
|
When this flag is used or multiple times or in combination with the
|
||||||
@@ -275,11 +299,28 @@ Follow symlinks.
|
|||||||
.RS
|
.RS
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B \-m, \-\-max\-count NUM
|
.B \-M, \-\-max\-columns \f[I]NUM\f[]
|
||||||
|
Don\[aq]t print lines longer than this limit in bytes.
|
||||||
|
Longer lines are omitted, and only the number of matches in that line is
|
||||||
|
printed.
|
||||||
|
.RS
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
|
.B \-m, \-\-max\-count \f[I]NUM\f[]
|
||||||
Limit the number of matching lines per file searched to NUM.
|
Limit the number of matching lines per file searched to NUM.
|
||||||
.RS
|
.RS
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
|
.B \-\-max\-filesize \f[I]NUM\f[]+\f[I]SUFFIX\f[]?
|
||||||
|
Ignore files larger than \f[I]NUM\f[] in size.
|
||||||
|
Directories will never be ignored.
|
||||||
|
.RS
|
||||||
|
.PP
|
||||||
|
\f[I]SUFFIX\f[] is optional and may be one of K, M or G.
|
||||||
|
These correspond to kilobytes, megabytes and gigabytes respectively.
|
||||||
|
If omitted the input is treated as bytes.
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
.B \-\-maxdepth \f[I]NUM\f[]
|
.B \-\-maxdepth \f[I]NUM\f[]
|
||||||
Descend at most NUM directories below the command line arguments.
|
Descend at most NUM directories below the command line arguments.
|
||||||
A value of zero searches only the starting\-points themselves.
|
A value of zero searches only the starting\-points themselves.
|
||||||
@@ -321,7 +362,7 @@ Note that .ignore files will continue to be respected.
|
|||||||
.RS
|
.RS
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B \-\-null
|
.B \-0, \-\-null
|
||||||
Whenever a file name is printed, follow it with a NUL byte.
|
Whenever a file name is printed, follow it with a NUL byte.
|
||||||
This includes printing filenames before matches, and when printing a
|
This includes printing filenames before matches, and when printing a
|
||||||
list of matching files such as with \-\-count, \-\-files\-with\-matches
|
list of matching files such as with \-\-count, \-\-files\-with\-matches
|
||||||
@@ -329,6 +370,12 @@ and \-\-files.
|
|||||||
.RS
|
.RS
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
|
.B \-o, \-\-only\-matching
|
||||||
|
Print only the matched (non\-empty) parts of a matching line, with each
|
||||||
|
such part on a separate output line.
|
||||||
|
.RS
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
.B \-\-path\-separator \f[I]SEPARATOR\f[]
|
.B \-\-path\-separator \f[I]SEPARATOR\f[]
|
||||||
The path separator to use when printing file paths.
|
The path separator to use when printing file paths.
|
||||||
This defaults to your platform\[aq]s path separator, which is / on Unix
|
This defaults to your platform\[aq]s path separator, which is / on Unix
|
||||||
|
45
doc/rg.1.md
45
doc/rg.1.md
@@ -49,8 +49,13 @@ Project home page: https://github.com/BurntSushi/ripgrep
|
|||||||
|
|
||||||
-g, --glob *GLOB* ...
|
-g, --glob *GLOB* ...
|
||||||
: Include or exclude files for searching that match the given glob. This always
|
: Include or exclude files for searching that match the given glob. This always
|
||||||
overrides any other ignore logic. Multiple glob flags may be used. Globbing
|
overrides any other ignore logic if there is a conflict, but is otherwise
|
||||||
rules match .gitignore globs. Precede a glob with a '!' to exclude it.
|
applied in addition to ignore files (e.g., .gitignore or .ignore). Multiple
|
||||||
|
glob flags may be used. Globbing rules match .gitignore globs. Precede a
|
||||||
|
glob with a '!' to exclude it.
|
||||||
|
|
||||||
|
The --glob flag subsumes the functionality of both the --include and
|
||||||
|
--exclude flags commonly found in other tools.
|
||||||
|
|
||||||
Values given to -g must be quoted or your shell will expand them and result
|
Values given to -g must be quoted or your shell will expand them and result
|
||||||
in unexpected behavior.
|
in unexpected behavior.
|
||||||
@@ -89,6 +94,10 @@ Project home page: https://github.com/BurntSushi/ripgrep
|
|||||||
-u flags will search binary files. -uu is equivalent to grep -r, and -uuu is
|
-u flags will search binary files. -uu is equivalent to grep -r, and -uuu is
|
||||||
equivalent to grep -a -r.
|
equivalent to grep -a -r.
|
||||||
|
|
||||||
|
Note that the -u flags are convenient aliases for other combinations of
|
||||||
|
flags. -u aliases '--no-ignore'. -uu aliases '--no-ignore --hidden'.
|
||||||
|
-uuu aliases '--no-ignore --hidden --text'.
|
||||||
|
|
||||||
-v, --invert-match
|
-v, --invert-match
|
||||||
: Invert matching.
|
: Invert matching.
|
||||||
|
|
||||||
@@ -114,9 +123,9 @@ Project home page: https://github.com/BurntSushi/ripgrep
|
|||||||
black. Styles are limited to nobold, bold, nointense or intense.
|
black. Styles are limited to nobold, bold, nointense or intense.
|
||||||
|
|
||||||
The format of the flag is {type}:{attribute}:{value}. {type} should be one
|
The format of the flag is {type}:{attribute}:{value}. {type} should be one
|
||||||
of path, line or match. {attribute} can be fg, bg or style. Value is either
|
of path, line, column or match. {attribute} can be fg, bg or style. Value
|
||||||
a color (for fg and bg) or a text style. A special format, {type}:none,
|
is either a color (for fg and bg) or a text style. A special format,
|
||||||
will clear all color settings for {type}.
|
{type}:none, will clear all color settings for {type}.
|
||||||
|
|
||||||
For example, the following command will change the match color to magenta
|
For example, the following command will change the match color to magenta
|
||||||
and the background color for line numbers to yellow:
|
and the background color for line numbers to yellow:
|
||||||
@@ -136,6 +145,13 @@ Project home page: https://github.com/BurntSushi/ripgrep
|
|||||||
--debug
|
--debug
|
||||||
: Show debug messages.
|
: Show debug messages.
|
||||||
|
|
||||||
|
-E, --encoding *ENCODING*
|
||||||
|
: Specify the text encoding that ripgrep will use on all files
|
||||||
|
searched. The default value is 'auto', which will cause ripgrep to do
|
||||||
|
a best effort automatic detection of encoding on a per-file basis.
|
||||||
|
Other supported values can be found in the list of labels here:
|
||||||
|
https://encoding.spec.whatwg.org/#concept-encoding-get
|
||||||
|
|
||||||
-f, --file FILE ...
|
-f, --file FILE ...
|
||||||
: Search for patterns from the given file, with one pattern per line. When this
|
: Search for patterns from the given file, with one pattern per line. When this
|
||||||
flag is used or multiple times or in combination with the -e/--regexp flag,
|
flag is used or multiple times or in combination with the -e/--regexp flag,
|
||||||
@@ -187,9 +203,20 @@ Project home page: https://github.com/BurntSushi/ripgrep
|
|||||||
-L, --follow
|
-L, --follow
|
||||||
: Follow symlinks.
|
: Follow symlinks.
|
||||||
|
|
||||||
-m, --max-count NUM
|
-M, --max-columns *NUM*
|
||||||
|
: Don't print lines longer than this limit in bytes. Longer lines are omitted,
|
||||||
|
and only the number of matches in that line is printed.
|
||||||
|
|
||||||
|
-m, --max-count *NUM*
|
||||||
: Limit the number of matching lines per file searched to NUM.
|
: Limit the number of matching lines per file searched to NUM.
|
||||||
|
|
||||||
|
--max-filesize *NUM*+*SUFFIX*?
|
||||||
|
: Ignore files larger than *NUM* in size. Directories will never be ignored.
|
||||||
|
|
||||||
|
*SUFFIX* is optional and may be one of K, M or G. These correspond to
|
||||||
|
kilobytes, megabytes and gigabytes respectively. If omitted the input is
|
||||||
|
treated as bytes.
|
||||||
|
|
||||||
--maxdepth *NUM*
|
--maxdepth *NUM*
|
||||||
: Descend at most NUM directories below the command line arguments.
|
: Descend at most NUM directories below the command line arguments.
|
||||||
A value of zero searches only the starting-points themselves.
|
A value of zero searches only the starting-points themselves.
|
||||||
@@ -216,12 +243,16 @@ Project home page: https://github.com/BurntSushi/ripgrep
|
|||||||
: Don't respect version control ignore files (e.g., .gitignore).
|
: Don't respect version control ignore files (e.g., .gitignore).
|
||||||
Note that .ignore files will continue to be respected.
|
Note that .ignore files will continue to be respected.
|
||||||
|
|
||||||
--null
|
-0, --null
|
||||||
: Whenever a file name is printed, follow it with a NUL byte.
|
: Whenever a file name is printed, follow it with a NUL byte.
|
||||||
This includes printing filenames before matches, and when printing
|
This includes printing filenames before matches, and when printing
|
||||||
a list of matching files such as with --count, --files-with-matches
|
a list of matching files such as with --count, --files-with-matches
|
||||||
and --files.
|
and --files.
|
||||||
|
|
||||||
|
-o, --only-matching
|
||||||
|
: Print only the matched (non-empty) parts of a matching line, with each such
|
||||||
|
part on a separate output line.
|
||||||
|
|
||||||
--path-separator *SEPARATOR*
|
--path-separator *SEPARATOR*
|
||||||
: The path separator to use when printing file paths. This defaults to your
|
: The path separator to use when printing file paths. This defaults to your
|
||||||
platform's path separator, which is / on Unix and \\ on Windows. This flag is
|
platform's path separator, which is / on Unix and \\ on Windows. This flag is
|
||||||
|
3
globset/COPYING
Normal file
3
globset/COPYING
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
This project is dual-licensed under the Unlicense and MIT licenses.
|
||||||
|
|
||||||
|
You may use this code under the terms of either license.
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.1.3" #:version
|
version = "0.1.4" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
Cross platform single glob and glob set matching. Glob set matching is the
|
Cross platform single glob and glob set matching. Glob set matching is the
|
||||||
@@ -21,7 +21,6 @@ bench = false
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
aho-corasick = "0.6.0"
|
aho-corasick = "0.6.0"
|
||||||
fnv = "1.0"
|
fnv = "1.0"
|
||||||
lazy_static = "0.2"
|
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
memchr = "1"
|
memchr = "1"
|
||||||
regex = "0.2.1"
|
regex = "0.2.1"
|
||||||
|
21
globset/LICENSE-MIT
Normal file
21
globset/LICENSE-MIT
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Andrew Gallant
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
24
globset/UNLICENSE
Normal file
24
globset/UNLICENSE
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org/>
|
@@ -1,5 +1,6 @@
|
|||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::hash;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::{Path, is_separator};
|
use std::path::{Path, is_separator};
|
||||||
@@ -76,7 +77,7 @@ impl MatchStrategy {
|
|||||||
///
|
///
|
||||||
/// It cannot be used directly to match file paths, but it can be converted
|
/// It cannot be used directly to match file paths, but it can be converted
|
||||||
/// to a regular expression string or a matcher.
|
/// to a regular expression string or a matcher.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq)]
|
||||||
pub struct Glob {
|
pub struct Glob {
|
||||||
glob: String,
|
glob: String,
|
||||||
re: String,
|
re: String,
|
||||||
@@ -84,6 +85,19 @@ pub struct Glob {
|
|||||||
tokens: Tokens,
|
tokens: Tokens,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Glob {
|
||||||
|
fn eq(&self, other: &Glob) -> bool {
|
||||||
|
self.glob == other.glob && self.opts == other.opts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl hash::Hash for Glob {
|
||||||
|
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.glob.hash(state);
|
||||||
|
self.opts.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for Glob {
|
impl fmt::Display for Glob {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
self.glob.fmt(f)
|
self.glob.fmt(f)
|
||||||
@@ -173,7 +187,7 @@ pub struct GlobBuilder<'a> {
|
|||||||
opts: GlobOptions,
|
opts: GlobOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||||
struct GlobOptions {
|
struct GlobOptions {
|
||||||
/// Whether to match case insensitively.
|
/// Whether to match case insensitively.
|
||||||
case_insensitive: bool,
|
case_insensitive: bool,
|
||||||
@@ -645,9 +659,18 @@ impl Tokens {
|
|||||||
for pat in patterns {
|
for pat in patterns {
|
||||||
let mut altre = String::new();
|
let mut altre = String::new();
|
||||||
self.tokens_to_regex(options, &pat, &mut altre);
|
self.tokens_to_regex(options, &pat, &mut altre);
|
||||||
parts.push(altre);
|
if !altre.is_empty() {
|
||||||
|
parts.push(altre);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is possible to have an empty set in which case the
|
||||||
|
// resulting alternation '()' would be an error.
|
||||||
|
if !parts.is_empty() {
|
||||||
|
re.push('(');
|
||||||
|
re.push_str(&parts.join("|"));
|
||||||
|
re.push(')');
|
||||||
}
|
}
|
||||||
re.push_str(&parts.join("|"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -101,8 +101,6 @@ or to enable case insensitive matching.
|
|||||||
extern crate aho_corasick;
|
extern crate aho_corasick;
|
||||||
extern crate fnv;
|
extern crate fnv;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate memchr;
|
extern crate memchr;
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
|
3
grep/COPYING
Normal file
3
grep/COPYING
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
This project is dual-licensed under the Unlicense and MIT licenses.
|
||||||
|
|
||||||
|
You may use this code under the terms of either license.
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "grep"
|
name = "grep"
|
||||||
version = "0.1.5" #:version
|
version = "0.1.6" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
Fast line oriented regex searching as a library.
|
Fast line oriented regex searching as a library.
|
||||||
|
21
grep/LICENSE-MIT
Normal file
21
grep/LICENSE-MIT
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Andrew Gallant
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
24
grep/UNLICENSE
Normal file
24
grep/UNLICENSE
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org/>
|
3
ignore/COPYING
Normal file
3
ignore/COPYING
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
This project is dual-licensed under the Unlicense and MIT licenses.
|
||||||
|
|
||||||
|
You may use this code under the terms of either license.
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ignore"
|
name = "ignore"
|
||||||
version = "0.1.7" #:version
|
version = "0.1.9" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
A fast library for efficiently matching ignore files such as `.gitignore`
|
A fast library for efficiently matching ignore files such as `.gitignore`
|
||||||
@@ -19,7 +19,7 @@ bench = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam = "0.2"
|
crossbeam = "0.2"
|
||||||
globset = { version = "0.1.3", path = "../globset" }
|
globset = { version = "0.1.4", path = "../globset" }
|
||||||
lazy_static = "0.2"
|
lazy_static = "0.2"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
memchr = "1"
|
memchr = "1"
|
||||||
|
21
ignore/LICENSE-MIT
Normal file
21
ignore/LICENSE-MIT
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Andrew Gallant
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
24
ignore/UNLICENSE
Normal file
24
ignore/UNLICENSE
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org/>
|
@@ -368,9 +368,10 @@ impl GitignoreBuilder {
|
|||||||
};
|
};
|
||||||
let mut literal_separator = false;
|
let mut literal_separator = false;
|
||||||
let has_slash = line.chars().any(|c| c == '/');
|
let has_slash = line.chars().any(|c| c == '/');
|
||||||
let is_absolute = line.chars().nth(0).unwrap() == '/';
|
let mut is_absolute = false;
|
||||||
if line.starts_with("\\!") || line.starts_with("\\#") {
|
if line.starts_with("\\!") || line.starts_with("\\#") {
|
||||||
line = &line[1..];
|
line = &line[1..];
|
||||||
|
is_absolute = line.chars().nth(0) == Some('/');
|
||||||
} else {
|
} else {
|
||||||
if line.starts_with("!") {
|
if line.starts_with("!") {
|
||||||
glob.is_whitelist = true;
|
glob.is_whitelist = true;
|
||||||
@@ -383,6 +384,7 @@ impl GitignoreBuilder {
|
|||||||
// simply banning wildcards from matching /.
|
// simply banning wildcards from matching /.
|
||||||
literal_separator = true;
|
literal_separator = true;
|
||||||
line = &line[1..];
|
line = &line[1..];
|
||||||
|
is_absolute = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If it ends with a slash, then this should only match directories,
|
// If it ends with a slash, then this should only match directories,
|
||||||
@@ -454,7 +456,7 @@ fn gitconfig_contents() -> Option<Vec<u8>> {
|
|||||||
fn excludes_file_default() -> Option<PathBuf> {
|
fn excludes_file_default() -> Option<PathBuf> {
|
||||||
env::var_os("XDG_CONFIG_HOME")
|
env::var_os("XDG_CONFIG_HOME")
|
||||||
.and_then(|x| if x.is_empty() { None } else { Some(PathBuf::from(x)) })
|
.and_then(|x| if x.is_empty() { None } else { Some(PathBuf::from(x)) })
|
||||||
.or_else(|| env::home_dir())
|
.or_else(|| env::home_dir().map(|p| p.join(".config")))
|
||||||
.map(|x| x.join("git/ignore"))
|
.map(|x| x.join("git/ignore"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,6 +572,7 @@ mod tests {
|
|||||||
not_ignored!(
|
not_ignored!(
|
||||||
ignot14, "./third_party/protobuf", "m4/ltoptions.m4",
|
ignot14, "./third_party/protobuf", "m4/ltoptions.m4",
|
||||||
"./third_party/protobuf/csharp/src/packages/repositories.config");
|
"./third_party/protobuf/csharp/src/packages/repositories.config");
|
||||||
|
not_ignored!(ignot15, ROOT, "!/bar", "foo/bar");
|
||||||
|
|
||||||
fn bytes(s: &str) -> Vec<u8> {
|
fn bytes(s: &str) -> Vec<u8> {
|
||||||
s.to_string().into_bytes()
|
s.to_string().into_bytes()
|
||||||
|
@@ -214,4 +214,10 @@ mod tests {
|
|||||||
assert!(ov.matched("src/foo", false).is_ignore());
|
assert!(ov.matched("src/foo", false).is_ignore());
|
||||||
assert!(ov.matched("src/foo", true).is_none());
|
assert!(ov.matched("src/foo", true).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn absolute_path() {
|
||||||
|
let ov = ov(&["!/bar"]);
|
||||||
|
assert!(ov.matched("./foo/bar", false).is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -103,6 +103,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("awk", &["*.awk"]),
|
("awk", &["*.awk"]),
|
||||||
("c", &["*.c", "*.h", "*.H"]),
|
("c", &["*.c", "*.h", "*.H"]),
|
||||||
("cbor", &["*.cbor"]),
|
("cbor", &["*.cbor"]),
|
||||||
|
("ceylon", &["*.ceylon"]),
|
||||||
("clojure", &["*.clj", "*.cljc", "*.cljs", "*.cljx"]),
|
("clojure", &["*.clj", "*.cljc", "*.cljs", "*.cljx"]),
|
||||||
("cmake", &["*.cmake", "CMakeLists.txt"]),
|
("cmake", &["*.cmake", "CMakeLists.txt"]),
|
||||||
("coffeescript", &["*.coffee"]),
|
("coffeescript", &["*.coffee"]),
|
||||||
@@ -115,12 +116,12 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("crystal", &["Projectfile", "*.cr"]),
|
("crystal", &["Projectfile", "*.cr"]),
|
||||||
("cs", &["*.cs"]),
|
("cs", &["*.cs"]),
|
||||||
("csharp", &["*.cs"]),
|
("csharp", &["*.cs"]),
|
||||||
("css", &["*.css"]),
|
("css", &["*.css", "*.scss"]),
|
||||||
("cython", &["*.pyx"]),
|
("cython", &["*.pyx"]),
|
||||||
("dart", &["*.dart"]),
|
("dart", &["*.dart"]),
|
||||||
("d", &["*.d"]),
|
("d", &["*.d"]),
|
||||||
("elisp", &["*.el"]),
|
("elisp", &["*.el"]),
|
||||||
("elixir", &["*.ex", "*.exs"]),
|
("elixir", &["*.ex", "*.eex", "*.exs"]),
|
||||||
("erlang", &["*.erl", "*.hrl"]),
|
("erlang", &["*.erl", "*.hrl"]),
|
||||||
("fish", &["*.fish"]),
|
("fish", &["*.fish"]),
|
||||||
("fortran", &[
|
("fortran", &[
|
||||||
@@ -133,7 +134,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("h", &["*.h", "*.hpp"]),
|
("h", &["*.h", "*.hpp"]),
|
||||||
("hbs", &["*.hbs"]),
|
("hbs", &["*.hbs"]),
|
||||||
("haskell", &["*.hs", "*.lhs"]),
|
("haskell", &["*.hs", "*.lhs"]),
|
||||||
("html", &["*.htm", "*.html"]),
|
("html", &["*.htm", "*.html", "*.ejs"]),
|
||||||
("java", &["*.java"]),
|
("java", &["*.java"]),
|
||||||
("jinja", &["*.jinja", "*.jinja2"]),
|
("jinja", &["*.jinja", "*.jinja2"]),
|
||||||
("js", &[
|
("js", &[
|
||||||
@@ -144,6 +145,7 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("kotlin", &["*.kt", "*.kts"]),
|
("kotlin", &["*.kt", "*.kts"]),
|
||||||
("less", &["*.less"]),
|
("less", &["*.less"]),
|
||||||
("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
|
("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
|
||||||
|
("log", &["*.log"]),
|
||||||
("lua", &["*.lua"]),
|
("lua", &["*.lua"]),
|
||||||
("m4", &["*.ac", "*.m4"]),
|
("m4", &["*.ac", "*.m4"]),
|
||||||
("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk", "*.mak"]),
|
("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk", "*.mak"]),
|
||||||
@@ -169,13 +171,14 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("rst", &["*.rst"]),
|
("rst", &["*.rst"]),
|
||||||
("ruby", &["Gemfile", "*.gemspec", ".irbrc", "Rakefile", "*.rb"]),
|
("ruby", &["Gemfile", "*.gemspec", ".irbrc", "Rakefile", "*.rb"]),
|
||||||
("rust", &["*.rs"]),
|
("rust", &["*.rs"]),
|
||||||
("sass", &["*.scss"]),
|
("sass", &["*.sass", "*.scss"]),
|
||||||
("scala", &["*.scala"]),
|
("scala", &["*.scala"]),
|
||||||
("sh", &["*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh"]),
|
("sh", &["*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh"]),
|
||||||
("spark", &["*.spark"]),
|
("spark", &["*.spark"]),
|
||||||
("stylus", &["*.styl"]),
|
("stylus", &["*.styl"]),
|
||||||
("sql", &["*.sql"]),
|
("sql", &["*.sql"]),
|
||||||
("sv", &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
|
("sv", &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
|
||||||
|
("svg", &["*.svg"]),
|
||||||
("swift", &["*.swift"]),
|
("swift", &["*.swift"]),
|
||||||
("swig", &["*.def", "*.i"]),
|
("swig", &["*.def", "*.i"]),
|
||||||
("taskpaper", &["*.taskpaper"]),
|
("taskpaper", &["*.taskpaper"]),
|
||||||
@@ -185,8 +188,10 @@ const DEFAULT_TYPES: &'static [(&'static str, &'static [&'static str])] = &[
|
|||||||
("ts", &["*.ts", "*.tsx"]),
|
("ts", &["*.ts", "*.tsx"]),
|
||||||
("txt", &["*.txt"]),
|
("txt", &["*.txt"]),
|
||||||
("toml", &["*.toml", "Cargo.lock"]),
|
("toml", &["*.toml", "Cargo.lock"]),
|
||||||
|
("twig", &["*.twig"]),
|
||||||
("vala", &["*.vala"]),
|
("vala", &["*.vala"]),
|
||||||
("vb", &["*.vb"]),
|
("vb", &["*.vb"]),
|
||||||
|
("vim", &["*.vim"]),
|
||||||
("vimscript", &["*.vim"]),
|
("vimscript", &["*.vim"]),
|
||||||
("wiki", &["*.mediawiki", "*.wiki"]),
|
("wiki", &["*.mediawiki", "*.wiki"]),
|
||||||
("xml", &["*.xml"]),
|
("xml", &["*.xml"]),
|
||||||
|
@@ -392,7 +392,9 @@ impl DirEntryRaw {
|
|||||||
/// continues.
|
/// continues.
|
||||||
/// * Fifth, if the path hasn't been whitelisted and it is hidden, then the
|
/// * Fifth, if the path hasn't been whitelisted and it is hidden, then the
|
||||||
/// path is skipped.
|
/// path is skipped.
|
||||||
/// * Sixth, if the path has made it this far then it is yielded in the
|
/// * Sixth, unless the path is a directory, the size of the file is compared
|
||||||
|
/// against the max filesize limit. If it exceeds the limit, it is skipped.
|
||||||
|
/// * Seventh, if the path has made it this far then it is yielded in the
|
||||||
/// iterator.
|
/// iterator.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WalkBuilder {
|
pub struct WalkBuilder {
|
||||||
@@ -400,6 +402,7 @@ pub struct WalkBuilder {
|
|||||||
ig_builder: IgnoreBuilder,
|
ig_builder: IgnoreBuilder,
|
||||||
parents: bool,
|
parents: bool,
|
||||||
max_depth: Option<usize>,
|
max_depth: Option<usize>,
|
||||||
|
max_filesize: Option<u64>,
|
||||||
follow_links: bool,
|
follow_links: bool,
|
||||||
sorter: Option<Arc<Fn(&OsString, &OsString) -> cmp::Ordering + 'static>>,
|
sorter: Option<Arc<Fn(&OsString, &OsString) -> cmp::Ordering + 'static>>,
|
||||||
threads: usize,
|
threads: usize,
|
||||||
@@ -412,6 +415,7 @@ impl fmt::Debug for WalkBuilder {
|
|||||||
.field("ig_builder", &self.ig_builder)
|
.field("ig_builder", &self.ig_builder)
|
||||||
.field("parents", &self.parents)
|
.field("parents", &self.parents)
|
||||||
.field("max_depth", &self.max_depth)
|
.field("max_depth", &self.max_depth)
|
||||||
|
.field("max_filesize", &self.max_filesize)
|
||||||
.field("follow_links", &self.follow_links)
|
.field("follow_links", &self.follow_links)
|
||||||
.field("threads", &self.threads)
|
.field("threads", &self.threads)
|
||||||
.finish()
|
.finish()
|
||||||
@@ -431,6 +435,7 @@ impl WalkBuilder {
|
|||||||
ig_builder: IgnoreBuilder::new(),
|
ig_builder: IgnoreBuilder::new(),
|
||||||
parents: true,
|
parents: true,
|
||||||
max_depth: None,
|
max_depth: None,
|
||||||
|
max_filesize: None,
|
||||||
follow_links: false,
|
follow_links: false,
|
||||||
sorter: None,
|
sorter: None,
|
||||||
threads: 0,
|
threads: 0,
|
||||||
@@ -464,6 +469,7 @@ impl WalkBuilder {
|
|||||||
it: None,
|
it: None,
|
||||||
ig_root: ig_root.clone(),
|
ig_root: ig_root.clone(),
|
||||||
ig: ig_root.clone(),
|
ig: ig_root.clone(),
|
||||||
|
max_filesize: self.max_filesize,
|
||||||
parents: self.parents,
|
parents: self.parents,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,6 +484,7 @@ impl WalkBuilder {
|
|||||||
paths: self.paths.clone().into_iter(),
|
paths: self.paths.clone().into_iter(),
|
||||||
ig_root: self.ig_builder.build(),
|
ig_root: self.ig_builder.build(),
|
||||||
max_depth: self.max_depth,
|
max_depth: self.max_depth,
|
||||||
|
max_filesize: self.max_filesize,
|
||||||
follow_links: self.follow_links,
|
follow_links: self.follow_links,
|
||||||
parents: self.parents,
|
parents: self.parents,
|
||||||
threads: self.threads,
|
threads: self.threads,
|
||||||
@@ -508,6 +515,12 @@ impl WalkBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether to ignore files above the specified limit.
|
||||||
|
pub fn max_filesize(&mut self, filesize: Option<u64>) -> &mut WalkBuilder {
|
||||||
|
self.max_filesize = filesize;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// The number of threads to use for traversal.
|
/// The number of threads to use for traversal.
|
||||||
///
|
///
|
||||||
/// Note that this only has an effect when using `build_parallel`.
|
/// Note that this only has an effect when using `build_parallel`.
|
||||||
@@ -650,6 +663,7 @@ pub struct Walk {
|
|||||||
it: Option<WalkEventIter>,
|
it: Option<WalkEventIter>,
|
||||||
ig_root: Ignore,
|
ig_root: Ignore,
|
||||||
ig: Ignore,
|
ig: Ignore,
|
||||||
|
max_filesize: Option<u64>,
|
||||||
parents: bool,
|
parents: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -667,7 +681,17 @@ impl Walk {
|
|||||||
if ent.depth() == 0 {
|
if ent.depth() == 0 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
skip_path(&self.ig, ent.path(), ent.file_type().is_dir())
|
|
||||||
|
let is_dir = ent.file_type().is_dir();
|
||||||
|
let max_size = self.max_filesize;
|
||||||
|
let should_skip_path = skip_path(&self.ig, ent.path(), is_dir);
|
||||||
|
let should_skip_filesize = if !is_dir && max_size.is_some() {
|
||||||
|
skip_filesize(max_size.unwrap(), ent.path(), &ent.metadata().ok())
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
should_skip_path || should_skip_filesize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,6 +848,7 @@ pub struct WalkParallel {
|
|||||||
paths: vec::IntoIter<PathBuf>,
|
paths: vec::IntoIter<PathBuf>,
|
||||||
ig_root: Ignore,
|
ig_root: Ignore,
|
||||||
parents: bool,
|
parents: bool,
|
||||||
|
max_filesize: Option<u64>,
|
||||||
max_depth: Option<usize>,
|
max_depth: Option<usize>,
|
||||||
follow_links: bool,
|
follow_links: bool,
|
||||||
threads: usize,
|
threads: usize,
|
||||||
@@ -886,6 +911,7 @@ impl WalkParallel {
|
|||||||
threads: threads,
|
threads: threads,
|
||||||
parents: self.parents,
|
parents: self.parents,
|
||||||
max_depth: self.max_depth,
|
max_depth: self.max_depth,
|
||||||
|
max_filesize: self.max_filesize,
|
||||||
follow_links: self.follow_links,
|
follow_links: self.follow_links,
|
||||||
};
|
};
|
||||||
handles.push(thread::spawn(|| worker.run()));
|
handles.push(thread::spawn(|| worker.run()));
|
||||||
@@ -1000,6 +1026,9 @@ struct Worker {
|
|||||||
/// The maximum depth of directories to descend. A value of `0` means no
|
/// The maximum depth of directories to descend. A value of `0` means no
|
||||||
/// descension at all.
|
/// descension at all.
|
||||||
max_depth: Option<usize>,
|
max_depth: Option<usize>,
|
||||||
|
/// The maximum size a searched file can be (in bytes). If a file exceeds
|
||||||
|
/// this size it will be skipped.
|
||||||
|
max_filesize: Option<u64>,
|
||||||
/// Whether to follow symbolic links or not. When this is enabled, loop
|
/// Whether to follow symbolic links or not. When this is enabled, loop
|
||||||
/// detection is performed.
|
/// detection is performed.
|
||||||
follow_links: bool,
|
follow_links: bool,
|
||||||
@@ -1106,7 +1135,15 @@ impl Worker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let is_dir = dent.file_type().map_or(false, |ft| ft.is_dir());
|
let is_dir = dent.file_type().map_or(false, |ft| ft.is_dir());
|
||||||
if !skip_path(ig, dent.path(), is_dir) {
|
let max_size = self.max_filesize;
|
||||||
|
let should_skip_path = skip_path(ig, dent.path(), is_dir);
|
||||||
|
let should_skip_filesize = if !is_dir && max_size.is_some() {
|
||||||
|
skip_filesize(max_size.unwrap(), dent.path(), &dent.metadata().ok())
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if !should_skip_path && !should_skip_filesize {
|
||||||
self.queue.push(Message::Work(Work {
|
self.queue.push(Message::Work(Work {
|
||||||
dent: dent,
|
dent: dent,
|
||||||
ignore: ig.clone(),
|
ignore: ig.clone(),
|
||||||
@@ -1253,6 +1290,30 @@ fn check_symlink_loop(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Before calling this function, make sure that you ensure that is really
|
||||||
|
// necessary as the arguments imply a file stat.
|
||||||
|
fn skip_filesize(
|
||||||
|
max_filesize: u64,
|
||||||
|
path: &Path,
|
||||||
|
ent: &Option<Metadata>
|
||||||
|
) -> bool {
|
||||||
|
let filesize = match *ent {
|
||||||
|
Some(ref md) => Some(md.len()),
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(fs) = filesize {
|
||||||
|
if fs > max_filesize {
|
||||||
|
debug!("ignoring {}: {} bytes", path.display(), fs);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn skip_path(ig: &Ignore, path: &Path, is_dir: bool) -> bool {
|
fn skip_path(ig: &Ignore, path: &Path, is_dir: bool) -> bool {
|
||||||
let m = ig.matched(path, is_dir);
|
let m = ig.matched(path, is_dir);
|
||||||
if m.is_ignore() {
|
if m.is_ignore() {
|
||||||
@@ -1282,6 +1343,11 @@ mod tests {
|
|||||||
file.write_all(contents.as_bytes()).unwrap();
|
file.write_all(contents.as_bytes()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wfile_size<P: AsRef<Path>>(path: P, size: u64) {
|
||||||
|
let file = File::create(path).unwrap();
|
||||||
|
file.set_len(size).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) {
|
fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) {
|
||||||
use std::os::unix::fs::symlink;
|
use std::os::unix::fs::symlink;
|
||||||
@@ -1438,6 +1504,32 @@ mod tests {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn max_filesize() {
|
||||||
|
let td = TempDir::new("walk-test-").unwrap();
|
||||||
|
mkdirp(td.path().join("a/b"));
|
||||||
|
wfile_size(td.path().join("foo"), 0);
|
||||||
|
wfile_size(td.path().join("bar"), 400);
|
||||||
|
wfile_size(td.path().join("baz"), 600);
|
||||||
|
wfile_size(td.path().join("a/foo"), 600);
|
||||||
|
wfile_size(td.path().join("a/bar"), 500);
|
||||||
|
wfile_size(td.path().join("a/baz"), 200);
|
||||||
|
|
||||||
|
let mut builder = WalkBuilder::new(td.path());
|
||||||
|
assert_paths(td.path(), &builder, &[
|
||||||
|
"a", "a/b", "foo", "bar", "baz", "a/foo", "a/bar", "a/baz",
|
||||||
|
]);
|
||||||
|
assert_paths(td.path(), builder.max_filesize(Some(0)), &[
|
||||||
|
"a", "a/b", "foo"
|
||||||
|
]);
|
||||||
|
assert_paths(td.path(), builder.max_filesize(Some(500)), &[
|
||||||
|
"a", "a/b", "foo", "bar", "a/bar", "a/baz"
|
||||||
|
]);
|
||||||
|
assert_paths(td.path(), builder.max_filesize(Some(50000)), &[
|
||||||
|
"a", "a/b", "foo", "bar", "baz", "a/foo", "a/bar", "a/baz",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)] // because symlinks on windows are weird
|
#[cfg(unix)] // because symlinks on windows are weird
|
||||||
#[test]
|
#[test]
|
||||||
fn symlinks() {
|
fn symlinks() {
|
||||||
|
@@ -1,14 +1,18 @@
|
|||||||
class RipgrepBin < Formula
|
class RipgrepBin < Formula
|
||||||
version '0.3.2'
|
version '0.5.0'
|
||||||
desc "Search tool like grep and The Silver Searcher."
|
desc "Search tool like grep and The Silver Searcher."
|
||||||
homepage "https://github.com/BurntSushi/ripgrep"
|
homepage "https://github.com/BurntSushi/ripgrep"
|
||||||
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-apple-darwin.tar.gz"
|
url "https://github.com/BurntSushi/ripgrep/releases/download/#{version}/ripgrep-#{version}-x86_64-apple-darwin.tar.gz"
|
||||||
sha256 "05869abe67104822d29081f12e31e3e90c29cac60ee50546387b17e9be45739c"
|
sha256 "5bfa8872c4f2a5d010ddec1c213d518056e62d4dd3b3f23a0ef099b85343dbdd"
|
||||||
|
|
||||||
conflicts_with "ripgrep"
|
conflicts_with "ripgrep"
|
||||||
|
|
||||||
def install
|
def install
|
||||||
bin.install "rg"
|
bin.install "rg"
|
||||||
man1.install "rg.1"
|
man1.install "rg.1"
|
||||||
|
|
||||||
|
bash_completion.install "complete/rg.bash-completion"
|
||||||
|
fish_completion.install "complete/rg.fish"
|
||||||
|
zsh_completion.install "complete/_rg"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
84
src/app.rs
84
src/app.rs
@@ -32,16 +32,6 @@ ARGS:
|
|||||||
OPTIONS:
|
OPTIONS:
|
||||||
{unified}";
|
{unified}";
|
||||||
|
|
||||||
/// Build a clap application with short help strings.
|
|
||||||
pub fn app_short() -> App<'static, 'static> {
|
|
||||||
app(false, |k| USAGES[k].short)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a clap application with long help strings.
|
|
||||||
pub fn app_long() -> App<'static, 'static> {
|
|
||||||
app(true, |k| USAGES[k].long)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a clap application parameterized by usage strings.
|
/// Build a clap application parameterized by usage strings.
|
||||||
///
|
///
|
||||||
/// The function given should take a clap argument name and return a help
|
/// The function given should take a clap argument name and return a help
|
||||||
@@ -49,10 +39,9 @@ pub fn app_long() -> App<'static, 'static> {
|
|||||||
///
|
///
|
||||||
/// This is an intentionally stand-alone module so that it can be used easily
|
/// This is an intentionally stand-alone module so that it can be used easily
|
||||||
/// in a `build.rs` script to build shell completion files.
|
/// in a `build.rs` script to build shell completion files.
|
||||||
fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
|
pub fn app() -> App<'static, 'static> {
|
||||||
where F: Fn(&'static str) -> &'static str {
|
|
||||||
let arg = |name| {
|
let arg = |name| {
|
||||||
Arg::with_name(name).help(doc(name)).next_line_help(next_line_help)
|
Arg::with_name(name).help(USAGES[name].short).long_help(USAGES[name].long)
|
||||||
};
|
};
|
||||||
let flag = |name| arg(name).long(name);
|
let flag = |name| arg(name).long(name);
|
||||||
|
|
||||||
@@ -64,16 +53,12 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
|
|||||||
.setting(AppSettings::UnifiedHelpMessage)
|
.setting(AppSettings::UnifiedHelpMessage)
|
||||||
.usage(USAGE)
|
.usage(USAGE)
|
||||||
.template(TEMPLATE)
|
.template(TEMPLATE)
|
||||||
// Handle help/version manually to make their output formatting
|
.help_message("Prints help information. Use --help for more details.")
|
||||||
// consistent with short/long views.
|
|
||||||
.arg(arg("help-short").short("h"))
|
|
||||||
.arg(flag("help"))
|
|
||||||
.arg(flag("version").short("V"))
|
|
||||||
// First, set up primary positional/flag arguments.
|
// First, set up primary positional/flag arguments.
|
||||||
.arg(arg("pattern")
|
.arg(arg("pattern")
|
||||||
.required_unless_one(&[
|
.required_unless_one(&[
|
||||||
"file", "files", "help-short", "help", "regexp", "type-list",
|
"file", "files", "help-short", "help", "regexp", "type-list",
|
||||||
"version",
|
"ripgrep-version",
|
||||||
]))
|
]))
|
||||||
.arg(arg("path").multiple(true))
|
.arg(arg("path").multiple(true))
|
||||||
.arg(flag("regexp").short("e")
|
.arg(flag("regexp").short("e")
|
||||||
@@ -96,6 +81,8 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
|
|||||||
.possible_values(&["never", "auto", "always", "ansi"]))
|
.possible_values(&["never", "auto", "always", "ansi"]))
|
||||||
.arg(flag("colors").value_name("SPEC")
|
.arg(flag("colors").value_name("SPEC")
|
||||||
.takes_value(true).multiple(true).number_of_values(1))
|
.takes_value(true).multiple(true).number_of_values(1))
|
||||||
|
.arg(flag("encoding").short("E").value_name("ENCODING")
|
||||||
|
.takes_value(true).number_of_values(1))
|
||||||
.arg(flag("fixed-strings").short("F"))
|
.arg(flag("fixed-strings").short("F"))
|
||||||
.arg(flag("glob").short("g")
|
.arg(flag("glob").short("g")
|
||||||
.takes_value(true).multiple(true).number_of_values(1)
|
.takes_value(true).multiple(true).number_of_values(1)
|
||||||
@@ -135,8 +122,8 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
|
|||||||
.arg(flag("files-without-match"))
|
.arg(flag("files-without-match"))
|
||||||
.arg(flag("with-filename").short("H"))
|
.arg(flag("with-filename").short("H"))
|
||||||
.arg(flag("no-filename"))
|
.arg(flag("no-filename"))
|
||||||
.arg(flag("heading"))
|
.arg(flag("heading").overrides_with("no-heading"))
|
||||||
.arg(flag("no-heading"))
|
.arg(flag("no-heading").overrides_with("heading"))
|
||||||
.arg(flag("hidden"))
|
.arg(flag("hidden"))
|
||||||
.arg(flag("ignore-file")
|
.arg(flag("ignore-file")
|
||||||
.value_name("FILE").takes_value(true)
|
.value_name("FILE").takes_value(true)
|
||||||
@@ -145,6 +132,8 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
|
|||||||
.arg(flag("max-count")
|
.arg(flag("max-count")
|
||||||
.short("m").value_name("NUM").takes_value(true)
|
.short("m").value_name("NUM").takes_value(true)
|
||||||
.validator(validate_number))
|
.validator(validate_number))
|
||||||
|
.arg(flag("max-filesize")
|
||||||
|
.value_name("NUM+SUFFIX?").takes_value(true))
|
||||||
.arg(flag("maxdepth")
|
.arg(flag("maxdepth")
|
||||||
.value_name("NUM").takes_value(true)
|
.value_name("NUM").takes_value(true)
|
||||||
.validator(validate_number))
|
.validator(validate_number))
|
||||||
@@ -154,7 +143,8 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
|
|||||||
.arg(flag("no-ignore"))
|
.arg(flag("no-ignore"))
|
||||||
.arg(flag("no-ignore-parent"))
|
.arg(flag("no-ignore-parent"))
|
||||||
.arg(flag("no-ignore-vcs"))
|
.arg(flag("no-ignore-vcs"))
|
||||||
.arg(flag("null"))
|
.arg(flag("null").short("0"))
|
||||||
|
.arg(flag("only-matching").short("o").conflicts_with("replace"))
|
||||||
.arg(flag("path-separator").value_name("SEPARATOR").takes_value(true))
|
.arg(flag("path-separator").value_name("SEPARATOR").takes_value(true))
|
||||||
.arg(flag("pretty").short("p"))
|
.arg(flag("pretty").short("p"))
|
||||||
.arg(flag("replace").short("r").value_name("ARG").takes_value(true))
|
.arg(flag("replace").short("r").value_name("ARG").takes_value(true))
|
||||||
@@ -165,6 +155,9 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
|
|||||||
.short("j").value_name("ARG").takes_value(true)
|
.short("j").value_name("ARG").takes_value(true)
|
||||||
.validator(validate_number))
|
.validator(validate_number))
|
||||||
.arg(flag("vimgrep"))
|
.arg(flag("vimgrep"))
|
||||||
|
.arg(flag("max-columns").short("M")
|
||||||
|
.value_name("NUM").takes_value(true)
|
||||||
|
.validator(validate_number))
|
||||||
.arg(flag("type-add")
|
.arg(flag("type-add")
|
||||||
.value_name("TYPE").takes_value(true)
|
.value_name("TYPE").takes_value(true)
|
||||||
.multiple(true).number_of_values(1))
|
.multiple(true).number_of_values(1))
|
||||||
@@ -199,7 +192,7 @@ lazy_static! {
|
|||||||
doc!(h, "help",
|
doc!(h, "help",
|
||||||
"Show verbose help output.",
|
"Show verbose help output.",
|
||||||
"When given, more details about flags are provided.");
|
"When given, more details about flags are provided.");
|
||||||
doc!(h, "version",
|
doc!(h, "ripgrep-version",
|
||||||
"Prints version information.");
|
"Prints version information.");
|
||||||
|
|
||||||
doc!(h, "pattern",
|
doc!(h, "pattern",
|
||||||
@@ -242,13 +235,21 @@ lazy_static! {
|
|||||||
red, blue, green, cyan, magenta, yellow, white and black. \
|
red, blue, green, cyan, magenta, yellow, white and black. \
|
||||||
Styles are limited to nobold, bold, nointense or intense.\n\n\
|
Styles are limited to nobold, bold, nointense or intense.\n\n\
|
||||||
The format of the flag is {type}:{attribute}:{value}. {type} \
|
The format of the flag is {type}:{attribute}:{value}. {type} \
|
||||||
should be one of path, line or match. {attribute} can be fg, bg \
|
should be one of path, line, column or match. {attribute} can \
|
||||||
or style. {value} is either a color (for fg and bg) or a text \
|
be fg, bg or style. {value} is either a color (for fg and bg) \
|
||||||
style. A special format, {type}:none, will clear all color \
|
or a text style. A special format, {type}:none, will clear all \
|
||||||
settings for {type}.\n\nFor example, the following command will \
|
color settings for {type}.\n\nFor example, the following \
|
||||||
change the match color to magenta and the background color for \
|
command will change the match color to magenta and the \
|
||||||
line numbers to yellow:\n\n\
|
background color for line numbers to yellow:\n\n\
|
||||||
rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo.");
|
rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo.");
|
||||||
|
doc!(h, "encoding",
|
||||||
|
"Specify the text encoding of files to search.",
|
||||||
|
"Specify the text encoding that ripgrep will use on all files \
|
||||||
|
searched. The default value is 'auto', which will cause ripgrep \
|
||||||
|
to do a best effort automatic detection of encoding on a \
|
||||||
|
per-file basis. Other supported values can be found in the list \
|
||||||
|
of labels here: \
|
||||||
|
https://encoding.spec.whatwg.org/#concept-encoding-get");
|
||||||
doc!(h, "fixed-strings",
|
doc!(h, "fixed-strings",
|
||||||
"Treat the pattern as a literal string.",
|
"Treat the pattern as a literal string.",
|
||||||
"Treat the pattern as a literal string instead of a regular \
|
"Treat the pattern as a literal string instead of a regular \
|
||||||
@@ -333,9 +334,9 @@ lazy_static! {
|
|||||||
provided are searched. Empty pattern lines will match all input \
|
provided are searched. Empty pattern lines will match all input \
|
||||||
lines, and the newline is not counted as part of the pattern.");
|
lines, and the newline is not counted as part of the pattern.");
|
||||||
doc!(h, "files-with-matches",
|
doc!(h, "files-with-matches",
|
||||||
"Only show the path of each file with at least one match.");
|
"Only show the paths with at least one match.");
|
||||||
doc!(h, "files-without-match",
|
doc!(h, "files-without-match",
|
||||||
"Only show the path of each file that contains zero matches.");
|
"Only show the paths that contains zero matches.");
|
||||||
doc!(h, "with-filename",
|
doc!(h, "with-filename",
|
||||||
"Show file name for each match.",
|
"Show file name for each match.",
|
||||||
"Prefix each match with the file name that contains it. This is \
|
"Prefix each match with the file name that contains it. This is \
|
||||||
@@ -371,6 +372,14 @@ lazy_static! {
|
|||||||
doc!(h, "max-count",
|
doc!(h, "max-count",
|
||||||
"Limit the number of matches.",
|
"Limit the number of matches.",
|
||||||
"Limit the number of matching lines per file searched to NUM.");
|
"Limit the number of matching lines per file searched to NUM.");
|
||||||
|
doc!(h, "max-filesize",
|
||||||
|
"Ignore files larger than NUM in size.",
|
||||||
|
"Ignore files larger than NUM in size. Does not ignore \
|
||||||
|
directories. \
|
||||||
|
\n\nThe input format accepts suffixes of K, M or G which \
|
||||||
|
correspond to kilobytes, megabytes and gigabytes. If no suffix \
|
||||||
|
is provided the input is treated as bytes. \
|
||||||
|
\n\nExample: --max-filesize 50K or --max-filesize 80M");
|
||||||
doc!(h, "maxdepth",
|
doc!(h, "maxdepth",
|
||||||
"Descend at most NUM directories.",
|
"Descend at most NUM directories.",
|
||||||
"Limit the depth of directory traversal to NUM levels beyond \
|
"Limit the depth of directory traversal to NUM levels beyond \
|
||||||
@@ -413,6 +422,10 @@ lazy_static! {
|
|||||||
printing a list of matching files such as with --count, \
|
printing a list of matching files such as with --count, \
|
||||||
--files-with-matches and --files. This option is useful for use \
|
--files-with-matches and --files. This option is useful for use \
|
||||||
with xargs.");
|
with xargs.");
|
||||||
|
doc!(h, "only-matching",
|
||||||
|
"Print only matched parts of a line.",
|
||||||
|
"Print only the matched (non-empty) parts of a matching line, \
|
||||||
|
with each such part on a separate output line.");
|
||||||
doc!(h, "path-separator",
|
doc!(h, "path-separator",
|
||||||
"Path separator to use when printing file paths.",
|
"Path separator to use when printing file paths.",
|
||||||
"The path separator to use when printing file paths. This \
|
"The path separator to use when printing file paths. This \
|
||||||
@@ -454,6 +467,11 @@ lazy_static! {
|
|||||||
"Show results with every match on its own line, including \
|
"Show results with every match on its own line, including \
|
||||||
line numbers and column numbers. With this option, a line with \
|
line numbers and column numbers. With this option, a line with \
|
||||||
more than one match will be printed more than once.");
|
more than one match will be printed more than once.");
|
||||||
|
doc!(h, "max-columns",
|
||||||
|
"Don't print lines longer than this limit in bytes.",
|
||||||
|
"Don't print lines longer than this limit in bytes. Longer lines \
|
||||||
|
are omitted, and only the number of matches in that line is \
|
||||||
|
printed.");
|
||||||
|
|
||||||
doc!(h, "type-add",
|
doc!(h, "type-add",
|
||||||
"Add a new glob for a file type.",
|
"Add a new glob for a file type.",
|
||||||
@@ -469,8 +487,8 @@ lazy_static! {
|
|||||||
permits specifying one or more other type names (separated by a \
|
permits specifying one or more other type names (separated by a \
|
||||||
comma) that have been defined and its rules will automatically \
|
comma) that have been defined and its rules will automatically \
|
||||||
be imported into the type specified. For example, to create a \
|
be imported into the type specified. For example, to create a \
|
||||||
type called src that matches C++, Python and Markdown files, one \
|
type called src that matches C++, Python and Markdown files, \
|
||||||
can use:\n\n\
|
one can use:\n\n\
|
||||||
--type-add 'src:include:cpp,py,md'\n\n\
|
--type-add 'src:include:cpp,py,md'\n\n\
|
||||||
Additional glob rules can still be added to the src type by \
|
Additional glob rules can still be added to the src type by \
|
||||||
using the --type-add flag again:\n\n\
|
using the --type-add flag again:\n\n\
|
||||||
|
131
src/args.rs
131
src/args.rs
@@ -5,11 +5,11 @@ use std::fs;
|
|||||||
use std::io::{self, BufRead};
|
use std::io::{self, BufRead};
|
||||||
use std::ops;
|
use std::ops;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use clap;
|
use clap;
|
||||||
|
use encoding_rs::Encoding;
|
||||||
use env_logger;
|
use env_logger;
|
||||||
use grep::{Grep, GrepBuilder};
|
use grep::{Grep, GrepBuilder};
|
||||||
use log;
|
use log;
|
||||||
@@ -41,6 +41,7 @@ pub struct Args {
|
|||||||
column: bool,
|
column: bool,
|
||||||
context_separator: Vec<u8>,
|
context_separator: Vec<u8>,
|
||||||
count: bool,
|
count: bool,
|
||||||
|
encoding: Option<&'static Encoding>,
|
||||||
files_with_matches: bool,
|
files_with_matches: bool,
|
||||||
files_without_matches: bool,
|
files_without_matches: bool,
|
||||||
eol: u8,
|
eol: u8,
|
||||||
@@ -54,7 +55,9 @@ pub struct Args {
|
|||||||
invert_match: bool,
|
invert_match: bool,
|
||||||
line_number: bool,
|
line_number: bool,
|
||||||
line_per_match: bool,
|
line_per_match: bool,
|
||||||
|
max_columns: Option<usize>,
|
||||||
max_count: Option<u64>,
|
max_count: Option<u64>,
|
||||||
|
max_filesize: Option<u64>,
|
||||||
maxdepth: Option<usize>,
|
maxdepth: Option<usize>,
|
||||||
mmap: bool,
|
mmap: bool,
|
||||||
no_ignore: bool,
|
no_ignore: bool,
|
||||||
@@ -62,6 +65,7 @@ pub struct Args {
|
|||||||
no_ignore_vcs: bool,
|
no_ignore_vcs: bool,
|
||||||
no_messages: bool,
|
no_messages: bool,
|
||||||
null: bool,
|
null: bool,
|
||||||
|
only_matching: bool,
|
||||||
path_separator: Option<u8>,
|
path_separator: Option<u8>,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
quiet_matched: QuietMatched,
|
quiet_matched: QuietMatched,
|
||||||
@@ -84,21 +88,7 @@ impl Args {
|
|||||||
///
|
///
|
||||||
/// Also, initialize a global logger.
|
/// Also, initialize a global logger.
|
||||||
pub fn parse() -> Result<Args> {
|
pub fn parse() -> Result<Args> {
|
||||||
let matches = app::app_short().get_matches();
|
let matches = app::app().get_matches();
|
||||||
if matches.is_present("help-short") {
|
|
||||||
let _ = ::app::app_short().print_help();
|
|
||||||
println!("");
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
if matches.is_present("help") {
|
|
||||||
let _ = ::app::app_long().print_help();
|
|
||||||
println!("");
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
if matches.is_present("version") {
|
|
||||||
println!("ripgrep {}", crate_version!());
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut logb = env_logger::LogBuilder::new();
|
let mut logb = env_logger::LogBuilder::new();
|
||||||
if matches.is_present("debug") {
|
if matches.is_present("debug") {
|
||||||
@@ -152,8 +142,10 @@ impl Args {
|
|||||||
.heading(self.heading)
|
.heading(self.heading)
|
||||||
.line_per_match(self.line_per_match)
|
.line_per_match(self.line_per_match)
|
||||||
.null(self.null)
|
.null(self.null)
|
||||||
|
.only_matching(self.only_matching)
|
||||||
.path_separator(self.path_separator)
|
.path_separator(self.path_separator)
|
||||||
.with_filename(self.with_filename);
|
.with_filename(self.with_filename)
|
||||||
|
.max_columns(self.max_columns);
|
||||||
if let Some(ref rep) = self.replace {
|
if let Some(ref rep) = self.replace {
|
||||||
p = p.replace(rep.clone());
|
p = p.replace(rep.clone());
|
||||||
}
|
}
|
||||||
@@ -182,8 +174,8 @@ impl Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new writer for single-threaded searching with color support.
|
/// Create a new writer for single-threaded searching with color support.
|
||||||
pub fn stdout(&self) -> termcolor::Stdout {
|
pub fn stdout(&self) -> termcolor::StandardStream {
|
||||||
termcolor::Stdout::new(self.color_choice)
|
termcolor::StandardStream::stdout(self.color_choice)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a handle to stdout for filtering search.
|
/// Returns a handle to stdout for filtering search.
|
||||||
@@ -223,6 +215,7 @@ impl Args {
|
|||||||
.after_context(self.after_context)
|
.after_context(self.after_context)
|
||||||
.before_context(self.before_context)
|
.before_context(self.before_context)
|
||||||
.count(self.count)
|
.count(self.count)
|
||||||
|
.encoding(self.encoding)
|
||||||
.files_with_matches(self.files_with_matches)
|
.files_with_matches(self.files_with_matches)
|
||||||
.files_without_matches(self.files_without_matches)
|
.files_without_matches(self.files_without_matches)
|
||||||
.eol(self.eol)
|
.eol(self.eol)
|
||||||
@@ -285,6 +278,7 @@ impl Args {
|
|||||||
wd.follow_links(self.follow);
|
wd.follow_links(self.follow);
|
||||||
wd.hidden(!self.hidden);
|
wd.hidden(!self.hidden);
|
||||||
wd.max_depth(self.maxdepth);
|
wd.max_depth(self.maxdepth);
|
||||||
|
wd.max_filesize(self.max_filesize);
|
||||||
wd.overrides(self.glob_overrides.clone());
|
wd.overrides(self.glob_overrides.clone());
|
||||||
wd.types(self.types.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);
|
||||||
@@ -314,6 +308,7 @@ impl<'a> ArgMatches<'a> {
|
|||||||
/// configuration.
|
/// configuration.
|
||||||
fn to_args(&self) -> Result<Args> {
|
fn to_args(&self) -> Result<Args> {
|
||||||
let paths = self.paths();
|
let paths = self.paths();
|
||||||
|
let line_number = self.line_number(&paths);
|
||||||
let mmap = try!(self.mmap(&paths));
|
let mmap = try!(self.mmap(&paths));
|
||||||
let with_filename = self.with_filename(&paths);
|
let with_filename = self.with_filename(&paths);
|
||||||
let (before_context, after_context) = try!(self.contexts());
|
let (before_context, after_context) = try!(self.contexts());
|
||||||
@@ -328,6 +323,7 @@ impl<'a> ArgMatches<'a> {
|
|||||||
column: self.column(),
|
column: self.column(),
|
||||||
context_separator: self.context_separator(),
|
context_separator: self.context_separator(),
|
||||||
count: self.is_present("count"),
|
count: self.is_present("count"),
|
||||||
|
encoding: try!(self.encoding()),
|
||||||
files_with_matches: self.is_present("files-with-matches"),
|
files_with_matches: self.is_present("files-with-matches"),
|
||||||
files_without_matches: self.is_present("files-without-match"),
|
files_without_matches: self.is_present("files-without-match"),
|
||||||
eol: b'\n',
|
eol: b'\n',
|
||||||
@@ -339,9 +335,11 @@ impl<'a> ArgMatches<'a> {
|
|||||||
hidden: self.hidden(),
|
hidden: self.hidden(),
|
||||||
ignore_files: self.ignore_files(),
|
ignore_files: self.ignore_files(),
|
||||||
invert_match: self.is_present("invert-match"),
|
invert_match: self.is_present("invert-match"),
|
||||||
line_number: self.line_number(),
|
line_number: line_number,
|
||||||
line_per_match: self.is_present("vimgrep"),
|
line_per_match: self.is_present("vimgrep"),
|
||||||
|
max_columns: try!(self.usize_of("max-columns")),
|
||||||
max_count: try!(self.usize_of("max-count")).map(|max| max as u64),
|
max_count: try!(self.usize_of("max-count")).map(|max| max as u64),
|
||||||
|
max_filesize: try!(self.max_filesize()),
|
||||||
maxdepth: try!(self.usize_of("maxdepth")),
|
maxdepth: try!(self.usize_of("maxdepth")),
|
||||||
mmap: mmap,
|
mmap: mmap,
|
||||||
no_ignore: self.no_ignore(),
|
no_ignore: self.no_ignore(),
|
||||||
@@ -349,6 +347,7 @@ impl<'a> ArgMatches<'a> {
|
|||||||
no_ignore_vcs: self.no_ignore_vcs(),
|
no_ignore_vcs: self.no_ignore_vcs(),
|
||||||
no_messages: self.is_present("no-messages"),
|
no_messages: self.is_present("no-messages"),
|
||||||
null: self.is_present("null"),
|
null: self.is_present("null"),
|
||||||
|
only_matching: self.is_present("only-matching"),
|
||||||
path_separator: try!(self.path_separator()),
|
path_separator: try!(self.path_separator()),
|
||||||
quiet: quiet,
|
quiet: quiet,
|
||||||
quiet_matched: QuietMatched::new(quiet),
|
quiet_matched: QuietMatched::new(quiet),
|
||||||
@@ -394,8 +393,8 @@ impl<'a> ArgMatches<'a> {
|
|||||||
self.values_of_os("file").map_or(false, |mut files| {
|
self.values_of_os("file").map_or(false, |mut files| {
|
||||||
files.any(|f| f == "-")
|
files.any(|f| f == "-")
|
||||||
});
|
});
|
||||||
let search_cwd = atty::on_stdin()
|
let search_cwd = atty::is(atty::Stream::Stdin)
|
||||||
|| !atty::stdin_is_readable()
|
|| !stdin_is_readable()
|
||||||
|| (self.is_present("file") && file_is_stdin)
|
|| (self.is_present("file") && file_is_stdin)
|
||||||
|| self.is_present("files")
|
|| self.is_present("files")
|
||||||
|| self.is_present("type-list");
|
|| self.is_present("type-list");
|
||||||
@@ -434,6 +433,9 @@ impl<'a> ArgMatches<'a> {
|
|||||||
///
|
///
|
||||||
/// If any pattern is invalid UTF-8, then an error is returned.
|
/// If any pattern is invalid UTF-8, then an error is returned.
|
||||||
fn patterns(&self) -> Result<Vec<String>> {
|
fn patterns(&self) -> Result<Vec<String>> {
|
||||||
|
if self.is_present("files") || self.is_present("type-list") {
|
||||||
|
return Ok(vec![self.empty_pattern()]);
|
||||||
|
}
|
||||||
let mut pats = vec![];
|
let mut pats = vec![];
|
||||||
match self.values_of_os("regexp") {
|
match self.values_of_os("regexp") {
|
||||||
None => {
|
None => {
|
||||||
@@ -563,6 +565,7 @@ impl<'a> ArgMatches<'a> {
|
|||||||
/// will need to search.
|
/// will need to search.
|
||||||
fn mmap(&self, paths: &[PathBuf]) -> Result<bool> {
|
fn mmap(&self, paths: &[PathBuf]) -> Result<bool> {
|
||||||
let (before, after) = try!(self.contexts());
|
let (before, after) = try!(self.contexts());
|
||||||
|
let enc = try!(self.encoding());
|
||||||
Ok(if before > 0 || after > 0 || self.is_present("no-mmap") {
|
Ok(if before > 0 || after > 0 || self.is_present("no-mmap") {
|
||||||
false
|
false
|
||||||
} else if self.is_present("mmap") {
|
} else if self.is_present("mmap") {
|
||||||
@@ -570,6 +573,10 @@ impl<'a> ArgMatches<'a> {
|
|||||||
} else if cfg!(target_os = "macos") {
|
} else if cfg!(target_os = "macos") {
|
||||||
// On Mac, memory maps appear to suck. Neat.
|
// On Mac, memory maps appear to suck. Neat.
|
||||||
false
|
false
|
||||||
|
} else if enc.is_some() {
|
||||||
|
// There's no practical way to transcode a memory map that isn't
|
||||||
|
// isomorphic to searching over io::Read.
|
||||||
|
false
|
||||||
} else {
|
} else {
|
||||||
// If we're only searching a few paths and all of them are
|
// If we're only searching a few paths and all of them are
|
||||||
// files, then memory maps are probably faster.
|
// files, then memory maps are probably faster.
|
||||||
@@ -578,13 +585,14 @@ impl<'a> ArgMatches<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if and only if line numbers should be shown.
|
/// Returns true if and only if line numbers should be shown.
|
||||||
fn line_number(&self) -> bool {
|
fn line_number(&self, paths: &[PathBuf]) -> bool {
|
||||||
if self.is_present("no-line-number") || self.is_present("count") {
|
if self.is_present("no-line-number") || self.is_present("count") {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
|
let only_stdin = paths == &[Path::new("-")];
|
||||||
self.is_present("line-number")
|
self.is_present("line-number")
|
||||||
|| self.is_present("column")
|
|| self.is_present("column")
|
||||||
|| atty::on_stdout()
|
|| (atty::is(atty::Stream::Stdout) && !only_stdin)
|
||||||
|| self.is_present("pretty")
|
|| self.is_present("pretty")
|
||||||
|| self.is_present("vimgrep")
|
|| self.is_present("vimgrep")
|
||||||
}
|
}
|
||||||
@@ -602,7 +610,7 @@ impl<'a> ArgMatches<'a> {
|
|||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
self.is_present("heading")
|
self.is_present("heading")
|
||||||
|| atty::on_stdout()
|
|| atty::is(atty::Stream::Stdout)
|
||||||
|| self.is_present("pretty")
|
|| self.is_present("pretty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -667,7 +675,7 @@ impl<'a> ArgMatches<'a> {
|
|||||||
} else if self.is_present("vimgrep") {
|
} else if self.is_present("vimgrep") {
|
||||||
false
|
false
|
||||||
} else if preference == "auto" {
|
} else if preference == "auto" {
|
||||||
atty::on_stdout() || self.is_present("pretty")
|
atty::is(atty::Stream::Stdout) || self.is_present("pretty")
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -687,7 +695,7 @@ impl<'a> ArgMatches<'a> {
|
|||||||
} else if self.is_present("vimgrep") {
|
} else if self.is_present("vimgrep") {
|
||||||
termcolor::ColorChoice::Never
|
termcolor::ColorChoice::Never
|
||||||
} else if preference == "auto" {
|
} else if preference == "auto" {
|
||||||
if atty::on_stdout() || self.is_present("pretty") {
|
if atty::is(atty::Stream::Stdout) || self.is_present("pretty") {
|
||||||
termcolor::ColorChoice::Auto
|
termcolor::ColorChoice::Auto
|
||||||
} else {
|
} else {
|
||||||
termcolor::ColorChoice::Never
|
termcolor::ColorChoice::Never
|
||||||
@@ -715,6 +723,29 @@ impl<'a> ArgMatches<'a> {
|
|||||||
Ok(ColorSpecs::new(&specs))
|
Ok(ColorSpecs::new(&specs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the text encoding specified.
|
||||||
|
///
|
||||||
|
/// If the label given by the caller doesn't correspond to a valid
|
||||||
|
/// supported encoding (and isn't `auto`), then return an error.
|
||||||
|
///
|
||||||
|
/// A `None` encoding implies that the encoding should be automatically
|
||||||
|
/// detected on a per-file basis.
|
||||||
|
fn encoding(&self) -> Result<Option<&'static Encoding>> {
|
||||||
|
match self.0.value_of_lossy("encoding") {
|
||||||
|
None => Ok(None),
|
||||||
|
Some(label) => {
|
||||||
|
if label == "auto" {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
match Encoding::for_label(label.as_bytes()) {
|
||||||
|
Some(enc) => Ok(Some(enc)),
|
||||||
|
None => Err(From::from(
|
||||||
|
format!("unsupported encoding: {}", label))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the approximate number of threads that ripgrep should use.
|
/// Returns the approximate number of threads that ripgrep should use.
|
||||||
fn threads(&self) -> Result<usize> {
|
fn threads(&self) -> Result<usize> {
|
||||||
if self.is_present("sort-files") {
|
if self.is_present("sort-files") {
|
||||||
@@ -776,6 +807,31 @@ impl<'a> ArgMatches<'a> {
|
|||||||
btypes.build().map_err(From::from)
|
btypes.build().map_err(From::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses the max-filesize argument option into a byte count.
|
||||||
|
fn max_filesize(&self) -> Result<Option<u64>> {
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
let max_filesize = match self.value_of_lossy("max-filesize") {
|
||||||
|
Some(x) => x,
|
||||||
|
None => return Ok(None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let re = Regex::new("^([0-9]+)([KMG])?$").unwrap();
|
||||||
|
let caps = try!(re.captures(&max_filesize)
|
||||||
|
.ok_or("invalid format for max-filesize argument"));
|
||||||
|
|
||||||
|
let value = try!(caps[1].parse::<u64>().map_err(|err|err.to_string()));
|
||||||
|
let suffix = caps.get(2).map(|x| x.as_str());
|
||||||
|
|
||||||
|
match suffix {
|
||||||
|
None => Ok(Some(value)),
|
||||||
|
Some("K") => Ok(Some(value * 1024)),
|
||||||
|
Some("M") => Ok(Some(value * 1024 * 1024)),
|
||||||
|
Some("G") => Ok(Some(value * 1024 * 1024 * 1024)),
|
||||||
|
_ => Err(From::from("invalid suffix for max-filesize argument"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if ignore files should be ignored.
|
/// Returns true if ignore files should be ignored.
|
||||||
fn no_ignore(&self) -> bool {
|
fn no_ignore(&self) -> bool {
|
||||||
self.is_present("no-ignore")
|
self.is_present("no-ignore")
|
||||||
@@ -869,3 +925,24 @@ impl QuietMatched {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if stdin is deemed searchable.
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn stdin_is_readable() -> bool {
|
||||||
|
use std::os::unix::fs::FileTypeExt;
|
||||||
|
use same_file::Handle;
|
||||||
|
|
||||||
|
let ft = match Handle::stdin().and_then(|h| h.as_file().metadata()) {
|
||||||
|
Err(_) => return false,
|
||||||
|
Ok(md) => md.file_type(),
|
||||||
|
};
|
||||||
|
ft.is_file() || ft.is_fifo()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if stdin is deemed searchable.
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn stdin_is_readable() -> bool {
|
||||||
|
// On Windows, it's not clear what the possibilities are to me, so just
|
||||||
|
// always return true.
|
||||||
|
true
|
||||||
|
}
|
||||||
|
145
src/atty.rs
145
src/atty.rs
@@ -1,145 +0,0 @@
|
|||||||
/*!
|
|
||||||
This atty module contains functions for detecting whether ripgrep is being fed
|
|
||||||
from (or to) a terminal. Windows and Unix do this differently, so implement
|
|
||||||
both here.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
use winapi::minwindef::DWORD;
|
|
||||||
#[cfg(windows)]
|
|
||||||
use winapi::winnt::HANDLE;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
pub fn stdin_is_readable() -> bool {
|
|
||||||
use std::os::unix::fs::FileTypeExt;
|
|
||||||
use same_file::Handle;
|
|
||||||
|
|
||||||
let ft = match Handle::stdin().and_then(|h| h.as_file().metadata()) {
|
|
||||||
Err(_) => return false,
|
|
||||||
Ok(md) => md.file_type(),
|
|
||||||
};
|
|
||||||
ft.is_file() || ft.is_fifo()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn stdin_is_readable() -> bool {
|
|
||||||
// ???
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if there is a tty on stdin.
|
|
||||||
#[cfg(unix)]
|
|
||||||
pub fn on_stdin() -> bool {
|
|
||||||
use libc;
|
|
||||||
0 < unsafe { libc::isatty(libc::STDIN_FILENO) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if there is a tty on stdout.
|
|
||||||
#[cfg(unix)]
|
|
||||||
pub fn on_stdout() -> bool {
|
|
||||||
use libc;
|
|
||||||
0 < unsafe { libc::isatty(libc::STDOUT_FILENO) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if there is a tty on stdin.
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn on_stdin() -> bool {
|
|
||||||
use kernel32::GetStdHandle;
|
|
||||||
use winapi::winbase::{
|
|
||||||
STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE,
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let stdin = GetStdHandle(STD_INPUT_HANDLE);
|
|
||||||
if console_on_handle(stdin) {
|
|
||||||
// False positives aren't possible. If we got a console then
|
|
||||||
// we definitely have a tty on stdin.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Otherwise, it's possible to get a false negative. If we know that
|
|
||||||
// there's a console on stdout or stderr however, then this is a true
|
|
||||||
// negative.
|
|
||||||
if console_on_fd(STD_OUTPUT_HANDLE)
|
|
||||||
|| console_on_fd(STD_ERROR_HANDLE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Otherwise, we can't really tell, so we do a weird hack.
|
|
||||||
msys_tty_on_handle(stdin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if there is a tty on stdout.
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn on_stdout() -> bool {
|
|
||||||
use kernel32::GetStdHandle;
|
|
||||||
use winapi::winbase::{
|
|
||||||
STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE,
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let stdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
if console_on_handle(stdout) {
|
|
||||||
// False positives aren't possible. If we got a console then
|
|
||||||
// we definitely have a tty on stdout.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Otherwise, it's possible to get a false negative. If we know that
|
|
||||||
// there's a console on stdin or stderr however, then this is a true
|
|
||||||
// negative.
|
|
||||||
if console_on_fd(STD_INPUT_HANDLE) || console_on_fd(STD_ERROR_HANDLE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Otherwise, we can't really tell, so we do a weird hack.
|
|
||||||
msys_tty_on_handle(stdout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if there is an MSYS tty on the given handle.
|
|
||||||
#[cfg(windows)]
|
|
||||||
unsafe fn msys_tty_on_handle(handle: HANDLE) -> bool {
|
|
||||||
use std::ffi::OsString;
|
|
||||||
use std::mem;
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
use std::os::windows::ffi::OsStringExt;
|
|
||||||
use std::slice;
|
|
||||||
|
|
||||||
use kernel32::{GetFileInformationByHandleEx};
|
|
||||||
use winapi::fileapi::FILE_NAME_INFO;
|
|
||||||
use winapi::minwinbase::FileNameInfo;
|
|
||||||
use winapi::minwindef::MAX_PATH;
|
|
||||||
|
|
||||||
let size = mem::size_of::<FILE_NAME_INFO>();
|
|
||||||
let mut name_info_bytes = vec![0u8; size + MAX_PATH];
|
|
||||||
let res = GetFileInformationByHandleEx(
|
|
||||||
handle,
|
|
||||||
FileNameInfo,
|
|
||||||
&mut *name_info_bytes as *mut _ as *mut c_void,
|
|
||||||
name_info_bytes.len() as u32);
|
|
||||||
if res == 0 {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
let name_info: FILE_NAME_INFO =
|
|
||||||
*(name_info_bytes[0..size].as_ptr() as *const FILE_NAME_INFO);
|
|
||||||
let name_bytes =
|
|
||||||
&name_info_bytes[size..size + name_info.FileNameLength as usize];
|
|
||||||
let name_u16 = slice::from_raw_parts(
|
|
||||||
name_bytes.as_ptr() as *const u16, name_bytes.len() / 2);
|
|
||||||
let name = OsString::from_wide(name_u16)
|
|
||||||
.as_os_str().to_string_lossy().into_owned();
|
|
||||||
name.contains("msys-") || name.contains("-pty")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if there is a console on the given file descriptor.
|
|
||||||
#[cfg(windows)]
|
|
||||||
unsafe fn console_on_fd(fd: DWORD) -> bool {
|
|
||||||
use kernel32::GetStdHandle;
|
|
||||||
console_on_handle(GetStdHandle(fd))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if there is a console on the given handle.
|
|
||||||
#[cfg(windows)]
|
|
||||||
unsafe fn console_on_handle(handle: HANDLE) -> bool {
|
|
||||||
use kernel32::GetConsoleMode;
|
|
||||||
let mut out = 0;
|
|
||||||
GetConsoleMode(handle, &mut out) != 0
|
|
||||||
}
|
|
456
src/decoder.rs
Normal file
456
src/decoder.rs
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
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 = try!(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 = try!(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 = try!(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 trancoded 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 trancoding.
|
||||||
|
///
|
||||||
|
/// 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 +=
|
||||||
|
try!(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 {
|
||||||
|
try!(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.
|
||||||
|
try!(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 = try!(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;
|
||||||
|
try!(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", "©");
|
||||||
|
}
|
@@ -1,11 +1,11 @@
|
|||||||
|
extern crate atty;
|
||||||
extern crate bytecount;
|
extern crate bytecount;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
extern crate encoding_rs;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
extern crate grep;
|
extern crate grep;
|
||||||
extern crate ignore;
|
extern crate ignore;
|
||||||
#[cfg(windows)]
|
|
||||||
extern crate kernel32;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
@@ -17,8 +17,6 @@ extern crate num_cpus;
|
|||||||
extern crate regex;
|
extern crate regex;
|
||||||
extern crate same_file;
|
extern crate same_file;
|
||||||
extern crate termcolor;
|
extern crate termcolor;
|
||||||
#[cfg(windows)]
|
|
||||||
extern crate winapi;
|
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::process;
|
use std::process;
|
||||||
@@ -46,7 +44,7 @@ macro_rules! eprintln {
|
|||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod args;
|
mod args;
|
||||||
mod atty;
|
mod decoder;
|
||||||
mod pathutil;
|
mod pathutil;
|
||||||
mod printer;
|
mod printer;
|
||||||
mod search_buffer;
|
mod search_buffer;
|
||||||
|
257
src/printer.rs
257
src/printer.rs
@@ -3,12 +3,32 @@ use std::fmt;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use regex::bytes::Regex;
|
use regex::bytes::{Regex, Replacer, Captures};
|
||||||
use termcolor::{Color, ColorSpec, ParseColorError, WriteColor};
|
use termcolor::{Color, ColorSpec, ParseColorError, WriteColor};
|
||||||
|
|
||||||
use pathutil::strip_prefix;
|
use pathutil::strip_prefix;
|
||||||
use ignore::types::FileTypeDef;
|
use ignore::types::FileTypeDef;
|
||||||
|
|
||||||
|
/// CountingReplacer implements the Replacer interface for Regex,
|
||||||
|
/// and counts how often replacement is being performed.
|
||||||
|
struct CountingReplacer<'r> {
|
||||||
|
replace: &'r [u8],
|
||||||
|
count: &'r mut usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> CountingReplacer<'r> {
|
||||||
|
fn new(replace: &'r [u8], count: &'r mut usize) -> CountingReplacer<'r> {
|
||||||
|
CountingReplacer { replace: replace, count: count }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> Replacer for CountingReplacer<'r> {
|
||||||
|
fn replace_append(&mut self, caps: &Captures, dst: &mut Vec<u8>) {
|
||||||
|
*self.count += 1;
|
||||||
|
caps.expand(self.replace, dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Printer encapsulates all output logic for searching.
|
/// Printer encapsulates all output logic for searching.
|
||||||
///
|
///
|
||||||
/// Note that we currently ignore all write errors. It's probably worthwhile
|
/// Note that we currently ignore all write errors. It's probably worthwhile
|
||||||
@@ -38,6 +58,8 @@ pub struct Printer<W> {
|
|||||||
/// Whether to print NUL bytes after a file path instead of new lines
|
/// Whether to print NUL bytes after a file path instead of new lines
|
||||||
/// or `:`.
|
/// or `:`.
|
||||||
null: bool,
|
null: bool,
|
||||||
|
/// Print only the matched (non-empty) parts of a matching line
|
||||||
|
only_matching: bool,
|
||||||
/// A string to use as a replacement of each match in a matching line.
|
/// A string to use as a replacement of each match in a matching line.
|
||||||
replace: Option<Vec<u8>>,
|
replace: Option<Vec<u8>>,
|
||||||
/// Whether to prefix each match with the corresponding file name.
|
/// Whether to prefix each match with the corresponding file name.
|
||||||
@@ -46,6 +68,8 @@ pub struct Printer<W> {
|
|||||||
colors: ColorSpecs,
|
colors: ColorSpecs,
|
||||||
/// The separator to use for file paths. If empty, this is ignored.
|
/// The separator to use for file paths. If empty, this is ignored.
|
||||||
path_separator: Option<u8>,
|
path_separator: Option<u8>,
|
||||||
|
/// Restrict lines to this many columns.
|
||||||
|
max_columns: Option<usize>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: WriteColor> Printer<W> {
|
impl<W: WriteColor> Printer<W> {
|
||||||
@@ -61,10 +85,12 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
heading: false,
|
heading: false,
|
||||||
line_per_match: false,
|
line_per_match: false,
|
||||||
null: false,
|
null: false,
|
||||||
|
only_matching: false,
|
||||||
replace: None,
|
replace: None,
|
||||||
with_filename: false,
|
with_filename: false,
|
||||||
colors: ColorSpecs::default(),
|
colors: ColorSpecs::default(),
|
||||||
path_separator: None,
|
path_separator: None,
|
||||||
|
max_columns: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +147,12 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Print only the matched (non-empty) parts of a matching line
|
||||||
|
pub fn only_matching(mut self, yes: bool) -> Printer<W> {
|
||||||
|
self.only_matching = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// A separator to use when printing file paths. When empty, use the
|
/// A separator to use when printing file paths. When empty, use the
|
||||||
/// default separator for the current platform. (/ on Unix, \ on Windows.)
|
/// default separator for the current platform. (/ on Unix, \ on Windows.)
|
||||||
pub fn path_separator(mut self, sep: Option<u8>) -> Printer<W> {
|
pub fn path_separator(mut self, sep: Option<u8>) -> Printer<W> {
|
||||||
@@ -130,9 +162,6 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
|
|
||||||
/// Replace every match in each matching line with the replacement string
|
/// Replace every match in each matching line with the replacement string
|
||||||
/// given.
|
/// given.
|
||||||
///
|
|
||||||
/// The replacement string syntax is documented here:
|
|
||||||
/// https://doc.rust-lang.org/regex/regex/bytes/struct.Captures.html#method.expand
|
|
||||||
pub fn replace(mut self, replacement: Vec<u8>) -> Printer<W> {
|
pub fn replace(mut self, replacement: Vec<u8>) -> Printer<W> {
|
||||||
self.replace = Some(replacement);
|
self.replace = Some(replacement);
|
||||||
self
|
self
|
||||||
@@ -144,6 +173,12 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configure the max. number of columns used for printing matching lines.
|
||||||
|
pub fn max_columns(mut self, max_columns: Option<usize>) -> Printer<W> {
|
||||||
|
self.max_columns = max_columns;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if and only if something has been printed.
|
/// Returns true if and only if something has been printed.
|
||||||
pub fn has_printed(&self) -> bool {
|
pub fn has_printed(&self) -> bool {
|
||||||
self.has_printed
|
self.has_printed
|
||||||
@@ -175,22 +210,14 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
pub fn path<P: AsRef<Path>>(&mut self, path: P) {
|
pub fn path<P: AsRef<Path>>(&mut self, path: P) {
|
||||||
let path = strip_prefix("./", path.as_ref()).unwrap_or(path.as_ref());
|
let path = strip_prefix("./", path.as_ref()).unwrap_or(path.as_ref());
|
||||||
self.write_path(path);
|
self.write_path(path);
|
||||||
if self.null {
|
self.write_path_eol();
|
||||||
self.write(b"\x00");
|
|
||||||
} else {
|
|
||||||
self.write_eol();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prints the given path and a count of the number of matches found.
|
/// Prints the given path and a count of the number of matches found.
|
||||||
pub fn path_count<P: AsRef<Path>>(&mut self, path: P, count: u64) {
|
pub fn path_count<P: AsRef<Path>>(&mut self, path: P, count: u64) {
|
||||||
if self.with_filename {
|
if self.with_filename {
|
||||||
self.write_path(path);
|
self.write_path(path);
|
||||||
if self.null {
|
self.write_path_sep(b':');
|
||||||
self.write(b"\x00");
|
|
||||||
} else {
|
|
||||||
self.write(b":");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.write(count.to_string().as_bytes());
|
self.write(count.to_string().as_bytes());
|
||||||
self.write_eol();
|
self.write_eol();
|
||||||
@@ -198,13 +225,11 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
|
|
||||||
/// Prints the context separator.
|
/// Prints the context separator.
|
||||||
pub fn context_separate(&mut self) {
|
pub fn context_separate(&mut self) {
|
||||||
// N.B. We can't use `write` here because of borrowing restrictions.
|
|
||||||
if self.context_separator.is_empty() {
|
if self.context_separator.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.has_printed = true;
|
|
||||||
let _ = self.wtr.write_all(&self.context_separator);
|
let _ = self.wtr.write_all(&self.context_separator);
|
||||||
let _ = self.wtr.write_all(&[self.eol]);
|
self.write_eol();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn matched<P: AsRef<Path>>(
|
pub fn matched<P: AsRef<Path>>(
|
||||||
@@ -216,7 +241,7 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
end: usize,
|
end: usize,
|
||||||
line_number: Option<u64>,
|
line_number: Option<u64>,
|
||||||
) {
|
) {
|
||||||
if !self.line_per_match {
|
if !self.line_per_match && !self.only_matching {
|
||||||
let column =
|
let column =
|
||||||
if self.column {
|
if self.column {
|
||||||
Some(re.find(&buf[start..end])
|
Some(re.find(&buf[start..end])
|
||||||
@@ -251,43 +276,71 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
) {
|
) {
|
||||||
if self.heading && self.with_filename && !self.has_printed {
|
if self.heading && self.with_filename && !self.has_printed {
|
||||||
self.write_file_sep();
|
self.write_file_sep();
|
||||||
self.write_heading(path.as_ref());
|
self.write_path(path);
|
||||||
|
self.write_path_eol();
|
||||||
} else if !self.heading && self.with_filename {
|
} else if !self.heading && self.with_filename {
|
||||||
self.write_non_heading_path(path.as_ref());
|
self.write_path(path);
|
||||||
|
self.write_path_sep(b':');
|
||||||
}
|
}
|
||||||
if let Some(line_number) = line_number {
|
if let Some(line_number) = line_number {
|
||||||
self.line_number(line_number, b':');
|
self.line_number(line_number, b':');
|
||||||
}
|
}
|
||||||
if let Some(c) = column {
|
if let Some(c) = column {
|
||||||
self.write((c + 1).to_string().as_bytes());
|
self.column_number(c + 1, b':');
|
||||||
self.write(b":");
|
|
||||||
}
|
}
|
||||||
if self.replace.is_some() {
|
if self.replace.is_some() {
|
||||||
let line = re.replace_all(
|
let mut count = 0;
|
||||||
&buf[start..end], &**self.replace.as_ref().unwrap());
|
let line = {
|
||||||
|
let replacer = CountingReplacer::new(
|
||||||
|
self.replace.as_ref().unwrap(), &mut count);
|
||||||
|
re.replace_all(&buf[start..end], replacer)
|
||||||
|
};
|
||||||
|
if self.max_columns.map_or(false, |m| line.len() > m) {
|
||||||
|
let msg = format!(
|
||||||
|
"[Omitted long line with {} replacements]", count);
|
||||||
|
self.write_colored(msg.as_bytes(), |colors| colors.matched());
|
||||||
|
self.write_eol();
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.write(&line);
|
self.write(&line);
|
||||||
|
if line.last() != Some(&self.eol) {
|
||||||
|
self.write_eol();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.write_matched_line(re, &buf[start..end]);
|
let line_buf = if self.only_matching {
|
||||||
}
|
let m = re.find(&buf[start..end]).unwrap();
|
||||||
if buf[start..end].last() != Some(&self.eol) {
|
&buf[start + m.start()..start + m.end()]
|
||||||
self.write_eol();
|
} else {
|
||||||
|
&buf[start..end]
|
||||||
|
};
|
||||||
|
self.write_matched_line(re, line_buf);
|
||||||
|
// write_matched_line guarantees to write a newline.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_matched_line(&mut self, re: &Regex, buf: &[u8]) {
|
fn write_matched_line(&mut self, re: &Regex, buf: &[u8]) {
|
||||||
if !self.wtr.supports_color() || self.colors.matched().is_none() {
|
if self.max_columns.map_or(false, |m| buf.len() > m) {
|
||||||
self.write(buf);
|
let count = re.find_iter(buf).count();
|
||||||
|
let msg = format!("[Omitted long line with {} matches]", count);
|
||||||
|
self.write_colored(msg.as_bytes(), |colors| colors.matched());
|
||||||
|
self.write_eol();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut last_written = 0;
|
if !self.wtr.supports_color() || self.colors.matched().is_none() {
|
||||||
for m in re.find_iter(buf) {
|
self.write(buf);
|
||||||
self.write(&buf[last_written..m.start()]);
|
} else {
|
||||||
let _ = self.wtr.set_color(self.colors.matched());
|
let mut last_written = 0;
|
||||||
self.write(&buf[m.start()..m.end()]);
|
for m in re.find_iter(buf) {
|
||||||
let _ = self.wtr.reset();
|
self.write(&buf[last_written..m.start()]);
|
||||||
last_written = m.end();
|
self.write_colored(
|
||||||
|
&buf[m.start()..m.end()], |colors| colors.matched());
|
||||||
|
last_written = m.end();
|
||||||
|
}
|
||||||
|
self.write(&buf[last_written..]);
|
||||||
|
}
|
||||||
|
if buf.last() != Some(&self.eol) {
|
||||||
|
self.write_eol();
|
||||||
}
|
}
|
||||||
self.write(&buf[last_written..]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context<P: AsRef<Path>>(
|
pub fn context<P: AsRef<Path>>(
|
||||||
@@ -300,28 +353,39 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
) {
|
) {
|
||||||
if self.heading && self.with_filename && !self.has_printed {
|
if self.heading && self.with_filename && !self.has_printed {
|
||||||
self.write_file_sep();
|
self.write_file_sep();
|
||||||
self.write_heading(path.as_ref());
|
self.write_path(path);
|
||||||
|
self.write_path_eol();
|
||||||
} else if !self.heading && self.with_filename {
|
} else if !self.heading && self.with_filename {
|
||||||
self.write_path(path.as_ref());
|
self.write_path(path);
|
||||||
if self.null {
|
self.write_path_sep(b'-');
|
||||||
self.write(b"\x00");
|
|
||||||
} else {
|
|
||||||
self.write(b"-");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(line_number) = line_number {
|
if let Some(line_number) = line_number {
|
||||||
self.line_number(line_number, b'-');
|
self.line_number(line_number, b'-');
|
||||||
}
|
}
|
||||||
|
if self.max_columns.map_or(false, |m| end - start > m) {
|
||||||
|
self.write(format!("[Omitted long context line]").as_bytes());
|
||||||
|
self.write_eol();
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.write(&buf[start..end]);
|
self.write(&buf[start..end]);
|
||||||
if buf[start..end].last() != Some(&self.eol) {
|
if buf[start..end].last() != Some(&self.eol) {
|
||||||
self.write_eol();
|
self.write_eol();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_heading<P: AsRef<Path>>(&mut self, path: P) {
|
fn separator(&mut self, sep: &[u8]) {
|
||||||
let _ = self.wtr.set_color(self.colors.path());
|
self.write(&sep);
|
||||||
self.write_path(path.as_ref());
|
}
|
||||||
let _ = self.wtr.reset();
|
|
||||||
|
fn write_path_sep(&mut self, sep: u8) {
|
||||||
|
if self.null {
|
||||||
|
self.write(b"\x00");
|
||||||
|
} else {
|
||||||
|
self.separator(&[sep]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_path_eol(&mut self) {
|
||||||
if self.null {
|
if self.null {
|
||||||
self.write(b"\x00");
|
self.write(b"\x00");
|
||||||
} else {
|
} else {
|
||||||
@@ -329,52 +393,43 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_non_heading_path<P: AsRef<Path>>(&mut self, path: P) {
|
|
||||||
let _ = self.wtr.set_color(self.colors.path());
|
|
||||||
self.write_path(path.as_ref());
|
|
||||||
let _ = self.wtr.reset();
|
|
||||||
if self.null {
|
|
||||||
self.write(b"\x00");
|
|
||||||
} else {
|
|
||||||
self.write(b":");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_number(&mut self, n: u64, sep: u8) {
|
|
||||||
let _ = self.wtr.set_color(self.colors.line());
|
|
||||||
self.write(n.to_string().as_bytes());
|
|
||||||
let _ = self.wtr.reset();
|
|
||||||
self.write(&[sep]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn write_path<P: AsRef<Path>>(&mut self, path: P) {
|
fn write_path<P: AsRef<Path>>(&mut self, path: P) {
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
let path = path.as_ref().as_os_str().as_bytes();
|
let path = path.as_ref().as_os_str().as_bytes();
|
||||||
match self.path_separator {
|
self.write_path_replace_separator(path);
|
||||||
None => self.write(path),
|
|
||||||
Some(sep) => self.write_path_with_sep(path, sep),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
fn write_path<P: AsRef<Path>>(&mut self, path: P) {
|
fn write_path<P: AsRef<Path>>(&mut self, path: P) {
|
||||||
let path = path.as_ref().to_string_lossy();
|
let path = path.as_ref().to_string_lossy();
|
||||||
|
self.write_path_replace_separator(path.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_path_replace_separator(&mut self, path: &[u8]) {
|
||||||
match self.path_separator {
|
match self.path_separator {
|
||||||
None => self.write(path.as_bytes()),
|
None => self.write_colored(path, |colors| colors.path()),
|
||||||
Some(sep) => self.write_path_with_sep(path.as_bytes(), sep),
|
Some(sep) => {
|
||||||
|
let transformed_path: Vec<_> = path.iter().map(|&b| {
|
||||||
|
if b == b'/' || (cfg!(windows) && b == b'\\') {
|
||||||
|
sep
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
self.write_colored(&transformed_path, |colors| colors.path());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_path_with_sep(&mut self, path: &[u8], sep: u8) {
|
fn line_number(&mut self, n: u64, sep: u8) {
|
||||||
let mut path = path.to_vec();
|
self.write_colored(n.to_string().as_bytes(), |colors| colors.line());
|
||||||
for b in &mut path {
|
self.separator(&[sep]);
|
||||||
if *b == b'/' || (cfg!(windows) && *b == b'\\') {
|
}
|
||||||
*b = sep;
|
|
||||||
}
|
fn column_number(&mut self, n: u64, sep: u8) {
|
||||||
}
|
self.write_colored(n.to_string().as_bytes(), |colors| colors.column());
|
||||||
self.write(&path);
|
self.separator(&[sep]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, buf: &[u8]) {
|
fn write(&mut self, buf: &[u8]) {
|
||||||
@@ -387,6 +442,14 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
self.write(&[eol]);
|
self.write(&[eol]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_colored<F>(&mut self, buf: &[u8], get_color: F)
|
||||||
|
where F: Fn(&ColorSpecs) -> &ColorSpec
|
||||||
|
{
|
||||||
|
let _ = self.wtr.set_color( get_color(&self.colors) );
|
||||||
|
self.write(buf);
|
||||||
|
let _ = self.wtr.reset();
|
||||||
|
}
|
||||||
|
|
||||||
fn write_file_sep(&mut self) {
|
fn write_file_sep(&mut self) {
|
||||||
if let Some(ref sep) = self.file_separator {
|
if let Some(ref sep) = self.file_separator {
|
||||||
self.has_printed = true;
|
self.has_printed = true;
|
||||||
@@ -432,7 +495,7 @@ impl fmt::Display for Error {
|
|||||||
match *self {
|
match *self {
|
||||||
Error::UnrecognizedOutType(ref name) => {
|
Error::UnrecognizedOutType(ref name) => {
|
||||||
write!(f, "Unrecognized output type '{}'. Choose from: \
|
write!(f, "Unrecognized output type '{}'. Choose from: \
|
||||||
path, line, match.", name)
|
path, line, column, match.", name)
|
||||||
}
|
}
|
||||||
Error::UnrecognizedSpecType(ref name) => {
|
Error::UnrecognizedSpecType(ref name) => {
|
||||||
write!(f, "Unrecognized spec type '{}'. Choose from: \
|
write!(f, "Unrecognized spec type '{}'. Choose from: \
|
||||||
@@ -443,12 +506,14 @@ impl fmt::Display for Error {
|
|||||||
}
|
}
|
||||||
Error::UnrecognizedStyle(ref name) => {
|
Error::UnrecognizedStyle(ref name) => {
|
||||||
write!(f, "Unrecognized style attribute '{}'. Choose from: \
|
write!(f, "Unrecognized style attribute '{}'. Choose from: \
|
||||||
nobold, bold.", name)
|
nobold, bold, nointense, intense.", name)
|
||||||
}
|
}
|
||||||
Error::InvalidFormat(ref original) => {
|
Error::InvalidFormat(ref original) => {
|
||||||
write!(f, "Invalid color speci format: '{}'. Valid format \
|
write!(
|
||||||
is '(path|line|match):(fg|bg|style):(value)'.",
|
f,
|
||||||
original)
|
"Invalid color speci format: '{}'. Valid format \
|
||||||
|
is '(path|line|column|match):(fg|bg|style):(value)'.",
|
||||||
|
original)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,6 +530,7 @@ impl From<ParseColorError> for Error {
|
|||||||
pub struct ColorSpecs {
|
pub struct ColorSpecs {
|
||||||
path: ColorSpec,
|
path: ColorSpec,
|
||||||
line: ColorSpec,
|
line: ColorSpec,
|
||||||
|
column: ColorSpec,
|
||||||
matched: ColorSpec,
|
matched: ColorSpec,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,7 +560,7 @@ pub struct ColorSpecs {
|
|||||||
/// The format of a `Spec` is a triple: `{type}:{attribute}:{value}`. Each
|
/// The format of a `Spec` is a triple: `{type}:{attribute}:{value}`. Each
|
||||||
/// component is defined as follows:
|
/// component is defined as follows:
|
||||||
///
|
///
|
||||||
/// * `{type}` can be one of `path`, `line` or `match`.
|
/// * `{type}` can be one of `path`, `line`, `column` or `match`.
|
||||||
/// * `{attribute}` can be one of `fg`, `bg` or `style`. `{attribute}` may also
|
/// * `{attribute}` can be one of `fg`, `bg` or `style`. `{attribute}` may also
|
||||||
/// be the special value `none`, in which case, `{value}` can be omitted.
|
/// be the special value `none`, in which case, `{value}` can be omitted.
|
||||||
/// * `{value}` is either a color name (for `fg`/`bg`) or a style instruction.
|
/// * `{value}` is either a color name (for `fg`/`bg`) or a style instruction.
|
||||||
@@ -533,6 +599,7 @@ enum SpecValue {
|
|||||||
enum OutType {
|
enum OutType {
|
||||||
Path,
|
Path,
|
||||||
Line,
|
Line,
|
||||||
|
Column,
|
||||||
Match,
|
Match,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,6 +630,7 @@ impl ColorSpecs {
|
|||||||
match user_spec.ty {
|
match user_spec.ty {
|
||||||
OutType::Path => user_spec.merge_into(&mut specs.path),
|
OutType::Path => user_spec.merge_into(&mut specs.path),
|
||||||
OutType::Line => user_spec.merge_into(&mut specs.line),
|
OutType::Line => user_spec.merge_into(&mut specs.line),
|
||||||
|
OutType::Column => user_spec.merge_into(&mut specs.column),
|
||||||
OutType::Match => user_spec.merge_into(&mut specs.matched),
|
OutType::Match => user_spec.merge_into(&mut specs.matched),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -579,6 +647,11 @@ impl ColorSpecs {
|
|||||||
&self.line
|
&self.line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the color specification for coloring column numbers.
|
||||||
|
fn column(&self) -> &ColorSpec {
|
||||||
|
&self.column
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the color specification for coloring matched text.
|
/// Return the color specification for coloring matched text.
|
||||||
fn matched(&self) -> &ColorSpec {
|
fn matched(&self) -> &ColorSpec {
|
||||||
&self.matched
|
&self.matched
|
||||||
@@ -654,6 +727,7 @@ impl FromStr for OutType {
|
|||||||
match &*s.to_lowercase() {
|
match &*s.to_lowercase() {
|
||||||
"path" => Ok(OutType::Path),
|
"path" => Ok(OutType::Path),
|
||||||
"line" => Ok(OutType::Line),
|
"line" => Ok(OutType::Line),
|
||||||
|
"column" => Ok(OutType::Column),
|
||||||
"match" => Ok(OutType::Match),
|
"match" => Ok(OutType::Match),
|
||||||
_ => Err(Error::UnrecognizedOutType(s.to_string())),
|
_ => Err(Error::UnrecognizedOutType(s.to_string())),
|
||||||
}
|
}
|
||||||
@@ -705,6 +779,7 @@ mod tests {
|
|||||||
assert_eq!(ColorSpecs::new(user_specs), ColorSpecs {
|
assert_eq!(ColorSpecs::new(user_specs), ColorSpecs {
|
||||||
path: ColorSpec::default(),
|
path: ColorSpec::default(),
|
||||||
line: ColorSpec::default(),
|
line: ColorSpec::default(),
|
||||||
|
column: ColorSpec::default(),
|
||||||
matched: expect_matched,
|
matched: expect_matched,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -740,6 +815,12 @@ mod tests {
|
|||||||
ty: OutType::Line,
|
ty: OutType::Line,
|
||||||
value: SpecValue::None,
|
value: SpecValue::None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let spec: Spec = "column:bg:green".parse().unwrap();
|
||||||
|
assert_eq!(spec, Spec {
|
||||||
|
ty: OutType::Column,
|
||||||
|
value: SpecValue::Bg(Color::Green),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@@ -3,8 +3,8 @@ The `search_buffer` module is responsible for searching a single file all in a
|
|||||||
single buffer. Typically, the source of the buffer is a memory map. This can
|
single buffer. Typically, the source of the buffer is a memory map. This can
|
||||||
be useful for when memory maps are faster than streaming search.
|
be useful for when memory maps are faster than streaming search.
|
||||||
|
|
||||||
Note that this module doesn't quite support everything that `search_stream` does.
|
Note that this module doesn't quite support everything that `search_stream`
|
||||||
Notably, showing contexts.
|
does. Notably, showing contexts.
|
||||||
*/
|
*/
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -113,8 +113,8 @@ impl<'a, W: WriteColor> BufferSearcher<'a, W> {
|
|||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn run(mut self) -> u64 {
|
pub fn run(mut self) -> u64 {
|
||||||
let binary_upto = cmp::min(4096, self.buf.len());
|
let binary_upto = cmp::min(10240, self.buf.len());
|
||||||
if !self.opts.text && is_binary(&self.buf[..binary_upto]) {
|
if !self.opts.text && is_binary(&self.buf[..binary_upto], true) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -248,6 +248,7 @@ impl<'a, R: io::Read, W: WriteColor> Searcher<'a, R, W> {
|
|||||||
/// If enabled, search binary files as if they were text.
|
/// If enabled, search binary files as if they were text.
|
||||||
pub fn text(mut self, yes: bool) -> Self {
|
pub fn text(mut self, yes: bool) -> Self {
|
||||||
self.opts.text = yes;
|
self.opts.text = yes;
|
||||||
|
self.inp.text(yes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,9 +267,6 @@ impl<'a, R: io::Read, W: WriteColor> Searcher<'a, R, W> {
|
|||||||
if !try!(self.fill()) {
|
if !try!(self.fill()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if !self.opts.text && self.inp.is_binary {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
while !self.terminate() && self.inp.pos < self.inp.lastnl {
|
while !self.terminate() && self.inp.pos < self.inp.lastnl {
|
||||||
let matched = self.grep.read_match(
|
let matched = self.grep.read_match(
|
||||||
&mut self.last_match,
|
&mut self.last_match,
|
||||||
@@ -501,10 +499,8 @@ pub struct InputBuffer {
|
|||||||
end: usize,
|
end: usize,
|
||||||
/// Set to true if and only if no reads have occurred yet.
|
/// Set to true if and only if no reads have occurred yet.
|
||||||
first: bool,
|
first: bool,
|
||||||
/// Set to true if and only if the contents of buf are determined to be
|
/// Set to true if all binary data should be treated as if it were text.
|
||||||
/// "binary" (i.e., not searchable text). Note that its value may be
|
text: bool,
|
||||||
/// falsely negative *or* falsely positive. It is only a heuristic.
|
|
||||||
is_binary: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputBuffer {
|
impl InputBuffer {
|
||||||
@@ -532,13 +528,23 @@ impl InputBuffer {
|
|||||||
lastnl: 0,
|
lastnl: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
first: true,
|
first: true,
|
||||||
is_binary: false,
|
text: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the end-of-line terminator used by this input buffer.
|
/// Set the end-of-line terminator used by this input buffer.
|
||||||
pub fn eol(&mut self, eol: u8) {
|
pub fn eol(&mut self, eol: u8) -> &mut Self {
|
||||||
self.eol = eol;
|
self.eol = eol;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If enabled, search binary files as if they were text.
|
||||||
|
///
|
||||||
|
/// Note that this may cause the buffer to load the entire contents of a
|
||||||
|
/// file into memory.
|
||||||
|
pub fn text(&mut self, yes: bool) -> &mut Self {
|
||||||
|
self.text = yes;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resets this buffer so that it may be reused with a new reader.
|
/// Resets this buffer so that it may be reused with a new reader.
|
||||||
@@ -547,7 +553,6 @@ impl InputBuffer {
|
|||||||
self.lastnl = 0;
|
self.lastnl = 0;
|
||||||
self.end = 0;
|
self.end = 0;
|
||||||
self.first = true;
|
self.first = true;
|
||||||
self.is_binary = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fill the contents of this buffer with the reader given. The reader
|
/// Fill the contents of this buffer with the reader given. The reader
|
||||||
@@ -582,8 +587,10 @@ impl InputBuffer {
|
|||||||
}
|
}
|
||||||
let n = try!(rdr.read(
|
let n = try!(rdr.read(
|
||||||
&mut self.buf[self.end..self.end + self.read_size]));
|
&mut self.buf[self.end..self.end + self.read_size]));
|
||||||
if self.first && is_binary(&self.buf[self.end..self.end + n]) {
|
if !self.text {
|
||||||
self.is_binary = true;
|
if is_binary(&self.buf[self.end..self.end + n], self.first) {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.first = false;
|
self.first = false;
|
||||||
// We assume that reading 0 bytes means we've hit EOF.
|
// We assume that reading 0 bytes means we've hit EOF.
|
||||||
@@ -613,11 +620,11 @@ impl InputBuffer {
|
|||||||
///
|
///
|
||||||
/// Note that this may return both false positives and false negatives.
|
/// Note that this may return both false positives and false negatives.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn is_binary(buf: &[u8]) -> bool {
|
pub fn is_binary(buf: &[u8], first: bool) -> bool {
|
||||||
if buf.len() >= 4 && &buf[0..4] == b"%PDF" {
|
if first && buf.len() >= 4 && &buf[0..4] == b"%PDF" {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
memchr(b'\x00', &buf[0..cmp::min(1024, buf.len())]).is_some()
|
memchr(b'\x00', buf).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Count the number of lines in the given buffer.
|
/// Count the number of lines in the given buffer.
|
||||||
|
@@ -2,11 +2,13 @@ use std::fs::File;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use encoding_rs::Encoding;
|
||||||
use grep::Grep;
|
use grep::Grep;
|
||||||
use ignore::DirEntry;
|
use ignore::DirEntry;
|
||||||
use memmap::{Mmap, Protection};
|
use memmap::{Mmap, Protection};
|
||||||
use termcolor::WriteColor;
|
use termcolor::WriteColor;
|
||||||
|
|
||||||
|
use decoder::DecodeReader;
|
||||||
use pathutil::strip_prefix;
|
use pathutil::strip_prefix;
|
||||||
use printer::Printer;
|
use printer::Printer;
|
||||||
use search_buffer::BufferSearcher;
|
use search_buffer::BufferSearcher;
|
||||||
@@ -27,6 +29,7 @@ pub struct WorkerBuilder {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Options {
|
struct Options {
|
||||||
mmap: bool,
|
mmap: bool,
|
||||||
|
encoding: Option<&'static Encoding>,
|
||||||
after_context: usize,
|
after_context: usize,
|
||||||
before_context: usize,
|
before_context: usize,
|
||||||
count: bool,
|
count: bool,
|
||||||
@@ -45,6 +48,7 @@ impl Default for Options {
|
|||||||
fn default() -> Options {
|
fn default() -> Options {
|
||||||
Options {
|
Options {
|
||||||
mmap: false,
|
mmap: false,
|
||||||
|
encoding: None,
|
||||||
after_context: 0,
|
after_context: 0,
|
||||||
before_context: 0,
|
before_context: 0,
|
||||||
count: false,
|
count: false,
|
||||||
@@ -80,6 +84,7 @@ impl WorkerBuilder {
|
|||||||
Worker {
|
Worker {
|
||||||
grep: self.grep,
|
grep: self.grep,
|
||||||
inpbuf: inpbuf,
|
inpbuf: inpbuf,
|
||||||
|
decodebuf: vec![0; 8 * (1<<10)],
|
||||||
opts: self.opts,
|
opts: self.opts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +111,15 @@ impl WorkerBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the encoding to use to read each file.
|
||||||
|
///
|
||||||
|
/// If the encoding is `None` (the default), then the encoding is
|
||||||
|
/// automatically detected on a best-effort per-file basis.
|
||||||
|
pub fn encoding(mut self, enc: Option<&'static Encoding>) -> Self {
|
||||||
|
self.opts.encoding = enc;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// If enabled, searching will print the path instead of each match.
|
/// If enabled, searching will print the path instead of each match.
|
||||||
///
|
///
|
||||||
/// Disabled by default.
|
/// Disabled by default.
|
||||||
@@ -181,8 +195,9 @@ impl WorkerBuilder {
|
|||||||
/// Worker is responsible for executing searches on file paths, while choosing
|
/// Worker is responsible for executing searches on file paths, while choosing
|
||||||
/// streaming search or memory map search as appropriate.
|
/// streaming search or memory map search as appropriate.
|
||||||
pub struct Worker {
|
pub struct Worker {
|
||||||
inpbuf: InputBuffer,
|
|
||||||
grep: Grep,
|
grep: Grep,
|
||||||
|
inpbuf: InputBuffer,
|
||||||
|
decodebuf: Vec<u8>,
|
||||||
opts: Options,
|
opts: Options,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +256,8 @@ impl Worker {
|
|||||||
path: &Path,
|
path: &Path,
|
||||||
rdr: R,
|
rdr: R,
|
||||||
) -> Result<u64> {
|
) -> Result<u64> {
|
||||||
|
let rdr = DecodeReader::new(
|
||||||
|
rdr, &mut self.decodebuf, self.opts.encoding);
|
||||||
let searcher = Searcher::new(
|
let searcher = Searcher::new(
|
||||||
&mut self.inpbuf, printer, &self.grep, path, rdr);
|
&mut self.inpbuf, printer, &self.grep, path, rdr);
|
||||||
searcher
|
searcher
|
||||||
@@ -274,8 +291,13 @@ impl Worker {
|
|||||||
return self.search(printer, path, file);
|
return self.search(printer, path, file);
|
||||||
}
|
}
|
||||||
let mmap = try!(Mmap::open(file, Protection::Read));
|
let mmap = try!(Mmap::open(file, Protection::Read));
|
||||||
let searcher = BufferSearcher::new(
|
let buf = unsafe { mmap.as_slice() };
|
||||||
printer, &self.grep, path, unsafe { mmap.as_slice() });
|
if buf.len() >= 3 && Encoding::for_bom(buf).is_some() {
|
||||||
|
// If we have a UTF-16 bom in our memory map, then we need to fall
|
||||||
|
// back to the stream reader, which will do transcoding.
|
||||||
|
return self.search(printer, path, file);
|
||||||
|
}
|
||||||
|
let searcher = BufferSearcher::new(printer, &self.grep, path, buf);
|
||||||
Ok(searcher
|
Ok(searcher
|
||||||
.count(self.opts.count)
|
.count(self.opts.count)
|
||||||
.files_with_matches(self.opts.files_with_matches)
|
.files_with_matches(self.opts.files_with_matches)
|
||||||
|
3
termcolor/COPYING
Normal file
3
termcolor/COPYING
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
This project is dual-licensed under the Unlicense and MIT licenses.
|
||||||
|
|
||||||
|
You may use this code under the terms of either license.
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "0.2.0" #:version
|
version = "0.3.2" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
A simple cross platform library for writing colored text to a terminal.
|
A simple cross platform library for writing colored text to a terminal.
|
||||||
@@ -17,4 +17,4 @@ name = "termcolor"
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
wincolor = { version = "0.1.1", path = "../wincolor" }
|
wincolor = { version = "0.1.3", path = "../wincolor" }
|
||||||
|
21
termcolor/LICENSE-MIT
Normal file
21
termcolor/LICENSE-MIT
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Andrew Gallant
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
@@ -6,9 +6,6 @@ by interacting with the Windows console. Several convenient abstractions
|
|||||||
are provided for use in single-threaded or multi-threaded command line
|
are provided for use in single-threaded or multi-threaded command line
|
||||||
applications.
|
applications.
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
|
||||||
[](https://crates.io/crates/wincolor)
|
|
||||||
|
|
||||||
[](https://travis-ci.org/BurntSushi/ripgrep)
|
[](https://travis-ci.org/BurntSushi/ripgrep)
|
||||||
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
[](https://ci.appveyor.com/project/BurntSushi/ripgrep)
|
||||||
[](https://crates.io/crates/termcolor)
|
[](https://crates.io/crates/termcolor)
|
||||||
@@ -25,7 +22,7 @@ Add this to your `Cargo.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
termcolor = "0.1"
|
termcolor = "0.3"
|
||||||
```
|
```
|
||||||
|
|
||||||
and this to your crate root:
|
and this to your crate root:
|
||||||
@@ -39,12 +36,13 @@ extern crate termcolor;
|
|||||||
The `WriteColor` trait extends the `io::Write` trait with methods for setting
|
The `WriteColor` trait extends the `io::Write` trait with methods for setting
|
||||||
colors or resetting them.
|
colors or resetting them.
|
||||||
|
|
||||||
`Stdout` and `StdoutLock` both satisfy `WriteColor` and are analogous to
|
`StandardStream` and `StandardStreamLock` both satisfy `WriteColor` and are
|
||||||
`std::io::Stdout` and `std::io::StdoutLock`.
|
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
|
`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
|
program, each thread might write to its own buffer. A buffer can be printed to
|
||||||
to stdout using a `BufferWriter`. The advantage of this design is that
|
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
|
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
|
access to global resources such as the Windows console. Moreover, this design
|
||||||
also prevents interleaving of buffer output.
|
also prevents interleaving of buffer output.
|
||||||
@@ -53,34 +51,34 @@ also prevents interleaving of buffer output.
|
|||||||
`io::Write`. These types are useful when you know exactly what you need. An
|
`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.
|
analogous type for the Windows console is not provided since it cannot exist.
|
||||||
|
|
||||||
### Example: using `Stdout`
|
### Example: using `StandardStream`
|
||||||
|
|
||||||
The `Stdout` type in this crate works similarly to `std::io::Stdout`, except
|
The `StandardStream` type in this crate works similarly to `std::io::Stdout`,
|
||||||
it is augmented with methods for coloring by the `WriteColor` trait. For
|
except it is augmented with methods for coloring by the `WriteColor` trait.
|
||||||
example, to write some green text:
|
For example, to write some green text:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use termcolor::{Color, ColorChoice, ColorSpec, Stdout, WriteColor};
|
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
|
||||||
|
|
||||||
let mut stdout = Stdout::new(ColorChoice::Always);
|
let mut stdout = StandardStream::stdout(ColorChoice::Always);
|
||||||
try!(stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
|
try!(stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
|
||||||
try!(writeln!(&mut stdout, "green text!"));
|
try!(writeln!(&mut stdout, "green text!"));
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example: using `BufferWriter`
|
### Example: using `BufferWriter`
|
||||||
|
|
||||||
A `BufferWriter` can create buffers and write buffers to stdout. It does *not*
|
A `BufferWriter` can create buffers and write buffers to stdout or stderr. It
|
||||||
implement `io::Write` or `WriteColor` itself. Instead, `Buffer` implements
|
does *not* implement `io::Write` or `WriteColor` itself. Instead, `Buffer`
|
||||||
`io::Write` and `io::WriteColor`.
|
implements `io::Write` and `io::WriteColor`.
|
||||||
|
|
||||||
This example shows how to print some green text to stdout.
|
This example shows how to print some green text to stderr.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
|
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
|
||||||
|
|
||||||
let mut bufwtr = BufferWriter::stdout(ColorChoice::Always);
|
let mut bufwtr = BufferWriter::stderr(ColorChoice::Always);
|
||||||
let mut buffer = bufwtr.buffer();
|
let mut buffer = bufwtr.buffer();
|
||||||
try!(buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
|
try!(buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
|
||||||
try!(writeln!(&mut buffer, "green text!"));
|
try!(writeln!(&mut buffer, "green text!"));
|
||||||
|
24
termcolor/UNLICENSE
Normal file
24
termcolor/UNLICENSE
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org/>
|
@@ -15,32 +15,33 @@ Windows console API, which requires synchronous communication.
|
|||||||
The `WriteColor` trait extends the `io::Write` trait with methods for setting
|
The `WriteColor` trait extends the `io::Write` trait with methods for setting
|
||||||
colors or resetting them.
|
colors or resetting them.
|
||||||
|
|
||||||
`Stdout` and `StdoutLock` both satisfy `WriteColor` and are analogous to
|
`StandardStream` and `StandardStreamLock` both satisfy `WriteColor` and are
|
||||||
`std::io::Stdout` and `std::io::StdoutLock`.
|
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
|
`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
|
program, each thread might write to its own buffer. A buffer can be printed to
|
||||||
to stdout using a `BufferWriter`. The advantage of this design is that
|
using a `BufferWriter`. The advantage of this design is that each thread can
|
||||||
each thread can work in parallel on a buffer without having to synchronize
|
work in parallel on a buffer without having to synchronize access to global
|
||||||
access to global resources such as the Windows console. Moreover, this design
|
resources such as the Windows console. Moreover, this design also prevents
|
||||||
also prevents interleaving of buffer output.
|
interleaving of buffer output.
|
||||||
|
|
||||||
`Ansi` and `NoColor` both satisfy `WriteColor` for arbitrary implementors of
|
`Ansi` and `NoColor` both satisfy `WriteColor` for arbitrary implementors of
|
||||||
`io::Write`. These types are useful when you know exactly what you need. An
|
`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.
|
analogous type for the Windows console is not provided since it cannot exist.
|
||||||
|
|
||||||
# Example: using `Stdout`
|
# Example: using `StandardStream`
|
||||||
|
|
||||||
The `Stdout` type in this crate works similarly to `std::io::Stdout`, except
|
The `StandardStream` type in this crate works similarly to `std::io::Stdout`,
|
||||||
it is augmented with methods for coloring by the `WriteColor` trait. For
|
except it is augmented with methods for coloring by the `WriteColor` trait.
|
||||||
example, to write some green text:
|
For example, to write some green text:
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
# fn test() -> Result<(), Box<::std::error::Error>> {
|
# fn test() -> Result<(), Box<::std::error::Error>> {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use termcolor::{Color, ColorChoice, ColorSpec, Stdout, WriteColor};
|
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
|
||||||
|
|
||||||
let mut stdout = Stdout::new(ColorChoice::Always);
|
let mut stdout = StandardStream::stdout(ColorChoice::Always);
|
||||||
try!(stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
|
try!(stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
|
||||||
try!(writeln!(&mut stdout, "green text!"));
|
try!(writeln!(&mut stdout, "green text!"));
|
||||||
# Ok(()) }
|
# Ok(()) }
|
||||||
@@ -48,18 +49,18 @@ try!(writeln!(&mut stdout, "green text!"));
|
|||||||
|
|
||||||
# Example: using `BufferWriter`
|
# Example: using `BufferWriter`
|
||||||
|
|
||||||
A `BufferWriter` can create buffers and write buffers to stdout. It does *not*
|
A `BufferWriter` can create buffers and write buffers to stdout or stderr. It
|
||||||
implement `io::Write` or `WriteColor` itself. Instead, `Buffer` implements
|
does *not* implement `io::Write` or `WriteColor` itself. Instead, `Buffer`
|
||||||
`io::Write` and `io::WriteColor`.
|
implements `io::Write` and `io::WriteColor`.
|
||||||
|
|
||||||
This example shows how to print some green text to stdout.
|
This example shows how to print some green text to stderr.
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
# fn test() -> Result<(), Box<::std::error::Error>> {
|
# fn test() -> Result<(), Box<::std::error::Error>> {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
|
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
|
||||||
|
|
||||||
let mut bufwtr = BufferWriter::stdout(ColorChoice::Always);
|
let mut bufwtr = BufferWriter::stderr(ColorChoice::Always);
|
||||||
let mut buffer = bufwtr.buffer();
|
let mut buffer = bufwtr.buffer();
|
||||||
try!(buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
|
try!(buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
|
||||||
try!(writeln!(&mut buffer, "green text!"));
|
try!(writeln!(&mut buffer, "green text!"));
|
||||||
@@ -184,20 +185,89 @@ impl ColorChoice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Satisfies `io::Write` and `WriteColor`, and supports optional coloring
|
/// `std::io` implements `Stdout` and `Stderr` (and their `Lock` variants) as
|
||||||
/// to stdout.
|
/// separate types, which makes it difficult to abstract over them. We use
|
||||||
pub struct Stdout {
|
/// some simple internal enum types to work around this.
|
||||||
wtr: LossyStdout<WriterInner<'static, io::Stdout>>,
|
|
||||||
|
enum StandardStreamType {
|
||||||
|
Stdout,
|
||||||
|
Stderr,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `StdoutLock` is a locked reference to a `Stdout`.
|
enum IoStandardStream {
|
||||||
|
Stdout(io::Stdout),
|
||||||
|
Stderr(io::Stderr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IoStandardStream {
|
||||||
|
fn new(sty: StandardStreamType) -> IoStandardStream {
|
||||||
|
match sty {
|
||||||
|
StandardStreamType::Stdout => IoStandardStream::Stdout(io::stdout()),
|
||||||
|
StandardStreamType::Stderr => IoStandardStream::Stderr(io::stderr()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lock(&self) -> IoStandardStreamLock {
|
||||||
|
match *self {
|
||||||
|
IoStandardStream::Stdout(ref s) => IoStandardStreamLock::StdoutLock(s.lock()),
|
||||||
|
IoStandardStream::Stderr(ref s) => IoStandardStreamLock::StderrLock(s.lock()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Write for IoStandardStream {
|
||||||
|
fn write(&mut self, b: &[u8]) -> io::Result<usize> {
|
||||||
|
match *self {
|
||||||
|
IoStandardStream::Stdout(ref mut s) => s.write(b),
|
||||||
|
IoStandardStream::Stderr(ref mut s) => s.write(b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
match *self {
|
||||||
|
IoStandardStream::Stdout(ref mut s) => s.flush(),
|
||||||
|
IoStandardStream::Stderr(ref mut s) => s.flush(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same rigamorale for the locked variants of the standard streams.
|
||||||
|
|
||||||
|
enum IoStandardStreamLock<'a> {
|
||||||
|
StdoutLock(io::StdoutLock<'a>),
|
||||||
|
StderrLock(io::StderrLock<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> io::Write for IoStandardStreamLock<'a> {
|
||||||
|
fn write(&mut self, b: &[u8]) -> io::Result<usize> {
|
||||||
|
match *self {
|
||||||
|
IoStandardStreamLock::StdoutLock(ref mut s) => s.write(b),
|
||||||
|
IoStandardStreamLock::StderrLock(ref mut s) => s.write(b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
match *self {
|
||||||
|
IoStandardStreamLock::StdoutLock(ref mut s) => s.flush(),
|
||||||
|
IoStandardStreamLock::StderrLock(ref mut s) => s.flush(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Satisfies `io::Write` and `WriteColor`, and supports optional coloring
|
||||||
|
/// to either of the standard output streams, stdout and stderr.
|
||||||
|
pub struct StandardStream {
|
||||||
|
wtr: LossyStandardStream<WriterInner<'static, IoStandardStream>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `StandardStreamLock` is a locked reference to a `StandardStream`.
|
||||||
///
|
///
|
||||||
/// This implements the `io::Write` and `WriteColor` traits, and is constructed
|
/// This implements the `io::Write` and `WriteColor` traits, and is constructed
|
||||||
/// via the `Write::lock` method.
|
/// via the `Write::lock` method.
|
||||||
///
|
///
|
||||||
/// The lifetime `'a` refers to the lifetime of the corresponding `Stdout`.
|
/// The lifetime `'a` refers to the lifetime of the corresponding `StandardStream`.
|
||||||
pub struct StdoutLock<'a> {
|
pub struct StandardStreamLock<'a> {
|
||||||
wtr: LossyStdout<WriterInner<'a, io::StdoutLock<'a>>>,
|
wtr: LossyStandardStream<WriterInner<'a, IoStandardStreamLock<'a>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// WriterInner is a (limited) generic representation of a writer. It is
|
/// WriterInner is a (limited) generic representation of a writer. It is
|
||||||
@@ -217,23 +287,23 @@ enum WriterInner<'a, W> {
|
|||||||
WindowsLocked { wtr: W, console: MutexGuard<'a, wincolor::Console> },
|
WindowsLocked { wtr: W, console: MutexGuard<'a, wincolor::Console> },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stdout {
|
impl StandardStream {
|
||||||
/// Create a new `Stdout` with the given color preferences.
|
/// Create a new `StandardStream` with the given color preferences.
|
||||||
///
|
///
|
||||||
/// The specific color/style settings can be configured when writing via
|
/// The specific color/style settings can be configured when writing via
|
||||||
/// the `WriteColor` trait.
|
/// the `WriteColor` trait.
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
pub fn new(choice: ColorChoice) -> Stdout {
|
fn create(sty: StandardStreamType, choice: ColorChoice) -> StandardStream {
|
||||||
let wtr =
|
let wtr =
|
||||||
if choice.should_attempt_color() {
|
if choice.should_attempt_color() {
|
||||||
WriterInner::Ansi(Ansi(io::stdout()))
|
WriterInner::Ansi(Ansi(IoStandardStream::new(sty)))
|
||||||
} else {
|
} else {
|
||||||
WriterInner::NoColor(NoColor(io::stdout()))
|
WriterInner::NoColor(NoColor(IoStandardStream::new(sty)))
|
||||||
};
|
};
|
||||||
Stdout { wtr: LossyStdout::new(wtr) }
|
StandardStream { wtr: LossyStandardStream::new(wtr) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new `Stdout` with the given color preferences.
|
/// Create a new `StandardStream` with the given color preferences.
|
||||||
///
|
///
|
||||||
/// If coloring is desired and a Windows console could not be found, then
|
/// If coloring is desired and a Windows console could not be found, then
|
||||||
/// ANSI escape sequences are used instead.
|
/// ANSI escape sequences are used instead.
|
||||||
@@ -241,25 +311,52 @@ impl Stdout {
|
|||||||
/// The specific color/style settings can be configured when writing via
|
/// The specific color/style settings can be configured when writing via
|
||||||
/// the `WriteColor` trait.
|
/// the `WriteColor` trait.
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn new(choice: ColorChoice) -> Stdout {
|
fn create(sty: StandardStreamType, choice: ColorChoice) -> StandardStream {
|
||||||
let con = wincolor::Console::stdout();
|
let con = match sty {
|
||||||
|
StandardStreamType::Stdout => wincolor::Console::stdout(),
|
||||||
|
StandardStreamType::Stderr => wincolor::Console::stderr(),
|
||||||
|
};
|
||||||
let is_win_console = con.is_ok();
|
let is_win_console = con.is_ok();
|
||||||
let wtr =
|
let wtr =
|
||||||
if choice.should_attempt_color() {
|
if choice.should_attempt_color() {
|
||||||
if choice.should_ansi() {
|
if choice.should_ansi() {
|
||||||
WriterInner::Ansi(Ansi(io::stdout()))
|
WriterInner::Ansi(Ansi(IoStandardStream::new(sty)))
|
||||||
} else if let Ok(console) = con {
|
} else if let Ok(console) = con {
|
||||||
WriterInner::Windows {
|
WriterInner::Windows {
|
||||||
wtr: io::stdout(),
|
wtr: IoStandardStream::new(sty),
|
||||||
console: Mutex::new(console),
|
console: Mutex::new(console),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
WriterInner::Ansi(Ansi(io::stdout()))
|
WriterInner::Ansi(Ansi(IoStandardStream::new(sty)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
WriterInner::NoColor(NoColor(io::stdout()))
|
WriterInner::NoColor(NoColor(IoStandardStream::new(sty)))
|
||||||
};
|
};
|
||||||
Stdout { wtr: LossyStdout::new(wtr).is_console(is_win_console) }
|
StandardStream { wtr: LossyStandardStream::new(wtr).is_console(is_win_console) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `StandardStream` with the given color preferences that
|
||||||
|
/// writes to standard output.
|
||||||
|
///
|
||||||
|
/// On Windows, if coloring is desired and a Windows console could not be
|
||||||
|
/// found, then ANSI escape sequences are used instead.
|
||||||
|
///
|
||||||
|
/// The specific color/style settings can be configured when writing via
|
||||||
|
/// the `WriteColor` trait.
|
||||||
|
pub fn stdout(choice: ColorChoice) -> StandardStream {
|
||||||
|
StandardStream::create(StandardStreamType::Stdout, choice)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `StandardStream` with the given color preferences that
|
||||||
|
/// writes to standard error.
|
||||||
|
///
|
||||||
|
/// On Windows, if coloring is desired and a Windows console could not be
|
||||||
|
/// found, then ANSI escape sequences are used instead.
|
||||||
|
///
|
||||||
|
/// The specific color/style settings can be configured when writing via
|
||||||
|
/// the `WriteColor` trait.
|
||||||
|
pub fn stderr(choice: ColorChoice) -> StandardStream {
|
||||||
|
StandardStream::create(StandardStreamType::Stderr, choice)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lock the underlying writer.
|
/// Lock the underlying writer.
|
||||||
@@ -268,16 +365,16 @@ impl Stdout {
|
|||||||
/// `WriteColor`.
|
/// `WriteColor`.
|
||||||
///
|
///
|
||||||
/// This method is **not reentrant**. It may panic if `lock` is called
|
/// This method is **not reentrant**. It may panic if `lock` is called
|
||||||
/// while a `StdoutLock` is still alive.
|
/// while a `StandardStreamLock` is still alive.
|
||||||
pub fn lock(&self) -> StdoutLock {
|
pub fn lock(&self) -> StandardStreamLock {
|
||||||
StdoutLock::from_stdout(self)
|
StandardStreamLock::from_stream(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StdoutLock<'a> {
|
impl<'a> StandardStreamLock<'a> {
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
fn from_stdout(stdout: &Stdout) -> StdoutLock {
|
fn from_stream(stream: &StandardStream) -> StandardStreamLock {
|
||||||
let locked = match *stdout.wtr.get_ref() {
|
let locked = match *stream.wtr.get_ref() {
|
||||||
WriterInner::Unreachable(_) => unreachable!(),
|
WriterInner::Unreachable(_) => unreachable!(),
|
||||||
WriterInner::NoColor(ref w) => {
|
WriterInner::NoColor(ref w) => {
|
||||||
WriterInner::NoColor(NoColor(w.0.lock()))
|
WriterInner::NoColor(NoColor(w.0.lock()))
|
||||||
@@ -286,12 +383,12 @@ impl<'a> StdoutLock<'a> {
|
|||||||
WriterInner::Ansi(Ansi(w.0.lock()))
|
WriterInner::Ansi(Ansi(w.0.lock()))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
StdoutLock { wtr: stdout.wtr.wrap(locked) }
|
StandardStreamLock { wtr: stream.wtr.wrap(locked) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn from_stdout(stdout: &Stdout) -> StdoutLock {
|
fn from_stream(stream: &StandardStream) -> StandardStreamLock {
|
||||||
let locked = match *stdout.wtr.get_ref() {
|
let locked = match *stream.wtr.get_ref() {
|
||||||
WriterInner::Unreachable(_) => unreachable!(),
|
WriterInner::Unreachable(_) => unreachable!(),
|
||||||
WriterInner::NoColor(ref w) => {
|
WriterInner::NoColor(ref w) => {
|
||||||
WriterInner::NoColor(NoColor(w.0.lock()))
|
WriterInner::NoColor(NoColor(w.0.lock()))
|
||||||
@@ -308,19 +405,19 @@ impl<'a> StdoutLock<'a> {
|
|||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
WriterInner::WindowsLocked{..} => {
|
WriterInner::WindowsLocked{..} => {
|
||||||
panic!("cannot call Stdout.lock while a StdoutLock is alive");
|
panic!("cannot call StandardStream.lock while a StandardStreamLock is alive");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
StdoutLock { wtr: stdout.wtr.wrap(locked) }
|
StandardStreamLock { wtr: stream.wtr.wrap(locked) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl io::Write for Stdout {
|
impl io::Write for StandardStream {
|
||||||
fn write(&mut self, b: &[u8]) -> io::Result<usize> { self.wtr.write(b) }
|
fn write(&mut self, b: &[u8]) -> io::Result<usize> { self.wtr.write(b) }
|
||||||
fn flush(&mut self) -> io::Result<()> { self.wtr.flush() }
|
fn flush(&mut self) -> io::Result<()> { self.wtr.flush() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WriteColor for Stdout {
|
impl WriteColor for StandardStream {
|
||||||
fn supports_color(&self) -> bool { self.wtr.supports_color() }
|
fn supports_color(&self) -> bool { self.wtr.supports_color() }
|
||||||
fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
|
fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
|
||||||
self.wtr.set_color(spec)
|
self.wtr.set_color(spec)
|
||||||
@@ -328,12 +425,12 @@ impl WriteColor for Stdout {
|
|||||||
fn reset(&mut self) -> io::Result<()> { self.wtr.reset() }
|
fn reset(&mut self) -> io::Result<()> { self.wtr.reset() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> io::Write for StdoutLock<'a> {
|
impl<'a> io::Write for StandardStreamLock<'a> {
|
||||||
fn write(&mut self, b: &[u8]) -> io::Result<usize> { self.wtr.write(b) }
|
fn write(&mut self, b: &[u8]) -> io::Result<usize> { self.wtr.write(b) }
|
||||||
fn flush(&mut self) -> io::Result<()> { self.wtr.flush() }
|
fn flush(&mut self) -> io::Result<()> { self.wtr.flush() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> WriteColor for StdoutLock<'a> {
|
impl<'a> WriteColor for StandardStreamLock<'a> {
|
||||||
fn supports_color(&self) -> bool { self.wtr.supports_color() }
|
fn supports_color(&self) -> bool { self.wtr.supports_color() }
|
||||||
fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
|
fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
|
||||||
self.wtr.set_color(spec)
|
self.wtr.set_color(spec)
|
||||||
@@ -420,7 +517,7 @@ impl<'a, W: io::Write> WriteColor for WriterInner<'a, W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes colored buffers to stdout.
|
/// Writes colored buffers to stdout or stderr.
|
||||||
///
|
///
|
||||||
/// Writable buffers can be obtained by calling `buffer` on a `BufferWriter`.
|
/// Writable buffers can be obtained by calling `buffer` on a `BufferWriter`.
|
||||||
///
|
///
|
||||||
@@ -430,7 +527,7 @@ impl<'a, W: io::Write> WriteColor for WriterInner<'a, W> {
|
|||||||
/// It is intended for a `BufferWriter` to be put in an `Arc` and written to
|
/// It is intended for a `BufferWriter` to be put in an `Arc` and written to
|
||||||
/// from multiple threads simultaneously.
|
/// from multiple threads simultaneously.
|
||||||
pub struct BufferWriter {
|
pub struct BufferWriter {
|
||||||
stdout: LossyStdout<io::Stdout>,
|
stream: LossyStandardStream<IoStandardStream>,
|
||||||
printed: AtomicBool,
|
printed: AtomicBool,
|
||||||
separator: Option<Vec<u8>>,
|
separator: Option<Vec<u8>>,
|
||||||
color_choice: ColorChoice,
|
color_choice: ColorChoice,
|
||||||
@@ -439,23 +536,23 @@ pub struct BufferWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BufferWriter {
|
impl BufferWriter {
|
||||||
/// Create a new `BufferWriter` that writes to stdout with the given
|
/// Create a new `BufferWriter` that writes to a standard stream with the
|
||||||
/// color preferences.
|
/// given color preferences.
|
||||||
///
|
///
|
||||||
/// The specific color/style settings can be configured when writing to
|
/// The specific color/style settings can be configured when writing to
|
||||||
/// the buffers themselves.
|
/// the buffers themselves.
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
pub fn stdout(choice: ColorChoice) -> BufferWriter {
|
fn create(sty: StandardStreamType, choice: ColorChoice) -> BufferWriter {
|
||||||
BufferWriter {
|
BufferWriter {
|
||||||
stdout: LossyStdout::new(io::stdout()),
|
stream: LossyStandardStream::new(IoStandardStream::new(sty)),
|
||||||
printed: AtomicBool::new(false),
|
printed: AtomicBool::new(false),
|
||||||
separator: None,
|
separator: None,
|
||||||
color_choice: choice,
|
color_choice: choice,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new `BufferWriter` that writes to stdout with the given
|
/// Create a new `BufferWriter` that writes to a standard stream with the
|
||||||
/// color preferences.
|
/// given color preferences.
|
||||||
///
|
///
|
||||||
/// If coloring is desired and a Windows console could not be found, then
|
/// If coloring is desired and a Windows console could not be found, then
|
||||||
/// ANSI escape sequences are used instead.
|
/// ANSI escape sequences are used instead.
|
||||||
@@ -463,11 +560,14 @@ impl BufferWriter {
|
|||||||
/// The specific color/style settings can be configured when writing to
|
/// The specific color/style settings can be configured when writing to
|
||||||
/// the buffers themselves.
|
/// the buffers themselves.
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn stdout(choice: ColorChoice) -> BufferWriter {
|
fn create(sty: StandardStreamType, choice: ColorChoice) -> BufferWriter {
|
||||||
let con = wincolor::Console::stdout().ok().map(Mutex::new);
|
let con = match sty {
|
||||||
let stdout = LossyStdout::new(io::stdout()).is_console(con.is_some());
|
StandardStreamType::Stdout => wincolor::Console::stdout(),
|
||||||
|
StandardStreamType::Stderr => wincolor::Console::stderr(),
|
||||||
|
}.ok().map(Mutex::new);
|
||||||
|
let stream = LossyStandardStream::new(IoStandardStream::new(sty)).is_console(con.is_some());
|
||||||
BufferWriter {
|
BufferWriter {
|
||||||
stdout: stdout,
|
stream: stream,
|
||||||
printed: AtomicBool::new(false),
|
printed: AtomicBool::new(false),
|
||||||
separator: None,
|
separator: None,
|
||||||
color_choice: choice,
|
color_choice: choice,
|
||||||
@@ -475,6 +575,30 @@ impl BufferWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new `BufferWriter` that writes to stdout with the given
|
||||||
|
/// color preferences.
|
||||||
|
///
|
||||||
|
/// On Windows, if coloring is desired and a Windows console could not be
|
||||||
|
/// found, then ANSI escape sequences are used instead.
|
||||||
|
///
|
||||||
|
/// The specific color/style settings can be configured when writing to
|
||||||
|
/// the buffers themselves.
|
||||||
|
pub fn stdout(choice: ColorChoice) -> BufferWriter {
|
||||||
|
BufferWriter::create(StandardStreamType::Stdout, choice)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `BufferWriter` that writes to stderr with the given
|
||||||
|
/// color preferences.
|
||||||
|
///
|
||||||
|
/// On Windows, if coloring is desired and a Windows console could not be
|
||||||
|
/// found, then ANSI escape sequences are used instead.
|
||||||
|
///
|
||||||
|
/// The specific color/style settings can be configured when writing to
|
||||||
|
/// the buffers themselves.
|
||||||
|
pub fn stderr(choice: ColorChoice) -> BufferWriter {
|
||||||
|
BufferWriter::create(StandardStreamType::Stderr, choice)
|
||||||
|
}
|
||||||
|
|
||||||
/// If set, the separator given is printed between buffers. By default, no
|
/// If set, the separator given is printed between buffers. By default, no
|
||||||
/// separator is printed.
|
/// separator is printed.
|
||||||
///
|
///
|
||||||
@@ -510,16 +634,16 @@ impl BufferWriter {
|
|||||||
if buf.is_empty() {
|
if buf.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let mut stdout = self.stdout.wrap(self.stdout.get_ref().lock());
|
let mut stream = self.stream.wrap(self.stream.get_ref().lock());
|
||||||
if let Some(ref sep) = self.separator {
|
if let Some(ref sep) = self.separator {
|
||||||
if self.printed.load(Ordering::SeqCst) {
|
if self.printed.load(Ordering::SeqCst) {
|
||||||
try!(stdout.write_all(sep));
|
try!(stream.write_all(sep));
|
||||||
try!(stdout.write_all(b"\n"));
|
try!(stream.write_all(b"\n"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match buf.0 {
|
match buf.0 {
|
||||||
BufferInner::NoColor(ref b) => try!(stdout.write_all(&b.0)),
|
BufferInner::NoColor(ref b) => try!(stream.write_all(&b.0)),
|
||||||
BufferInner::Ansi(ref b) => try!(stdout.write_all(&b.0)),
|
BufferInner::Ansi(ref b) => try!(stream.write_all(&b.0)),
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
BufferInner::Windows(ref b) => {
|
BufferInner::Windows(ref b) => {
|
||||||
// We guarantee by construction that we have a console here.
|
// We guarantee by construction that we have a console here.
|
||||||
@@ -527,7 +651,7 @@ impl BufferWriter {
|
|||||||
let console_mutex = self.console.as_ref()
|
let console_mutex = self.console.as_ref()
|
||||||
.expect("got Windows buffer but have no Console");
|
.expect("got Windows buffer but have no Console");
|
||||||
let mut console = console_mutex.lock().unwrap();
|
let mut console = console_mutex.lock().unwrap();
|
||||||
try!(b.print(&mut *console, &mut stdout));
|
try!(b.print(&mut *console, &mut stream));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.printed.store(true, Ordering::SeqCst);
|
self.printed.store(true, Ordering::SeqCst);
|
||||||
@@ -905,25 +1029,25 @@ impl WindowsBuffer {
|
|||||||
self.colors.push((pos, spec));
|
self.colors.push((pos, spec));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print the contents to the given stdout handle, and use the console
|
/// Print the contents to the given stream handle, and use the console
|
||||||
/// for coloring.
|
/// for coloring.
|
||||||
fn print(
|
fn print(
|
||||||
&self,
|
&self,
|
||||||
console: &mut wincolor::Console,
|
console: &mut wincolor::Console,
|
||||||
stdout: &mut LossyStdout<io::StdoutLock>,
|
stream: &mut LossyStandardStream<IoStandardStreamLock>,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
let mut last = 0;
|
let mut last = 0;
|
||||||
for &(pos, ref spec) in &self.colors {
|
for &(pos, ref spec) in &self.colors {
|
||||||
try!(stdout.write_all(&self.buf[last..pos]));
|
try!(stream.write_all(&self.buf[last..pos]));
|
||||||
try!(stdout.flush());
|
try!(stream.flush());
|
||||||
last = pos;
|
last = pos;
|
||||||
match *spec {
|
match *spec {
|
||||||
None => try!(console.reset()),
|
None => try!(console.reset()),
|
||||||
Some(ref spec) => try!(spec.write_console(console)),
|
Some(ref spec) => try!(spec.write_console(console)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try!(stdout.write_all(&self.buf[last..]));
|
try!(stream.write_all(&self.buf[last..]));
|
||||||
stdout.flush()
|
stream.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear the buffer.
|
/// Clear the buffer.
|
||||||
@@ -1121,33 +1245,33 @@ impl FromStr for Color {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LossyStdout<W> {
|
struct LossyStandardStream<W> {
|
||||||
wtr: W,
|
wtr: W,
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
is_console: bool,
|
is_console: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: io::Write> LossyStdout<W> {
|
impl<W: io::Write> LossyStandardStream<W> {
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
fn new(wtr: W) -> LossyStdout<W> { LossyStdout { wtr: wtr } }
|
fn new(wtr: W) -> LossyStandardStream<W> { LossyStandardStream { wtr: wtr } }
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn new(wtr: W) -> LossyStdout<W> {
|
fn new(wtr: W) -> LossyStandardStream<W> {
|
||||||
LossyStdout { wtr: wtr, is_console: false }
|
LossyStandardStream { wtr: wtr, is_console: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
fn wrap<Q: io::Write>(&self, wtr: Q) -> LossyStdout<Q> {
|
fn wrap<Q: io::Write>(&self, wtr: Q) -> LossyStandardStream<Q> {
|
||||||
LossyStdout::new(wtr)
|
LossyStandardStream::new(wtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn wrap<Q: io::Write>(&self, wtr: Q) -> LossyStdout<Q> {
|
fn wrap<Q: io::Write>(&self, wtr: Q) -> LossyStandardStream<Q> {
|
||||||
LossyStdout::new(wtr).is_console(self.is_console)
|
LossyStandardStream::new(wtr).is_console(self.is_console)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn is_console(mut self, yes: bool) -> LossyStdout<W> {
|
fn is_console(mut self, yes: bool) -> LossyStandardStream<W> {
|
||||||
self.is_console = yes;
|
self.is_console = yes;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -1157,7 +1281,7 @@ impl<W: io::Write> LossyStdout<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: WriteColor> WriteColor for LossyStdout<W> {
|
impl<W: WriteColor> WriteColor for LossyStandardStream<W> {
|
||||||
fn supports_color(&self) -> bool { self.wtr.supports_color() }
|
fn supports_color(&self) -> bool { self.wtr.supports_color() }
|
||||||
fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
|
fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
|
||||||
self.wtr.set_color(spec)
|
self.wtr.set_color(spec)
|
||||||
@@ -1165,7 +1289,7 @@ impl<W: WriteColor> WriteColor for LossyStdout<W> {
|
|||||||
fn reset(&mut self) -> io::Result<()> { self.wtr.reset() }
|
fn reset(&mut self) -> io::Result<()> { self.wtr.reset() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: io::Write> io::Write for LossyStdout<W> {
|
impl<W: io::Write> io::Write for LossyStandardStream<W> {
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
self.wtr.write(buf)
|
self.wtr.write(buf)
|
||||||
|
228
tests/tests.rs
228
tests/tests.rs
@@ -432,6 +432,63 @@ sherlock!(context_line_numbers, "world|attached",
|
|||||||
assert_eq!(lines, expected);
|
assert_eq!(lines, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sherlock!(max_filesize_parse_error_length, "Sherlock", ".",
|
||||||
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
|
cmd.arg("--max-filesize").arg("44444444444444444444");
|
||||||
|
wd.assert_err(&mut cmd);
|
||||||
|
});
|
||||||
|
|
||||||
|
sherlock!(max_filesize_parse_error_suffix, "Sherlock", ".",
|
||||||
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
|
cmd.arg("--max-filesize").arg("45k");
|
||||||
|
wd.assert_err(&mut cmd);
|
||||||
|
});
|
||||||
|
|
||||||
|
sherlock!(max_filesize_parse_no_suffix, "Sherlock", ".",
|
||||||
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.remove("sherlock");
|
||||||
|
wd.create_size("foo", 40);
|
||||||
|
wd.create_size("bar", 60);
|
||||||
|
|
||||||
|
cmd.arg("--max-filesize").arg("50").arg("--files");
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = "\
|
||||||
|
foo
|
||||||
|
";
|
||||||
|
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
sherlock!(max_filesize_parse_k_suffix, "Sherlock", ".",
|
||||||
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.remove("sherlock");
|
||||||
|
wd.create_size("foo", 3048);
|
||||||
|
wd.create_size("bar", 4100);
|
||||||
|
|
||||||
|
cmd.arg("--max-filesize").arg("4K").arg("--files");
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = "\
|
||||||
|
foo
|
||||||
|
";
|
||||||
|
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
sherlock!(max_filesize_parse_m_suffix, "Sherlock", ".",
|
||||||
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.remove("sherlock");
|
||||||
|
wd.create_size("foo", 1000000);
|
||||||
|
wd.create_size("bar", 1400000);
|
||||||
|
|
||||||
|
cmd.arg("--max-filesize").arg("1M").arg("--files");
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = "\
|
||||||
|
foo
|
||||||
|
";
|
||||||
|
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
sherlock!(ignore_hidden, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
sherlock!(ignore_hidden, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
wd.remove("sherlock");
|
wd.remove("sherlock");
|
||||||
wd.create(".sherlock", hay::SHERLOCK);
|
wd.create(".sherlock", hay::SHERLOCK);
|
||||||
@@ -988,6 +1045,91 @@ clean!(regression_279, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
|||||||
assert_eq!(lines, "");
|
assert_eq!(lines, "");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/405
|
||||||
|
clean!(regression_405, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.create_dir("foo/bar");
|
||||||
|
wd.create_dir("bar/foo");
|
||||||
|
wd.create("foo/bar/file1.txt", "test");
|
||||||
|
wd.create("bar/foo/file2.txt", "test");
|
||||||
|
cmd.arg("-g").arg("!/foo/**");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
assert_eq!(lines, format!("{}:test\n", path("bar/foo/file2.txt")));
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/428
|
||||||
|
clean!(regression_428_color_context_path, "foo", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.create("sherlock", "foo\nbar");
|
||||||
|
cmd.arg("-A1").arg("-H").arg("--no-heading").arg("-N")
|
||||||
|
.arg("--colors=match:none").arg("--color=always");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = format!("\
|
||||||
|
{colored_path}:foo
|
||||||
|
{colored_path}-bar
|
||||||
|
", colored_path=format!("\x1b\x5b\x6d\x1b\x5b\x33\x35\x6d{path}\x1b\x5b\x6d", path=path("sherlock")));
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/428
|
||||||
|
clean!(regression_428_unrecognized_style, "Sherlok", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
cmd.arg("--colors=match:style:");
|
||||||
|
wd.assert_err(&mut cmd);
|
||||||
|
|
||||||
|
let output = cmd.output().unwrap();
|
||||||
|
let err = String::from_utf8_lossy(&output.stderr);
|
||||||
|
let expected = "\
|
||||||
|
Unrecognized style attribute ''. Choose from: nobold, bold, nointense, intense.
|
||||||
|
";
|
||||||
|
assert_eq!(err, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||||
|
clean!(feature_1_sjis, "Шерлок Холмс", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
let sherlock =
|
||||||
|
b"\x84Y\x84u\x84\x82\x84|\x84\x80\x84{ \x84V\x84\x80\x84|\x84}\x84\x83";
|
||||||
|
wd.create_bytes("foo", &sherlock[..]);
|
||||||
|
cmd.arg("-Esjis");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
assert_eq!(lines, "foo:Шерлок Холмс\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||||
|
clean!(feature_1_utf16_auto, "Шерлок Холмс", ".",
|
||||||
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
|
let sherlock =
|
||||||
|
b"\xff\xfe(\x045\x04@\x04;\x04>\x04:\x04 \x00%\x04>\x04;\x04<\x04A\x04";
|
||||||
|
wd.create_bytes("foo", &sherlock[..]);
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
assert_eq!(lines, "foo:Шерлок Холмс\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||||
|
clean!(feature_1_utf16_explicit, "Шерлок Холмс", ".",
|
||||||
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
|
let sherlock =
|
||||||
|
b"\xff\xfe(\x045\x04@\x04;\x04>\x04:\x04 \x00%\x04>\x04;\x04<\x04A\x04";
|
||||||
|
wd.create_bytes("foo", &sherlock[..]);
|
||||||
|
cmd.arg("-Eutf-16le");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
assert_eq!(lines, "foo:Шерлок Холмс\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/1
|
||||||
|
clean!(feature_1_eucjp, "Шерлок Холмс", ".",
|
||||||
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
|
let sherlock =
|
||||||
|
b"\xa7\xba\xa7\xd6\xa7\xe2\xa7\xdd\xa7\xe0\xa7\xdc \xa7\xb7\xa7\xe0\xa7\xdd\xa7\xde\xa7\xe3";
|
||||||
|
wd.create_bytes("foo", &sherlock[..]);
|
||||||
|
cmd.arg("-Eeuc-jp");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
assert_eq!(lines, "foo:Шерлок Холмс\n");
|
||||||
|
});
|
||||||
|
|
||||||
// See: https://github.com/BurntSushi/ripgrep/issues/7
|
// See: https://github.com/BurntSushi/ripgrep/issues/7
|
||||||
sherlock!(feature_7, "-fpat", "sherlock", |wd: WorkDir, mut cmd: Command| {
|
sherlock!(feature_7, "-fpat", "sherlock", |wd: WorkDir, mut cmd: Command| {
|
||||||
wd.create("pat", "Sherlock\nHolmes");
|
wd.create("pat", "Sherlock\nHolmes");
|
||||||
@@ -1024,6 +1166,32 @@ be, to a very large extent, the result of luck. Sherlock Holmes
|
|||||||
assert_eq!(lines, expected);
|
assert_eq!(lines, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/34
|
||||||
|
sherlock!(feature_34_only_matching, "Sherlock", ".",
|
||||||
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
|
cmd.arg("--only-matching");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = "\
|
||||||
|
sherlock:Sherlock
|
||||||
|
sherlock:Sherlock
|
||||||
|
";
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/34
|
||||||
|
sherlock!(feature_34_only_matching_line_column, "Sherlock", ".",
|
||||||
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
|
cmd.arg("--only-matching").arg("--column").arg("--line-number");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = "\
|
||||||
|
sherlock:1:57:Sherlock
|
||||||
|
sherlock:3:49:Sherlock
|
||||||
|
";
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
// See: https://github.com/BurntSushi/ripgrep/issues/45
|
// See: https://github.com/BurntSushi/ripgrep/issues/45
|
||||||
sherlock!(feature_45_relative_cwd, "test", ".",
|
sherlock!(feature_45_relative_cwd, "test", ".",
|
||||||
|wd: WorkDir, mut cmd: Command| {
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
@@ -1199,6 +1367,36 @@ clean!(feature_109_case_sensitive_part2, "test", ".",
|
|||||||
wd.assert_err(&mut cmd);
|
wd.assert_err(&mut cmd);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/129
|
||||||
|
clean!(feature_129_matches, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.create("foo", "test\ntest abcdefghijklmnopqrstuvwxyz test");
|
||||||
|
cmd.arg("-M26");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = "foo:test\nfoo:[Omitted long line with 2 matches]\n";
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/129
|
||||||
|
clean!(feature_129_context, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.create("foo", "test\nabcdefghijklmnopqrstuvwxyz");
|
||||||
|
cmd.arg("-M20").arg("-C1");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = "foo:test\nfoo-[Omitted long context line]\n";
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/129
|
||||||
|
clean!(feature_129_replace, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.create("foo", "test\ntest abcdefghijklmnopqrstuvwxyz test");
|
||||||
|
cmd.arg("-M26").arg("-rfoo");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = "foo:foo\nfoo:[Omitted long line with 2 replacements]\n";
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
// See: https://github.com/BurntSushi/ripgrep/issues/159
|
// See: https://github.com/BurntSushi/ripgrep/issues/159
|
||||||
clean!(feature_159_works, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
clean!(feature_159_works, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
wd.create("foo", "test\ntest");
|
wd.create("foo", "test\ntest");
|
||||||
@@ -1245,6 +1443,15 @@ clean!(feature_275_pathsep, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
|||||||
assert_eq!(lines, "fooZbar:test\n");
|
assert_eq!(lines, "fooZbar:test\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/419
|
||||||
|
sherlock!(feature_419_zero_as_shortcut_for_null, "Sherlock", ".",
|
||||||
|
|wd: WorkDir, mut cmd: Command| {
|
||||||
|
cmd.arg("-0").arg("--count");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
assert_eq!(lines, "sherlock\x002\n");
|
||||||
|
});
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn binary_nosearch() {
|
fn binary_nosearch() {
|
||||||
let wd = WorkDir::new("binary_nosearch");
|
let wd = WorkDir::new("binary_nosearch");
|
||||||
@@ -1326,6 +1533,27 @@ fn regression_270() {
|
|||||||
assert_eq!(lines, path("foo:-test\n"));
|
assert_eq!(lines, path("foo:-test\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/391
|
||||||
|
#[test]
|
||||||
|
fn regression_391() {
|
||||||
|
let wd = WorkDir::new("regression_391");
|
||||||
|
wd.create_dir(".git");
|
||||||
|
wd.create("lock", "");
|
||||||
|
wd.create("bar.py", "");
|
||||||
|
wd.create(".git/packed-refs", "");
|
||||||
|
wd.create(".git/description", "");
|
||||||
|
|
||||||
|
let mut cmd = wd.command();
|
||||||
|
cmd.arg("--no-ignore").arg("--hidden").arg("--follow").arg("--files")
|
||||||
|
.arg("--glob")
|
||||||
|
.arg("!{.git,node_modules,plugged}/**")
|
||||||
|
.arg("--glob")
|
||||||
|
.arg("*.{js,json,php,md,styl,scss,sass,pug,html,config,py,cpp,c,go,hs}");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
assert_eq!(lines, "bar.py\n");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn type_list() {
|
fn type_list() {
|
||||||
let wd = WorkDir::new("type_list");
|
let wd = WorkDir::new("type_list");
|
||||||
|
@@ -46,6 +46,13 @@ impl WorkDir {
|
|||||||
self.create_bytes(name, contents.as_bytes());
|
self.create_bytes(name, contents.as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new file with the given name and size.
|
||||||
|
pub fn create_size<P: AsRef<Path>>(&self, name: P, filesize: u64) {
|
||||||
|
let path = self.dir.join(name);
|
||||||
|
let file = nice_err(&path, File::create(&path));
|
||||||
|
nice_err(&path, file.set_len(filesize));
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new file with the given name and contents in this directory.
|
/// Create a new file with the given name and contents in this directory.
|
||||||
pub fn create_bytes<P: AsRef<Path>>(&self, name: P, contents: &[u8]) {
|
pub fn create_bytes<P: AsRef<Path>>(&self, name: P, contents: &[u8]) {
|
||||||
let path = self.dir.join(name);
|
let path = self.dir.join(name);
|
||||||
|
3
wincolor/COPYING
Normal file
3
wincolor/COPYING
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
This project is dual-licensed under the Unlicense and MIT licenses.
|
||||||
|
|
||||||
|
You may use this code under the terms of either license.
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "wincolor"
|
name = "wincolor"
|
||||||
version = "0.1.1" #:version
|
version = "0.1.3" #:version
|
||||||
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
description = """
|
description = """
|
||||||
A simple Windows specific API for controlling text color in a Windows console.
|
A simple Windows specific API for controlling text color in a Windows console.
|
||||||
|
21
wincolor/LICENSE-MIT
Normal file
21
wincolor/LICENSE-MIT
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Andrew Gallant
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
24
wincolor/UNLICENSE
Normal file
24
wincolor/UNLICENSE
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org/>
|
@@ -3,7 +3,7 @@ use std::mem;
|
|||||||
|
|
||||||
use kernel32;
|
use kernel32;
|
||||||
use winapi::{DWORD, HANDLE, WORD};
|
use winapi::{DWORD, HANDLE, WORD};
|
||||||
use winapi::winbase::STD_OUTPUT_HANDLE;
|
use winapi::winbase::{STD_ERROR_HANDLE, STD_OUTPUT_HANDLE};
|
||||||
use winapi::wincon::{
|
use winapi::wincon::{
|
||||||
FOREGROUND_BLUE as FG_BLUE,
|
FOREGROUND_BLUE as FG_BLUE,
|
||||||
FOREGROUND_GREEN as FG_GREEN,
|
FOREGROUND_GREEN as FG_GREEN,
|
||||||
@@ -44,13 +44,11 @@ impl Drop for Console {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Console {
|
impl Console {
|
||||||
/// Create a new Console to stdout.
|
/// Get a console for a standard I/O stream.
|
||||||
///
|
fn create_for_stream(handle_id: DWORD) -> io::Result<Console> {
|
||||||
/// If there was a problem creating the console, then an error is returned.
|
|
||||||
pub fn stdout() -> io::Result<Console> {
|
|
||||||
let mut info = unsafe { mem::zeroed() };
|
let mut info = unsafe { mem::zeroed() };
|
||||||
let (handle, res) = unsafe {
|
let (handle, res) = unsafe {
|
||||||
let handle = kernel32::GetStdHandle(STD_OUTPUT_HANDLE);
|
let handle = kernel32::GetStdHandle(handle_id);
|
||||||
(handle, kernel32::GetConsoleScreenBufferInfo(handle, &mut info))
|
(handle, kernel32::GetConsoleScreenBufferInfo(handle, &mut info))
|
||||||
};
|
};
|
||||||
if res == 0 {
|
if res == 0 {
|
||||||
@@ -64,6 +62,20 @@ impl Console {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// Applies the current text attributes.
|
||||||
fn set(&mut self) -> io::Result<()> {
|
fn set(&mut self) -> io::Result<()> {
|
||||||
let attr = self.cur_attr.to_word();
|
let attr = self.cur_attr.to_word();
|
||||||
|
Reference in New Issue
Block a user